Angular基础教程

前言

Angular相比于原生js有什么优势? 什么是单页面应用(SPA)呢?

优势:

  1. 项目架构很清晰
  2. 代码的复用性强
  3. 代码的可测试强,可以添加e2e测试,单元测试

单页面应用相比较之前请求多个html页面的优势

  1. 用户体验好,一次性加载所有的js和css,内容的改变不需要重新加载整个页面
  2. 前后端分离

Angular开发环境搭建

在原生js里一般通过<Script>标签引入一些第三方库,随着框架的的普及,之前引入的方式略low,工作中也很少用到这种方式,在学习Angular时候,我们就采用脚手架形式的安装。这就需要我们安装Node.js,用里边的npm来进行安装。

node安装

http://nodejs.cn/

可以通过 node -v 以及 npm -v 的形式来检查是否安装成功

脚手架安装

node安装成功之后,可以使用npm命令安装脚手架

npm install -g @angular/cli  // 装的是最新的版本
npm install -g @angular/cli@xx.xx.x // 也可以安装指定版本

创建一个新项目并执行

ng new project_name  // project_name用户可以自定义项目名
cd project_name
ng serve
浏览器输入http://localhost:4200 便可看到相关页面

[图片上传失败...(image-b13261-1637659554980)]

输入命令之后,按照接下来的提示,进行选择,第一个是否选择在严格模式下创建这个项目;第二是否在创建项目的时候帮你把router文件一并创建出来;第三是用那种css类型进行编码。可以按照上图的选择情况进行创建。

项目目录介绍

[图片上传失败...(image-6ff86f-1637659554980)]

组件介绍

组件的概念

[图片上传失败...(image-537545-1637659554980)]
介绍组件之前,我们先来介绍一下组件模块之前的关系,框架都是推崇组件化开发的,页面上所有的元素都可以看做一个组件,具体页面中组件划分的粒度是多大,还是按照自己的个人开发习惯来进行。在angular中我们知道了app模块是我们的根模块,他管理了整个单页面应用中涉及到其他模块组件。我们来熟悉一下app.module.ts这个根模块文件。

[图片上传失败...(image-702fde-1637659554980)]

@NgModule是 angular中的装饰器,是需要从'@angular/core'导入进来
作用: 帮助开发者组织业务代码,开发者可以利用 NgModule 把关系比较紧密的组件组织到一起,他不仅可以控制组件,还可以控制指令,管道等(本期我们只要先学会组件,指令和管道后期会讲到)

  1. declarations:用来放组件、指令、管道的声明, 组件、指令、管道都必须属于一个模块,而且只能属于一个模块。

  2. imports:用来导入外部模块而非组件。

  3. exports: 我们这个模块需要导出的一些组件,指令,模块等; 如果别的模块导入了我们这个模块,那么别的模块就可以直接使用我们在这里导出的组件,指令模块等.

  4. providers:需要使用的Service都放在这里。指定应用程序的根级别需要使用的service

  5. bootstrap:定义启动组件。你可能注意到了这个配置项是一个数组,也就是说可以指定做个组件作为启动点。一般这个不需要改动。

由上述可见: angular中单页面是有一个根模块的,这个模块中包含了很多组件,也就是说多个组件组成了模块。

hello world以及组件讲解

首先我们先将app.component.html中的内容全部清空,为我们接下来的组件学习做准备。

1. 在app文件夹下,新建一个组件文件,文件名为hello-world.component.ts

[图片上传失败...(image-b51833-1637659554980)]
从上图片中,我们可以得知组件的组成有以下三部分组成:

  • (类名的命名是根据组件的文件名来决定的,首字母大写的驼峰命名方式)

  • 装饰器@component, 需要从'@angular/core'中导入,其作用是把某个类标记为Angular组件,并为他配置一些元数据,目前这里只涉及到3个元数据,其中selector也称作是选择器,我们可以把他理解成我们自定义组件的名字,一般他的命名也是app-组件文件的名字,更多的元数据应用可以参照官网。

  • HTML模板,就是template。

2. 上边已经提到到,组件必须在模块中声明才可以正常使用,所以我在根模块声明一下

[图片上传失败...(image-e5e65a-1637659554980)]
3. 我们将定义好的组件在app的html中中引用一下

[图片上传失败...(image-2029ac-1637659554980)]

这个时候页面上已经可以完美的展现出 hello world了,这样一个简单的组件也就封装好了。

组件中的插值表达式应用

假如有个用户在上述的组件中不希望展示”hello world“,怎么样动态灵活的显示在上述组件中呢?

其实在angular中我们可以使用插值的方式,将一些变量或者表达式嵌入到文本中,默认是用{{}}来作为定界符

// hello-world.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: '<h1> {{title}} </h1>',
  styles: ['h1 { color: red; }']
})

export class HelloWorldComponent {
  public title = '学习Angular第一课';
}

此时打开浏览器页面,就可以看到页面上展示” 学习Angular第一课

组件中内置指令的介绍

首先我们来看一下什么是指令?其实指令是为 Angular 应用程序中的元素添加额外行为的类
这里主要介绍一下属性型指令结构型指令
属性型指令:

  1. NgClass: 动态添加和删除一组css类
// NgClass与表达式一起使用,通过ngClassHasColor来决定是否添加ngclass-color这个类
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <div [ngClass]="ngClassHasColor ? 'ngclass-color' : ''">练习Ngclass的使用</div>
  `,
  styles: [
    'h1 { color: red; }',
    '.ngclass-color {color: green}'
  ]
})

export class HelloWorldComponent {
  public ngClassHasColor = true;
}

  1. NgStyle: 动态添加和删除一组内联样式
1. import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <div [ngStyle]="{'color': 'black', 'font-size': '18px'}">练习Ngstyle的使用</div>
  `,
  styles: []
})

export class HelloWorldComponent {}

// 或者我们可以将多个内联样式封装到一个对象中,这样我们就可以动态控制对象中的值,
// 只要将对象的变量直接赋值给[ngStyle]
2. import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <div [ngStyle]=ngStyleObj>练习Ngstyle的使用</div>
  `,
  styles: []
})

export class HelloWorldComponent {
  public ngStyleObj = {
    'color': 'black',
    'font-size': '18px'
  };
}


  1. NgModel: 将数据的双向绑定添加到HTML表单元素上
    结构型指令:
  2. NgFor: 重复循环渲染某一个节点
// 在js中,如果需要渲染列表,我们是需要手动遍历数据,然后动态生成元素,这样才能实现列表的渲染
// 在angular中,我们仅仅需要使用NgFor这个语法糖就可以实现数据的循环展示
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <ul>
    <li *ngFor="let subject of studySubjects">{{subject}}</li>
  </ul>
  `,
  styles: []
})

export class HelloWorldComponent {
  public studySubjects = [
    '环境搭建',
    '项目目录介绍',
    '组件介绍',
    'ng-zorro组件库的引入介绍'
  ];
}
// 除此之外,我们还可以获取到循环对象的角标index,并在模板中使用它,如下所示:
<ul>
    <li *ngFor="let subject of studySubjects;  let i=index">{{ i+1 }} - {{ subject }}</li>
 </ul>

循环结果如下所示:
[图片上传失败...(image-91ad8a-1637659554980)]

  1. NgIf: 从模板中创建或销毁子视图,通俗说是控制组件或者元素的显示和隐藏
// 假如有个需求:
// 说是用户身份是’backend‘的就可以看到上述的循环列表;其他的用户都不能看到,在Angular中改怎么实现呢?
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <ul *ngIf="userRole === 'backend'">
    <li *ngFor="let subject of studySubjects;  let i=index">{{ i+1 }} - {{ subject }}</li>
  </ul>
  `,
  styles: []
})

export class HelloWorldComponent {
  public userRole='backend'; // 只要userRole的值不是backend,那么上述的列表就不会展示
}

  1. NgSwitch: 类似于js中的switch的逻辑,也是按照满足固定的条件显示某些模板/视图

NgSwitch是一组属性(三个) 分别是: NgSwitch; NgSwitchCase; ngSwitchDefault

// 现在假如有一个需求:
// 说是假如用户的年龄是12则显示小学毕业
// 假如用户的年龄是18则显示高中毕业
// 假如用户的年龄是22则显示大学毕业
// 其他情况全都显示未知
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <div [ngSwitch]="age">
   <span *ngSwitchCase="'12'">小学毕业</span>
   <span *ngSwitchCase="'18'">高中毕业</span>
   <span *ngSwitchCase="'22'">大学毕业</span>
   <span *ngSwitchDefault>未知</span>
  </div>
  `,
  styles: []
})

export class HelloWorldComponent {
  public age = 18;  // 页面显示”高中毕业“
}

组件中属性的绑定

在上述内容中,我们发现很多用[]包裹起来的值,比如之前提到的[ngClass],[ngStyle],其实这些被中括号包裹起来的,我们就称作是属性绑定。他的作用就是动态设置 HTML 元素或指令属性值。接下来我们介绍一下在开发中常见的属性值。

基本属性绑定

import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <img [src]="itemImageUrl">
  <button [disabled]="isDisabledBtn">Disabled Button</button>
  <span [innerHtml]="spanText"></span>
  `,
  styles: []
})

export class HelloWorldComponent {
  public itemImageUrl = '../../assets/bg.png';
  public isDisabledBtn = false;
  public spanText='通过属性绑定来展示span标签的内容';
}

绑定到class的Attribute(平时开发推荐使用,且用的频率很高)

此方式可以动态的添加和删除单个或者多个类,与上述的[ngClass]很类似,但是用法更加的简单

//动态添加单个class和多个class
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `

  <div [class.textcolor]="isSingleClass">尝试绑定单个class</div>
  <div [class]="multipleClass">尝试绑定多个class</div>
  `,
  styles: [
    '.bgColor{background-color: yellow}',
    '.textcolor{color: red}'
  ]
})

export class HelloWorldComponent {
  public isSingleClass: boolean;
  public multipleClass = {  // 所以这里生效的只有bgColor这个类
    bgColor: true,
    textcolor: false,
  };
}

// 同理属性style也是一样的也可以像class一样

组件中管道的使用

管道用来对字符串、货币金额、日期和其他显示数据进行转换和格式化。它是一些简单的函数,angular中提供了一下内置的管道方法,同时我们也可以自定义管道。

  • DatePipe:根据本地环境中的规则格式化日期值。
  • UpperCasePipe:把文本全部转换成大写。
  • LowerCasePipe :把文本全部转换成小写。
  • CurrencyPipe :把数字转换成货币字符串,根据本地环境中的规则进行格式化。
  • DecimalPipe:把数字转换成带小数点的字符串,根据本地环境中的规则进行格式化。
  • PercentPipe :把数字转换成百分比字符串,根据本地环境中的规则进行格式化。
// 以date这个内置管道作为例子
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <p>小明的生日是{{ birthday | date }}</p>
  <p>小明的生日是{{ birthday | date:"MM/dd/yy" }}</p>
  `,
  styles: []
})

export class HelloWorldComponent {
  public birthday = new Date(1988, 3, 15);
}

//结果
小明的生日是Apr 15, 1988
小明的生日是04/15/88

该组件的 birthday 值通过管道操作符(|)流向 date 函数。
date: 后边的值是传入到这个管道的参数,如果有多个参数可以用多个:来分割传入

组件的事件绑定-方法和参数

组件的事件绑定确实比原生js的事件绑定简便很多,我们先来介绍一下angular中是怎么样实现事件绑定的吧~

  1. 按钮的事件绑定
//我们希望通过点击按钮实现一个累加器
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello-world',
  template: `
  <button (click)="addCount()">当前数值加1</button>
  <span>当前值是:{{curNumber}}</span>
  `,
  styles: []
})

export class HelloWorldComponent {
  public curNumber = 1;

  public addCount(): void {
    this.curNumber++;
  }
}

  1. 事件绑定中的$event对象

在编写响应事件时,是可以接受一个$event参数的,这个参数就是关于响应事件的一些内容.

<button (click)="addCount($event)">当前数值加1</button>

 public addCount(event): void {
    console.log(event);
    this.curNumber++;
  }
  1. 事件的传参
<button (click)="addCount($event, 5)">当前数值加1</button>

 public addCount(event, paramsNumber: number): void {
     this.curNumber = this.curNumber + paramsNumber; // 可以实现每次加5
  }
  1. 一个按钮同时调用两个方法
// 通过分号去分割两个方法
<button (click)="addCount($event, 5);consoleCount()">当前数值加1</button>

// 定义的两个方法
 public addCount(event: any, paramsNumber: number): void {
    this.curNumber = this.curNumber + paramsNumber;
  }

  public consoleCount(): void {
    console.log(this.curNumber);
  }

组件的声明方式

上述示例中,我们是把html,css已经ts的逻辑写到同一个文件中的,在真正开发的过程中,如果逻辑较复杂这种内联式的组件声明是不推荐的,一般情况下我们常用的是外部引用文件的形式,如下所示

// template和style都是通过引入外层的文件,这样就可以把html和css的代码放到单独的文件进行开发
import { Component } from '@angular/core';

@Component({
  selector: 'app-outside-introduction',
  templateUrl: './outside-introduction.component.html',
  styleUrls: ['./outside-introduction.component.scss']
})

export class OutsideIntroductionComponent {}

具体的组件OutsideIntroductionComponen创建如下:

[图片上传失败...(image-3d8197-1637659554980)]

组件的父子组件传值

@Input()@Output() 为子组件提供了一种与其父组件通信的方法。 @Input() 允许父组件更新子组件中的数据。相反,@Output() 允许子组件向父组件发送数

父组件向子组件里传值是通过属性进行传值的

// app-outside-introduction是app.component的子组件,现在通过这个组件来学习一下父子传值

// outside-introduction.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-outside-introduction',
  templateUrl: './outside-introduction.component.html',
  styleUrls: ['./outside-introduction.component.scss']
})

export class OutsideIntroductionComponent {
 // @Input()是专门用来实现传值的,需要提前在'@angular/core引入
 // 这句声明,表示希望在父组件引入子组件的html页面中,从父组件中传入一个值给到showNumber
  @Input() public showNumber: number;
}

// outside-introduction.component.html
<div>显示父组件传进来的值{{showNumber}}</div>

// 父组件app.component.html
<div>父组件中的值:{{defaultNum}}</div>
<app-outside-introduction [showNumber]="defaultNum"></app-outside-introduction>

// 父组件app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  public defaultNum = 5;
}

上述核心代码其实只要两步:

1. 子组件通过@Input声明要进行通信的变量
@Input() public showNumber: number;

2.父组件在引入的时候通过属性绑定来传递在ts中定义好的变量
<app-outside-introduction [showNumber]="defaultNum"></app-outside-introduction>
export class AppComponent {
  public defaultNum = 5;
}

子组件向父组件传值,通过事件的形式来实现

假如上述子组件有个累加器的功能,希望将内部不断累计的东西传递给父组件,并在父组件显示改怎么做呢?

子组件
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-outside-introduction',
  templateUrl: './outside-introduction.component.html',
  styleUrls: ['./outside-introduction.component.scss']
})

export class OutsideIntroductionComponent {
  @Input() public showNumber: number;
  // 下边的逻辑主要实现自组价向父组件emit一个值
  @Output() public curCountChange = new EventEmitter<number>();
  public curCount = 1;
  public addCount(): void {
    this.curCount++;
    this.curCountChange.emit(this.curCount);
  }
}
父组件
// 这里的事件名curCountChange必须和子组件定义@Output()的名字是一样的,=后边的方法名可以自己随意定义
// `$event`是子组件传过来的值
<app-outside-introduction [showNumber]="defaultNum" (curCountChange)="handlCurCountChange($event)"></app-outside-introduction>

  public handlCurCountChange(value: number): void {
    // 这里的value就是子组件传过来的值
    this.valueFromChild = value;
  }

详细介绍一下子组件中的@output

  • @Output() - 一个装饰器函数,它把该属性标记为数据从子组件进入父组件的一种途径
  • curCountChange - 这个 @Output() 的名字
  • EventEmitter<number> - 这个 @Output() 的类型,就是子组件传给父组件的数据类型
  • new EventEmitter<number>() - 使用 Angular 来创建一个新的事件发射器,它发出的数据是 number 类型的。
  • curCountChange.emit() - 通过emit方法来向父组件传递值
    最终父组件通过事件的形式接受子组件穿过来的值

组件的双向绑定

其实双向绑定就是特殊一点或者是一个更加简洁的父子组件传值,他是通过[()] 语法来组合属性和事件绑定,唯一特殊的点在于他们的命名,为了使双向数据绑定有效,@Output() 属性的名字必须遵循 inputChange 模式,其中 input 是相应 @Input() 属性的名字。例如,如果 @Input() 属性为 size ,则 @Output() 属性必须为 sizeChange
在这里我们不做过多的赘述,直接通过表单元素中使用最多的[(ngModel)]来认识双向绑定。

// 这个input其实是angular封装过的一个组件
// 这里用到双向绑定[(ngModel)]的前提是,需要在根组件中先将FormsModule导入进来
// import { FormsModule } from '@angular/forms';

 <input [(ngModel)]="inputValue">
 
 export class OutsideIntroductionComponent {
  public inputValue = 5;
}

针对双向绑定要注意的点:

[(ngModel)] 语法只能设置数据绑定属性。
如果用户想在input框中的值更改的时候做一些额外的事情,应该怎么办? ===> 我们可以把这个组合模式拆解开来,具体如下:

<input [(ngModel)]="inputValue" (ngModelChange)="consoleValue($event)">

  public consoleValue(value: string): void {
    console.log(value);
  }

组件的生命周期

angular组件的生命周期共有9个,但是在真正开发过程中用到只有3-4个,今天我们主要讲一下常用的生命周期吧~

[图片上传失败...(image-903c24-1637659554980)]

具体的生命周期请参考:生命周期详情

ts在angular组件中的使用

其实在上述所有的文件中,我们经常看到一些public,void这种关键字的声明,包括我们组件中用到的class,这些都是typeScript中的一些内容,不过我们常用的还是ts中一些类型的定义,在angular我们是要求所有的变量,函数都是要用类型定义的~
首先我们先来简单的认识一下ts

ts是js的superType,也就是说他更加的规范,而且可以在编译阶段就能针对不规范的代码进行提示.

[图片上传失败...(image-4ec2c1-1637659554981)]
常见的一些类型定义:

  以下为类型注解/类型定义
  
  public a: number;
  public b: boolean;
  public c: string;
  public d: any;
  public e: number[] = [1, 2, 31];
  public f: any[] = [1, true,'a', false];
  
  //枚举定义
  enum Color {
   Red,
   Green,
   Blue,
 }
  public backgroundColor = Color;
  // 如果使用backgroundColor.Red 返回的值就是0, 因为enum默认就是按照顺序排列里边的内容的
类型推断,像这些简单的类型,如果直接赋值的话,那么ts可以智能推断出这个什么类型
public curCount = 1;
public inputValue = 5;

[图片上传失败...(image-282c2d-1637659554981)]

在项目中引入ng-zorro组件库

  1. 在本项目下安装指定版本的组件库 npm install ng-zorro-antd@8.5.2 --save

npm install安装文件的具体位置分析

  • npm install xxx: 安装项目到项目目录下,不会将模块依赖写入devDependenciesdependencies
  • npm install -g xxx: -g是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm cinfig prefix的位置
  • npm install -save xxx-save的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。当是在项目所必须的,则用这个命令
  • npm install -save-dev xxx-save-dev的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。当是开发过程中所依赖的需要用这个命令
  1. 在app.module中引入这个组件

[图片上传失败...(image-650ccc-1637659554981)]

  1. 在全局的style.scss文件中引入组件样式
/* You can add global styles to this file, and also import other style files */
@import "~ng-zorro-antd/style/index.min.css"; /* 引入基本样式 */
@import "~ng-zorro-antd/button/style/index.min.css"; /* 引入组件样式 */
  1. 在angular.json文件中引入svg和样式
{ "assets":[
    ...
    { "glob": "**/*", 
      "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/", 
      "output": "/assets/" } 
      ],
  "styles": [ 
     ... 
     "node_modules/ng-zorro-antd/ng-zorro-antd.min.css"]
  }

如想了解更加详细的引入UI组件,请参考快速安装ng-zorro-antd

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容