python工业互联网应用实战18—前后端分离模式之jquery vs vue

  前面我们分三章来说明了使用django template与jquery的差别,通过jquery如何来实现前后端的分离,同时再9章节使用vue.js 我们浅尝辄止的介绍了JQueryvue的切换,由于监控界面没有数据提交,无法很好的体现处我说的vue的优势,所以笔者增加本章节来进一步的对比两者异同点(没有对比就没有伤害),读者可以通过代码去体会vue的优势。vue作为精简版本的MVVM,完成双向绑定后我们编程的时候可以更专注于model本身,而不像JQuery还需要知道DOM元素的标签去读写值。两者都是前端脚本框架,实际项目中,有些时候我们会混合使用两者的。

1.1. 重构模板tasks.html

  说干就干,我们通过重构tasks.html模板切换到vue模式下来理解和对比两者的差异吧,MVVM要点之一:MVVM 将数据双向绑定(data-binding)作为核心思想,View Model 之间没有联系,它们通过 ViewModel 这个桥梁进行交互。

<!DOCTYPE html>
<html lang="en" xmlns="//www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>任务列表</title>
    <link href="//cdn.bootcss.com/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
     <script src="//cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="//cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div id="content-main" class="container">
    <div><a href='0/change/'>新增</a></div>

    {% csrf_token %}
        <table id="id_task_table">
            <tr>
                <th>ID</th>
                <th>任务号</th>
                <th>源地址</th>
                <th>目标地址</th>
                <th>条码</th>
                <th>状态</th>
                <th>优先级</th>
                <th>开始时间</th>
                <th>结束时间</th>
                <th>作业数量</th>
                <th>操作</th>
            </tr>
            <tr v-for="task in taskList">  <!---->
                <td>[[task.TaskId]]</td>
                <td><a :href="task.TaskId + '/view/'">[[task.TaskNum]]</a></td>
                <td>[[task.Source]]</td>
                <td>[[task.Target]]</td>
                <td>[[task.Barcode]]</td>
                <td>[[task.State]]</td>
                <td>[[task.Priority]]</td>
                <td>[[task.BeginDate]]</td>
                <td>[[task.EndDate]]</td>
                <td>[[task.SystemDate]]</td>
                <td>[[task.JobCount]]</td>
                <td>[[task.JobCount]]</td>
                <td><a :id="task.TaskId+'-decompose'" :href="task.TaskId+'/decompose/'">分解</a> <a :id="task.TaskId+'-start'" :href="task.TaskId+'/start/'">下达</a> <a :id="task.TaskId+'-change'" :href="task.TaskId+'/change/'">修改</a> <a :id="task.TaskId+'-delete'" href='#' @click="taskDel(task.TaskId)">删除</a></td>
            </tr>
        </table>
    </div>
<script>

    var vm = new Vue({
        el: '#content-main',
        data: {
            taskList: [],
        },
        delimiters: ['[[', ']]'], 
        mounted() {
            this.getData()
        },
        methods: {
            getData: function () {
                _this = this
                //本例使用ajax实现vue的异步请求
                $.ajax({
                    url: "/task/taskGetList/", success: function (result) {
                        d = result
                        _this.taskList = d.rows    //
                        
                        //for (const task of d.rows) {
                        //    var row = "";
                        //    row += "<tr>";
                        //    row += "<td>" + task.TaskId + "</td>";
                        //    row += "<td><a href='" + task.TaskId + "/view/'>" + task.TaskNum + "</a></td>";//①
                        //    row += "<td>" + task.Source + "</td>";
                        //    row += "<td>" + task.Target + "</td>";
                        //    row += "<td>" + task.Barcode + "</td>";
                        //    row += "<td>" + task.State + "</td>";
                        //    row += "<td>" + task.Priority + "</td>";
                        //    row += "<td>" + (task.BeginDate ? task.BeginDate : '-') + "</td>";
                        //    row += "<td>" + (task.EndDate ? task.EndDate : '-') + "</td>";
                        //    row += "<td>" + task.SystemDate + "</td>";
                        //    row += "<td>" + task.JobCount + "</td>";
                        //    row += "<td><a id='" + task.TaskId + "-decompose' href='" + task.TaskId + "/decompose/'>分解</a> <a id='" + task.TaskId + "-start' href='" + task.TaskId + "/start/'>下达</a>"
                        //        + (task.State == '未处理' ? " <a id='" + task.TaskId + "-change' href='" + task.TaskId + "/change/'>修改</a>" : "")
                        //        + " <a id='" + task.TaskId + "-delete' href='#' onclick=taskDel(" + task.TaskId + ")>删除</a></td>";  //②
                        //    row += "</tr>";
                        //    $("#id_task_table tbody").append(row);

                        //}
                    }
                });
            },
            taskDel: function (pk) {
                //异步从后台获得值
                url_str = "/task/taskDel/"
                data = {
                    pk: pk,
                    csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val()
                }
                $.ajax({
                    type: 'POST', url: url_str, data: data, success: function (result) {
                        //alert('HEHE')
                        window.location.replace("/task/"); 
                    }
                });
            },

        },
    });            

</script>
</body>
</html>

  标注①:原来jquery写DOM标签的赋值操作改成了vue的循环模板绑定;标注②:ajax异步请求获得的数据集直接赋值给vue model层即可,vue MVVM会感知数据变化而把数据同步渲染到view层。

  上代码如上菜,相对于一大堆原理图,阅读代码、比较代码如直接下筷!自行品味vue是否可口可乐!于笔者这个改变确实爽点很多!

1.1.1. 运行结果

  代码改动不是很大的情况下,我们任务列表不知不觉中就迁移到MVVM模式下了,刚开始使用vue的时候没注意,后来vue是国人写的,那个眼睛瞪大了很久,只要写代码也能基本衣食无忧,我们还是能出牛人的嘛。哎,那帮踢球的不早就衣食无忧了!?

1.2. 重构模板taskChange.html

  上面小节我们见识了Vuemodel层变化后数据自动同步到view的方向,双向绑定的另一个面就是: View 的变化会自动同步到 Model,当用户操作 ViewViewModel 感知到变化,然后自动更新 Model层数据,我们提交数据到后台直接提交model层即可。下面我们来改造taskChange.html支持vue吧。

<!DOCTYPE html>
<html lang="en" xmlns="//www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
    <link href="//cdn.bootcss.com/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="//cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="//cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id="content-main" class="container">
        <h1>任务详情</h1>
        <div class="row"><div><b>对象标识:</b></div><div id="task_id">{{pk}}</div></div>        
        <form method="post" id="edit_form"> 
            <div class="row"><div><b>任务号:</b></div><input v-bind:disabled="pk>0" v-model="model.TaskNum"  /></div> <!---->
            <div class="row"><div><b>源地址:</b></div><input   v-model="model.Source"/></div>
            <div class="row"><div><b>目标地址:</b></div><input  v-model="model.Target" /></div>
            <div class="row"><div><b>条码:</b></div><input  v-bind:disabled="pk>0" v-model="model.Barcode"/><input type="button" value="提交" @click="saveData()"></div>
           {% csrf_token %}
        </form>
        <div class="row"><div><b>任务状态:</b></div><div id="state">[[model.State]]</div></div>
        <div class="row"><div><b>优先级:</b></div><div id="priority">[[model.Priority]]</div></div>
        <div class="row"><div><b>开始时间:</b></div><div id="begin_date">[[model.BeginDate]]</div></div>
        <div class="row"><div><b>结束时间:</b></div><div id="end_date">[[model.EndDate]]</div></div>
        <div class="row"><div><b>作业数量:</b></div><div id="job_count">[[model.JobCount]]</div></div>
    </div>
    <script>

    var vm = new Vue({
        el: '#content-main',
        data: {
            model: {},
            pk: parseInt($('#task_id').text()), 

        },
        delimiters: ['[[', ']]'], 
        mounted() {
            this.getData()
        },

        methods: {
            getData: function () {
                _this = this
                url_str = "/task/taskGet/" + this.pk + '/'
                $.ajax({
                    url: url_str, success: function (result) {
                        _this.model = result.model
                           // ③  注意不在jquery写元素值
                        //if (task.pk) {   修改
                        //    $('#id_task_num').removeAttr('disabled')
                        //    $('#id_barcode').removeAttr('disabled')

                        //} else { // 新增
                        //    $('#id_task_num').attr('disabled',true)
                        //    $('#id_barcode').attr('disabled',true)
                        //}

                        //$('#id_source').val(task.Source)
                        //$('#id_target').val(task.Target)
                        //$('#id_task_num').val(task.TaskNum)
                        //$('#id_barcode').val(task.Barcode)

                        //$('#state').text(task.State)
                        //$('#priority').text(task.Priority)
                        //$('#begin_date').text(task.BeginDate)
                        //$('#end_date').text(task.EndDate)
                        //$('#job_count').text(task.JobCount)
                    }
                });
            },
            taskDel: function (pk) {
                url_str = "/task/taskDel/"
                data = {
                    pk: pk,
                    csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val()
                }
                $.ajax({
                    type: 'POST', url: url_str, data: data, success: function (result) {
                        window.location.replace("/task/"); 
                    }
                });
            },
            saveData:function() {
                //异步提交数据到后台
                url_str = "/task/taskSave/" + this.pk + '/' 
                //data = {
                //    source: $('#id_source').val(),
                //    target: $('#id_target').val(),
                //    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val() 
                //}
                //if ($('#task_id').text() <= 0)
                //    data.taskNum = $('#id_task_num').val()  
                //    data.barcode = $('#id_barcode').val() 
                
                // ④ 不再判断处理直接通过model获取提交后台值
                data = {
                    source: this.model.Source, target: this.model.Target, taskNum: this.model.TaskNum,
                    barcode: this.model.Barcode, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
                }
                $.ajax({
                    type: 'POST', url: url_str, data: data, success: function (result) {
                        window.location.replace("/task/");
                    }
                });
            },
        },
    });
    </script>
</body>
</html>

  标注①:原来jquery写DOM标签的赋值操作改成了vue v-model绑定,v-bind:disabled等等;标注②:获取通过django模板传过来的对象标识; 标注③:ajax异步请求获得的数据集直接赋值给vue model层,MVVM会感知数据变化而把数据同步渲染到view层。;标注④:提交数据到后台直接操作model值,后面会进一步优化提交方法。

  数据提交到后台同样也不在关注DOM上的元素和标签,直接操作model层即可,项目的实战中,为了简化交互赋值操作,我们常常采用获取的model修改属性值后直接提交回去的方式,这样就进一步简化了代码。

1.3. 简化提交代码

1.3.1. 前端代码

 saveData:function() {
                //异步提交数据到后台
                url_str = "/task/taskSave/" + this.pk + '/' 
                
                // ④ 不再判断处理直接通过model获取提交后台值
                //data = {
                //    source: this.model.Source, target: this.model.Target, taskNum: this.model.TaskNum,
                //    barcode: this.model.Barcode, csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()
                //}

                data = this.model
                data.csrfmiddlewaretoken=$("[name='csrfmiddlewaretoken']").val()

                $.ajax({
                    type: 'POST', url: url_str, data: data, success: function (result) {
                        window.location.replace("/task/");
                    }
                });

            },

  运行修改任务点击提交,调试窗口会提示后台服务器错误!

1.3.2. 后端代码

  错误的原因主要是因为,model属性大小写的问题,我们进一步重构后台代码来支持这一提交模式的变更。

def taskSave(request,pk):
    if int(pk) > 0:
        data={"Source":request.POST['Source'],"Target":request.POST['Target']}
        model = Task.objects.filter(pk=pk).update(**data)
    else:
        data={"Source":request.POST['Source'],"Target":request.POST['Target'],"Barcode":request.POST['Barcode'],\
              "TaskNum":request.POST['TaskNum'],  "State":1,"Priority":1,}
        model=Task.objects.create(**data)


    data={'total':1,'success':True}

    return  JsonResponse(data)

  简单的调整后端获取参数大小写,前端技术栈切换到vue了,采用vue我们大大的简化了提交数据的操作,由于MVVM模式实现UI端的业务逻辑分层,modelview就聚焦专注相应的功能领域,大大的提高了开发和维护效率。高内聚、低耦合这个开发的底层逻辑应该是我们日常开发过程需要构建的核心的思维模型!

1.4. 小结

  本章节vue vs jquery给我们总体的的印象也是好像改动不大嘛,咋个就切换技术栈了,当然没有完全说jquery就不用了,那个方便用那个,那个便于团队开发、协作和维护。代码写的简明又能搞定功能,应该是coder的追求之一吧。简单又好用,这点是vue成功的地方,也是该向vue的作者学习地方。