angular10 新功能,angular10完全解读

  angular10 新功能,angular10完全解读

  本文讲述了angular10中的组件,并介绍了组件、组件的生命周期、组件之间的通信以及动态组件的基本用法。让我们一起来看看它们。

  【相关教程推荐:《angular教程》】

  

组件构成要素

   html模板typescript,定义行为css组,可以引入多个css文件,所以可以在一个组件中定义多个css文件。//从angular的主模块引入组件(组件装饰器或组件注释)。

  从“@angular/core”导入{ Component };

  //在装饰器中将元数据声明为json

  @组件({

  //它指定一个名为app-root的元素。这个元素是index.html文件中的一个占位符。

  //为什么这个组件链接到门户索引?因为门户main.ts中绑定的主模块是appModule

  选择器: app-root ,//在模板中找到对应的标签,然后创建并插入组件实例。

  Url:。/app.component.html ,//html模板

  样式URL:[。/app.component.css],//css样式,可以引入多个css文件。

  //可以选择这个属性(内联模板)和templateUrl(外部模板)。模板后面可以直接跟html字符串。

  //注意模板语法中用的是插值表达式(反引号),不能用$ {}插入值。

  模板:` h1{{title}}/h1

  })

  //编写逻辑代码的组件控制器

  导出类AppComponent {

  title= myAngular

  //构造函数可用于声明和初始化属性。

  angular中有一点要特别记住:依赖关系只能通过构造函数注入。

  构造函数(){}

  }

先决条件

  安装Angular CLI(NPM Install-g @ Angular/CLI)并建立Angular项目(ng new)。如果不满足这两个条件,请参考构建环境。

  

如何创建一个组件

  使用Angular CLI创建组件

  Ng生成组件项目名称//创建组件

  g c project-name//缩写一些常用于创建组件的其他选项。

  g c project-name-skip-tests//创建一个组件而不安装测试文件。

  G c项目名称-内联样式//缩写-s,内联样式

  G c项目-名称-内联-模板//缩写-t,内联模板

  g c project-name-module=module-name//指定在哪个模块中引用了创建的组件。它将用于多模块项目。除了通过angular cli自动生成组件,还可以手动创建组件(不推荐),这里不做介绍。

  

组件的生命周期(高阶内容,掌握ngOnInit、ngOnDestroy、ngOnChanges、ngAfterViewInit())

  工作中只有两个生命周期(ngOninit和ngOnDestroy)可以经常使用,其他生命周期很少使用;但如果你能掌握组件的生命周期,你会对angular有更深的理解。

  生命周期含义

  当Angular实例化组件类并呈现组件视图及其子视图时,组件实例的生命周期开始。生命周期总是伴随着变化检测。Angular将检查数据绑定属性何时更改,并根据需要更新视图和组件实例。当Angular销毁组件实例并从DOM中移除它所呈现的模板时,生命周期结束。当Angular在执行期间创建、更新和销毁实例时,指令具有类似的生命周期。

  应用:

  您的应用程序可以使用生命周期挂钩方法来触发组件或指令生命周期中的关键事件,以初始化新实例,在必要时启动更改检测,在更改检测期间响应更新,并在删除实例之前进行清理。

  如何实现生命周期事件

  每个组件或指令可以实现一个或多个生命周期挂钩,这些挂钩可以在适当的时候对组件或指令实例进行操作。

  每个接口都有一个唯一的钩子方法,它们的名称由接口名加上ng前缀组成。例如,OnInit接口的hook方法称为ngOnInit()。如果在组件或指令类中实现这个方法,Angular会在第一次检查组件或指令的输入属性后立即调用它。

  从“@angular/core”导入{ Component };

  @组件({

  选择器:“应用程序根目录”,

  样式URL:[。/app.component.css],

  模板:` h1{{title}}/h1

  })

  //实现OnInit生命周期,可以实现多个生命周期。

  导出类AppComponent实现OnInit{

  title= myAngular

  构造函数(){}

  ngOnInit(){

  Console.log(Angular在第一次检查组件的输入属性后调用组件一次)

  }

  }生命周期概览

  计时用法注意,当绑定的输入属性的值改变时调用ngOnChanges(),当Angular设置或重置数据绑定的输入属性时,第一次调用必须发生在ngOnInit()之前。此方法接受具有当前和以前属性值的SimpleChanges对象。这种情况经常发生,因此您在这里执行的任何操作都会显著影响性能。NgOnInit()只在第一轮ngOnChanges()完成后调用一次。在Angular显示数据绑定并首次设置指令/组件的输入属性后,初始化指令/组件。获得组件的初始数据很重要。仅调用ngDoCheck()一次,每次执行更改检测时调用ngOnChanges(),第一次执行更改检测时调用ngOnInit()。检测并在Angular自身无法或不愿检测到变化时做出反应。它的发生频率与ngOnChanges一样高,ngaftercontent()只在第一个ngDoCheck()之后调用一次。当Angular将外部内容投影到组件视图或指令所在的视图时调用。每次Angular检查投射到组件或指令中的内容时,仅调用ngaftercontentChecked()ngaftercontent()一次,并在每次ngDoCheck()后调用。NgAfterViewInit()在第一个ngAfterContentChecked()之后调用,并且只调用一次。在组件视图及其子视图初始化后调用。仅调用ngafterviewchecked()ngafterviewit()一次,并在每次ngAfterContentChecked()之后调用。在组件视图和子视图的每次更改检测之后调用。Angular销毁指令/组件之前调用NgOnDestroy()。Angular每次在销毁指令/组件之前调用并清理时。在这里取消订阅可观察对象并分离事件处理程序,以防止内存泄漏。

  取消订阅可观察对象,

  清除计时器,

  在全局或应用程序服务中注册此指令注册的所有回调。非常重要重点生命周期详解

  初始化组件和指令 ngOnInit

  在构造函数外部执行复杂的初始化。构件的构造应该便宜且安全。例如,您不应该在组件构造函数中获取数据。当在测试中创建一个组件或者在决定显示它之前,您不应该担心新的组件会试图联系远程服务器。ngOnInit() 是组件获取初始数据的好地方。角度设置输入属性后设置组件。构造函数应该只将初始局部变量设置为简单值。记住,只有在构造完成之后才会设置指令的数据绑定输入属性。如果您希望根据这些属性初始化指令,请在运行ngOnInit()时将它们设置为在实例销毁时进行清理 ngOnDestroy

  这是释放资源的地方,不会被垃圾自动收集。如果不这样做,就有内存泄漏的风险。

  取消订阅可观察对象和DOM事件。停止间隔计时器。在全局或应用程序服务中注册此指令注册的所有回调。ngOnDestroy()方法也可以用来通知应用程序的其他部分组件即将消失页面埋点

  您可以通过ngOnInit和ngOnDestroy方法计算组件中的页面持续时间,

  更好的方法是通过指令实现ngOnInit和ngOnDestroy的生命周期,可以用来统计页面持续时间。

  您还可以通过传送保护装置记录页面进入和离开堆栈所需的时间。

  @指令({选择器:[appSpy]})

  导出类SpyDirective实现OnInit,OnDestroy {

  构造函数(私有记录器:LoggerService) { }

  ngOnInit(){ this . logit(` on init `);}

  ngOnDestroy(){ this . logit(` on destroy `);}

  私有logIt(消息:字符串){

  this . logger . log(` spy # $ { nextId } $ { msg } `);

  }

  }使用变更检测钩子 ngOnchanges

  一旦Angular检测到这个组件或指令的输入属性已经改变,它将调用它的ngOnChanges()方法。

  因为这个方法会频繁执行,所以要注意计算量,会影响性能。

  //您可以监听输入属性的变化

  ngOnChanges(更改:简单更改){

  for (const propName in changes) {

  const chng=changes[propName];

  const cur=JSON。stringify(chng。当前值);

  const prev=JSON。stringify(chng。前一值);

  这个。变更日志。push(` $ { propName }:当前值=$ { cur },上一个值=$ { prev } `);

  }

  }

