zoukankan      html  css  js  c++  java
  • Vue组件

    原文链接:点我

    已经有很多成熟的智能输入框组件,如Form.js。但是现在MVVM框架,如vue、react的为了实现双向数据绑定会重绘所有的元素,这样就会难以兼容使用。所以笔者开发了Vue组件-智能输入框。

    包含的功能大同小异:

    1. 获得焦点时显示所有备选项
    2. 失去焦点时隐藏备选项面板
    3. 输入字符后,检索可能的备选项
    4. 支持上下键和回车键进行选中
    5. 支持点击选中
    6. 支持多选
    7. 以逗号进行多选的分割

    效果图:

    图1:

    图片描述

    图2:

    图片描述

    智能输入框组件封装

    将Vue组件封装到js文件中,smartInput.js:

      1 // 智能输入框Vue组件
      2 Vue.component('smart-input', {
      3     template: `<div class="friendSearchContainer">
      4         <input v-model="input" class="form-control smartInput"
      5             placeholder="输入文本自动检索,上下键选取,回车选中,可点选"
      6             data-toggle="tooltip" @click="init" @keydown="search" @blur="blur" />
      7         <ul v-show="searching" class="friendSearchList">
      8             <p v-if="!filtered.length">空数据</p>
      9             <li v-else v-for="(item, index) in filtered" @click.stop="clickOne">{{ item }}</li>
     10         </ul>
     11         <div v-show="searching" class="friendSearchModal" @click="searching=false"></div>
     12     </div>`,
     13     // 接收list/multiple/value参数
     14     props: ['props'],
     15     data() {
     16         return {
     17             searching: false,
     18             timer: null,
     19             filtered: {},
     20             input: '',
     21             focusIndex: 0,
     22             invalidData: ''
     23         };
     24     },
     25     computed: {
     26         listLength() {
     27             return this.filtered.length;
     28         },
     29         key() {
     30             return /(?:.*,)*(.*)$/.exec(this.input)[1];
     31         }
     32     },
     33     mounted() {
     34         // 支持初始化参数值
     35         this.input = this.props.value || '';
     36     },
     37     methods: {
     38         // 调整联想搜索面板的大小和位置
     39         init(e) {
     40             this.searching = true;
     41             this.filtered = this.props.list;
     42         },
     43         // 失去焦点时关闭面板,主要是按下tab键切换时的作用,随之带来的是所有相关的事件都要清除该定时器
     44         blur() {
     45             this.timer = setTimeout(() => {
     46                 this.searching = false;
     47             }, 200);
     48         },
     49         // 在上下键索引后调整视口
     50         scrollViewport() {
     51             let ul = $(this.$el).find('ul');
     52             ul.find('li.hover').removeClass('hover');
     53             ul.find('li').eq(this.focusIndex).addClass('hover');
     54             $('.friendSearchList').scrollTop(this.focusIndex * 26 - 26);
     55         },
     56         // 联想搜索的主体功能函数,这里使用keydown是为了保证持续性的上下键能够保证执行
     57         search(e) {
     58             let preSearching = this.searching;
     59             // 非搜索状态进行点击,则呼出面板
     60             if (!this.searching) {
     61                 this.searching = true;
     62             }
     63             e = e || window.event;
     64             // 通过上下键和回车选择
     65             if (e.keyCode === 38) {
     66                 this.focusIndex = (this.focusIndex - 1 + this.listLength) % this.listLength;
     67                 this.scrollViewport();
     68             } else if (e.keyCode === 40) {
     69                 this.focusIndex = (this.focusIndex + 1 + this.listLength) % this.listLength;
     70                 this.scrollViewport();
     71             } else if (e.keyCode === 13) {
     72                 if (preSearching && this.focusIndex < this.listLength) {
     73                     this.selectOne();
     74                 }
     75             } else {
     76                 // 延时搜索,降低卡顿
     77                 clearTimeout(this.timer);
     78                 this.timer = setTimeout(() => {
     79                     // 进行可选项过滤
     80                     this.filtered = this.props.list.filter(item => {
     81                         return item.toLowerCase().includes(this.key.toLowerCase());
     82                     });
     83                     this.focusIndex = 0;
     84                 }, 800);
     85             }
     86         },
     87         clickOne(e) {
     88             let target = $((e || event).target);
     89             clearTimeout(this.timer);
     90             e = e || window.event;
     91             let value = target.text();
     92             this.focusIndex = target.index();
     93             if (this.props.multiple) {
     94                 let arr = this.input.split(',');
     95                 let has = target.hasClass('active');
     96                 if (has) {
     97                     target.removeClass('active');
     98                     let index = arr.indexOf(value);
     99                     arr.splice(index, 1);
    100                     this.input = arr.join(',');
    101                 } else {
    102                     target.addClass('active');
    103                     arr.splice(arr.length - 1, 1, value);
    104                     this.input = arr.join(',') + ',';
    105                 }
    106             } else {
    107                 target.addClass('active').siblings('li').removeClass('active');
    108                 this.input = value;
    109                 this.searching = false;
    110             }
    111         },
    112         // 选择一个参数
    113         selectOne(e) {
    114             clearTimeout(this.timer);
    115             let target = $(this.$el).find('ul li').eq(this.focusIndex);
    116             let value = target.text();
    117             if (this.props.multiple) {
    118                 let arr = this.input.split(',');
    119                 let has = target.hasClass('active');
    120                 if (has) {
    121                     target.removeClass('active');
    122                     let index = arr.indexOf(value);
    123                     arr.splice(index, 1);
    124                     this.input = arr.join(',');
    125                 } else {
    126                     target.addClass('active');
    127                     arr.splice(arr.length - 1, 1, value);
    128                     this.input = arr.join(',') + ',';
    129                 }
    130             } else {
    131                 target.addClass('active').siblings('li').removeClass('active');
    132                 this.input = value;
    133                 this.searching = false;
    134             }
    135         }
    136     },
    137     watch: {
    138         input(val) {
    139             let inputArr = val.split(',');
    140             if (this.props.multiple) {
    141                 inputArr.pop();
    142                 let invalidData = [];
    143                 inputArr.forEach(item => {
    144                     if (!this.props.list.includes(item)) {
    145                         invalidData.push(item);
    146                     }
    147                 });
    148                 let $input = $('input', $(this.$el));
    149                 if (invalidData.length) {
    150                     $input.attr('title', invalidData.join(',') + '数据不合法');
    151                     $input.tooltip();
    152                 } else {
    153                     $input.tooltip('hide');
    154                 }
    155             }
    156             // 触发标签内声明的sync函数,用于传递数据给父组件
    157             this.$emit('sync', this.input);
    158         }
    159     }
    160 });

    将样式表封装为smartInput.css:

     1 // smartInput输入框需要的样式表
     2 .friendSearchContainer {
     3     position: relative;
     4 }
     5 .friendSearchList {
     6     width: 100%;
     7     padding: 6px 12px;
     8     overflow-y: scroll;
     9     max-height: 300px;
    10     background: #fff;
    11     z-index: 10;
    12     box-shadow: 0 10px 10px rgba(0, 0, 0, .2);
    13     border: 1px solid #ccc;
    14     position: absolute;
    15 }
    16 .friendSearchList li {
    17     padding: 3px 12px;
    18 }
    19 .friendSearchList li:hover {
    20     background-color: #36bc7f;
    21     color: #fff;
    22 }
    23 .friendSearchList li.active {
    24     background: #337ab7;
    25     color: #fff;
    26 }
    27 .friendSearchList li.hover {
    28     background-color: #36bc7f;
    29     color: #fff;
    30 }
    31 .friendSearchList li.active:hover {
    32     background-color: #36bc7f;
    33 }
    34 .friendSearchModal {
    35     position: fixed;
    36     top: 0;
    37     left: 0;
    38     height: 100%;
    39     width: 100%;
    40     z-index: 1;
    41 }

    使用方式:

    1. 在页面中引入vue.js和bootstrap库
    2. 在页面中引入smartInput.js和smartInput.css
    3. 在你的页面中建立vue对象:new Vue({el: '#root'})
    4. 在root根组件里直接添加<smart-input>标签即可

    实例:

    在html页面里新建DOM,直接包含<smart-input></smart-input>标签即可:

     1 <div role="tabpanel" class="tab-pane active" id="flowDispatch">
     2      <div class="row">
     3           <div class="col-md-6">
     4                <div class="form-group">
     5                     <label for="service" class="col-sm-2 control-label">业务:</label>
     6                     <div class="col-sm-10">
     7                           <smart-input id="service" placeholder="Email" @sync="syncService" :props="serviceList"></smart-input>
     8                     </div>
     9                </div>
    10            </div>
    11            <div class="col-md-6">
    12                 <div class="form-group">
    13                      <label for="service" class="col-sm-2 control-label">地区:</label>
    14                      <div class="col-sm-10">
    15                           <smart-input id="service" placeholder="Email" @sync="syncArea" :props="areaList"></smart-input>
    16                      </div>
    17                  </div>
    18            </div>
    19       </div>
    20 </div>

    在index.js里初始化Vue对象:

     1 $(function () {
     2     let flowDispatch = new Vue({
     3         el: '#flowDispatch',
     4         data: {
     5             serviceList: {
     6                 list: ['apk','pcs','opencdn','kafka','cdn','ssl'],
     7                 // 支持参数多选
     8                 multiple: true
     9             },
    10             service: '',
    11             areaList: {
    12                 list: ['河北','河南','山东','天津','重庆','全国'],
    13                 // 支持初始值设定
    14                 value: '我是初始值'
    15             },
    16             area: ''
    17         },
    18         mounted() {
    19             this.init();
    20         },
    21         methods: {
    22             // 初始化页面参数
    23             init() {
    24                 // 接口获取数据列表,而不是硬写死
    25                 // this.getArea();
    26             },
    27             // 获取地区列表
    28             getArea() {
    29                 OSS.apiAjaxAccess({
    30                     url: '?r=gslb/api/area',
    31                     statusCodeCheck: true,
    32                     success: data => {
    33                         this.areaList.list = data.data;
    34                     }
    35                 });
    36             },
    37             // 跟智能输入框同步选中的业务
    38             syncService(data) {
    39                 this.service = data;
    40             },
    41             syncArea(data) {
    42                 this.area = data;
    43             },
    44         }
    45     });
    46 });

    接口文档

    我们只需要在初始化的vue对象里设置好相关的属性即可生效:

    1 serviceList: {
    2     list: ['apk','pcs','opencdn','kafka','cdn','ssl'],
    3     multiple: true,
    4     value: '我是初始值'
    5 },

    暂时只支持这3个参数。

    后续需要完善的功能:

    1. 支持自定义分割符,添加参数delimiter: '-'
    2. 支持数据校验(不合法的不允许输入),添加参数stric: true
    3. 完善接口文档和补充在线测试用例

    作者:时来运转
    大佬们好,我是Web前端菜鸟,初来乍到,想跟诸位共同学习成长;
    综上是我每日闲时整理笔记,文章如有侵权请诸位及时告知我,谢谢关照!

  • 相关阅读:
    FIS
    git笔记 常规使用
    隐藏文字
    清除浮动的几种方法
    chrome livestyle插件
    Vue3中的微任务队列解析
    JavaScript通过父节点ID递归生成JSON树
    JavaScripts调用摄像头【MediaDevices.getUserMedia()】
    JavaScripts之变量作用域提升问题
    Webpack之 webpack-dev-server 中的 contentBase配置及作用
  • 原文地址:https://www.cnblogs.com/myfate/p/15246583.html
Copyright © 2011-2022 走看看