前言:

我的项目开发的时候刚好遇到一个需要,须要在输入框输出名字的时候,弹出相应的人员列表提供抉择,而后将数据赋值给输入框。我的项目是应用iview组件的,一开始想着在自定义iview的下拉抉择,起初发现成果并不现实。为了实现性能,就在iview输入框的根底上进行了组件封装,上面就来讲下组件封装的过程。

思路:

对于组件封装,首先须要确定性能,组件的整体构造,前面再去解决组件的数据交互逻辑。

过程:

组件的构造以及款式:

话不多说,先把组件根本的构造款式贴出来。

组件布局:

<template>    <div class="selectInput">        <div class="input-box">            <Input type="text" />        </div>        <div class="input-dropdown">            <div class="dropdown-title">                您可能须要查找            </div>            <div class="dropdown-content">                <ul class="content-list">                    <li class="list-item">                        <span class="item-avatar">李</span>                        <span class="item-name">李四</span>                    </li>                </ul>                <!-- 没有数据时的提示信息 -->                <div class="content-msg">                    暂无数据                </div>            </div>        </div>    </div></template>

既然是输出下拉,就须要一个输入框以及一个下拉列表。

款式:

.selectInput {    position: relative;    .input-dropdown {        min-width: 200px;        position: absolute;        top: 35px;        left: 0;        border: 1px solid #cfcfcf;        border-radius: 5px;        .dropdown-title {            padding: 5px 10px;            color: #e1e1e1;            background-color: #f8f8f8;        }        .dropdown-content {            .content-list {                margin: 0;                padding: 0;                .list-item {                    margin: 0;                    padding: 5px 10px ;                    list-style: none;                    &:hover {                        cursor: pointer;                        background-color: #f7f5f5;                    }                    .item-avatar {                        display: inline-block;                        width: 30px;                        height: 30px;                        line-height: 30px;                        color: #ffffff;                        background-color: #b6c4de;                        border-radius: 50%;                        text-align: center;                        margin-right: 10px;                    }                    .item-name {                        display: inline-block;                        color: #666666;                    }                }            }            .content-msg {                padding: 10px;                color: #cccccc;            }        }    }}

写完布局以及款式之后的成果:

组件的性能逻辑:

1. 确定父子组件数据传递的props
props: {    // 父组件传递的输入框初始值    value: {        type: String,        default: ''    },    // 下拉列表的题目    dropdownTitle: {        type: String,        default: '您可能要找'    },    // 下拉列表搜寻数据为空时的提醒    dropdownMsg: {        type: String,        default: '数据为空!'    },    // 下拉列表的初始数组    dropdownList: {        type: Array,        default: () => []    },    // 输入框的提醒    placeholder: {        type: String,        default: '请输出名字'    },}
2. 定义组件的data
data() {    return {        //  管制下拉列表显示        dropdownShow: false,        //  管制下拉列表数据为空提醒显示        dropdownMsgShow: false,        //  输入框值        inputValue: '',        //  搜寻后的下拉列表,用于渲染下拉        searchDataList: []    }}
3. 下拉列表的搜寻逻辑
<template>    <div class="selectInput">        <div class="input-box">            <Input :placeholder="placeholder" v-model="inputValue" @input.native="handleInput" type="text" />        </div>        <div v-show="dropdownShow" class="input-dropdown">            <div class="dropdown-title">                {{ dropdownTitle }}            </div>            <div class="dropdown-content">                <ul class="content-list">                    <li v-for="(item, index) in searchDataList" :key="index" @click="handleChoose(item.name)" class="list-item">                        <span class="item-avatar">{{ item.avatar }}</span>                        <span class="item-name">{{ item.name }}</span>                    </li>                </ul>                <!-- 没有数据时的提示信息 -->                <div v-show="dropdownMsgShow" class="content-msg">                    {{ dropdownMsg }}                </div>            </div>        </div>    </div></template>

通过dropdownShow去管制下拉列表的显示暗藏,给输入框绑定一个inputValue值和一个input事件。

// 输入框输出处理函数handleInput: debounce(function () {    let _this = this;    _this.searchDataList = [];    if (_this.inputValue === '') {        _this.dropdownShow = false;    } else {        _this.dropdownList.map(v => {            if (v.name.indexOf(_this.inputValue) >= 0) {                _this.searchDataList.push(v);            }        });        _this.searchDataList.length > 0 ? _this.dropdownMsgShow = false : _this.dropdownMsgShow = true;        _this.dropdownShow = true;    }})

handleInput我做了防抖解决,在这个函数外面通过监听输入框输出事件,判断输入框的inputValue是否为空,若为空则间接暗藏下拉列表。不为空则循环迭代从父组件传递过去的dropdownList,并将符合条件的item存进searchDataList,而后在组件中通过v-for渲染出数据。
最初通过判断searchDataList的长度给dropdownMsgShow赋值,来管制搜寻数据的提示信息。

4. 搜寻后的点击抉择解决

给下拉列表的每一项li绑定一个点击事件handleChoose

// 下拉抉择处理函数handleChoose: function (val) {    let _this = this;    _this.inputValue = val;     _this.dropdownShow = false;}

点击之后对输入框进行赋值,并暗藏下拉列表。

5. 给组件增加一个clickoutside指令

自定义clickoutside指令,当点击组件外的区域时暗藏下拉列表。

directives: {    // 自定义指令用于解决点击组件区域之外的click事件    clickoutside: {        bind(el, binding, vnode) {            function documentHandler(e) {                if (el.contains(e.target)) {                    return false;                }                if (binding.expression) {                    binding.value(e);                }            }            el.__vueClickOutSize__ = documentHandler;            document.addEventListener("click", documentHandler);        },        unbind(el, binding) {            document.removeEventListener("click", el.__vueClickOutSize__);                delete el.__vueClickOutSize__;        },    },},

给指令绑定一个敞开事件handleClose

// 下拉列表暗藏处理函数handleClose: function() {    let _this = this;    if (_this.dropdownShow) {        if (_this.searchDataList.length === 1) {            _this.inputValue = _this.searchDataList[0].name;        } else {            _this.inputValue = '';        }    }    _this.dropdownShow = false;},

在这个函数里我做了一个解决,当点击的时候,搜寻列表有数据时,会默认选中第一个,否则清空输入框。

对于函数防抖以及clickoutside,网上有大佬发了一些对于这些的文章,我在这里就不进行赘述了。
至此,组件封装实现,组件的大体思路是这样子,具体的逻辑解决能够依据理论状况进行相应的调整。

最初附上整个组件的代码:
调用代码:

<template>    <div id="blog">        <select-input :dropdownList="personnelList"></select-input>    </div></template><script>/* 引入输出下拉组件 */import selectInput from './component/selectInput';export default {    name: 'blog',    components: {        selectInput    },    data() {        return {            personnelList: [                {                    avatar: '张',                    name: '张三'                },                {                    avatar: '李',                    name: '李四'                },                {                    avatar: '王',                    name: '王五'                },                {                    avatar: '赵',                    name: '赵六'                },                {                    avatar: '李',                    name: '李师师'                }            ]        }    },    methods: {    },    created() {    },    mounted() {    }}</script><style lang="less" scoped>#blog {    padding: 50px;    width: 500px;    margin: 100px auto 0;    background: #ffffff;    height: 500px;}</style>

组件代码:

<template>    <div v-clickoutside="handleClose" class="selectInput">        <div class="input-box">            <Input :placeholder="placeholder" v-model="inputValue" @input.native="handleInput" type="text" />        </div>        <div v-show="dropdownShow" class="input-dropdown">            <div class="dropdown-title">                {{ dropdownTitle }}            </div>            <div class="dropdown-content">                <ul class="content-list">                    <li v-for="(item, index) in searchDataList" :key="index" @click="handleChoose(item.name)" class="list-item">                        <span class="item-avatar">{{ item.avatar }}</span>                        <span class="item-name">{{ item.name }}</span>                    </li>                </ul>                <div v-show="dropdownMsgShow" class="content-msg">                    {{ dropdownMsg }}                </div>            </div>        </div>    </div></template><script>// 引入函数防抖import { debounce } from "@/plugins/util/debounce";export default {    name: 'selectInput',    directives: {        // 自定义指令用于解决点击组件区域之外的click事件        clickoutside: {            bind(el, binding, vnode) {                function documentHandler(e) {                    if (el.contains(e.target)) {                        return false;                    }                    if (binding.expression) {                        binding.value(e);                    }                }                el.__vueClickOutSize__ = documentHandler;                document.addEventListener("click", documentHandler);            },            unbind(el, binding) {                document.removeEventListener("click", el.__vueClickOutSize__);                delete el.__vueClickOutSize__;            },        },    },    props: {        // 父组件传递的输入框初始值        value: {            type: String,            default: ''        },        // 下拉列表的题目        dropdownTitle: {            type: String,            default: '您可能要找'        },        // 下拉列表搜寻数据为空时的提醒        dropdownMsg: {            type: String,            default: '数据为空!'        },        // 下拉列表的初始数组        dropdownList: {            type: Array,            default: () => []        },        // 输入框的提醒        placeholder: {            type: String,            default: '请输出名字'        },    },    data() {        return {            //  管制下拉列表显示            dropdownShow: false,            //  管制下拉列表数据为空提醒显示            dropdownMsgShow: false,            //  输入框值            inputValue: '',            //  搜寻后的下拉列表,用于渲染下拉            searchDataList: []        }    },    methods: {        // 下拉列表暗藏处理函数        handleClose: function() {            let _this = this;            if (_this.dropdownShow) {                if (_this.searchDataList.length === 1) {                    _this.inputValue = _this.searchDataList[0].name;                } else {                    _this.inputValue = '';                }            }            _this.dropdownShow = false;        },        // 输入框输出处理函数        handleInput: debounce(function () {            let _this = this;            _this.searchDataList = [];            if (_this.inputValue === '') {                _this.dropdownShow = false;            } else {                _this.dropdownList.map(v => {                    if (v.name.indexOf(_this.inputValue) >= 0) {                        _this.searchDataList.push(v);                    }                });                _this.searchDataList.length > 0 ? _this.dropdownMsgShow = false : _this.dropdownMsgShow = true;                _this.dropdownShow = true;            }        }),        // 下拉抉择处理函数        handleChoose: function (val) {            let _this = this;            _this.inputValue = val;             _this.dropdownShow = false;        }    },    created() {    },    mounted() {    }}</script><style lang="less" scoped>.selectInput {    position: relative;    .input-dropdown {        min-width: 200px;        position: absolute;        top: 35px;        left: 0;        border: 1px solid #cfcfcf;        border-radius: 5px;        .dropdown-title {            padding: 5px 10px;            color: #e1e1e1;            background-color: #f8f8f8;        }        .dropdown-content {            .content-list {                margin: 0;                padding: 0;                .list-item {                    margin: 0;                    padding: 5px 10px ;                    list-style: none;                    &:hover {                        cursor: pointer;                        background-color: #f7f5f5;                    }                    .item-avatar {                        display: inline-block;                        width: 30px;                        height: 30px;                        line-height: 30px;                        color: #ffffff;                        background-color: #b6c4de;                        border-radius: 50%;                        text-align: center;                        margin-right: 10px;                    }                    .item-name {                        display: inline-block;                        color: #666666;                    }                }            }            .content-msg {                padding: 10px;                color: #cccccc;            }        }    }}</style>