组件之间的通信(重要,必须掌握)

  一、父子组件的通信(基础)

  父传子

  1、父组件通过属性绑定向子组件传值

  从" @角度/核心"导入{组件};

  @组件({

  选择器:应用程序-父级,

  模板:` 1

  app-child [msg]=msg/app-child

  `

  })

  导出类ParentComponent {

  msg=父组件传的值;

  }2、子组件通过@输入接收数据

  从" @角度/核心"导入{组件,输入};

  @组件({

  选择器:"应用程序-子代",

  模板:` 1

  p{{msg}}/p

  `

  })

  导出类子组件{

  @ Input()msg:String;

  }子传父

  1、子组件通过自定义事件,向父组件发送数据

  从" @角度/核心"导入{组件,输入,事件发射器,输出}。

  ,

  @组件({

  选择器:"应用程序-子代",

  模板:` 1

  p{{msg}}/p

  按钮(点击)=vote()发送/按钮

  `

  })

  导出类子组件{

  @ Input()msg:String;

  @ Output()voted=new EventEmitterboolean();

  vote() {

  this.voted.emit(子组件传的值);

  }

  }2、父组件通过监听自定义事件,接收子组件的传值

  从" @角度/核心"导入{组件};

  @组件({

  选择器:应用程序-父级,

  模板:` 1

  app-child [msg]=msg (已投票)=已投票($event)/app-child

  `

  })

  导出类ParentComponent {

  msg=父组件传的值;

  已投票(val){ //监听自定义事件的传值

  控制台。日志(瓦尔)

  }

  }子组件怎么监听输入属性值的变化?(2种方法)

  1、可以使用一个输入属性@Input()的二传手,以拦截父组件中值的变化。

  从" @角度/核心"导入{组件,输入};

  @组件({

  选择器:"应用程序-子代",

  模板:" H3"{ name } }"/H3

  })

  导出类子组件{

  @Input()

  get name(): string { return this ._ name}

  集合名称(名称:字符串){

  这个. name=(name name.trim()) 无名称集;

  }

  private _ name=

  }2、通过ngOnChange()来截听输入属性值的变化

  当需要监视多个、交互式输入属性的时候,本方法比用属性的作曲者更合适。

  从" @角度/核心"导入{组件、输入、OnChanges、简单更改}。

  @组件({

  选择器:"应用程序-版本-子代",

  模板:` 1

  h3Version {{major}}{{minor}}/h3

  h4更改日志:/h4

  保险商实验所

  li *ngFor=允许更改changeLog“{ change } }/Li

  /ul

  `

  })

  导出类VersionChildComponent实现在线更改{

  @Input()专业:数字;

  @ Input()minor:number;

  changeLog:string[]=[];

  ngOnChanges(更改:简单更改){//ngOnchange适合监听子组件多个输入属性的变化

  const log:string[]=[];

  for (const propName in changes) {

  const changed prop=changes[prop name];

  const to=JSON。stringify(已更改道具。当前值);

  如果(换了道具。isfirst change()){

  log . push(` p将{ propName }的初始值设置为$ { to } `);

  }否则{

  const from=JSON。stringify(已更改道具。前一值);

  log.push(`${propName}从${from}更改为$ { to } `);

  }

  }

  this.changeLog.push(log.join(,));

  }

  }父组件怎么读取子组件的属性和调用子组件的方法?(2种方法)

  1、通过本地变量代表子组件

  父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法,如下例所示。

  思考:父组件可以通过这种方式读取子组件的私有属性和私有方法吗?

  父组件

  从" @角度/核心"导入{组件};

  从""导入{ CountdownTimerComponent } ./倒计时器。组件;

  @组件({

  选择器:应用程序-倒计时-家长-lv ,

  模板:` 1

  h3发射倒计时(通过本地变量)/h3

  按钮(点击)=定时器。Start() Start/button//调用子组件方法

  按钮(点击)=timer.stop()停止/按钮

  div class= seconds { { timer。秒} }/div//读取子组件属性

  应用-倒计时-定时器#定时器/app-倒计时-定时器

  `,

  样式网址:[./assets/demo.css]

  })

  导出类CountdownLocalVarParentComponent { }子组件

  从" @角度/核心"导入{组件,销毁时};

  @组件({

  选择器:"应用程序倒计时定时器",

  模板:" p{{message}}/p "

  })

  导出类CountdownTimerComponent实现OnDestroy {

  intervalId=0;

  消息="";

  秒=11;

  ngOnDestroy(){ this。清除定时器();}

  start(){ this。倒计时();}

  stop() {

  这个。清除定时器();

  this.message=`保持在T-${this.seconds}秒`;

  }

  private clear timer(){清除间隔(this。intervalid);}

  私人倒计时(){

  这个。清除定时器();

  这个。间隔id=窗口。setinterval(()={

  这个。秒-=1;

  if (this.seconds===0) {

  this.message=发射!;

  }否则{

  如果(这个。秒0){这个。秒=10;} //重置

  这个。message=` t-$ { this。秒}秒并且正在计数`;

  }

  }, 1000);

  }

  }2、父组件调用@viewChild()(基础,推荐使用)

  这个本地变量方法是个简单便利的方法。但是它也有局限性(只能在模板超文本标记语言中使用),因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的分时(同分时)代码对子组件没有访问权。

  当父组件类需要访问子组件时,可以把子组件作为查看孩子,注入到父组件里面。

  父组件类中访问子组件的属性和方法:

  从" @角度/核心"导入{ AfterViewInit,view child };

  从" @角度/核心"导入{组件};

  从""导入{ CountdownTimerComponent } ./倒计时器。组件;//引入子组件

  @组件({

  选择器:应用程序-倒计时-家长-vc ,

  模板:` 1

  h3发射倒计时(通过ViewChild)/h3

  按钮(点击)=start()开始/按钮

  按钮(点击)=stop()停止/按钮

  div class= seconds“{ seconds()} }/div

  应用-倒计时定时器/app-倒计时定时器

  `,

  样式网址:[./assets/demo.css]

  })

  导出类CountdownViewChildParentComponent实现AfterViewInit {

  //通过@ViewChild属性装饰器,将子组件CountdownTimerComponent注入到私有属性计时器组件里面。

  @ view child(CountdownTimerComponent)

  私有计时器组件:CountdownTimerComponent

  秒(){返回0;}

  //有角度创建了组件的子视图后会调用它,注意获取子组件的属性,要在子组件视图加载之后

  ngAfterViewInit

  //访问子组件属性

  setTimeout(()=this。秒=()=这个。计时器组件。秒,0);

  }

  start(){ this。计时器组件。start();} //访问子组件的方法

  stop(){ this。计时器组件。stop();}

  }注意:(使用场景很多,必须掌握)

  ngAfterViewInit()生命周期钩子是非常重要的一步。被注入的计时器组件只有在有角的显示了父组件视图之后才能访问,所以它先把秒数显示为0.

  然后有角的会调用ngAfterViewInit生命周期钩子,但这时候再更新父组件视图的倒计时就已经太晚了。Angular 的单向数据流规则会阻止在同一个周期内更新父组件视图。应用在显示秒数之前会被迫再等一轮。

  使用setTimeout()来等下一轮,然后改写秒数()方法,这样它接下来就会从注入的这个计时器组件里获取秒数的值。

  二、组件通过服务来通信(发布订阅者模式,基础,必须掌握)

  父组件和它的子组件共享同一个服务,利用该服务在组件家族内部实现双向通信。

  不仅局限于父子组件,只要组件与组件共享同一个服务,就可以实现数据通信。

  !parent.component.html

  p style= width:1000 px;边距:自动

  p class= card style= width:500 px;浮动:左侧

  卡片标题父组件/p

  卡体

  h5 class=卡片标题父组件/h5

   p class=表单组

  =服务输出的标签父组件服务输入:/标签

  输入类型=文本

   class=窗体-控件

  id=serviceoutput

  占位符=服务输入

  [(ngModel)]=serviceInput

  /p

  button class= BTN BTN小学(点击)=点击服务()服务方式/按钮

  /p

  /p

  子应用程序/子应用程序

  /p!child.component.html

  p class= card style= width:500 px;

  卡片标题子组件/p

  卡体

  h5 class=卡片标题子组件/h5

   p class=表单组

  =服务输出的标签子组件服务输入:/标签

  输入类型=文本

  窗体-控件

  id=serviceoutput

  占位符=服务输入

  [(ngModel)]=serviceInput

  /p

  button class= BTN BTN小学(点击)=点击服务()服务方式/按钮

  /p

  /p//服务重点

  //meditor.service.ts

  从" @角度/核心"导入{内射};

  从" rxjs/Subject "导入{主题};

  从" rxjs/可观察"导入{可观察的};

  @可注射()

  出口级医疗服务{

  private subject=new SubjectMeditorMsg();

  构造函数(){}

  //获取订阅者

  public get observable():observableeditormsg {

  返回这个。主题。asobservable();

  }

  //推送信息

  公共推送(消息:媒体消息)

  这个。主题。下一个(msg);

  }

  }

  //中间者信息

  导出接口介质g {

  id:字符串;

  正文:任意;

  }订阅:订阅=null//初始化一个订阅对象

  //子组件构造函数,用于监听数据推送

  建造者(私人医生:医生服务){

  这个。订阅=冥想者。获得可观察的().订阅(

  消息={

  控制台。日志(消息);

  if (msg.id===parent) { //id为父母,获取父组件数据

  这个。服务输入=消息。身体;

  }

  }

  );

  }

  //子组件将数据推送到中间着,给订阅者

  单击服务(){

  this.meditor.push({id: parent ,body:this。服务输入});

  }

  //父组件构造函数,用于监听数据推送

  建造者(私人医生:医生服务){

  这个。订阅=冥想者。获得可观察的().订阅(

  消息={

  控制台。日志(消息);

  if (msg.id===child) { //id为孩子,获取子组件数据

  这个。服务输入=消息。身体;

  }

  }

  );

  }

  //父组件将数据推送到中间着,给订阅者

  单击服务(){

  this.meditor.push({id: parent ,body:this。服务输入});

  }

  //注意:订阅一个对象,就是在生命周期结束前,要取消订阅。

  ngOnDestroy() {

  这个。订阅。退订();

  }思考:这种发布订阅者模式适合全局状态管理吗?

  三、可以通过本地缓存来实现通信(Cookie,LocalStorage、SessionStorage)

  !- catch_namae_type.ts -

  //目的是好维护

  //项目当中用到的页面缓存,需要在这里进行声明;键值保持一致

  //声明规则,不同类型的缓存使用前缀会话_/本地_/cookie_

  //动态设置缓存不用在这里声明,但是需要在键后面加_noSetType_ 标识

  导出常量CatchNameType={

  会话用户信息:会话用户信息,//用户信息

  session_toekn: session_token ,//token

  local _ log in info: local _ log in info ,//本地缓存用户名密码

  };

  !-接住-ts

  从" @角度/核心"导入{内射};

  //定义这个类,主要是看全局定义了哪些本地缓存

  从""导入{ CatchNameType } ./catch _ namae _ type ;

  //- 缓存工具类(三类方法)

  //Cookie(方法有:设置/获取/删除)

  //SStorage(sessionStorage)(方法有:设置/获取/移除/清除)

  //LStorage(localStorage)(方法有:设置/获取/移除/清除)

  @可注射({

  提供了:"根",

  })

  导出类捕获{

  //cookie

  公共静态Cookie={

  /**

  * cookie存贮

  * @param key属性

  * @param值值

  * @param字符串过期过期时间,单位天

  */

  设置(键:字符串,值:任意,过期:任意):void {

  如果(接住。is _ set _ catch _ name _ type(key)){

  const d=new Date();

  d。设置日期(d . getdate()expire);

  文档。cookie=` $ { key }=$ { value }expires=$ { d . toda testring()} `;

  }

  },

  get(key: string): string {

  const cookieStr=unescape(document。cookie);

  const arr=cookiestr。拆分(;);

  设cookieValue=

  //ts lint:disable-next-line:prefere-for-of

  对于(设I=0;长度;i ) {

  const temp=arr[i].拆分(=);

  if (temp[0]===key) {

  cookieValue=temp[1];

  打破;

  }

  }

  返回烹饪价值

  },

  remove(key: string): void {

  文档。cookie=` $ { encodeURIComponent(key)}=;expires=$ { new Date()} `;

  },

  };

  //会话存储

  公共静态存储={

  set(key: string,value: any): void {

  如果(接住。is _ set _ catch _ name _ type(key)){

  sessionStorage.setItem(key,JSON。string ify(值));

  }

  },

  get(key: string): any {

  const jsonString=

  会话存储。getitem(key)===未定义

  ?不明确的

  :会话存储。getitem(key);

  返回jsonString?JSON。parse(JSON字符串):null

  },

  remove(key: string): void {

  会话存储。移除项目(键);

  },

  clear(): void {

  会话存储。clear();

  },

  };

  //本地存储

  公共静态存储={

  set(key: string,value: any): void {

  如果(接住。is _ set _ catch _ name _ type(key)){

  localStorage.setItem(key,JSON。stringify(value));

  }

  },

  get(key: string): any {

  const jsonString=

  本地存储。getitem(key)===未定义

  ?不明确的

  :本地存储。getitem(key);

  返回jsonString?JSON。parse(JSON字符串):null

  },

  remove(key: string): void {

  本地存储。移除项目(键);

  },

  clear(): void {

  本地存储。clear();

  },

  };

  //设置缓存的时候是否在catch_name_type里面声明

  static is _ set _ catch _ name _ type(key:string):boolean {

  让允许=假

  //对动态设置缓存不进行检查

  if (key.indexOf(_noSetType_ )!==-1) {

  允许=真

  console.log(动态设置缓存,键);

  允许退货;

  }

  //对命名规则进行检查

  常量名称规则=

  key.indexOf(session_ )!==-1

  key.indexOf(local_ )!==-1

  key.indexOf(cookie_ )!==-1;

  如果(!nameRule) {

  允许=假

  console.log(命名规则错误,键);

  允许退货;

  }

  //静态设置的缓存需要配置类型

  Object.values(CatchNameType).forEach((item)={

  if (item===key) {

  允许=真

  }

  });

  如果(!允许){

  console.log(缓存操作失败,请检查配置缓存类型);

  }

  允许退货;

  }

  }四、页面路由传参也可以实现单向通信

  这部分内容,我会在路由章节整理。

  组件通信总结

  所以组件通信大概有如下几种:

  1、父子组件通信(1 、@Input() @output 2、本地变量#val 3 、@viewChild())

  2、通过服务

  3、页面缓存

  4、页面级组件传参(两个页面等同于两个组件)

  

动态组件

   组件的模板不会永远是固定的。应用可能会需要在运行期间按需加载一些新的组件。

  通过下面的例子可以了解动态组件的基本使用

  1、创建组件,被引入的组件

  @组件({

  模板: spanhello world/span

  })

  导出类动态组件{ }2、创建容器组件,用于加载动态组件

  @组件({

  选择器:"应用程序容器",

  模板:` 1

  div #动态容器/div

  按钮(单击)=createComponent()创建/按钮

  `

  })

  导出类AppContainerComponent {

  //声明容器

  @ViewChild(dynamicContainer ,{ read: ViewContainerRef })容器:ViewContainerRef

  }在AppContainerComponent组件中,通过@ViewChild装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回

  组件实例或相应的数字正射影像图元素,但这个示例中,我们需要获取ViewContainerRef实例也就是视图容器。可以在视图容器中创建、插入、删除组件等。

  3、动态创建组件

  在创建组件之前,需要注入ComponentFactoryResolver服务对象,该服务是动态加载组件的核心,可以将一个组件实例呈现到另一个

  组件视图上。

  使用ComponentFactoryResolve将已声明但未实例化的组件解析成为可以动态加载的组件//依赖组件类型获取对应的工厂,从名字上可以看出该工厂是用来初始化组件的

  const component factory=thiscomponentforyresolver(动态组件);

  //调用视图容器的创建组件方法并将组件添加到容器上。该方法内部会调用工厂的创造方法来初始化组件。

  const modalContainerRef=this . container . create component(component factory);4、为组件属性赋值

  通过以下方式为组件属性赋值

  modalcontainerref . instance . property=* * *;5、销毁组件

  使用后记得销毁组件。

  modalcontainerref . destroy();6、组件注册

  为了动态创建组件,组件需要在模块的entryComponents中声明。因为entryComponents中声明的组件Angular将创建一个

  ComponentFactory并存储在ComponentFactoryResolver中,这是动态加载的必要步骤。

  有关编程的更多信息,请访问:编程入门!那就是上面这篇文章看到的angular10组件相关概念的细节。更多请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: