联动表单实现思路

2018-02-20  本文已影响792人  djyuning

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

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

重点

需求

思路

调用设计:

<!-- 原生调用 -->
<my-select></my-select>

<!-- 事件接管 -->
<my-select @change="newChange"></my-select>

<!-- 使用给定值初始化 -->
<my-select selected="新闻,国内新闻" @change="newChange"></my-select>

实现

最终实现效果:

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

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

最终的代码如下:

<!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();
      });

    },
上一篇下一篇

猜你喜欢

热点阅读