当前位置: 首页 > 工具软件 > Ember Charts > 使用案例 >

Ember -Routes

林亦
2023-12-01

http://guides.emberjs.com/v1.11.0/routing/


在用户和我们的应用程序交互的时候,会在不同的状态之间切换。Ember.js为我们提供了一个有用的工具来在某种程度上管理这些状态
并规模化我们的应用程序。


为了理解其重要性,假设我们写一个web应用程序来管理一个博客。在任何时间,我们都应该能够回答诸如:当前用户登录了吗?
它们是管理员吗?它们在找那片文章?设置的屏幕开着吗? 它们正在当前审批它的帖子吗?


在Ember.js中,你应用程序的每个可能状态都表现为一个URL。因为所有我们上面问到的问题---我们登录了吗?我们在找什么样的帖子?--
都被路由处理器封装为URLs,以简单准确的方式回答它们。




在任何特定的时间,我们的应用程序都有一个或者多个活动的路由处理器(route handlers),下面的原因有可能改变活动着的处理器:
1、用户跟视图交互时,产生一个事件造成了URL的变化。
2、用户手动改变URL(比如,回退按钮),或者页面首次加载。


当当前的URL发生变化时,新的活动路由处理器会进行如下处理:
1、有条件的重定向到一个新的URL。
2、更新一个controller以便让它表示特定的model
3、改变屏幕上的模板,或者将一个新模板添加到一个已有的outlet


记录下路由的变化:
随着你的应用程序复杂程度的提升,能看到真正的路由器真正的行为很重要。
我们可以通过修改应用程序配置让Ember.js 记录下传输事件到日志文件。
config/environment.js
ENV.APP.LOG_TRANSITIONS = true;


设置一个ROOT URL:
如果你的Ember应用程序是同域名下多个web应用程序之一,那么就有必要为路由器指定一个根URL。默认情况下,Ember会假设它服务于你域名的根URL。


比如,如果你想让你的博客系统服务于http://emberjs.com/blog/ URL下,那你需要制定一个 /blog/ 作为根URL


app/router.js


Ember.Router.extend({
rootURL:'/blog/'
});




定义我们的路由:
http://guides.emberjs.com/v1.11.0/routing/defining-your-routes/


当我们的Ember应用程序启动时,路由器Router负责通过匹配当前的URL和我们与定义的路由项(routes)来决定显示的模板,加载数据,以及装配应用程序状态。


我们可以调用Ember应用程序router对象的map方法来定义URL映射。 在调用map方法时,我们需要传入一个函数function,该函数将会通过将this值赋给某个对象进行调用,
该对象我们可以用来创建路由。


app/router.js
Router.map(
function(){
this.route('about', {path: '/about'});
this.route('favorites', {path: '/favs'});
}
);


现在当用户访问 /about 时,Ember.js 会渲染 about 模板,访问 /favs URL时,会渲染 favorites模板。
需要注意的是我们在定义上面路由同时也获得了一些额外的路由定义:application 和 index 它们映射到 / 。
在定义映射时,如果path和路由名相同,我们可以省略path,下面的定义和上面的等同。


Router.map(function() {
  this.route('about');
  this.route('favorites', { path: '/favs' });
});


