zoukankan      html  css  js  c++  java
  • Vue.js——基于$.ajax实现数据的跨域增删查改

    概述

    之前的开发中示例数据都是local的,实际都是服务端的数据。

    前端和服务端之间的数据交互一般是通过ajax请求来完成的。

    说起ajax请求,大家第一时间会想到jQuery。除了拥有强大的DOM处理能力,jQuery提供了较丰富的ajax处理方法,它不仅支持基于XMLHttpRequest的ajax请求,也能处理跨域的JSONP请求。

    Vue.js和jQuery一起使用基本没有冲突,尽可放心大胆地使用。

    本文的主要内容如下:

    • 同源策略和跨域概念
    • 跨域资源共享
    • JSONP概念
    • REST Web Services
    • 基于$.ajax实现跨域GET请求
    • 基于$.ajax实现JSONP请求
    • 基于$.ajax实现跨域POST请求
    • 基于$.ajax实现跨域PUT请求
    • 基于$.ajax实现跨域DELETE请求

    本文的服务端程序和客户端程序是部署在不同服务器上的,本文所有示例请求都是跨域的。

    基础概念

    同源策略和跨域概念

    同源策略(Same-orgin policy)限制了一个源(orgin)中加载脚本或脚本与来自其他源(orgin)中资源的交互方式。

    如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(orgin)。

    同源之外的请求都可以称之为跨域请求。

    下表给出了相对http://store.company.com/dir/page.html同源检测的示例:

     我们可以简单粗暴地理解为同一站点下的资源相互访问都是同源的,跨站点的资源访问都是跨域的。

    跨域资源共享

    跨域资源共享(CORS)是一份浏览器技术的规范,提供了Web服务器从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是JSONP模式的现代版。

    与JSONP不同,CORS除了支持GET方法以外,还支持其他HTTP方法。

    用CORS可以让网页设计师用一般的XMLHTTPRequest,这种方式的错误处理比JSONP要来的好。

    另一方面,JSONP可以在不支持CORS的老旧浏览器上运作,现代的浏览器都支持CORS。

    JSONP概念

    由于同源策略,一般来说不允许JavaScript跨域访问其他服务器的页面对象,但是HTML的<script>元素是一个例外

    利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON资料,而这种使用模式就是所谓的 JSONP。

    用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

    REST Web Services简介

    HTTP协议是Web的标准之一,HTTP协议包含一些标准的操作方法,例如:GET, POST, PUT, Delete等,用这些方法能够实现对Web资源的CURD操作,下表列出了这些方法的操作定义。

     在REST应用中,默认通过HTTP协议,并且使用GET、POST、PUT和DELETE方法对资源进行操作,这样的设计风格和Web标准完全契合。

    REST的最佳应用场景是公开服务,使用HTTP并遵循REST原则的Web服务,我们称之为RESTful Web Service。

    RESTful Web Service从以下三个方面进行资源定义

    • 直观简短的资源地址:URI,比如:http://example.com/resources/
    • 传输的资源:Web Service接受与返回的互联网媒体类型,比如JSON,XML等
    • 对资源的操作:Web Service在该资源上所支持的一系列请求方法(比如:GET,POST,PUT或Delete)

    下图展示了RESTful Web Service的执行流程

     本文的服务端程序是基于ASP.NET Web API构建的。
    在了解了这些基础知识后,我们就分别来构建服务端程序和客户端程序吧。

    服务端环境准备

    创建组件和AjaxHelper

    本文的示例仍然是表格组件的CURD,只不过这次咱们的数据是从服务端获取的。
    在实现数据的CURD之前,我们先准备好一些组件和Ajax帮助方法。

    创建和注册simple-grid组件

    simple-grid组件用于绑定和显示数据

    <template id="grid-template">
        <table>
            <thead>
                <tr>
                    <th v-for="col in columns">
                        {{ col | capitalize}}
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(index,entry) in dataList">
                    <td v-for="col in columns">
                        {{entry[col]}}
                    </td>
                </tr>
            </tbody>
        </table>
    </template>
    
    <script src="js/vue.js"></script>
    <script>
        Vue.component('simple-grid', {
            template: '#grid-template',
            props: ['dataList', 'columns']
        })
    </script>

    创建和注册modal-dialog组件

    数据的新建和编辑将使用模态对话框,modal-dialog组件的作用就在于此。

    <template id="dialog-template">
        <div class="dialogs">
            <div class="dialog" v-bind:class="{ 'dialog-active': show }">
                <div class="dialog-content">
                    <div class="close rotate">
                        <span class="iconfont icon-close" @click="close"></span>
                    </div>
                    <slot name="header"></slot>
                    <slot name="body"></slot>
                    <slot name="footer"></slot>
                </div>
            </div>
            <div class="dialog-overlay"></div>
        </div>
    </template>
    
    <script>
        Vue.component('modal-dialog', {
            template: '#dialog-template',
            props: ['show'],
            methods: {
                close: function() {
                    this.show = false
                }
            }
        })
    </script>

    AjaxHelper

    基于$.ajax声明一个简单的AjaxHelper构造器,

    AjaxHelper构造器的原型对象包含5个方法,分别用于处理GET, POST, PUT, DELETE和JSONP请求。

    function AjaxHelper() {
    	this.ajax = function(url, type, dataType, data, callback) {
    		$.ajax({
    			url: url,
    			type: type,
    			dataType: dataType,
    			data: data,
    			success: callback,
    			error: function(xhr, errorType, error) {
    				alert('Ajax request error, errorType: ' + errorType +  ', error: ' + error)
    			}
    		})
    	}
    }
    AjaxHelper.prototype.get = function(url, data, callback) {
    	this.ajax(url, 'GET', 'json', data, callback)
    }
    AjaxHelper.prototype.post = function(url, data, callback) {
    	this.ajax(url, 'POST', 'json', data, callback)
    }
    
    AjaxHelper.prototype.put = function(url, data, callback) {
    	this.ajax(url, 'PUT', 'json', data, callback)
    }
    
    AjaxHelper.prototype.delete = function(url, data, callback) {
    	this.ajax(url, 'DELETE', 'json', data, callback)
    }
    
    AjaxHelper.prototype.jsonp = function(url, data, callback) {
    	this.ajax(url, 'GET', 'jsonp', data, callback)
    }
    
    AjaxHelper.prototype.constructor = AjaxHelper

    实现GET请求

    发送get请求

    var ajaxHelper = new AjaxHelper()
    
    var demo = new Vue({
        el: '#app',
        data: {
            gridColumns: ['customerId', 'companyName', 'contactName', 'phone'],
            gridData: [],
            apiUrl: 'http://localhost:15341/api/Customers'
        },
        ready: function() {
            this.getCustomers()
        },
        methods: {
    
            getCustomers: function() {
                // 定义vm变量,让它指向this,this是当前的Vue实例
                var vm = this,
                    callback = function(data) {
                        // 由于函数的作用域,这里不能用this
                        vm.$set('gridData', data)
                    }
                ajaxHelper.get(vm.apiUrl, null, callback)
            }
        }
    })

    由于客户端和服务端Web API是分属于不同站点的,它们是不同的源,这构成了跨域请求。
    这时请求是失败的,浏览器会提示一个错误:
    No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1::8020' is therefore not allowed access.         

    跨域解决方案

    现在碰到了请求跨域的问题,结合前面讲的一些概念,我们大致可以猜到解决跨域请求的两种方式

    • 在服务端启用CORS。
    • 让无服务端拥有处理JSONP的能力。

    这两种跨域解决方案的区别是什么呢?

    • JSONP只支持GET请求;CORS则支持GET、POST、PUT、DELETE等标准的HTTP方法
    • 使用JSONP时,服务端要处理客户端请求的callback参数("callback"这个名称是可以指定的);而使用CORS则不需要提供这样的处理。
    • JSONP从服务端获取到的是script文件;CORS则是一段XML或JSON或其他格式的数据
    • JSONP支持IE8, IE9复古的浏览器;CORS则支持现代主流的浏览器

    选择JSONP还是CORS?除了极少数的情况,我们都应当选择CORS作为最佳的跨域解决方案。

    启用CORS

    在服务端启用cors,实现允许跨域。

    再刷新页面,现在数据可以正常显示了。

    文章开头也说了,CORS的跨域方式支持现代的主流浏览器,但是不支持IE9及以下这些古典的浏览器。

    如果我们偏要在IE9浏览器中(Vue.js不支持IE8及以下的浏览器,所以不考虑IE 6,7,8)实现跨域访问,该怎么做呢?
    答案是使用JSONP请求。

    实现JSONP请求

    将getCustomers方法的get请求变更为jsonp请求:

    getCustomers: function() {
        // 定义vm变量,让它指向this,this是当前的Vue实例
        var vm = this,
            callback = function(data) {
                // 由于函数的作用域,这里不能用this
                vm.$set('gridData', data)
            }
        ajaxHelper.jsonp(vm.apiUrl, null, callback)
    }

    刷新页面,请求虽然成功了,但画面没有显示数据,并且弹出了请求错误的消息。

    让WebAPI支持JSONP请求

    在Nuget Package Manager中输入以下命令:

    Install-Package WebApiContrib.Formatting.Jsonp
    

    然后在Global.asax的Application_Start()方法中注册JsonpMediaTypeFormatter:

    // 注册JsonpMediaTypeFormatter,让WebAPI能够处理JSONP请求
    config.Formatters.Insert(0, new JsonpMediaTypeFormatter(jsonFormatter));

    刷新页面,使用JSONP请求也能够正常显示数据了。

    实现POST请求

    1. 创建表单对话框

    添加一个Create按钮,然后使用modal-dialog组件创建一个表单对话框:

    <div id="app">
    
    	<!--...已省略-->
    	<div class="container">
    		<div class="form-group">
    			<button @click="this.show = true">Create</button>
    		</div>
    	</div>
    	<modal-dialog v-bind:show.sync="show">
    
    		<header class="dialog-header" slot="header">
    			<h1 class="dialog-title">Create New Customer</h1>
    		</header>
    
    		<div class="dialog-body" slot="body">
    			<div v-show="item.customerId" class="form-group">
    				<label>Customer Id</label>
    				<input type="text" v-model="item.customerId" disabled="disabled" />
    			</div>
    			<div class="form-group">
    				<label>Company Name</label>
    				<input type="text" v-model="item.companyName" />
    			</div>
    
    			<div class="form-group">
    				<label>Contact Name</label>
    				<input type="text" v-model="item.contactName" />
    			</div>
    
    			<div class="form-group">
    				<label>Phone</label>
    				<input type="text" v-model="item.phone" />
    			</div>
    			<div class="form-group">
    				<label></label>
    				<button @click="createCustomer">Save</button>
    			</div>
    		</div>
    	</modal-dialog>
    </div>
    

      注意:新建Customer时,由于item.customerId为空,所以item.customerId关联的表单不会显示

          在修改Customer时,item.customerId关联的表单会显示出来。另外,item.customerId是不可编辑的。

    2. 修改Vue实例

    var demo = new Vue({
        el: '#app',
        data: {
            show: false,
            gridColumns: [{
                name: 'customerId',
                isKey: true
            }, {
                name: 'companyName'
            }, {
                name: 'contactName'
            }, {
                name: 'phone'
            }],
            gridData: [],
            apiUrl: 'http://localhost:15341/api/Customers',
            item: {}
        },
        ready: function() {
            this.getCustomers()
        },
        methods: {
            // ... 已省略
            createCustomer: function() {
                var vm = this,
                    callback = function(data) {
                        vm.$set('item', {})
                            // 添加成功后,重新加载页面数据
                        vm.getCustomers()
                    }
                    // 将vm.item直接POST到服务端
                ajaxHelper.post(vm.apiUrl, vm.item, callback)
                this.show = false
            }
        }
    })

    修改Vue实例的data选项:

    • 添加show属性:用于显示或隐藏表单对话框
    • 修改gridColumns属性:列包含两个属性,name表示列名称,isKey表示列是否为主键列
    • 添加item属性:用于新增Customer或修改Customer

    添加createCustomer方法:

    createCustomer: function() {
        var vm = this,
            callback = function(data) {
                vm.$set('item', {})
                // 添加成功后,重新加载页面数据
                vm.getCustomers()
            }
            // 将vm.item直接POST到服务端
            ajaxHelper.post(vm.apiUrl, vm.item, callback)
            this.show = false
    }

    实现PUT请求

    1. 修改sample-grid的template

    给主键列添加链接,绑定click事件,事件指向loadEntry方法,loadEntry方法用于加载当前选中的数据。

    <template id="grid-template">
        <table>
            <thead>
                <tr>
                    <th v-for="col in columns">
                        {{ col.name | capitalize}}
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(index,entry) in dataList">
                    <td v-for="col in columns">
                        <span v-if="col.isKey"><a href="javascript:void(0)" @click="loadEntry(entry[col.name])">{{ entry[col.name] }}</a></span>
                        <span v-else>{{ entry[col.name] }}</span>
                    </td>
                </tr>
            </tbody>
        </table>
    </template>

    2. 修改simple-grid的methods选项

    在simple-grid的methods选项下,添加一个loadEntry方法,

    该方法调用$.dispatch向父组件派发事件load-entry

    并将key作为事件的入参,load-entry是绑定在父组件的事件名称。

    Vue.component('simple-grid', {
        template: '#grid-template',
        props: ['dataList', 'columns'],
        methods: {
            loadEntry: function(key) {
                this.$dispatch('load-entry', key)
            }
        }
    })

    3. 修改Vue实例的HTML

    在Vue实例的simple-grid标签上绑定自定义事件load-entryload-entry事件指向loadCustomer方法,loadCustomer方法用于加载选中的Customer数据。

    <div id="app">
        <!--...已省略-->
        <div class="container">
            <simple-grid :data-list="gridData" :columns="gridColumns" v-on:load-entry="loadCustomer">
            </simple-grid>
        </div>
        <!--...已省略-->
    </div>

    我们应将2和3结合起来看,下图阐述了从点击simple-grid数据的链接开始,到最终打开对话框的完整过程:

     注意:load-entry是Vue实例的事件,而不是simple-grid组件的事件,尽管load-entry是写在simple-grid标签上的。详情请参考上一篇文章的编译作用域

    由于在新建模式和修改模式下标题内容是不同的,因此需要修改modal-dialog的slot="header"部分。
    根据item.customerId是否有值确定修改模式和新建模式,修改模式下显示"Edit Customer - xxx",新建模式下显示"Create New Customer"

    <modal-dialog v-bind:show.sync="show">
    
        <header class="dialog-header" slot="header">
            <h1 class="dialog-title">{{ item.customerId ? 'Edit Customer - ' + item.contactName : 'Create New Customer' }}</h1>
        </header>
    </modal-dialog>

    4. 修改Vue实例

    为data选项添加title属性,用于显示对话框的标题。

    var demo = new Vue({
        el: '#app',
        data: {
            // ...已省略
            title: ''
            // ...已省略
        }
        // ...已省略
    })

    然后追加3个方法:loadCustomersaveCustomerupdateCustomer

    loadCustomer: function(customerId) {
        var vm = this
        vm.gridData.forEach(function(item) {
            if (item.customerId === customerId) {
                // 使用$.set设置item
                vm.$set('item', item)
                return
            }
        }),
        vm.$set('show', true)
    },
    saveCustomer: function() {
        this.item.customerId ? this.updateCustomer() : this.createCustomer()
        this.show = false
    },
    updateCustomer: function() {
        // 定义vm变量,让它指向this,this是当前的Vue实例
        var vm = this,
            callback = function(data) {
                // 更新成功后,重新加载页面数据
                vm.getCustomers()
            }
        // 将vm.item直接PUT到服务端
        ajaxHelper.put(vm.apiUrl + '/' + vm.item.customerId, vm.item, callback)
    }

    saveCustomer方法根据item.customerId是否有值确定修改模式和新建模式,如果是新建模式则调用createCustomer方法,如果是修改模式则调用updateCustomer方法。

    另外,需要将Save按钮的click事件绑定到saveCustomer方法。

    <div class="dialog-body" slot="body">
        <!--...已省略-->
        <div class="form-group">
            <label></label>
            <button @click="saveCustomer">Save</button>
        </div>
        <!--...已省略-->
    </div>

    5. 添加$watch

    使用$watch跟踪data选项show属性的变化,每当关闭对话框时就重置item。

    demo.$watch('show', function(newVal, oldVal){
        if(!newVal){
            this.item = {}
        }
    })

    为什么要这么做呢?

    因为每次打开对话框,不知道是以新建模式还是修改模式打开的,

    如果不重置item,倘若先以修改模式打开对话框,再以新建模式打开对话框,

    新建模式的对话框将会显示上次打开的数据。

    实现DELETE请求

    1. 修改simple-grid组件

    在methods选项中添加方法deleteEntry

    deleteEntry: function(entry) {
        this.$dispatch('delete-entry', entry)
    }

    调用$.dispatch向父组件派发事件delete-entry

    2. 修改Vue实例的HTML

    在simple-grid标签上绑定自定义事件delete-entry,该事件指向deleteCustomer方法。

    <div id="app">
        <!--...已省略-->
        <div class="container">
            <simple-grid :data-list="gridData" :columns="gridColumns" 
                v-on:load-entry="loadCustomer" 
                v-on:delete-entry="deleteCustomer">
            </simple-grid>
        </div>
        <!--...已省略-->
    </div>

    总结

    本篇介绍了同源策略、跨域、CORS和REST等概念,并结合Vue.js和$.ajax实现了一个简单的CURD跨域示例。
    下一篇,我们将使用Vue的插件vue-resource来完成这些工作。

    转自:https://www.cnblogs.com/keepfool/p/5648674.html

  • 相关阅读:
    团队项目的NABC(截图软件)
    《梦断代码》读后感_3
    《梦断代码》读后感_2
    毕设今日总结(二)
    毕业设计今日总结(一)
    QT中文乱码解决方法
    课堂练习——最大联通之数组
    《浪潮之巅》读书笔记3
    《浪潮之巅》读书笔记2
    《浪潮之巅》读书笔记1
  • 原文地址:https://www.cnblogs.com/Vincent-yuan/p/13263603.html
Copyright © 2011-2022 走看看