小案例学 Vue 最终章之任意条件组合查询(干货满满)

1,498 阅读5分钟

前言

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战。恰逢掘金八月更文挑战,今天给大家分享是小案例学 Vue 的最终章,也算是这一系列的完结篇了吧,说巧不巧,今天也是 8 月更文挑战的最后一天,明天就是迎来崭新的九月咯 ~ 不知不觉,来掘金的第一个月过去了,虽然注册时间到现在不满一个月,但是我对掘金的第一印象还是非常好的,深深地被这里的氛围所感染,无论是平台的海纳百川、发文作者开源的分享精神,还是读者的求知、不吝赞美。正因为如此,让我拥有了不断输出优质的文章内容的不竭动力…… 希望我这个月的最后一篇 Blog 你能收获满满,废话少叙,马上进入正题。

页面效果展示

动画.gif

需求介绍

  1. 首先,这是个多条件组合查询页面,查询字段包含一个用户的所有属性,你可以自定义查询条件进行查询符合条件的用户信息,查询到的信息以表格的形式显示,如果查询不到任何信息的话,弹出弹框提醒用户,并呈现出一个空表格栏,接下来用户有两个选择,一时继续查询,而是退出查询返回菜单。

  2. 其次,为了更好的用户体验,点击查询字段,展开为表单,如果取消选择查询该字段,只需点击取消按钮即可,此时返回为最初状态,并不将查询内容作为参数内容进行传递。

  3. 最后,开始查询之前,验证表单数据,非空以及特殊数据的校验,通过验证后将请求数据发送到后台,经过数据库筛选数据,将查询的结果返回呈现给用户。

具体流程如下

未命名文件.jpg

代码实现

HTML 代码

<div id="selFiledBox" v-cloak>
    <div v-if="selFiledBoxShow">
        <h2>{{title}}</h2>
        <div class="selFiled" v-if="!showFillBox.fill_uid">
            <input type="checkbox" name="fill_uid" id="filed_uid" v-on:click="handleFill($event)">
            <label for="filed_uid">用户ID</label>
        </div>
        <div class="fillValue" v-else>
            <b>用户ID</b>
            <input type="number" id="uid" autocomplete="off" v-model="selFields.uid" v-on:keyup="clearMsg">
            <button class="cancelBut" name="fill_uid" v-on:click="handleFill($event)">取消</button>
        </div>

        <div class="selFiled" v-if="!showFillBox.fill_username">
            <input type="checkbox" name="fill_username" id="filed_username" v-on:click="handleFill($event)">
            <label for="filed_username">用户名</label>
        </div>
        <div class="fillValue" v-else>
            <b>用户名</b>
            <input type="text" id="username" autocomplete="off" v-model="selFields.username"
                   v-on:keyup="clearMsg">
            <button class="cancelBut" name="fill_username" v-on:click="handleFill($event)">取消</button>
        </div>

        <div class="selFiled" v-if="!showFillBox.fill_name">
            <input type="checkbox" name="fill_name" id="filed_name" v-on:click="handleFill($event)">
            <label for="filed_name">姓名</label>
        </div>
        <div class="fillValue" v-else>
            <b>姓名</b>
            <input type="text" id="name" autocomplete="off" v-model="selFields.name" v-on:keyup="clearMsg">
            <button class="cancelBut" name="fill_name" v-on:click="handleFill($event)">取消</button>
        </div>

        <div class="selFiled" v-if="!showFillBox.fill_gender">
            <input type="checkbox" name="fill_gender" id="filed_gender" v-on:click="handleFill($event)">
            <label for="filed_gender">性别</label>
        </div>
        <div class="fillValue" v-else>
            <b>性别</b>
            <input type="radio" name="gender" id="male" value="1" v-model="selFields.gender">
            <label for="male" style="display: inline-block;margin-right: 20px;"></label>
            <input type="radio" name="gender" id="female" value="0" v-model="selFields.gender">
            <label for="female"></label>
            <button class="cancelBut" name="fill_gender" v-on:click="handleFill($event)">取消</button>
        </div>

        <div class="selFiled" v-if="!showFillBox.fill_birthday">
            <input type="checkbox" name="fill_birthday" id="field_birthday" v-on:click="handleFill($event)">
            <label for="field_birthday">生日</label>
        </div>
        <div class="fillValue" v-else>
            <b>生日</b>
            <input type="date" id="birthday" v-model="selFields.birthday" v-on:keyup="clearMsg">
            <button class="cancelBut" name="fill_birthday" v-on:click="handleFill($event)">取消</button>
        </div>

        <div class="selFiled" v-if="!showFillBox.fill_age">
            <input type="checkbox" name="fill_age" id="filed_age" v-on:click="handleFill($event)">
            <label for="filed_age">年龄范围</label>
        </div>
        <div class="fillValue" v-else>
            <b>年龄范围</b>
            <input type="number" id="beginAge" min="0" max="100" autocomplete="off" style="width: 3em;"
                   v-model="ageRange.beginAge" v-on:keyup="clearMsg">
            <span class="tips">岁到</span>
            <input type="number" id="endAge" min="0" max="100" autocomplete="off" style="width: 3em;"
                   v-model="ageRange.endAge" v-on:keyup="clearMsg">
            <span class="tips"></span>
            <button class="cancelBut" name="fill_age" v-on:click="handleFill($event)">取消</button>
        </div>

        <div class="selFiled" v-if="!showFillBox.fill_mobile">
            <input type="checkbox" name="fill_mobile" id="filed_mobile" v-on:click="handleFill($event)">
            <label for="filed_mobile">手机号码</label>
        </div>
        <div class="fillValue" v-else>
            <b>手机号码</b>
            <input type="tel" id="mobile" autocomplete="off" v-model="selFields.mobile" v-on:keyup="clearMsg">
            <button class="cancelBut" name="fill_mobile" v-on:click="handleFill($event)">取消</button>
        </div>
        <div class="selFiled" style="width: max-content;margin-top: 0.8em;">
            <span style="color: red; font-size: smaller;">{{msg}}</span>
        </div>
        <div class="selButBox">
            <button id="selBut" style="margin: 2em 1em 0 0" v-on:click="checkForm">开始查询</button>
            <button id="cancelSelBut" onclick="history.back()">取消查询</button>
        </div>
    </div>
    <div id="queryResultBox" v-else>
        <my-table id="my-table">
            <my-thead slot="my-thead"></my-thead>
            <my-tbody slot="my-tbody" v-for="(user, index) in users" v-bind:user="user" v-bind:index="index"
                      v-bind:key="index"></my-tbody>
        </my-table>
        <div style="margin-top: 1em;">
            <button @click="continueQuery">继续查询</button>
            <button onclick="history.back()" style="display: inline-block;margin-left: 10px">返回主菜单</button>
        </div>
    </div>
</div>

点击变换为另一种形式,需要大量用到 v-if 判断,绑定点击事件,通过函数传递改变判断条件去实现这种效果。

查询结果展示表格,依然是套用之前写的 component,具体可以参考这里 哆啦A梦的传送门,这里就不作过多的赘述了。

CSS 代码

[v-cloak] {
    display: none;
}

h2 {
    font-weight: normal;
}

#selFiledBox {
    position: relative;
}

.selFiled {
    width: 8em;
    display: block;
    margin: 1.5em 4.5em 0;
}

.selFiled input {
    visibility: hidden;
}

.selFiled input[type="checkbox"] + label {
    position: relative;
    cursor: pointer;
}

.selFiled input + label::before {
    content: '';
    position: absolute;
    /*父元素为label,针对父元素进行绝对定位*/
    left: -24px;
    top: 4px;
    width: 12px;
    height: 12px;
    border: 1px solid #999;
    border-radius: 50%;
    /*圆角边框*/
    box-shadow: 0 0 6px #24b7e5;
}

.selFiled input:checked + label::after {
    content: '';
    position: absolute;
    /*针对label进行绝对定位*/
    left: -20px;
    top: 8px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    /*设置圆角*/
    background: #24b7e5;
}

.selButBox {
    position: absolute;
    left: 10em;
}

.fillValue {
    position: relative;
    margin-top: 1.5em;
}

.fillValue b {
    display: inline-block;
    width: 4em;
    padding: 5px;
}

.tips {
    font-size: small;
    font-weight: bold;
    color: #888;
}

.fillValue input[type="radio"] {
    margin: 5px 3px 5px 0;
}

样式比较简单粗糙,这里不做过多的渲染美化,如果对页面有较高要求的,可以自己多加完善页面展示细节哦 ~

Vue 核心代码

数据绑定部分

data: {
    selFiledBoxShow: true,
    title: '请选择你想要查询的字段',
    showFillBox: {
        fill_uid: false,
        fill_username: false,
        fill_name: false,
        fill_gender: false,
        fill_birthday: false,
        fill_age: false,
        fill_mobile: false
    },
    selFields: {},
    ageRange: {},
    users: [],
    msg: null
}
  • selFiledBoxShow => 是否显示可选择的字段名
  • title => 操作提示信息头
  • showFillBox => 是否显示可供输入的表单,初始默认全不显示
  • selFields => 用来存储选择过查询字段 image.png

点击取消按钮后,对应的属性值置为 undefined 来做一个标识,表示不想查询该字段。

image.png

  • ageRange => 由于年龄范围比较特殊,有两个输入框,这里单独拎出来,用来存储起末年龄,并通过该对象对起末年龄输入的数值进行校验和判断
  • users => 存放查询到的用户信息的数组
  • msg => 用户输入的反馈提醒消息

数据监听部分

watch: {
    selFields: {
        handler: function () {
            if (null !== this.msg) {
                this.msg = null;
            }
        },
        deep: true
    }
}

开启对 selFields 对象中的属性深度监听,一旦用户根据提醒消息修改查询内容,则将提醒消息隐藏,提升用户查询体验,真是一个贴心小功能呢!

方法定义部分

methods: {
    handleFill: function (e) {
        // 获取点击时该元素对象
        const object = e.target;
        var flag = object.name;
        var index = flag.indexOf('_');
        var inputId = flag.substring(index + 1);
        if (!this.showFillBox[flag]) {
            this.showFillBox[flag] = true;
            if (inputId === 'age') {
                this.ageRange.beginAge = null;
                this.ageRange.endAge = null;
                this.$nextTick(function () {
                    $('#beginAge').focus();
                });
            } else if (inputId !== 'gender') { // 避开特殊情况
                // 当页面切换之后,需要等待页面渲染完成,否则document.getElementById()不能获取到dom中的元素
                this.$nextTick(function () {
                    const target = $(`#${inputId}`);
                    target.focus();
                    // 这里的 null 表示,点击后未填写任何内容
                    this.selFields[inputId] = null;
                });
            } else {
                this.$nextTick(function () {
                    $('#male').click();
                });
            }
        } else {
            if (inputId === 'age') {
                this.ageRange.beginAge = undefined;
                this.ageRange.endAge = undefined;
            } else {
                // 点击取消按钮后,所选的属性值变为 undefined,表示不查询该字段
                this.selFields[inputId] = undefined;
            }
            if (this.msg !== null) {
                this.msg = null;
            }
            this.showFillBox[flag] = false;
        }
    },
    checkForm: function () {
        checkFillFields();
    },
    clearMsg: function () {
        if (null !== this.msg) {
            this.msg = null;
        }
    },
    continueQuery: function () {
        this.selFiledBoxShow = true;
    }
}

handleFill() 方法主要的作用是处理点击弹出查询框以及标志一些用户点击后但不想查询的字段,使用 nullundefined 区分。此外,这里有个细节就是当查询输入框出现后,立刻聚焦,等待用户输入。

clearMsg() 方法绑定在 keyup 按键按起时触发执行,这里是将提醒消息给实时隐藏掉。

continueQuery() 方法很简单,就是让用户重新选择查询字段,将当前查询结果数据表格重新隐藏,显示可供选择的查询字段,等待用户第二次进行任意条件组合查询。

checkForm() 方法通过 checkFillFields() 函数检查填写的字段值,完成对用户输入数据校验。首先是通过 findEmptyKey() 函数找到选择了该字段但未填写或填写格式错误的 key ,对应地提醒用户修改查询内容或者取消选择查询的字段。最后,如果验证都通过了,提交查询数据到后台进行查询筛选对符合条件的用户列表进行呈现。

function checkFillFields() {
    if (!doNothing()) {
        switch (findEmptyKey()) {
            case 'uid':
                vm.msg = '查询的用户ID不能为空!';
                break;
            case 'username':
                vm.msg = '查询的用户名不能为空!';
                break;
            case 'name':
                vm.msg = '查询的姓名不能为空!';
                break;
            case 'birthday':
                vm.msg = '查询的出生年月不能为空!';
                break;
            case 'mobile':
                vm.msg = '查询的手机号码不能为空!';
                break;
            default:
                if (vm.ageRange.beginAge !== undefined) {
                    if (vm.ageRange.beginAge === null || vm.ageRange.endAge === null || vm.ageRange.beginAge.match(/^[ ]*$/) || vm.ageRange.endAge.match(/^[ ]*$/)) {
                        vm.msg = '请填写查询的起末年龄范围!';
                        return;
                    }else if (parseInt(vm.ageRange.beginAge) > parseInt(vm.ageRange.endAge)) {
                        vm.msg = '你是不是傻?';
                        return;
                    }
                }
                if (vm.selFields.mobile !== undefined) {
                    if (!(/^1([345789])\d{9}$/.test(vm.selFields.mobile))) {
                        // 手机格式的检验
                        vm.msg = '查询的手机号码格式有误!';
                        return;
                    }
                }
                const selFields = vm.selFields;
                const ageRange = vm.ageRange;
                // 数据验证完毕
                $.ajax({
                    type: 'POST',
                    url: '/user/customQueryUserInfo',
                    data: JSON.stringify({
                        //把两个对象作为一个新对象的属性传给后端
                        "selFields": selFields,
                        "ageRange": ageRange
                    }),
                    dataType: "json",
                    contentType: "application/json;charset=utf-8",
                    success: function (data) {
                        handleQueryResult(data);
                    },
                    error: function (error) {
                        alert(error);
                    }
                });
        }
    } else {
        vm.msg = '请先选择查询字段!';
    }
}

// 判断是否表单是否为修改为空,是则返回对应的 key
function findEmptyKey() {
    const entries = Object.entries(vm.selFields);
    var key = null;
    for (const [k, v] of entries) {
        if (v !== undefined) {
            if (v === null) {
                key = k;
                break;
            } else {
                if ((v.toString()).match(/^[ ]*$/)) {
                    key = k;
                    break;
                }
            }
        }
    }
    return key;
}

其中,还有一个 doNothing() 函数用来检查属性值是否都是 undefined,则表示用户可能未选择任何字段或者选择了但是又全部取消了,判断用户有没有选择至少一个字段,如果没有,则告诉用户请先选择查询字段以开始。

function doNothing() {
    var flag = false;
    const values1 = Object.values(vm.selFields);
    const values2 = Object.values(vm.ageRange);
    if (values1.length !== 0) {
        if (values2.length !== 0) {
            if (checkFun(new Set(values1)) && checkFun(new Set(values2))) {
                flag = true;
            }
        } else {
            if (checkFun(new Set(values1))) {
                flag = true;
            }
        }
    } else {
        if (values2.length === 0) {
            flag = true;
        } else if (checkFun(new Set(values2))) {
            flag = true;
        }
    }
    return flag;
}

function checkFun(set) {
    var flag = false;
    if (set.size === 1) {
        set.forEach((value, key) => flag = value === undefined);
    }
    return flag;
}

结尾

到此,这个任意条件组合查询小案例就实现完成了,小案例学 Vue 系列 到此结束,这个系列含盖了这四种主要且常见的业务模块,但在具体的项目中还是要根据实际情况灵活使用,不管怎么变,只要基础打得牢固,任何需求都不在话下,通通实现 ^_^

撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。