美文网首页
联动表单实现思路

联动表单实现思路

作者: djyuning | 来源:发表于2018-02-20 22:50 被阅读792次

    Vue.js 下由很多优秀的 UI 框架,这些框架无一例外的都提供了表单控件,但提供联动下拉的基本没有,原因很简单,联动表单的数据比较灵活,统一进行封装意义不大。

    手头的项目用到联动表单的地方比较多,如:地区联动、无限分类联动等,又鉴于使用的是自建数据,所以无法直接使用现有的组件替代。

    重点

    需求

    • 支持多级联动效果;
    • 支持使用给定值初始化;
    • 灵活调用;

    思路

    调用设计:

    <!-- 原生调用 -->
    <my-select></my-select>
    
    <!-- 事件接管 -->
    <my-select @change="newChange"></my-select>
    
    <!-- 使用给定值初始化 -->
    <my-select selected="新闻,国内新闻" @change="newChange"></my-select>
    

    实现

    最终实现效果:

    实现起来还是比较简单的,大纲如下:

    • 方法 getChilds :该方法可以按统一的格式获取数据,它接受一个所选文字参数,传递到后端进行数据获取,后端查找对应的数据并返回给前端;
    • 方法 selectChange :该方法是 select 表单的 @change 监听,它传回 2 个参数,一个是选择的值,一个是操作表单的索引;
    • 数据 propsSelected :解析 selected 属性,打散为数组使用,在 mounted 中,遍历解析好的参数,逐层获取对应的数据,如:selected 打散后的数据为 ['新闻','国内新闻']

    上面是所需的一些方法和变量,下面是逻辑:

    • mounted 中使用 propsSelected 渲染出所有可用的下拉表单(初始化),未给出默认值时,使用最原始的值获取第一层数据(getChilds);
    • 使用 selectChange 组件内单个下拉表单的选择事件,每次选择时,更新 propsSelected 中对应的值,删除表单索引后的值,即修剪 propsSelected 内容,如果选择的是默认值值,调用 change 事件,否则继续获取下一级数据,如果后端传回的数据为空,则直接调用 change 事件,否则继续追加数据,调用 change 事件。

    最终的代码如下:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <link rel="stylesheet" href="//unpkg.com/iview/dist/styles/iview.css">
    </head>
    <body>
    
        <div id="app">
            
            <h1>{{title}}</h1>
    
            <tp-select :selected="tempCategories" @change="newChnage"></tp-select>
            <h4>{{tempCategories}}</h4>
    
            <tp-select @change="newSelect"></tp-select>
            <h4>{{tempSelect}}</h4>
    
        </div>
    
        <script type="text/javascript" src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
        <script type="text/javascript" src="https://cdn.bootcss.com/vue-resource/1.3.4/vue-resource.min.js"></script>
        <script type="text/javascript" src="//unpkg.com/iview/dist/iview.min.js"></script>
        <script type="text/javascript">
    
            Vue.component('tp-select', {
    
                template: '<div><i-select class="picker" v-for="(data, key) in tempData" :key="key" v-model="propsSelected[key]" @on-change="selectChange(propsSelected[key], key)">'
                                    + '<i-option :value="defaultOption">{{defaultOption}}</i-option>'
                                    + '<i-option v-for="(cate, sub) in data" :key="`${key},${sub}`" :value="cate.name">{{cate.name}}</i-option>'
                                    + '</i-select></div>',
    
                props: {
    
                    selected: String,
    
                },
    
                data() {
                    return {
                        defaultOption: '请选择',
                        tempData: [],
                        propsSelected: this.propsToArray('selected'),
                    };
                },
    
                methods: {
    
                    propsToArray(key) {
                        let item = this.$props[key] || this.defaultOption || '请选择';
                        return item.split(',');
                    },
    
                    getChild(parent, success, error) {
    
                        this.$http.get('./data.php?name='+ (parent ? parent : '')).then(res => {
                            if(success && success instanceof Function) {
                                success(res.body, res);
                            }
                        }, res => {
                            if(error && error instanceof Function) {
                                error(res);
                                return;
                            }
                            throw res;
                        });
    
                    },
    
                    selectChange(val, key) {
    
                        // 每次都应移除 key 之后的数据
                        this.tempData = this.tempData.slice(0, key + 1);
                        this.propsSelected = this.propsSelected.slice(0, key + 1);
    
                        // 如果选择的是 请选择 项,那么就没有继续发送请求的必要了
                        if (val === this.defaultOption ) {
                            this.$emit('change', this.propsSelected.toString());
                            return;
                        };
    
                        // 根据选择的值加载对应的子级
                        this.getChild(val, categories => {
                            // 如果后端没有返回数据,说明选择到此结束
                            if (!categories || categories.length <= 0) {
                                this.$emit('change', this.propsSelected.toString());
                                return;
                            };
    
                            // 更新数据,触发回调
                            this.tempData.push(categories);
                            this.propsSelected.push(this.defaultOption);
                            this.$emit('change', this.propsSelected.toString());
                        });
    
                    }
    
                },
    
                mounted() {
    
                    // 初始化
                    this.propsSelected.forEach((item, index) => {
                        // 当前的列表是由当前值的上一个值获取的
                        if(index >= 1) {
                            this.getChild(this.propsSelected[index - 1], categories => {
                                this.tempData.splice(index, 1, categories);
                            });
                            return;
                        }
                        // 如果传入的是 0,那么应该获取第一层数据用于生成第一个下拉表单
                        this.getChild(0, categories => {
                            this.tempData.splice(0, 1, categories);
                        });
    
                    });
    
                }
    
            });
    
            new Vue({
    
                el: '#app',
    
                data(){
                    return {
                        title: '联动效果实现',
                        tempCategories: '科技,数码产品',
                        tempSelect: null,
                    };
                },
    
                methods: {
    
                    newChnage(changed) {
                        this.tempCategories = changed;
                    },
    
                    newSelect(changed) {
                        this.tempSelect = changed;
                    }
    
                },
    
                mounted() {}
    
            });
        </script>
    </body>
    </html>
    

    父组件可以通过 @change 实时获取到新的选择的值并更新到本组件内,由于子组件中并未实际的操作 props 中传入的变量,所以,也不用担变量被修改。

    示例用到了一个 PHP 文件,内容如下:

    <?php
    
    $parent = isset($_GET['name']) ? $_GET['name'] : null;
    
    $category = array(
        array(
            'name' => '默认分类',
        ),
        array(
            'name' => '新闻',
            'child'=> array(
                array(
                    'name' => '国内新闻',
                ),
                array(
                    'name' => '国际新闻',
                ),
            ),
        ),
        array(
            'name' => '科技',
            'child'=> array(
                array(
                    'name' => '业界',
                ),
                array(
                    'name' => '手机',
                ),
                array(
                    'name' => '数码产品',
                ),
            ),
        ),
        array(
            'name' => '游戏',
            'child'=> array(
                array(
                    'name' => '网络游戏',
                ),
                array(
                    'name' => '电子竞技',
                ),
                array(
                    'name' => '热门游戏',
                ),
            ),
        ),
    );
    
    if(!$parent || empty($parent)) {
        echo json_encode($category);
        exit;
    }
    
    $use = null;
    
    foreach ($category as $item) {
        if( $item['name'] === $parent ) {
            $use = isset($item['child']) ? $item['child'] : NULL;
        }
    }
    
    echo $use ? json_encode($use) : FALSE;
    

    异步触发

    如果请求数据使用的参数和界面显示使用的参数是一致的,那么应该不会存在异步更新问题。但是,看下面的例子:

    Props  传入的 selected 参数:湖南省,株洲市,茶陵县,潞水镇
    API 数据请求使用的参数是:CN03
    

    那么问题出现了,我们的初始化流程就会变成:初始第一层数据 - 根据湖南省查找出对应的标号CN03 - 使用 CN03 作为参数继续获取数据 - 根据株洲市查找出对应的标号CN0302 - ····· ,但是,因为期间会涉及到数据请求延时,所以,这里可能就会出现错误,例如:循环到 茶陵县 的时候,株洲市 的数据还没加载,于是就会报错!!!解决方法是使用异步处理:

    首先,建立一个 Promise 方法:

    readAreaData(block, index) {
      return new Promise(resolve => {
          // 获取地区对应的标号 Array.find(***);
          let area = this.getAreaNum(block, index);
    
          // Ajax加载数据
          this.loadAreaData(area, areas => {
            if (areas.length > 0) {
              this.areaTemp.splice(index + 1);
              this.areaTemp.push(areas);
            }
          resolve();
        });
      })
    },
    

    然后,在初始化的时候,使用异步方法调用上面的 Promise 方法:

        mounted() {
    
          // 初始化遍历
          let eachChecked = async () => {
            for (let i = 0; i < this.checked.length; i++) {
              await this.readAreaData(this.checked[i], i)
            }
          };
    
          // 获取到第一层数据后,开始根据已选数据逐个加载下拉菜单
          this.loadAreaData('CN', areaTop => {
            this.areaTemp.splice(0, 1, areaTop);
            eachChecked();
          });
    
        },
    

    相关文章

      网友评论

          本文标题:联动表单实现思路

          本文链接:https://www.haomeiwen.com/subject/plxntftx.html