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的作者學習地方。