当前位置:   article > 正文

Ext JS Data Package_sencha extjs5.x 的数据包data package讲解

sencha extjs5.x 的数据包data package讲解

Data package让我们可以在我们的代码或者application中加载和保存数据。最重要的一点是,data package可以让我们链接或者绑定数据到Ext JS组件。data apckage是由多个类组成,其中最重要的三个类为

  • Ext.data.Model
  • Store
  • Ext.data.proxy.Proxy
    每个application差不多都会使用上面的三个类, 它们受到很多卫星类(围绕它们)的支持, 如下图所示

这里写图片描述

Ajax

在学习data package 之前,我们需要知道如何向服务器发送Ajax请求。

Ext JS提供了一个单例的对像Ext.Ajax, 我们可以通过它,向服务器发送请求

Ext.Ajax.request({
    url:"serverside/myfirstdata.json"
});
console.log("Next lines of code...");
  • 1
  • 2
  • 3
  • 4

以上的请求是异步的,它不会阻止下一行代码的执行,你也可以运行同步的Ajax请求 Ext.Aajx.async = false

更多Ajax的配置可以查看http://docs.sencha.com/extjs/6.0.2-classic/Ext.Ajax.html#cfg-async

在上面的代码中,我没有处理服务器响应,我们需要在ajax配置中,配置一个callback函数, 这个函数在服务器响应时执行,同时还有 success or failure。

Ext.Ajax.request({
    url:"serverside/myfirstdata.json",
    success: function(response,options){
        console.log('success function executed, here we can do some
        stuff !');
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code
        ' + response.status);
    },
    callback: function( options, success, response ){
        console.log('Callback executed, we can do some stuff !');
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

output

success function executed, here we can do some stuff !
Callback executed, we can do some stuff !
  • 1
  • 2

success会在服务器响应状态为200-299时执行,表示请求成功。如果响应为403, 404, 500, 503, 则执行failure方法。

success or failure都会接收两个参数,第一个参数是服务器响应对像, 通过它可以获得响应文本和响应头。第二个参数是我们对发起的Ajax请求的配置。在我们的例子中,options为URL, success, failure三个属性组成。

callback 函数将会一直执行,而不管是failure或者success. 并且这个函数接受三个参数: options 是请求时的配置, success是一个布尔值,如果请求成功,则为true, 否则为false. response参数是一个XMLHttpRequest对像,包含了响应的信息。

假设我们获得的响应为

{
    "success": true,
    "msg": "This is a success message..!"
}
  • 1
  • 2
  • 3
  • 4

对于success 来说, 响应返回的是纯文本的数据,我们需要将它解码为JSON数据

success: function(response,options){
    var data = Ext.decode(response.responseText);
    Ext.Msg.alert("Message", data.msg);
},
  • 1
  • 2
  • 3
  • 4

或者

callback: function( options, success, response ){
    if(success){
        var data= Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果我们请求的是一个xml资源,而不是JSON

<?xml version="1.0" encoding="UTF-8"?>
<response success="true">
    <msg>This is a success message in XML format</msg>
</response>
  • 1
  • 2
  • 3
  • 4

则代码为

Ext.Ajax.request({
    url:"myfirstdata.xml",
    success: function(response,options){
       var data = response.responseXML;
        var node = data.getElementsByTagName("msg")[0];
        Ext.Msg.alert("Message", node.firstChild.data);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code  ' + response.status);
    },
    callback: function( options, success, response ){
        console.log('Callback executed, we can do some stuff !');
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Passing parameters to Ajax request

为了相应的信息,我们需要向Ajax请求中传递需要的参数,我们将使用以下的代码来传递参数

//myfirstparams.php
<?php
    header('Content-Type:application/json');
    $output = array(
        "msg"=> "Message response text using the following params:<br> x=" . $_POST["x"] . ", y=" . $_POST["y"]
    );
echo json_encode($output);

//index.html

Ext.Ajax.request({
    url:"myfirstparams.php",
    method: "POST", //默认为GET
    params: {
        x: 200,
        y: 300,
    },
    success: function(response,options){
       var data = Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code  ' + response.status);
    },
    callback: function( options, success, response ){
        console.log('Callback executed, we can do some stuff !');
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Setting timeout to Ajax request calls

有时,服务器可能会长时间没有反应, 而Ext JS 默认等待响应的时间为30秒。根据需要,我们可以设置等待请求的时间

Ext.Ajax.request({
    url: "serverside/myfirstparams.php",
    method: 'POST',
    params: {x:200, y:300},
    timeout: 50000, 
    success: function(response,options){
        var data = Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code
        ' + response.status);
        Ext.Msg.alert("Message", 'server-side failure:' +
                response.status);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

现在我们知道如何获取数据,但我们还需要一种方法来处理数据。 Ext JS为我们提供了一个简单的方法来管理数据。

Models

Ext.data.Model是data package的核心。在application中, 模型表示一个实体。比如一个电子商务的app, 可能会有Users, Products, Orders等模型。简单的讲,一个模型就是定义了一组字段,以及相关的业务逻辑。

以下是构成模型的几个部分

  • Fields 数据的名称,类型和值
  • Proxies 用来persist data或pull data
  • Validations 检验数据的有效性
  • Associations 与其它model的关系

这里写图片描述

Creating a Model

在创建一个model时,最好先创建一个公共的基础类。这个basic class允许我们配置所有model类的公共部分,比如它的id以及id字段的类型,同时,schema也在这个类中配置。schema用来管理所有的model, 相当于mysql中的schema数据库,而每个model相当于一张表。这个基础类如下所示

Ext.Ajax.request({
    url: "serverside/myfirstparams.php",
    method: 'POST',
    params: {x:200, y:300},
    timeout: 50000,
    success: function(response,options){
        var data = Ext.decode(response.responseText);
        Ext.Msg.alert("Message", data.msg);
    },
    failure: function(response,options){
        Ext.Msg.alert("Message", 'server-side failure with status code
        ' + response.status);
        Ext.Msg.alert("Message", 'server-side failure:' +
                response.status);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上面我们讲解了base model的重要性,接下来看看如何一步步的创建一个model

Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model', // step 1
    idProperty:'clientId ', // step 2
    fields:[// step 3
        {name: 'clientId', type: 'int'},
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'string'},
        {name: 'clientSince', type: 'date', dateFormat:'Y-m-d H:i'}
    ]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在第一步,我们让它继承于Ext.data.Model. 这类为所有的model提供了所有的功能。

第二步定义的属性每一条记录的ID, 在这里,我们使用的是clientId字段,如果我们在fields中没有定义clientId, model将会自动为我们默认产生一个id属性。

在第三步,我们为我们的模型定义了字段,这个属性是一个数组,数组中的每一个元素是一个对像,它包含了对每一个字段的配置。 在这里,我们只是设置了字段的名字和类型,在最后一个字段中,我们设置了一个dateFormat属性。

字段的更多配置可以查看Ext.data.field文档

数据的类型,包含以下几中

  • String
  • Integer
  • Float
  • Boolean
  • Date (使用此类型时,记得使用dateFormat属性,以确保解析正确的日期值)
  • Auto (表示对接受到的数据,不进行转换)

根据定义的model,我们创建一个数据

var myclient = Ext.create('Myapp.model.Client',{
    clientId:10001,
    name:'Acme corp',
    phone:'+52-01-55-4444-3210',
    website:'www.acmecorp.com',
    status:'Active',
    clientSince:'2010-01-01 14:35'
});
console.log(myclient);
console.log("My client's name is = " + myclient.data.name);
console.log("My client's website is = " + myclient.data.name);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

创建完一条数据记录后,我们可以使用get和set方法,读写这个记录每个字段的值

// GET METHODS
var nameClient = myclient.get('name');
var websiteClient = myclient.get('website');
console.log("My client's info= " + nameClient + " - " +
        websiteClient);
// SET Methods
myclient.set('phone','+52-01-55-0001-8888'); // single value
console.log("My client's new phone is = " +
        myclient.get('phone'));
myclient.set({ //Multiple values
    name: 'Acme Corp of AMERICA LTD.',
    website:'www.acmecorp.net'
});
console.log("My client's name changed to = " +  myclient.get("name"));
console.log("My client's website changed to = " +  myclient.get("website") );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

设置方法可以是单个的值,也是传递一个对像,同时设置多个值

其实所有的数据都是保存存在data属性上,我们应该使用get 和 set方法来读写数据,但因为某些原因,我们需要访问所有的数据时,我们可以使用这个data对像

//READ
console.log("My client's name:" + myclient.data.name);
console.log("My client's website:" + myclient.data.website);
// Write
myclient.data.name = "Acme Corp ASIA LTD.";
myclient.data.website = "www.acmecorp.biz";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

虽然可以通过这种方法设置,但这不是最佳实践,因为能过setting方法设置时,会执行更多的任务,比如,标识我们的模型为脏数据,保存上一个值,在此之后,我们可以拒绝或者接收改变,还有一个其它的重要步骤。

Mappings

当在model中定义了一个字段,我们可以为这个字段定义一个mapping属性,告诉字个字段从哪获取数据。

{
    "success" :"true",
    "id":"id",
    "records":[
        {
        "id": 10001,
        "name": "Acme corp2",
        "phone": "+52-01-55-4444-3210",
        "x0001":"acme_file.pdf"
        }
    ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

比如上面有一个”x0001”的字段,可以它在client模型中的名称为contractFileName, 所以我们需要使用mapping属性。

Ext.define('Myapp.model.Client',{
    extend: 'Ext.data.Model',
    idProperty: 'clientId ',
    fields:[
        {name: 'clientId', type: 'int' },
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'contractFileName', type: 'string', mapping:'x0001'}
    ]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Validations

从Ext JS 4开始,可以直接在model中对数据进行验证。

Ext.define('MyApp.model.User', {
    extend: 'Ext.data.Model',
    fields: ...,
    validators: {
        name: [  //需要校验的字段
            'presence',
            { type: 'length', min: 7 },
            { type: 'exclusion', list: ['Bender'] }
        ]
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Validators是一个对像,这个对像的每个键对应于要检验的字段名。每个字段的检验规则,可以为一个对像配置,或者这些配置组成的数组。在这个例子中,我们是验证name字段,它的长度为7个字符以上,它的值不能为 “Bender”.

有的验证规则,接收额外的配置 - 比如 length校验器,可以有min 和 max属性。 format 可以一个matcher等等。 以下是Ext JS内置的检验器,并且还可以自定义规则

  • Presence - 保证这个字段必须有值,空字符串无效
  • Length - 保证字符串的长度在min和max之前。min 和 max这两个约束条件是可选的
  • Format - 保证字符串匹配一个正则表达式format.
  • Inclusion - 保证一个值必须在指定的列表中,比如,gender只能为 male或者 female
  • Exclusion - 保证一个值不能出现在指定的列表中

更多的检验器,可以查看Ext.data.validator空间下的子类, 了解如何传递参数, 比如Presence, http://docs.sencha.com/extjs/6.0.2-classic/Ext.data.validator.Presence.html

接下来,让我们创建一个违返这些规则的一条记录

/ now lets try to create a new user with as many validation
// errors as we can
var newUser = new MyApp.model.User({
    id: 10,
    name: 'Bender'
});

// run some validation on the new user we just created
console.log('Is User valid?', newUser.isValid());

//returns 'false' as there were validation errors

var errors = newUser.getValidation(),
    error  = errors.get('name');

console.log("Error is: " + error);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在上面的代码中,最关键的函数是getValidation(), 它运行所有的检验规则,但只返回每一个字段,第一条没有通过的规则。这些检验记录是被懒性创建,只在需要的时候才会更新。在这里,错误为Length must be greater than 7.

当我们提供的名字超过规定的长度时

newUser.set('name', 'Bender Bending Rodriguez');
errors = newUser.getValidation();
  • 1
  • 2

这条记录符合所有的检验,它包含7个字符,同时也不跟列表中的Bender匹配。

newUser.isValid()将会返回true, 当我们调用getValidation(), 新的检验记录将被更新,数据不在是脏数据。它所有的字段都将设置为true.

以下是另一个例子

Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model',
    idProperty:'clientId ',
    fields:[
        {name: 'clientId', type: 'int' },
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'string'},
        {name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'}
    ],
    validators:{
        name:[
            { type:'presence'}
        ],
        website:[
            { type:'presence', allowEmpty:true},
            { type:'length', min: 5, max:250 }
        ]
    }
}); 

//Step 1
var myclient = Ext.create('Myapp.model.Client',{
    clientId : '10001',
    name : 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35'
});

if (myclient.isValid()){ //Step 2
    console.log("myclient model is correct");
}
console.log(myclient);
console.log("My client's name is = " + myclient.data.name);
console.log("My client's website is = " + myclient.data.website);
// SET methods //Step 3
myclient.set('name','');
myclient.set('website','');
if (myclient.isValid()){//Step 4
    console.log("myclient model is correct");
} else {
//Step 5
    console.log("myclient model has errors");
    var errors = myclient.validate();
    errors.each(function(error){
        console.log(error.field,error.message);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

在第5步,我们使用了validate方法,它将返回一个验证失败的集合。并且输出字段以及错误信息. 这个集合的类型为Ext.data.ErrorCollection, 它扩展于Ext.util.MixedCollection. 因此我们可以使用each方法来遍历每一条错误记录.

这里写图片描述

Custom field types

在我们的application中不同的model之前,经常需要反复使用某个字段类型,在 Ext JS 4在,我们可以通过自动义检验器实现。 但在Ext JS 5开始,推荐使用自定义字段类型来代替自定义检验器。能过以下代码,我们将创建一个自定义字段

Ext.define('Myapp.fields.Status',{
    extend: 'Ext.data.field.String', //Step 1
    alias: 'data.field.status',//Step 2
    validators: {//Step 3
    type: 'inclusion',
    list: [ 'Active', 'Inactive'],
    message: 'Is not a valid status value, please select the
        proper options[Active, Inactive]'
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 继承于Ext.data.field.String
  2. 我们为这个字段类型,定义了一个别名。这个别名不能重复,或者说覆盖一个已经存在于Ext.data.field中的子类
  3. 为这个字段类型设置校验器
Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model',
    idProperty:'clientId ',
    fields:[
        {name: 'clientId', type: 'int' },
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'status'}, //Using custom field
        {name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'}
    ],
    validators:{
            ...
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里,我们使用的是{name: 'status', type: 'status'}, 这是因为我们给自定义类型,取了一个别名(alias: ‘data.field.status’). 下面让我们创建一段代码来测试

var myclient = Ext.create('Myapp.model.Client',{
    clientId: '10001',
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35'
});
if(myclient.isValid()){
    console.log("myclient model is correct");
}
// SET methods
myclient.set('status','No longer client');
if(myclient.isValid()){
    console.log("myclient model is correct");
} else {
    console.log("myclient model has errors");
    var errors = myclient.validate();
    errors.each(function(error){
        console.log(error.field,error.message);
    });
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Relationships

我们可以创建模型之间的关系。比如,一个Client会接触多名employees, 享受多种Service.

  • Employees for contact(Client联系的员工, 包含name, title, gender, email, phone, cell phone, 等字段)
  • Services (service ID, service name, service price, branch where service provided)

在Ext JS中支持one-to-many, one-to-one, and many-to-many关联

关于更多Association Type,可以查看http://docs.sencha.com/extjs/6.2.0-classic/Ext.data.schema.Association.html

One-to-many associations
Ext.define('Myapp.model.Client',{
    extend:'Ext.data.Model', // step 1
    requires: ['Myapp.model.Employee'],
    idProperty:'id ',
    fields:[.... ],
    hasMany:{
        model:'Myapp.model.Employee',
        name:'employees',
        associationKey: 'employees'
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用了hasManay属性,我们定义了一个one-to-many的关系。hasMany属性可以是一个对像数组,每一个对像都包含一个model属性. 表明一个Client实例,可以有多个 Myapp.model.Employee对像。

此外,我们可以在创建Client类时,定义获得与它相关联model数据的函数名称。在这里,我们使用employees; 如果我们没有定义任何的函数名称, Ext JS将使用子模型的名字,加上”s”, 作为函数名称

现在我们创建一个Employee类,它位于appcode/model/Employee.js

Ext.define('Myapp.model.Employee',{
    extend:'Ext.data.Model',
    idProperty:'id ',
    fields:[
        {name: 'id', type: 'int' },
        {name: 'clientid' , type: 'int'},
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'email' , type: 'string'},
        {name: 'gender' , type: 'string'}
    ]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

为了测试model之前的关系,我们创建以下代码


var myclient = Ext.create('Myapp.model.ClientWithContacts',{
    id: 10001,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35'
});
//Step 2
myclient.employees().add(
    {
        id:101, clientId:10001, name:'Juan Perez', phone:'+52-05-2222-333',
        email:'juan@test.com', gender:'male'},
    {
        id:102, clientId:10001, name:'Sonia Sanchez', phone:
            '+52-05-1111-444', email:'sonia@test.com',gender:'female'}
);
//Step 3
myclient.employees().each(function(record){
    console.log(record.get('name') + ' - ' + record.get('email') );
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  1. 我们创建一个Client的记录
  2. 我们执行employees方法,这个方法是在定义关联类时,指定的name属性。这个方法返回一个Ext.data.Store实例。它是一个集合,用来管理相应的模型。所以我们可以通过它的add方法,添加两个Employee对像。
  3. 我们遍历myclient中的employees集合。每个记录表示一个Employee对像,所以我们可以使用get方法,获得它每个字段的值
One-to-one associations

为了描述这种关系,我们创建了一个合同类,来与 Client相关联

Ext.define('Myapp.model.Contract',{
    extend:'Ext.data.Model',
    idProperty:'id ',
    fields:[
        {name: 'id', type: 'int' },
        {name: 'contractId', type: 'string'},
        {name: 'documentType', type: 'string'}
    ]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果你所看到的,这是一个正常的类。现在我们来定义Customer类

Ext.define('Myapp.model.Customer',{
    extend:'Ext.data.Model',
    requires: ['Myapp.model.Contract'],
    idProperty:'id ',
    fields:[
        {name: 'id', type: 'int'},
        {name: 'name' , type: 'string'},
        {name: 'phone' , type: 'string'},
        {name: 'website' , type: 'string'},
        {name: 'status' , type: 'string'},
        {name: 'clientSince' , type: 'date', dateFormat: 'Y-m-d H:i'},
        {name: 'contractInfo' , reference: 'Contract', unique:true}
    ]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

我们添加了一个字段,称为contractInfo, 但我们使用reference代替了type属性。它表示contractInfo的值为一个Contract类的实例。

var myclient = Ext.create('Myapp.model.Customer',{
    id: 10001,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website: 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35',
    contractInfo:{
        id:444,
        contractId:'ct-001-444',
        documentType:'PDF'
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我们在这里是直接为contractInfo赋值一个对像. 如下所示

这里写图片描述

你看到上图中contractInfo是一个对像,它的字段跟Contract模型中定义的字段相同。如果contractInfo对像中没有定义Contract中的字段,则整个contractInfo则为undefined.

这里写图片描述

Exmaple

我们以一个博客应用为例,它有User和Post两个model. 每一个User都可以创建Post. 所以在这种情况下,一个user 可以有多个post. 但一个post只能属于创建它的用户. 这是一个ManyToOne的关系。我们可以通过下面的代码,表达它们的关系

Ext.define('MyApp.model.User', {
    extend: 'MyApp.model.Base',

    fields: [{
        name: 'name',
        type: 'string'
    }]
});

Ext.define('MyApp.model.Post', {
    extend: 'MyApp.model.Base',

    fields: [{
        name: 'userId',
        reference: 'User', // the entityName for MyApp.model.User
        type: 'int'
    }, {
        name: 'title',
        type: 'string'
    }]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

通过以上面的方式,可以很容易的实现我们应用中不同Model之前的关系。每一个Model可以跟其它的Model产生关联。同时,模型定义的顺序可以是任意的。一旦,你创建了这个Model类型的记录, 通过这条记录,就可以遍历与之相关联的数据。如果你想要获得一个用户的所有post. 你可以通过以下的代码

// Loads User with ID 1 and related posts and comments
// using User's Proxy
MyApp.model.User.load(1, {
    callback: function(user) {
        console.log('User: ' + user.get('name'));

        user.posts(function(posts){
            posts.each(function(post) {
                console.log('Post: ' + post.get('title'));
            });
        });
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

每一个User model都有多个Posts, 它会被添加一个user.posts()函数,调用user.posts()返回一个Post model的Store集合.

不仅我们可以加载数据,还可以创建一条新的记录

user.posts().add({
    userId: 1,
    title: 'Post 10'
});

user.posts().sync();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面的代码会创建一个Post实例,并且自动赋值userId字段为用户的id. 调用sync(),通过post的代理,保存这个新的Post(schema的代理配置决定). 这是一个异步操作,如果你需要获得操作完成后的通知,可以给它传递一个回调函数。

相返的,在Post model中,也会生成一个新的方法

MyApp.model.Post.load(1, {
    callback: function(post) {

        post.getUser(function(user) {
            console.log('Got user from post: ' + user.get('name'));
        });                           
    }
});

MyApp.model.Post.load(2, {
    callback: function(post) {
        post.setUser(100);                         
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在加载函数中, getUser()是一个异步操作,所以需要一个回调函数,来获得用户的实例。setUser()方法只是简单的更新userId(有时称为 “外键”)为100,并且保存Post model. 通常也可以传递一个回调函数,在保存操作完成后,触发回调函数,知道操作是否成功.

加载嵌套的数据

当定义了associations后,可以在单个请求中,加载相关联系的记录。比如, 一个服务器响应如下数据

{
    "success": true,
    "user": [{
        "id": 1,
        "name": "Philip J. Fry",
        "posts": [{
            "title": "Post 1"
        },{
            "title": "Post 2"
        },{
            "title": "Post 3"
        }]
    }]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

框架可以自动解析这个当个响应中的嵌套数据。而不用创建两个请求。

Stores

一个store是某一个模型对像的集合(比如多个users集合,invoices集合),作为一个客户端缓存来管理我们本地的数据。我们可以使用这个集合执行多种任务,比如排序,分组, filtering. 我们也可以使用一个有效的proxies从服务器上拉数据,和一个render解析服务器响应,并且填充这个集合.

一个store经常被添加到widgets/componens,用来显示数据。比如组件中的grid, tree, combo box, 或者data view,都会使用一个store来管理数据。当我们创建一个自定义 widget,我们也应该使用一个store来管理数据。

为了创建一个store, 我们需要使用Ext.data.Store类。代码如下

Ext.define('MyApp.store.Customers',{
    extend : 'Ext.data.Store', //Step 1
    model : 'Myapp.model.Customer' //Step 2
});

//Ext JS 6的写法 http://docs.sencha.com/extjs/6.0.2-classic/guides/core_concepts/data_package.html

var store = new Ext.data.Store ({
    model: 'MyApp.model.User'
});

store.load({
    callback:function(){
        var first_name = this.first().get('name');
         console.log(first_name);
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  1. 为了定义一个store, 我们需要让它继承于Ext.data.Store, 这个类负责处理models.
  2. 我们需要为创建的store,关联一个模型。它必须为一个有效的model类。

当我们创建了一个store类后,可以使用这个store来存取数据

var store = Ext.create("MyApp.store.Customers");
//counting the elements in the store
console.log(store.count());
  • 1
  • 2
  • 3

Adding new elements

我们可以创建一个Customer数据,并且通过add 或者insert方法,将这个新的元素添加到store.

//Step 1 (define /create new model instance)
var mynewcustomer = Ext.create('Myapp.model.Customer',{
    id: 10001,
    name: 'Acme corp',
    phone: '+52-01-55-4444-3210',
    website : 'www.acmecorp.com',
    status: 'Active',
    clientSince: '2010-01-01 14:35',
    contractInfo:{
        id:444,
        contractId:'ct-001-444',
        documentType:'PDF'
    }
});
store.add(mynewcustomer); //Step 2
console.log("Records in store:" + store.getCount() );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

也可以使用以下的方法

//Method 2 for add Records
store.add({
    id: 10002,
    name: 'Candy Store LTD',
    phone: '+52-01-66-3333-3895',
    website : 'www.candyworld.com',
    status: 'Active',
    clientSince: '2011-01-01 14:35',
    contractInfo:{
        id:9998,
        contractId:'ct-001-9998',
        documentType:'DOCX'
    }
});
console.log("Records in store:" + store.getCount());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

如果想一次添加多个,我们可以传递一个数组给 add方法。

// Method 3 for add multiple records
var mynewcustomer = Ext.create('Myapp.model.Customer', { ...});
var mynewcustomerb = Ext.create('Myapp.model.Customer', {
...});
store.add([mynewcustomer, mynewcustomerb]);
console.log("Records in store:" + store.getCount());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

store使用add方法,会将新的元素添加到集合的最后位置,如果我们想添加一个元素到第一的位置,或才其它位置,则可以使用insert方法

Inline data

new Ext.data.Store({
    model: 'MyApp.model.User',
    data: [{
        id: 1,
        name: "Philip J. Fry"
    },{
        id: 2,
        name: "Hubert Farnsworth"
    },{
        id: 3,
        name: "Turanga Leela"
    },{
        id: 4,
        name: "Amy Wong"
    }]
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

遍历store中的记录

store.each(function(record, index){
    console.log(index, record.get("name"));
});
  • 1
  • 2
  • 3

each方法接受一个函数。这个函数将被store中的每一条记录执行。这个匿名函数接收两个参数,每一次遍历时的record和index.

我们也可以给each方法传递第二个参数,表明这个匿名函数的作用域。

store中检索记录

通过位置索引
如果我们想获得指定位置的model, 我们可以使用getAt方法

var modelTest = store.getAt(2); 
console.log(modelTest.get("name"));
  • 1
  • 2

First and last records

var first = store.first();
var last = store.last();
console.log(first.get("name"), last.get("name"));
  • 1
  • 2
  • 3

* By range*

var list = store.getRange(1,3); //获取位置1开始的,3条记录,包含位1, 2, 3的记录
Ext.each(list,function(record,index){
    console.log(index,record.get("name"));
});
  • 1
  • 2
  • 3
  • 4

By ID

var record = store.getById(10001);
console.log(modelTest.get("name"));
  • 1
  • 2

Removing records

我们有三种方法删除记录

store.remove(record);
store.each(function(record,index){
console.log(index,record.get("name"));
});
  • 1
  • 2
  • 3
  • 4

在上面的代码中,remove传递的参数是一个model引用。

我们也可以一次性删除多个记录。我们只需要把要删除的model数组传递给它

store.remove([first,last]);
store.each(function(record,index){
console.log(record.get("name"));
});
  • 1
  • 2
  • 3
  • 4

有时,我们没有model的引用,在这种情况下,我们可以通过记录的位置进行删除

store.removeAt(2);
store.each(function(record,index){
    console.log(index,record.get("name"));
});
  • 1
  • 2
  • 3
  • 4

如果我们想要删除所有的记录, 我们仅需要调用removeAll方法.

store.removeAll();
console.log("Records:",store.count()); // Records: 0
  • 1
  • 2

Sorting and Grouping

new Ext.data.Store({
    model: 'MyApp.model.User',

    sorters: ['name','id'],
    filters: {
        property: 'name',
        value   : 'Philip J. Fry'
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Retrieving remote data

到目前为此,我们使用的都是本地数据,但在真实的应用中,我们的数据是保存在数据库当中的,或者需要通过web服务才能获取到数据。

Ext JS使用proxies发送和接收数据。proxies通常配置在store或者model。也可以像我们在最开始的base model代码,在schema中定义.

在Ext JS中Proxies负现处理model的数据,我们可以说proxy是一个处理和操作数据(parsing, organizing等等)的类. 所以store可以通过proxie来读取和保存,或者发送数据到服务器。

一个proxy使用一个reader来解码接收到的数据,使用一个writer将数据编码为正确的格式,并且发送到数据源。reader有Array, JSON, XML三种类型,而writer只有JSON, XML类型。

proxies主要分为两种类型, Client 和 Server,如果我们想要改变我们数据源,我们仅需要改变proxy类型,而其它的都不需要改变。比如,我们为store或model定义一个Ajax proxy, 然后我们可以将它设置为 local storage proxy.

Client Proxy

Server Proxy

Ajax proxy

Ext.define('Myapp.store.customers.Customers',{
    extend:'Ext.data.Store',
    model: 'Myapp.model.Customer',
    proxy:{
        type:'ajax',
        url: 'serverside/customers.php',
        reader: {
            type:'json',
            rootProperty:'records' //json中的数据字段
        }
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

之后我们就可以创建这个store, 并且从远程加载数据

//Step 1
var store = Ext.create("Myapp.store.customers.Customers");
//Step 2
store.load(function(records, operation, success) {
    console.log('loaded records');//Step 3
    Ext.each(records, function(record, index, records){
        console.log( record.get("name") + ' - ' +
        record.data.contractInfo.contractId );
    });
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在我们执行例子之前,我们应该在服务器上创建一个serverside/customers.json

{
    "success":true,
    "id":"id",
    "records":[
    {
        "id": 10001,
        "name": "Acme corp2",
        "phone": "+52-01-55-4444-3210",
        "website": "www.acmecorp.com",
        "status": "Active",
        "clientSince": "2010-01-01 14:35",
        "contractInfo":{
            "id":444,
            "contractId":"ct-001-444",
            "documentType":"PDF"
        }
    },{
        "id": 10002,
        "name": "Candy Store LTD",
        "phone": "+52-01-66-3333-3895",
        "website": "www.candyworld.com",
        "status": "Active",
        "clientSince": "2011-01-01 14:35",
        "contractInfo":{
            "id":9998,
            "contractId":"ct-001-9998",
            "documentType":"DOCX"
        }
    }
]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

Readers

Readers让Ext JS知道如何处理响应.

如我们上面的例子

reader: {
    type:'json',
    rootProperty:'records'
}
  • 1
  • 2
  • 3
  • 4

type属性表示使用什么格式来解析。它可以为json, xml, array.

rootProperty允许我们定义一个在服务器响应中的属性名字,服务器返回的数据都是这个字段下。在JSON响应中,它可以为一个数组。 在我们的例子中,我们设为records, 因为我们的JSON使用的是这个名字。如果我们嵌套的是一个对像,可以使用如下的方式

{
    "success" :"true",
    "id":"id",
    "output":{
        "appRecords":[{ our data .... }],
        "customerRecords":[{ our data .... }]
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
reader: {
    type:'json',
    rootProperty:'output.customerRecords'
}
  • 1
  • 2
  • 3
  • 4

XML reader

XML reader相对于JSON来说有些变化,因此,我们需要在这个render中,配置其它属性, 以确保XML可以正常解析

proxy:{
    type:'ajax',
    url: 'serverside/customers.xml',
    reader: {
        type: 'xml',
        rootProperty: 'data',
        record:'customer',
        totalProperty: 'total',
        successProperty: 'success'
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • rootProperty 定义了 XML文件中查找records的节点。
  • 我们还添加了一个record属性,在XML中每一个记录的名称
  • totalProperty和successProperty, store有的功能需要使用到这个标签下的值

现在,让我们创建一个XML文件 serverside/customers.xml

<?xml version="1.0" encoding="UTF-8"?>
<data>
    <success>true</success>
    <total>2</total>
    <customer>
        <id>10001</id>
        <name>Acme corp2</name>
        <phone>+52-01-55-4444-3210</phone>
        <website>www.acmecorp.com</website>
        <status>Active</status>
        <clientSince>2010-01-01 14:35</clientSince>
        <contractInfo>
            <id>444</id>
            <contractId>ct-001-444</contractId>
            <documentType>PDF</documentType>
        </contractInfo>
    </customer>
    <customer>
        <id>10002</id>
        <name>Candy Store LTD</name>
        <phone>+52-01-66-3333-3895</phone>
        <website>www.candyworld.com</website>
        <status>Active</status>
        <clientSince>2011-01-01 14:35</clientSince>
        <contractInfo>
            <id>9998</id>
            <contractId>ct-001-9998</contractId>
            <documentType>DOCX</documentType>
        </contractInfo>
</customer>
</data>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

Sending data

Ext.define('Myapp.store.customers.CustomersSending',{
    extend:'Ext.data.Store',
    model: 'Myapp.model.Customer',
    autoLoad:false,
    autoSync:true,
    proxy:{
        type:'ajax',
        url: 'serverside/customers.json',
        api: {
            read : 'serverside/customers.json',
            create : 'serverside/process.php?action=new',
            update : 'serverside/process.php?action=update',
            destroy : 'serverside/process.php?action=destroy'
        },
        reader: {
            type:'json',
            rootProperty:'records'
         },
        writer:{
            type:'json',
            encode:true,
            rootProperty:'paramProcess',
            allowSingle:false,
            writeAllFields:true,
            root:'records'
        },
        actionMethods:{
            create: 'POST',
            read: 'GET',
            update: 'POST',
            destroy: 'POST'
        }
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  1. 设置了一个api 属性: 为CRUD分别设置一个url
  2. 设置一个writer属性
    • type: ‘json’: 以JSON格式发送数据
    • encode: 表示在传递数据到服务器前,是否经过Ext JS进行编码
    • rootProperty: 包信息的属性的名字
    • writeAllFields: 传递所有的记录到服务器,如果为false, 则只发送个改的字段。
  3. 对CRUD所对应的方法类型
Ext.Loader.setConfig({
    enabled: true,
    paths:{ Myapp:'appcode' }
});
Ext.require([
    'Ext.data.*',
    'Myapp.model.Contract',
    'Myapp.model.Customer',
    'Myapp.store.customers.CustomersSending'
]);
Ext.onReady(function(){
    var store = Ext.create("Myapp.store.customers.CustomersSending");
//Step 1
    store.load({ // Step 2 load Store in order to get all records
        scope: this,
        callback: function(records, operation, success) {
            console.log('loaded records');
            Ext.each(records, function(record, index, records){
                console.log( record.get("name") + ' - ' +
                        record.data.contractInfo.contractId );
            });
            var test=11;
            console.log('Start adding model / record...!');
// step 3 Add a record
            var mynewCustomer = Ext.create('Myapp.model.Customer',{
                clientId : '10003',
                name: 'American Notebooks Corp',
                phone: '+52-01-55-3333-2200',
                website : 'www.notebooksdemo.com',
                status : 'Active',
                clientSince: '2015-06-01 10:35',
                contractInfo:{
                    "id":99990,
                    "contractId":"ct-00301-99990",
                    "documentType":"DOC"
                }
            });
            //因为我们设置了autoSync属性为true,所以会发送数据到服务器
            store.add(mynewCustomer);
// step 4 update a record
            console.log('Updating model / record...!');
            var updateCustomerModel = store.getAt(0);
            updateCustomerModel.beginEdit();
            updateCustomerModel.set("website","www.acmecorpusa.com");
            updateCustomerModel.set("phone","+52-01-33-9999-3000");
            updateCustomerModel.endEdit();
// step 5 delete a record
            console.log('deleting a model / record ...!');
            var deleteCustomerModel = store.getAt(1);
            store.remove(deleteCustomerModel);
        }
    });
});
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

Schema

介绍

一个Schema是相关Ext.data.Model和之前关系Ext.data.schema.Association的集合。

Schema Instances

默认情况下这个类以单例创建,作为模式服务于所有的实体(model中没有明确指明schema配置, 也没有继承schema配置)

当一个entity指定了一个schema, 相关联的类,就能够查找(or create)这个entity类的实例,然后继承这个实例。

Importnat: 所有相关的entities 必须属性单个schema实例,这样才能正确的连接它们的关联性。

Configuring Schemas

控制schema配置的最好方式是定义一个 base model

Ext.define('MyApp.model.Base', {
    extend: 'Ext.data.Model',

    fields: [{
        name: 'id',
        type: 'int'
    }],

    schema: {
        namespace: 'MyApp.model',  // generate auto entityName

        proxy: {     // Ext.util.ObjectTemplate
            type: 'ajax',
            url: '{entityName}.json',
            reader: {
                type: 'json',
                rootProperty: '{entityName:lowercase}'
            }
        }
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

通过使用base class, 你所有的model类在声明创建时,都已要有拥有schema中的默认配置。

Relative Naming

当描述两个model之前的关系时,需要使用简单的名称,它不包含共同的namespace部分. 它称为entityName, 而不是类名称。 默认情况下, entityName是整个类的名称。但是,如果使用了namespace属性时,共同的部分会被丢弃, 我们可以得到简单的名称。MyApp.model.FooentityNameFoo. MyApp.model.foo.Thingfoo.Thing

Association Naming

在描述关系时,有不同的术语。最能说明阐明这些术语的一个简单例子是,User与 Group这种多对多的关系

  • entityName - “User”和”Group”为entityName, 它们都是从完整的类名中简写获得(App.model.User and App.model.Group)
  • associationName - 当我们谈论关系时,特别是many-to-many. 给它们命名就非常重要。Associations(关联)不被涉级的任何一个实体拥有, 所以这时,这个名称就类似于一个entityName. 默认的associationName为”GroupUsers”.
  • left and right - Association(关联)描述了两个实例之前的关系。在谈论具体的关系时,我们可以使用两个参与者的entityName(比如User or Group). 当谈论一个抽像的associations
    比如”GroupUsers”这个关系,”User”称为 left, 而”Group”称为right. 在一个many-to-many的关联时, 我们可以任意的选择left和right. 当涉及到外键时,left 则是被包含的外键

Custom Naming Conventions

Schema的一个工作就是管理名字的生成(比如entityName). 这个工作它是委派给namer类。 如果你需要以其它的方法生成名字,你可以为你的定提供自定义的namer

 Ext.define('MyApp.model.Base', {
     extend: 'Ext.data.Model',

     schema: {
         namespace: 'MyApp.model',
         namer: 'custom'
     }
 });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

类似于alias创建一个类,如下所示

 Ext.define('MyApp.model.CustomNamer', {
     extend: 'Ext.data.schema.Namer',
     alias: 'namer.custom',
     ...
 });
  • 1
  • 2
  • 3
  • 4
  • 5
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/322198
推荐阅读
相关标签
  

闽ICP备14008679号