在我们的模板内部,我们可以使用{{link-to}} 来在不同的路由之间导航,使用我们在定义route时设置的name 来指定路由。
{{#link-to 'index'}}<img class="logo">{/link-to}}
<nav>
{{#link-to 'about'}}About{{/link-to}}
{{#link-to 'favorites'}}Favorites{{/link-to}}
</nav>


{{link-to}}助手类会自动添加一个active 样式名到当前活动的路由项。


我们可以通过创建一个Ember.Route的子类来自定义一个路由的行为,比如,自定义当用户访问 / 时会发生的内容,创建一个名为index的路由。
app/routes/index.js
export default Ember.Route.extend({
setupController: function(controller){
//Set the IndexController's title
controller.set('title','My App');
}
});


该controller:index 是index模板的起始上下文,我们设置了title属性,现在我们可以使用它了:
<!-- get the title from the IndexController -->
<h1>{{title}}</h1>


如果我们没有显式声明 controller:index, Ember.js会自动为我们生成一个。


Ember.js自动识别出route的名字和传递给该route的基于该名字的controller。


URL        Route Name        Controller           Route              Template
                                app/controllers/ app/routes/    app/templates/
/        index            index.js         index.js        index.hbs
/about    about            about.js         about.js        about.hbs
/favs    favorites        favorites.js     favorites.js    favorites.hbs




嵌套Routes:
我们可以通过传入一个this.route方法来定义嵌套路由:
app/router.js
Router.map(function(){
this.route('posts', {path: '/posts'}, function(){
this.route('new');
});
});
上面的路由器可以创建如下的路由项:
URL             Route Name         Controller             Route             Template
                                     app/controllers/    app/routes/      app/templat
--------------------------------------------------------------------------------------------------------
/             index             index.js            index.js      index.js


N/A             posts             posts.js            posts.js      posts.hbs


/posts         posts.index     posts.js               posts.js          posts.hbs
                                     /posts/index.js    /posts/index.js  posts/index.hbs
                                                                              
/posts/new     posts.new         posts.js               posts.js          posts.hbs
                                     /posts/new.js        /posts/new.js  /templates/posts/new.hbs
这里转向/posts 或者创建一个指向postsroute的链接最终会转向posts.index或者链接到posts.index 
一个嵌套的路由名字中要包含其父级路由名,如果你想转向一个路由(无论是通过transitionTo还是{{#link-to}}),必须确保使用
一个route的全限定名(比如 posts.new而非new)。


访问 /  时和你希望的那样,会渲染index模板。
访问 /posts 时会稍有不同,它会首先渲染posts模板,然后再渲染posts/index 模板到posts模板的outlet。


最后,访问/posts/new 将首先渲染posts模板,然后渲染posts/new模板到 posts模板的outlet。




多单词Model名:
对于多单词组成model名都必须是camel格式,即首字母大写。动态变量部分除外。比如一个名为BigMac的model有一个资源路径为
/bigMacs/:big_mac_id, 路由名为bigMac, 模板名为bigMac


动态分段 Dynamic Segments:
路由处理器的一个任务是把一个URL转化为一个model。
比如,如果我们有一个路由 this.route('posts'); 那么我们的路由处理器可能是如下内容:
app/posts/route.js
export default Ember.Route.extend({
model: function(){
return $.getJSON("url/to/some/posts.json");
}
}):
posts模板将收到一个可用的post列表作为自己的上下文。


因为/posts 表示为一个固定的model,我们不需要任何的额外信息来判断要获取什么。然而,如果我们想一个路由表示一个单独的post,
我们有不想硬编码每个可能的post到一个路由器,动态分段就派上用场了。


一个动态分段式一个URL的一部分,它以 :开头后面是标识符。
app/router.js
Router.map(function(){
this.route('posts');
this.route('post',{path: '/post/:post_id'});
}):


app/routes/post.js
export default Ember.Route.extend({
model: function(params){
return $.getJSON("url/to/some/posts/" + params.post_id + ".json");
}
});


如果我们的model没有在URL中使用id属性,那么我们应该在路由上定义一个序列化方法。
app/router.js
Router.map(function(){
this.route('post', {path: '/posts/:post_slug'}};
});


app/routes/post.js
export default Ember.Route.extend({
model: function(params){
//the server returns {slug: 'foo-post'}
return Ember.$.getJSON('/posts/' + params.post_slug);
},

serialize: function(model){
//this will make the URL /posts/foo-post
return {post_slug: model.get('slug')};
}
});


默认的serialize 方法将model的id插入到路由的动态分段中(上例中是 :post_id)




初始化路由:
有一些路由在我们的应用程序中是直接可用的:
route:application ,当我们的应用程序启动时进入的便是它,它负责渲染application.hbs模板。
route:index, 是默认路由,当用户访问 / 时会渲染index.hbs模板,除非你重写了 / 路由。
上面两种路由是我们每个应用程序的一部分,所以我们不需要在app/router.js中定义它们。


通配符和通用路由定义:
我们可以定义匹配多个路由的通用路由,如果我们想通吃一些路由,特别是用户输入不是我们应用程序管理的错误URL时,这样做比较有用。


//app/router.js
Router.map(function(){
this.route('catchall', {path: '/*wildcard'});
});


跟定义有动态分段变量的所有路由一样,你在使用{{link-to}}或者transitionTo 编程进入该route时必须提供一个上下文。
app/routes/application.js
export default Ember.Route.extend({
actions: {
error: function(){
this.transitionTo('catchall', 'application-error');
}
}
});
上面的代码,如果有错误向上传递给Application 路由,我们的应用程序将进入catchall 路由并在显示/application-error URL。




生成的对象:Generated Objects
http://guides.emberjs.com/v1.11.0/routing/generated-objects/


在路由说明中介绍过,无论何时我们定义一个新的route,Ember.js都会试着根据命名规则去查找对应的Route,Controller,View和Tamplate类。
如果任何这些类中任何一个没有被实现,就会在内存中帮你自动生成一个。


生成的路由:
//app/router.js
Router.map(function(){
this.route('posts');
});


当我们导航到 /posts 时,Ember.js 会查找 route:posts, 如果没有找到,它会自动生成一个给我们。


生成Controller:
如果我们定位到路由 posts 时,Ember.js会查找名为controller:posts的controller,如果我们没有定义它,Ember.js会为我们生成一个。


生成视图和模板:
一个路由还需要一个视图和一个模板,如果我们没有为其定义,Ember.js 同样会为我们生成一个。
一个自动生成的模板是空的,如果是个资源模板,该模板会作为一个outlet,所以嵌套路由会毫无障碍的被插入。等价于
{{outlet}}




为路由指定一个Model:
http://guides.emberjs.com/v1.11.0/routing/specifying-a-routes-model/


在我们的应用程序中,模板由数据模型支持。但是模板怎么知道它应该显示那个数据模型呢?
比如,我们有一个photos.hbs模板,它怎么知道该显示那个model呢?


这就是Ember.Route的工作之一,我们可以通过定义一个和模板名称相同的路由route并实现它的model钩子,来告诉模板要显示那个model。
比如,为photos模板提供一些model数据,我们定义了如下的route 对象:
app/routes/photos.js
export default Ember.Route.extend({
model: function(){
return [{
title: "Tomster",
url: "http://emberjs.com/images/about/ember-productivity-sm.png"
},{
title: "Eiffel Tower",
url: "http://emberjs.com/images/about/ember-structure-sm.png"
}];
}
});


简单的Ember.js应用实例:
App = Ember.Application.create();


App.Router.map(function() {
  this.resource('photos');
});


App.PhotosRoute = Ember.Route.extend({
  model: function() {
    return [{
      title: "Tomster",
      url: "http://emberjs.com/images/about/ember-productivity-sm.png"
    }, {
      title: "Eiffel Tower",
      url: "http://emberjs.com/images/about/ember-structure-sm.png"
    }];
  }
});


App.IndexRoute = Ember.Route.extend({
  redirect: function() {
    this.transitionTo('photos');
  }
});


异步加载Models:
上面的代码中,model数据是从route的model钩子里同步返回的。 这就意味着数据是立刻可用的并且你的应用程序不需要等待它被加载。
这里我们是通过硬编码直接返回数组hash。


当然,这种情况不总是可行的。通常数据不是同步可用的。而是必须异步通过网络加载的。比如我们可能想从一台服务器的JSON API来获取一个照片列表。


在数据异步可用的情况下,我们可能仅从model钩子中返回一个承诺,Ember将在渲染模板之前会等待直到承诺被解析完成才渲染模板。
如果还对承诺promise不熟悉,大体可以这么理解,它是一个表示最终值的对象。
比如,我们使用jQuery的getJSON()方法,它会返回一个最终会从网络返回JSON数据的承诺,Ember用这个承诺对象来获知什么时候它会有足够的数据继续渲染。


关于Promise详细说明参见http://guides.emberjs.com/v1.11.0/routing/asynchronous-routing/#toc_a-word-on-promises


看个实际例子,这里有一个路由加载发送给Github上的Ember.js的最新pull请求:
app/routes/pull-requests.js
export default Ember.Route.extend({
model: function(){
return Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls');
}
});
上面的代码看上去是同步加载的,为了其便于理解和思考,它实际上完全是异步加载的。
因为jQuery的getJSON()方法返回的是一个承诺promise而非真正的数据。 Ember将判断我们从model钩子中已经返回的promise,
并等待直到promise被解析,才会渲染pullRequests模板。


关于jQuery的XHR功能,参加jQuery.ajax 文档:http://api.jquery.com/jQuery.ajax/


因为Ember支持promise, 所以它可以和任何持久类合作使用它们作为它公共API的一部分。 我们也可以使用这些promise内建的规则来让我们的代码更加漂亮。


比如,假设我们想修改上面的例子让我们的模板仅显示三个最近的pull请求。 我们可以依靠promise链来在数据传递给模板之前修改JSON请求返回的数据


app/routes/pull-requests.js
export default Ember.Route.extend({
model: function(){
var url='https://api.github.com/repos/emberjs/ember.js/pulls';
return Ember.$.getJSON(url).then(function(data){
return data.splice(0,3);
});
}
});




装配带有Model的Controller:
那么我们从model 钩子返回数据以后到底发生了什么?
默认情况下,从我们model钩子返回的数据将被赋给相关controller的model属性。
比如,如果你的route:posts 从它的model钩子返回一个对象,该对象将被设置为controller:posts的model属性值。


这就是为什么模板直到它可以渲染那个model,它们查看与之关联的controller的model属性。比如,photos模板无论controller:photos的model
属性是否设置,都会渲染。


查看http://guides.emberjs.com/v1.11.0/routing/setting-up-a-controller/ 学习如何改变这个默认的行为,注意,如果我们
重写默认的行为并且没有设置controller的model属性,你的模板将没有任何model的data可以被渲染出来。




动态 Model:
有一些路由总是显示相同的model,比如/photos 路由将总是显示应用程序中同一个照片列表。 如果用户离开这个路由后回退,model将不会发生变化。


然而,我们经常需要一种路由,其model依赖于用户的交互而发生变化。比如,一个照片浏览器程序。 /photos 路由将渲染photos模板和其model中的一个照片列表,从来不发生变化。
但是当用户单机一张特定的照片时,我们想显示photo模板和其对应的model数据。当用户回到照片列表单击另外一张照片时,我们还是需要显示photo模板,但此时对应的
model就变了。


像这种情况,在URL中包含一些信息来决定哪个模板和哪个model将被显示就变的非常重要了。


在Ember中,这种方式是通过设置dynamic segments动态分段变量完成。


一个动态分段变量是URL的一部分,它会被当前的model的id填充,动态分段变量通常以冒号开始。上面的照片显示例子可以表示如下:
app/router.js
Router.map(function(){
this.route('photo',{path: 'photos/:photo_id'});
});
在这个例子中,photo路由有一个动态分段变量 :photo_id ,当用户打开该路由显示一张特定的照片model时(通常是通过{{link-to}}助手类),model的ID将被自动的植入到URL中。


比如,如果我们使用一个id属性为47的model转向photo路由,我们浏览器中的URL将会被更新为:
/photos/47


那么如果我们直接在URL中包含动态分段变量访问应用程序会怎样呢?
举个例子,我们可能重新加载页面,或者发送连接给一个朋友,让朋友来单击它。
在这一点上,因为我们从头开始启动的应用程序,之前显示的真正脚本model对象已经丢失,我们所剩下的只有URL中的动态分段变量值ID了。


幸运的是,Ember将从URL中获取动态分段变量的值并把它们作为一个hash表当作model钩子的第一个参数传递给钩子。


app/router.js
Router.map(function(){
this.route('photo', {path: '/photos/:photo_id'});
});


app/routes/photo.js
export default Ember.Route.extend({
model: function(params){
return Ember.$.getJSON('/photos/' + params.photo_id);
}
});


在带有动态片段变量的路由对应的model钩子中,我们的工作室把动态分段变量代表的ID转换为可以被该路由对应的模板渲染的model。
上例中,我们使用photo ID(params.photo_id)来构造了一个表示那张照片的JSOn数据的URL。一旦我们有了这个URL,我们使用jQuery返回一个对JSON model数据的承诺promise。


注意:当我们通过URL进入一个带有动态分段的路由时将只有它的model钩子被调用。 如果是通过转向(比如 使用{{link-to}}),这时候一个model上下文早就已经存在,model钩子将不会被执行。
而 没有动态分段变量的路由将总是执行model钩子。


更新我们已有的Model:
如果我们的model数据经常被修改,那么我们可能需要周期的更新它。


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Refresh model in route</title>
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
  <script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
  <script src="http://builds.emberjs.com/tags/v1.5.1/ember.js"></script>
</head>
<body>
  <script type="text/x-handlebars">
    <h1>application</h2>
    {{outlet}}
  </script>
   <script type="text/x-handlebars" data-template-name="index">
    <h2>index</h2>
    {{#each pr in model}}
      <div><em>Login</em>: {{pr.user.login}}</div>
      <div><em>Created at</em>: {{pr.created_at}}</div>
      <div><em>Title</em>: {{pr.title}}</div>
      <hr/>
    {{/each}}
    <button {{action "getLatest"}}>Get latest model</button>
  </script>
</body>


Emberjs:


App = Ember.Application.create();


App.IndexRoute = Ember.Route.extend({
  model: function() {
    var url = 'https://api.github.com/repos/emberjs/ember.js/pulls';
    return Ember.$.getJSON(url).then(function(data) {
      return data.splice(0, 3);
    });
  },
  actions: {
    invalidateModel: function() {
      Ember.Logger.log('Route is now refreshing...');
      this.refresh();
    }
  }
  
Controller可以发送一个行为给路由,上面例子中,IndexController暴露一个行为getLatest ,它会发送路由一个名为invalidateModel的行为Action。
调用路由的refresh方法将迫使Ember重新执行model钩子。


Ember DATA:
许多Ember开发人员现在开始使用一个model类库来查找和保存记录比手动管理Ajax调用要容易的多。特别是,使用一个model库可以让你缓存已经加载过的数据记录,
大大提高了你应用程序的性能。


Ember Data就是一个为Ember专门创建的model库,我们将在model向导中介绍它。




装配Controller:
http://guides.emberjs.com/v1.11.0/routing/setting-up-a-controller/


URL的改变也会引起显示在屏幕上的模板发生变化,模板通常只有在有一些信息源需要显示时才用到。


在Ember.js中,模板是从controller中获取要显示的信息的。


Ember应用程序中两个内建的controller: Ember.ObjectController和Ember.ArrayController 使得 一个controller很容易展示一个model属性给模板,同时还能增加一些特定显示的属性。


要告诉controller哪个model将要被显示,我们在路由处理器的setupController 钩子中设置model属性。


app/router.js
Router.map(function(){
this.route('post', '/posts/:post_id'});
});


//app/routes/post.js
export default Ember.Route.extend({
//The code below is the default behavior, so if this is all you need, you do not need to provide a setupController implementation at all.
setupController: function(controller, model){
controller.set('model', model);
}
});


这里setupController钩子接收路由处理器的关联controller作为其第一个参数,此例中,PostRoute的setupController接收应用程序的controller:posts实例。
要指定一个非默认的controller,则可以设置route的 controllerName属性:


app/routes/special-post.js
export default Ember.Route.extend({
controllerName: 'post'
});


接收路由处理器的model为第二个参数,默认的setupController钩子设置关联的Controller的model属性为route处理器model。


如果我们想配置一个不是和路由处理器关联的controller,则使用controllerFor方法:
app/routes/post.js


export default Ember.Route.extend({
setupController: function(controller, model){
this.controllerFor('topPost').set('model',model);
}
});
在本例中配置了一个跟post路由无关的controller:topPostController,以及一个model。






渲染一个模板:
http://guides.emberjs.com/v1.11.0/routing/rendering-a-template/


对于一个路由处理器来说其中一个最重要的工作就是将合适的模板渲染到屏幕上。
默认情况下,路由处理器会将其对应模板渲染到其最亲近的父级模板里。
app/router.js
//定义路由映射
Router.map(function(){
this.route('posts');
});


//定义对应的路由处理器 route handler
app/routes/posts.js
export default Ember.Route.extend();


如果我们想渲染一个跟本路由处理器无关的模板,则需要实现renderTemplate 钩子:


app/routes/post.js
export default Ember.Route.extend({
renderTemplate: function(){
this.render('favoritePost');
}
});


如果我们不想使用route处理器的controller,那就将想使用的controller名值对hash传入。
export default Ember.Route.extend({
renderTemplate: function(){
this.render({controller: 'favoritePost'});
}
});


Ember允许我们为outlet设置名字,比如下面的代码用不同的名字定义了两个outlets:
<div class="toolbar">{{outlet "toolbar"}}</div>
<div class="sidebar">{{outlet "sidebar"}}</div>


所以,如果我们打算把posts模板渲染到sidebar outlet,我们可以这么写:


app/routes/posts.js
export default Ember.Route.extend({
renderTemplate: function(){
this.render({outlet: 'sidebar'});
}
});


上面所说的所有内容都可以任意组合起来使用:
app/routes/posts.js


export default Ember.Route.extend({
renderTemplate: function(){
var controller = this.controllerFor('favoritePost');
//Render the 'favoritePost' template into the outlet 'posts', and use the 'favoritePost' Controller
this.render('favoritePost',{
outlet: 'posts',
controller: controller
});
}
});


如果我们想渲染两个不同的模板到一个路由的两个不同模板的outlet中:
app/routes/post.js


export default Ember.Route.extend({
renderTemplate:function(){
this.render('favoritePost', { // the template to render
into: 'posts',            // the template to render into
outlet: 'posts'           // the name of the outlet in that template
controller: 'blogPost'    // the controller to use for the template
});
this.render('comments',
into: 'favoritePost',
outlet: 'comment',
controller: 'blogPost'
});
}
});




重定向:
http://guides.emberjs.com/v1.11.0/routing/redirection/


转换和重定向
从一个路由route中调用transitionTo, 或者从controller里调用 transitionToRoute都将停止任何当前正在进行的转换而开始一个新的转换。
功能类似重定向。 transitionTo需要的参数和行为跟{{link-to}}助手非常像。


如果我们转入到一个没有动态分段变量的路由,那么该路由的model钩子将总是被执行。
如果我们要转入的路由有动态分段变量,我们需要传入一个model或者为每个动态分段变量指定标识。 如果传入model则可以跳过动态分段的model钩子执行,
传入标识值是会运行model钩子,并能够从Params中访问到这些标识值。




在已有起始的Model时候:
如果我们想从一个route转入到另外一个route, 我们可以在我们的路由处理器的beforeModel 钩子中进行。


app/router.js


Router.map(function(){
this.route('posts');
});


//app/routes/index.js


export default Ember.Route.extend({
beforeModel: function(){
this.transitionTo('posts');
}
});


在已有目的Model的时候:
如果我们需要当前model信息来决定重定向,我们应该使用afterModel或者redirect 钩子。
他们接收解析model作为第一个参数,转换为第二个参数,函数作为别名。(事实上,afterModel的默认实现就是调用了redirect)


app/router.js


Router.map(function(){
this.route('posts');
this.route('post',{path: '/post/:post_id'});
});


app/routes/post.js


export default Ember.Route.extend({
afterModel: function(posts, transition){
if(posts.get('length' === 1){
this.transitionTo('post', posts.get('firstObject'));
}
}
});
当转入到posts路由时事实证明只有一个post,当前的转入将被抛弃而转入到PostRoute并且以单个 post对象作为它的model。


基于其他应用程序状态:
我们可以基于其它应用程序状态来进行转入:


app/router.js


Router.map(function(){
this.route('topCharts', function(){
this.route('choose', {path: '/'});
this.route('albums');
this.route('songs');
this.route('artists');
this.route('playlists');
});
});


app/routes/top-charts-choose.js


export default Ember.Route.extend({
  beforeModel: function() {
    var lastFilter = this.controllerFor('application').get('lastFilter');
    this.transitionTo('topCharts.' + (lastFilter || 'songs'));
  }
});




app/routes/filter.js
// Superclass to be used by all of the filter routes: albums, songs, artists, playlists
export default Ember.Route.extend({
  activate: function() {
    var controller = this.controllerFor('application');
    controller.set('lastFilter', this.templateName);
  }
});


在这个示例中,导航到 / URL立刻转入到上一个过滤URL 。首次,它会转入/songs URL。


我们的路由还可以只在某些情形下选择转入,如果beforeModel 钩子没有被中止,或者转入一个新的路由,那么剩余的钩子(model, afterModel, setupController, renderTemplate都会
照常执行。




































转载于:https://my.oschina.net/u/924064/blog/423926

 类似资料: