細(xì)說 Angular 2+ 的表單(二):響應(yīng)式表單

細(xì)說 Angular 2+ 的表單(一):模板驅(qū)動(dòng)型表單

響應(yīng)式表單

響應(yīng)式表單乍一看還是很像模板驅(qū)動(dòng)型表單的涮毫,但響應(yīng)式表單需要引入一個(gè)不同的模塊: ReactiveFormsModule 而不是 FormsModule

import {ReactiveFormsModule} from "@angular/forms";
@NgModule({
  // 省略其他
    imports: [..., ReactiveFormsModule],
  // 省略其他
})
// 省略其他

與模板驅(qū)動(dòng)型表單的區(qū)別

接下來我們還是利用前面的例子,用響應(yīng)式表單的要求改寫一下:

<form [formGroup]="user" (ngSubmit)="onSubmit(user)">
  <label>
    <span>電子郵件地址</span>
    <input type="text" formControlName="email" placeholder="請(qǐng)輸入您的 email 地址">
  </label>
  <div *ngIf="user.get('email').hasError('required') && user.get('email').touched" class="error">
    email 是必填項(xiàng)
  </div>
  <div *ngIf="user.get('email').hasError('pattern') && user.get('email').touched" class="error">
    email 格式不正確
  </div>
  <div>
    <label>
      <span>密碼</span>
      <input type="password" formControlName="password" placeholder="請(qǐng)輸入您的密碼">
    </label>
    <div *ngIf="user.get('password').hasError('required') && user.get('password').touched" class="error">
      密碼是必填項(xiàng)
    </div>
    <label>
      <span>確認(rèn)密碼</span>
      <input type="password" formControlName="repeat" placeholder="請(qǐng)?jiān)俅屋斎朊艽a">
    </label>   
    <div *ngIf="user.get('repeat').hasError('required') && user.get('repeat').touched" class="error">
      確認(rèn)密碼是必填項(xiàng)
    </div>
    <div *ngIf="user.hasError('validateEqual') && user.get('repeat').touched" class="error">
      確認(rèn)密碼和密碼不一致
    </div>
  </div>
  <div formGroupName="address">
    <label>
      <span>省份</span>
      <select formControlName="province">
        <option value="">請(qǐng)選擇省份</option>
        <option [value]="province" *ngFor="let province of provinces">{{province}}</option>
      </select>
    </label>
    <label>
      <span>城市</span>
      <select formControlName="city">
        <option value="">請(qǐng)選擇城市</option>
        <option [value]="city" *ngFor="let city of (cities$ | async)">{{city}}</option>
      </select>
    </label>
    <label>
      <span>區(qū)縣</span>
      <select formControlName="area">
        <option value="">請(qǐng)選擇區(qū)縣</option>
        <option [value]="area" *ngFor="let area of (areas$ | async)">{{area}}</option>
      </select>
    </label>
    <label>
      <span>地址</span>
      <input type="text" formControlName="addr">
    </label>
  </div>
  <button type="submit" [disabled]="user.invalid">注冊(cè)</button>
</form>

這段代碼和模板驅(qū)動(dòng)型表單的那段看起來差不多宝剖,但是有幾個(gè)區(qū)別:

  • 表單多了一個(gè)指令 [formGroup]="user"
  • 去掉了對(duì)表單的引用 #f="ngForm"
  • 每個(gè)控件多了一個(gè) formControlName
  • 但同時(shí)每個(gè)控件也去掉了驗(yàn)證條件拯钻,比如 required吉嚣、minlength
  • 在地址分組中用 formGroupName="address" 替代了 ngModelGroup="address"

模板上的區(qū)別大概就這樣了观话,接下來我們來看看組件的區(qū)別:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from "@angular/forms";
@Component({
  selector: 'app-model-driven',
  templateUrl: './model-driven.component.html',
  styleUrls: ['./model-driven.component.css']
})
export class ModelDrivenComponent implements OnInit {
  
  user: FormGroup;
  
  ngOnInit() {
    // 初始化表單
    this.user = new FormGroup({
      email: new FormControl('', [Validators.required, Validators.pattern(/([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}/)]),
      password: new FormControl('', [Validators.required]),
      repeat: new FormControl('', [Validators.required]),
      address: new FormGroup({
        province: new FormControl(''),
        city: new FormControl(''),
        area: new FormControl(''),
        addr: new FormControl('')
      })
    });
  }
  
  onSubmit({value, valid}){
    if(!valid) return;
    console.log(JSON.stringify(value));
  }
}

從上面的代碼中我們可以看到,這里的表單( FormGroup )是由一系列的表單控件( FormControl )構(gòu)成的逊彭。其實(shí) FormGroup 的構(gòu)造函數(shù)接受的是三個(gè)參數(shù): controls(表單控件『數(shù)組』咸灿,其實(shí)不是數(shù)組,是一個(gè)類似字典的對(duì)象) 侮叮、 validator(驗(yàn)證器) 和 asyncValidator(異步驗(yàn)證器) 避矢,其中只有 controls 數(shù)組是必須的參數(shù),后兩個(gè)都是可選參數(shù)。

// FormGroup 的構(gòu)造函數(shù)
constructor(
  controls: {
    [key: string]: AbstractControl;
  }, 
  validator?: ValidatorFn, 
  asyncValidator?: AsyncValidatorFn
)

我們上面的代碼中就沒有使用驗(yàn)證器和異步驗(yàn)證器的可選參數(shù)审胸,而且注意到我們提供 controls 的方式是亥宿,一個(gè) key 對(duì)應(yīng)一個(gè) FormControl 。比如下面的 keypassword砂沛,對(duì)應(yīng)的值是 new FormControl('', [Validators.required]) 烫扼。這個(gè) key 對(duì)應(yīng)的就是模板中的 formControlName 的值,我們模板代碼中設(shè)置了 formControlName="password" 碍庵,而表單控件會(huì)根據(jù)這個(gè) password 的控件名來跟蹤實(shí)際的渲染出的表單頁(yè)面上的控件(比如 <input formcontrolname="password">)的值和驗(yàn)證狀態(tài)映企。

password: new FormControl('', [Validators.required])

那么可以看出,這個(gè)表單控件的構(gòu)造函數(shù)同樣也接受三個(gè)可選參數(shù)静浴,分別是:控件初始值( formState )堰氓、控件驗(yàn)證器或驗(yàn)證器數(shù)組( validator )和控件異步驗(yàn)證器或異步驗(yàn)證器數(shù)組( asyncValidator )。上面的那行代碼中苹享,初始值為空字符串双絮,驗(yàn)證器是『必選』,而異步驗(yàn)證器我們沒有提供得问。

// FormControl 的構(gòu)造函數(shù)
constructor(
  formState?: any, // 控件初始值
  validator?: ValidatorFn | ValidatorFn[], // 控件驗(yàn)證器或驗(yàn)證器數(shù)組
  asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] // 控件異步驗(yàn)證器或異步驗(yàn)證器數(shù)組
)

由此可以看出囤攀,響應(yīng)式表單區(qū)別于模板驅(qū)動(dòng)型表單的的主要特點(diǎn)在于:是由組件類去創(chuàng)建、維護(hù)和跟蹤表單的變化宫纬,而不是依賴模板焚挠。

那么我們是否在響應(yīng)式表單中還可以使用 ngModel 呢?當(dāng)然可以哪怔,但這樣的話表單的值會(huì)在兩個(gè)不同的位置存儲(chǔ)了: ngModel 綁定的對(duì)象和 FormGroup 宣蔚,這個(gè)在設(shè)計(jì)上我們一般是要避免的,也就是說盡管可以這么做认境,但我們不建議這么做。

FormBuilder 快速構(gòu)建表單

上面的表單構(gòu)造起來雖然也不算太麻煩挟鸠,但是在表單項(xiàng)目逐漸多起來之后還是一個(gè)挺麻煩的工作叉信,所以 Angular 提供了一種快捷構(gòu)造表單的方式 -- 使用 FormBuilder。

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
@Component({
  selector: 'app-model-driven',
  templateUrl: './model-driven.component.html',
  styleUrls: ['./model-driven.component.css']
})
export class ModelDrivenComponent implements OnInit {
  
  user: FormGroup;
  
  constructor(private fb: FormBuilder) {
  }
  
  ngOnInit() {
    // 初始化表單
    this.user = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required],
      repeat: ['', Validators.required],
      address: this.fb.group({
        province: [],
        city: [],
        area: [],
        addr: []
      })
    });
  }
  // 省略其他部分
}

使用 FormBuilder 我們可以無需顯式聲明 FormControl 或 FormGroup 艘希。 FormBuilder 提供三種類型的快速構(gòu)造: control , grouparray 硼身,分別對(duì)應(yīng) FormControl, FormGroup 和 FormArray。 我們?cè)诒韱沃凶畛R姷囊环N是通過 group 來初始化整個(gè)表單覆享。上面的例子中佳遂,我們可以看到 group 接受一個(gè)字典對(duì)象作為參數(shù),這個(gè)字典中的 key 就是這個(gè) FormGroup 中 FormControl 的名字撒顿,值是一個(gè)數(shù)組丑罪,數(shù)組中的第一個(gè)值是控件的初始值,第二個(gè)是同步驗(yàn)證器的數(shù)組,第三個(gè)是異步驗(yàn)證器數(shù)組(第三個(gè)并未出現(xiàn)在我們的例子中)吩屹。這其實(shí)已經(jīng)在隱性的使用 FormBuilder.control 了跪另,可以參看下面的 FormBuilder 中的 control 函數(shù)定義,其實(shí) FormBuilder 利用我們給出的值構(gòu)造了相對(duì)應(yīng)的 control

control(
    formState: Object, 
    validator?: ValidatorFn | ValidatorFn[], 
    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]
    ): FormControl;

此外還值得注意的一點(diǎn)是 address 的處理煤搜,我們可以清晰的看到 FormBuilder 支持嵌套免绿,遇到 FormGroup 時(shí)僅僅需要再次使用 this.fb.group({...}) 即可。這樣我們的表單在擁有大量的表單項(xiàng)時(shí)擦盾,構(gòu)造起來就方便多了嘲驾。

自定義驗(yàn)證

對(duì)于響應(yīng)式表單來說,構(gòu)造一個(gè)自定義驗(yàn)證器是非常簡(jiǎn)單的迹卢,比如我們上面提到過的的驗(yàn)證 密碼重復(fù)輸入密碼 是否相同的需求距淫,我們?cè)陧憫?yīng)式表單中來試一下。

  validateEqual(passwordKey: string, confirmPasswordKey: string): ValidatorFn {
    return (group: FormGroup): {[key: string]: any} => {
      const password = group.controls[passwordKey];
      const confirmPassword = group.controls[confirmPasswordKey];
      if (password.value !== confirmPassword.value) {
        return { validateEqual: true };
      }
      return null;
    }
  }

這個(gè)函數(shù)的邏輯比較簡(jiǎn)單:我們接受兩個(gè)字符串(是 FormControl 的名字),然后返回一個(gè) ValidatorFn婶希。但是這個(gè)函數(shù)里面就奇奇怪怪的榕暇,
比如 (group: FormGroup): {[key: string]: any} => {...} 是什么意思啊喻杈?還有彤枢,這個(gè) ValidatorFn 是什么鬼?我們來看一下定義:

export interface ValidatorFn {
    (c: AbstractControl): ValidationErrors | null;
}

這樣就清楚了筒饰, ValidatorFn 是一個(gè)對(duì)象定義缴啡,這個(gè)對(duì)象中有一個(gè)方法,此方法接受一個(gè) AbstractControl 類型的參數(shù)(其實(shí)也就是我們的 FormControl瓷们,而 AbstractControl 為其父類)业栅,而這個(gè)方法還要返回 ValidationErrors ,這個(gè) ValidationErrors 的定義如下:

export declare type ValidationErrors = {
    [key: string]: any;
};

回過頭來再看我們的這句 (group: FormGroup): {[key: string]: any} => {...}谬晕,大家就應(yīng)該明白為什么這么寫了碘裕,我們其實(shí)就是在返回一個(gè) ValidatorFn 類型的對(duì)象。只不過我們利用 javascript/typescript 對(duì)象展開的特性把 ValidationErrors 寫成了 {[key: string]: any} 攒钳。

弄清楚這個(gè)函數(shù)的邏輯后帮孔,我們?cè)趺词褂媚兀糠浅:?jiǎn)單不撑,先看代碼:

    this.user = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required],
      repeat: ['', Validators.required],
      address: this.fb.group({
        province: [],
        city: [],
        area: [],
        addr: []
      })
    }, {validator: this.validateEqual('password', 'repeat')});

和最初的代碼相比文兢,多了一個(gè)參數(shù),那就是 {validator: this.validateEqual('password', 'repeat')}焕檬。FormBuilder 的 group 函數(shù)接受兩個(gè)參數(shù)姆坚,第一個(gè)就是那串長(zhǎng)長(zhǎng)的,我們叫它 controlsConfig实愚,用于表單控件的構(gòu)造兼呵,以及每個(gè)表單控件的驗(yàn)證器兔辅。但是如果一個(gè)驗(yàn)證器是要計(jì)算多個(gè) field 的話,我們可以把它作為整個(gè) group 的驗(yàn)證器萍程。所以 FormBuilder 的 group 函數(shù)還接收第二個(gè)參數(shù)幢妄,這個(gè)參數(shù)中可以提供同步驗(yàn)證器或異步驗(yàn)證器。同樣還是一個(gè)字典對(duì)象茫负,是同步驗(yàn)證器的話蕉鸳,key 寫成 validator,異步的話寫成 asyncValidator 忍法。

現(xiàn)在我們可以保存代碼潮尝,啟動(dòng) ng serve 到瀏覽器中看一下結(jié)果了:

響應(yīng)式表單對(duì)于多值驗(yàn)證的處理
響應(yīng)式表單對(duì)于多值驗(yàn)證的處理

FormArray 有什么用?

我們?cè)谫?gòu)物網(wǎng)站經(jīng)常遇到需要維護(hù)多個(gè)地址饿序,因?yàn)槲覀冇行┥唐废M偷焦久闶В行┬枰偷郊依铮€有些給父母采購(gòu)的需要送到父母那里原探。這就是一個(gè)典型的 FormArray 可以派上用場(chǎng)的場(chǎng)景乱凿。所有的這些地址的結(jié)構(gòu)都是一樣的,有省咽弦、市徒蟆、區(qū)縣和街道地址,那么對(duì)于處理這樣的場(chǎng)景型型,我們來看看在響應(yīng)式表單中怎么做段审。

首先,我們需要把 HTML 模板改造一下闹蒜,現(xiàn)在的地址是多項(xiàng)了寺枉,所以我們需要在原來的地址部分外面再套一層,并且聲明成 formArrayName="addrs"绷落。 FormArray 顧名思義是一個(gè)數(shù)組姥闪,所以我們要對(duì)這個(gè)控件數(shù)組做一個(gè)循環(huán),然后讓每個(gè)數(shù)組元素是 FormGroup嘱函,只不過這次我們的 [formGroupName]="i" 是讓 formGroupName 等于該數(shù)組元素的索引甘畅。

<div formArrayName="addrs">
    <button (click)="addAddr()">Add</button>
    <div *ngFor="let item of user.controls['addrs'].controls; let i = index;">
      <div [formGroupName]="i">
        <label>
          <span>省份</span>
          <select formControlName="province">
            <option value="">請(qǐng)選擇省份</option>
            <option [value]="province" *ngFor="let province of provinces">{{province}}</option>
          </select>
        </label>
        <label>
          <span>城市</span>
          <select formControlName="city">
            <option value="">請(qǐng)選擇城市</option>
            <option [value]="city" *ngFor="let city of (cities$ | async)">{{city}}</option>
          </select>
        </label>
        <label>
          <span>區(qū)縣</span>
          <select formControlName="area">
            <option value="">請(qǐng)選擇區(qū)縣</option>
            <option [value]="area" *ngFor="let area of (areas$ | async)">{{area}}</option>
          </select>
        </label>
        <label>
          <span>地址</span>
          <input type="text" formControlName="street">
        </label>
      </div>
    </div>
  </div>

改造好模板后,我們需要在類文件中也做對(duì)應(yīng)處理往弓,去掉原來的 address: this.fb.group({...}),換成 addrs: this.fb.array([]) 蓄氧。而

this.user = this.fb.group({
  email: ['', [Validators.required, Validators.email]],
  password: ['', Validators.required],
  repeat: ['', Validators.required],
  addrs: this.fb.array([])
}, {validator: this.validateEqual('password', 'repeat')});

但這樣我們是看不到也增加不了新的地址的函似,因?yàn)槲覀冞€沒有處理添加的邏輯呢,下面我們就添加一下:其實(shí)就是建立一個(gè)新的 FormGroup喉童,然后加入 FormArray 數(shù)組中撇寞。

  addAddr(): void {
    (<FormArray>this.user.controls['addrs']).push(this.createAddrItem());
  }

  private createAddrItem(): FormGroup {
    return this.fb.group({
      province: [],
      city: [],
      area: [],
      street: []
    })
  }

到這里我們的結(jié)構(gòu)就建好了,保存后,到瀏覽器中去試試添加多個(gè)地址吧蔑担!

FormArray 處理結(jié)構(gòu)相同的多組表單項(xiàng)
FormArray 處理結(jié)構(gòu)相同的多組表單項(xiàng)

響應(yīng)式表單的優(yōu)勢(shì)

首先是可測(cè)試能力牌废。模板驅(qū)動(dòng)型表單進(jìn)行單元測(cè)試是比較困難的,因?yàn)轵?yàn)證邏輯是寫在模板中的啤握。但驗(yàn)證器的邏輯單元測(cè)試對(duì)于響應(yīng)式表單來說就非常簡(jiǎn)單了鸟缕,因?yàn)槟愕尿?yàn)證器無非就是一個(gè)函數(shù)而已。

當(dāng)然除了這個(gè)優(yōu)點(diǎn)排抬,我們對(duì)表單可以有完全的掌控:從初始化表單控件的值懂从、更新和獲取表單值的變化到表單的驗(yàn)證和提交,這一系列的流程都在程序邏輯控制之下蹲蒲。

而且更重要的是番甩,我們可以使用函數(shù)響應(yīng)式編程的風(fēng)格來處理各種表單操作,因?yàn)轫憫?yīng)式表單提供了一系列支持 Observable 的接口 API 届搁。那么這又能說明什么呢缘薛?有什么用呢?

首先是無論表單本身還是控件都可以看成是一系列的基于時(shí)間維度的數(shù)據(jù)流了卡睦,這個(gè)數(shù)據(jù)流可以被多個(gè)觀察者訂閱和處理宴胧,由于 valueChanges 本身是個(gè) Observable,所以我們就可以利用 RxJS 提供的豐富的操作符么翰,將一個(gè)對(duì)數(shù)據(jù)驗(yàn)證牺汤、處理等的完整邏輯清晰的表達(dá)出來。當(dāng)然現(xiàn)在我們不會(huì)對(duì) RxJS 做深入的討論浩嫌,后面有專門針對(duì) RxJS 進(jìn)行講解的章節(jié)檐迟。

this.form.valueChanges
        .filter((value) => this.user.valid)
        .subscribe((value) => {
           console.log("現(xiàn)在時(shí)刻表單的值為 ",JSON.stringify(value));
        });

上面的例子中,我們?nèi)〉帽韱沃档淖兓肽停缓筮^濾掉表單存在非法值的情況追迟,然后輸出表單的值。這只是非常簡(jiǎn)單的一個(gè) Rx 應(yīng)用骚腥,隨著邏輯復(fù)雜度的增加敦间,我們后面會(huì)見證 Rx 卓越的處理能力。

慕課網(wǎng) Angular 視頻課上線: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末束铭,一起剝皮案震驚了整個(gè)濱河市廓块,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌契沫,老刑警劉巖带猴,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異懈万,居然都是意外死亡拴清,警方通過查閱死者的電腦和手機(jī)靶病,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來口予,“玉大人娄周,你說我怎么就攤上這事』ν#” “怎么了煤辨?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)牙甫。 經(jīng)常有香客問我掷酗,道長(zhǎng),這世上最難降的妖魔是什么窟哺? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任泻轰,我火速辦了婚禮,結(jié)果婚禮上且轨,老公的妹妹穿的比我還像新娘浮声。我一直安慰自己,他們只是感情好旋奢,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布泳挥。 她就那樣靜靜地躺著,像睡著了一般至朗。 火紅的嫁衣襯著肌膚如雪屉符。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天锹引,我揣著相機(jī)與錄音矗钟,去河邊找鬼。 笑死嫌变,一個(gè)胖子當(dāng)著我的面吹牛吨艇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腾啥,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼东涡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了倘待?” 一聲冷哼從身側(cè)響起疮跑,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凸舵,沒想到半個(gè)月后祸挪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贞间,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年贿条,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片增热。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡整以,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出峻仇,到底是詐尸還是另有隱情公黑,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布摄咆,位于F島的核電站凡蚜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吭从。R本人自食惡果不足惜朝蜘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涩金。 院中可真熱鬧谱醇,春花似錦、人聲如沸步做。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)全度。三九已至煮剧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間将鸵,已是汗流浹背勉盅。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咨堤,地道東北人菇篡。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像一喘,于是被迫代替她去往敵國(guó)和親驱还。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容