現有iOS項目中嵌入幾個 React Native 頁面

1.搭建環境

具體步驟參考官方文檔,環境弄好后,工程目錄如下

  • 原iOS項目被放在了根目錄的iOS文件夾下(沒做安卓,所以沒有安卓的路徑)
  • React Native 的iOS入口是 index.ios.js
  • 其他 React Native 的代碼放在了 component文件夾
  • main.jsbundle 為我們所寫 React Native 代碼的集合,發布時才生成(或方便真機調試)

2.入口

RN入口index.ios.js

'use strict'; //使用嚴格模式
import React, { Component } from 'react';
import {
  AppRegistry,//用于注冊組件
  StyleSheet,//使用樣式
  Text,
  View,
  Image,
  NavigatorIOS,//導航控制器
  TouchableHighlight,//點擊效果
  NativeModules//調用native方法
} from 'react-native';

import Repayment from './component/repayment';
import SettlementAccountList from './component/SettlementAccountList';

export default class MECRM extends Component {

  _handleNavigationBackRequest() {
    var RNBridge = NativeModules.RNBridge;
    RNBridge.back();
   }

  _settlementAccountList() {
    var status = this.props["status"];
    if (status === 0 || status === 3) {
      this.refs['nav'].push({
        title: '返款人信息表',
        component: SettlementAccountList,
        barTintColor: '#7B9DFD',
        tintColor: 'white',
        passProps: {
        }
      })
    }
  }

  render() {
    return (
        <NavigatorIOS
            ref='nav'
            initialRoute={{
                component: Repayment,//注冊的組件名一定要大寫
                title: '返款申請',
                rightButtonIcon: require('image!contacts'),
                leftButtonTitle: '返回',
                onLeftButtonPress: () => this._handleNavigationBackRequest(),
                onRightButtonPress: () => this._settlementAccountList(),
                passProps: {
                  orderid: this.props["orderid"],
                  status: this.props["status"],
                  price: this.props["price"]
                },
                barTintColor: '#7B9DFD'
            }}
            style={{flex: 1}}
            itemWrapperStyle={styles.itemWrapper}
            tintColor="white"
            titleTextColor ='white'
        />
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('RNBackApply', () => MECRM);

index.ios.js作為RN代碼入口,關鍵點是 NavigatorIOS 標簽:

  • ref='nav',把 NavigatorIOS 對象標記為‘nav’方便調用,類似iOS開發中的tag,this.refs['nav']便能找到 NavigatorIOS 對象。(彌補this傳遞的麻煩)
  • initialRoute 初始化路由,這里初始化起始頁為Repayment,然后點擊左右按鈕分別執行handleNavigationBackRequest(返回native頁面)、settlementAccountList(跳轉到返款賬號列表頁面)
  • passProps,傳遞 orderid、status、price到Repayment頁面(此處這3個參數是從naive傳遞到index.ios.js,index.ios.js再傳遞給了Repayment)


native入口

let jsCodeLocation = URL(string: "http://localhost:8081/index.ios.bundle?platform=ios")
let mockData:NSDictionary = ["orderid": self.orderId,
                             "status" : self.orderDetailModel.status,
                             "price"  : self.orderDetailModel.cost]
let rootView = RCTRootView(bundleURL: jsCodeLocation,
                          moduleName: "RNBackApply",
                   initialProperties: mockData as [NSObject : AnyObject],
                       launchOptions: nil)
let vc = UIViewController()
vc.view = rootView
self.navigationController?.isNavigationBarHidden = true
self.navigationController?.pushViewController(vc, animated: true)
  • jsCodeLocation RN執行文件路徑,這里的路徑為開發時使用,發布時需更換為main.jsbundle的路徑
  • mockData為從native傳遞到RN的數據
  • moduleName: "RNBackApply"與index.ios.js中registerComponent('RNBackApply', () => MECRM)對應

3.構建頁面

前面提到起始頁為Repayment,那么Repayment是怎么實現如上圖的喃?以下為簡要實現

'use strict';
import React, { Component } from 'react';
import {
   View,
   Text,
   StyleSheet,
   ScrollView,
   TouchableHighlight,//整塊區域有按壓效果
   AlertIOS,
   TouchableOpacity,//文字有按壓效果
   TextInput,
   Image,
   NativeModules,
   DeviceEventEmitter//通知
 } from 'react-native';

import PayTypeChoice from './PayTypeChoice';//注意路徑是以當前文件為準

export default class repayment extends Component {
  constructor(props) {
    super(props);
    var defaultMoney = (this.props["price"]*0.2<0.01?0.01:this.props["price"]);
    this.state = {events: {
                    info: {
                      id: '',
                      orderId: this.props["orderid"].toString(),
                      account: '',
                      accountType: 1,
                      accountName: '',
                      bankName: '',
                      branchName: '',
                      money: defaultMoney.toString(),
                      status: '',
                      remark: '',
                      failReason: '',
                    }
                  }};
    this._applySettlementRequest();
    this._accountInfoChoiced();
  }

  _accountInfoChoiced() {
      this.subscription = DeviceEventEmitter.addListener('accountInfoChoiced',(accountInfo) => {
          var newEvents = this.state.events;
          newEvents.info.account = accountInfo.account;
          newEvents.info.accountName = accountInfo.accountName;
          newEvents.info.accountType = accountInfo.accountType;
          newEvents.info.bankName = accountInfo.bankName;
          newEvents.info.branchName = accountInfo.branchName;
          this.setState({events: newEvents});
      })
  }
    
  _renderRow(title: string, subTitle: string, placeholder: string, onChangeText: Function, maxLength: int) {
    var status = this.props["status"];
    return (
         <View>
             <View style={styles.row}>
               <Text style={styles.rowText}>
               {title}
               </Text>
               {(status === 0 || status === 3)?
                 <TextInput
                   style={styles.rowInputText}
                   autoCapitalize={'none'}
                   maxLength = {maxLength}
                   onChangeText={onChangeText}
                   value={subTitle}
                   placeholder={placeholder}
                   selectionColor='#0064FF'
                   clearButtonMode={'while-editing'}
                   returnKeyType={'done'}
                 />
                 :
                 <TextInput
                   style={styles.rowInputText}
                   autoCapitalize={'none'}
                   onChangeText={onChangeText}
                   value={subTitle}
                   placeholder={placeholder}
                   selectionColor='#0064FF'
                   clearButtonMode={'while-editing'}
                   returnKeyType={'done'}
                   editable={false}
                 />
               }
             </View>
             <View style={styles.separator} />
         </View>
    );
  }
  
    ......
        
  _renderButton(onPress: Function) {
     var status = this.props["status"];
     var buttonString = '申請返款';
     switch (status) {
       case 0:
         var buttonString = '申請返款';
         break;
       case 1:
         var buttonString = '返款處理中';
         break;
       case 2:
         var buttonString = '已返款';
         break;
       case 3:
         var buttonString = '已拒絕,重新申請';
         break;
       case 4:
         var buttonString = '待審核';
         break;
     }
     var canPost = false;
     var orderInfo = this.state.events.info;
     if ((status === 0 || status === 3) && orderInfo.accountName.length > 0 && orderInfo.account.length > 0 && orderInfo.money.length > 0 ) {
        if (orderInfo.accountType === 2) {
          if (orderInfo.bankName.length > 0 && orderInfo.branchName.length > 0) {
             canPost = true;
           }
         } else {
           canPost = true;
         }
     }
     return (
         <View style={styles.container}>
           <View>
             {canPost?
               <TouchableOpacity style={styles.button} onPress={onPress}>
               <Text style={styles.buttonText}>{buttonString}</Text>
               </TouchableOpacity>
               :
               <View style={styles.disableButton}>
               <Text style={styles.buttonText}>{buttonString}</Text>
               </View>
             }
           </View>
         </View>
     );
  }

  _onButtonPress() {
    var orderInfo = this.state.events.info;
    orderInfo.money = Number(orderInfo.money*100);
    if(isNaN(orderInfo.money)){
      AlertIOS.alert(
        '請輸入正確的返款金額',
      )
      return;
    }
    var orderPrice = (this.props["price"]*100);
    if (orderInfo.money > orderPrice*0.8) {
      AlertIOS.alert(
        '',
        '當前返款大于支付金額的80%,是否繼續?',
        [
          {text: '返回修改'},
          {text: '繼續發起', onPress: () => {
            var RNBridge = NativeModules.RNBridge;
            RNBridge.setSettlement(orderInfo,(error, events) => {
              if (error) {
                // console.error(error);
              } else {
                this._handleNavigationBackRequest();
              }
            })
          }}
        ]
      )
      return;
    }
    var RNBridge = NativeModules.RNBridge;
    RNBridge.setSettlement(orderInfo,(error, events) => {
      if (error) {
        // console.error(error);
      } else {
        this._handleNavigationBackRequest();
      }
    })
  };


  _applySettlementRequest() {
    var status = this.props["status"];
// status參數說明
// 0    未申請
// 1    返款中
// 2    返款成功
// 3    返款失敗
    if (status === 0) {
      return
    }
    var RNBridge = NativeModules.RNBridge;
    var orderid = this.props["orderid"].toString();
    RNBridge.applySettlement(orderid,(error, events) => {
      if (error) {
        console.error(error);
      } else {
        events.info.money = (events.info.money/100).toString();
        this.setState({events: events});
      }
    })
  }

  render() {
    var orderInfo = this.state.events.info;
    var status = this.props["status"];
    return (
        <ScrollView style={styles.list}>
        <View style={styles.line}/>
        <View style={styles.group}>
        <View>
          {this._renderPayTypeRow(() => {
            this.props.navigator.push({
              title: '返款方式',
              component: PayTypeChoice,
              barTintColor: '#7B9DFD',
              tintColor: 'white',
              passProps: {
                accountType: this.state.events.info.accountType,
                getPayType:(accountType)=>{
                  var newEvents = this.state.events;
                  newEvents.info.accountType = accountType;
                  this.setState({events: newEvents});
                }
              }
            })
          })}
          {this._renderRow('姓名', orderInfo.accountName, '請輸入姓名', (accountName) => {
            var newEvents = this.state.events;
            newEvents.info.accountName = accountName;
            this.setState({events: newEvents});
          },10)}
          <View>
            {(orderInfo.accountType === 2)?
            <View>
            {this._renderRow('開戶銀行', orderInfo.bankName, '請輸入開戶銀行', (bankName) => {
              var newEvents = this.state.events;
              newEvents.info.bankName = bankName;
              this.setState({events: newEvents});
            })}
            {this._renderRow('開戶支行', orderInfo.branchName, '請輸入開戶支行', (branchName) => {
              var newEvents = this.state.events;
              newEvents.info.branchName = branchName;
              this.setState({events: newEvents});
            })}
            </View>
              :
              null
            }
          </View>
          
          ......
          
        </View>
        </View>
        <View style={styles.line}/>
        {this._renderButton(() => {
          this._onButtonPress();
        })}
        </ScrollView>
      );
  }
}

const styles = StyleSheet.create({
    ......
});
  • constructor 初始化數據,這里的數據結構和網絡請求結果保持一致。
  • renderRow 函數以及被省略掉的其他renderXXXRow函數只是讓總的render函數沒那么臃腫,返回一些JSX片段,在構建界面中根據不同條件展示不同樣式是常見需求,但是JSX中不支持 if.else,只支持三目運算符?:,在上面代碼中多次用到。
  • 需求功能1:點擊返款方式,跳轉到返款方式選擇頁面,然后把選擇的方式回傳。

    這里頁面傳值采用的方式是將修改返款方式后的操作作為一個函數傳遞給下一個頁面,實現如下。

Repayment.js

{this._renderPayTypeRow(() => {
    this.props.navigator.push({
        title: '返款方式',
        component: PayTypeChoice,
        barTintColor: '#7B9DFD',
        tintColor: 'white',
        passProps: {
            accountType: this.state.events.info.accountType,
            getPayType:(accountType) => {
                var newEvents = this.state.events;
                newEvents.info.accountType = accountType;
                this.setState({events: newEvents});
            }
        }
    })
})}

PayTypeChoice.js

render() {
  return (
      <ScrollView style={styles.list}>
      <View style={styles.line}/>
      <View style={styles.group}>
      <View>
        {this._renderRow('支付寶', this.state.alipay,() => {
          this.props.getPayType(1);
          this.props.navigator.popToTop()
        })}
        <View style={styles.separator} />
        {this._renderRow('銀行卡', this.state.bankcard,() => {
          this.props.getPayType(2);
          this.props.navigator.popToTop()
        })}
      </View>
      </View>
      <View style={styles.line}/>
      </ScrollView>
    );
}

Repayment.js中的getPayType就是傳遞到下一個頁面,當返款方式選擇后以執行的函數。在PayTypeChoice.js中當cell點擊的時候將返款方式作為參數傳入,例如this.props.getPayType(1),就將返款方式設置為了支付寶。

  • 需求功能2:點擊右上角圖標,跳轉到返款賬號列表頁,然后把選擇的賬號信息帶回來填充頁面。

    如之前所述,點擊圖標跳轉的邏輯是寫在index.ios.js文件中的

     _settlementAccountList() {
       var status = this.props["status"];
       if (status === 0 || status === 3) {
         this.refs['nav'].push({
           title: '返款人信息表',
           component: SettlementAccountList,
           barTintColor: '#7B9DFD',
           tintColor: 'white',
           passProps: {
           }
         })
       }
     }

想通過剛才傳遞函數的方式達到頁面傳值,那么index.ios.js就要先獲取到Repayment用于回調的函數,然后再傳遞給SettlementAccountList。很麻煩,并且我嘗試了一下沒成功。這種時候,通知就顯得非常簡單粗暴了,運用React Native中的通知組件DeviceEventEmitter,頁面傳值都不是事兒。

當賬號信息被選擇時在SettlementAccountList中發送通知

 _onPressCell(rowData: string) {
    this.props.navigator.popToTop()
    DeviceEventEmitter.emit('accountInfoChoiced', rowData);
 }

在Repayment中接收通知

 _accountInfoChoiced() {
    this.subscription = DeviceEventEmitter.addListener('accountInfoChoiced',(accountInfo) => {
        var newEvents = this.state.events;
        newEvents.info.account = accountInfo.account;
        newEvents.info.accountName = accountInfo.accountName;
        newEvents.info.accountType = accountInfo.accountType;
        newEvents.info.bankName = accountInfo.bankName;
        newEvents.info.branchName = accountInfo.branchName;
        this.setState({events: newEvents});
    })
 }
  • 需求功能3:在進入頁面時拉取之前填寫的返款信息,點擊左上的返回按鈕回到Native頁面,以及返款賬號信息頁面拉取已有的信息。這3點都是調用的Native方法。雖然RN也有網絡請求方法,但是APP中的網絡請求會有公共參數、公共的鑒權方法、錯誤處理等,所以網絡請求還是選擇走Native的好。

創建待RN調用的Native方法的步驟,在官方文檔中也講得很清楚,這里貼出我寫的代碼片段(因為Objective-C寫著更方便就沒用Swift,偷懶了一下)

RNBridge.m

#import "RNBridge.h"
#import <UIKit/UIKit.h>
#import <MECRM-Swift.h>

@implementation RNBridge
RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(back)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        navi.navigationBarHidden = NO;
        [navi popViewControllerAnimated:YES];
    });
}

//獲取返款信息
RCT_EXPORT_METHOD(applySettlement:(NSString *)orderID callback:(RCTResponseSenderBlock)callback)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        UIViewController * vc = navi.viewControllers.lastObject;
        [vc startAnimating];
        NSString *path = [NSString stringWithFormat:@"/order/%@/applySettlement",orderID];
        [NetworkTool GET:path parameters:nil successHandler:^(id _Nonnull result) {
            [vc stopAnimating];
            callback(@[[NSNull null], result]);
        } failureHandler:^(NSError * _Nullable error) {
            [vc stopAnimating];
            callback(@[error.localizedDescription, [NSNull null]]);
        }];
    });
}

//設置返款信息
RCT_EXPORT_METHOD(setSettlement:(NSDictionary *)orderInfo callback:(RCTResponseSenderBlock)callback)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        UIViewController * vc = navi.viewControllers.lastObject;
        [vc startAnimating];
        NSString *orderID = orderInfo[@"orderId"];
        NSString *path = [NSString stringWithFormat:@"/order/%@/applySettlement",orderID];
        [NetworkTool POST:path parameters:orderInfo successHandler:^(id _Nonnull result) {
            [vc stopAnimating];
            callback(@[[NSNull null], result]);
        } failureHandler:^(NSError * _Nullable error) {
            [vc stopAnimating];
            callback(@[error.localizedDescription, [NSNull null]]);
        }];
    });
}

//返款人信息表
RCT_EXPORT_METHOD(getSettlementAccount:(NSInteger)start callback:(RCTResponseSenderBlock)callback)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController *)[self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        UIViewController * vc = navi.viewControllers.lastObject;
        [vc startAnimating];
        NSString *path = [NSString stringWithFormat:@"/order/getSettlementAccount?start=%ld&limit=%d",(long)start,100];
        [NetworkTool GET:path parameters:nil successHandler:^(id _Nonnull result) {
            [vc stopAnimating];
            callback(@[[NSNull null], result]);
        } failureHandler:^(NSError * _Nullable error) {
            [vc stopAnimating];
            callback(@[error.localizedDescription, [NSNull null]]);
        }];
    });
}

在RN中調用返回方法

_handleNavigationBackRequest() {
    var RNBridge = NativeModules.RNBridge;
    RNBridge.back();
}

在RN中獲取已填寫的返款信息

_applySettlementRequest() {
    var status = this.props["status"];
    // status參數說明
    // 0    未申請
    // 1    返款中
    // 2    返款成功
    // 3    返款失敗
    if (status === 0) {
      return
    }
    var RNBridge = NativeModules.RNBridge;
    var orderid = this.props["orderid"].toString();
    RNBridge.applySettlement(orderid,(error, events) => {
      if (error) {
        console.error(error);
      } else {
        events.info.money = (events.info.money/100).toString();
        this.setState({events: events});
      }
    })
}

4.調試

在模擬器中 command+D 調出RN的菜單,點擊Debug JS Remotely。

在需要調試的代碼前面加debugger,例如

_onButtonPress() {
    debugger
    var orderInfo = this.state.events.info;
    orderInfo.money = Number(orderInfo.money*100);
    if(isNaN(orderInfo.money)){
        AlertIOS.alert(
            '請輸入正確的返款金額',
        )
        return;
    }
    ......
};

簡陋,夠用?(°?‵?′??)

5.上線以及熱更新

上線的時候需要將代碼中的jsCodeLocation修改一下

//let jsCodeLocation = URL(string: "http://localhost:8081/index.ios.bundle?platform=ios")
let jsCodeLocation = Bundle.main.url(forResource: "main", withExtension: "jsbundle")

這個main.jsbundle是需要手動生成的,生成方法如下:

1.在React Native項目根目錄下運行 npm start

2.使用curl命令生成 main.jsbundle

curl http://localhost:8081/index.ios.bundle -o main.jsbundle

這樣進入RN頁面的時候,頂上就不再有提示信息了。如果提示找不到這個main.jsbundle文件,記得把main.jsbundle拖到iOS工程中引用一下。

打開main.jsbundle文件,你會發現里面包含了你所寫的所有js文件內容。所以其實你寫的RN邏輯全在這里面。那么RN的熱更新就很好理解了,更新這個文件就好了。不管你自己實現還是選擇什么第三方熱更新方案,都是在各種花式更新這個main.jsbundle文件而已。

6.一些補充和問題(碎碎念)

之前只講了push跳轉頁面,modal喃?舉例一發

<View style={{marginTop: 22}}>
    <Modal
        animationType={"slide"}
        transparent={false}
        visible={this.state.modalVisible}
        onRequestClose={() => {alert("closed")}}
    >
        <View style={{marginTop: 22}}>
            <View>
            <Text>Hello World!</Text>
                <TouchableHighlight onPress={() => {
                this.setModalVisible(!this.state.modalVisible)
              }}>
                <Text>Hide Modal</Text>
              </TouchableHighlight>
        </View>
        </View>
    </Modal>
    <TouchableHighlight onPress={() => {this.setModalVisible(true)}}>
        <Text>Show Modal</Text>
    </TouchableHighlight>
</View>

另外補充一個小問題,如果npm start命令不好使的時候,嘗試一下react-native start

還有一個遺留問題,返回native頁面的時候我是調用的native方法返回的,難道RN自己不能返回嗎,我其實是嘗試這樣的,也覺得這很理所當然。打印native的navi.viewControllers,最后的UIViewController就是RN頁面,NavigatorIOS的pop方法調用了竟然沒反應,難道是我姿勢不對?

(lldb) po navi.viewControllers
<__NSArrayI 0x600000254160>(
<MECRM.MainOrderViewController: 0x7f82a1226ac0>,
<MECRM.OrderDetailViewController: 0x7f829f54e2c0>,
<UIViewController: 0x7f82a1004ed0>
)

RN官方提供了NavigatorIOS和Navigator,使用方法大同小異,總感覺帶個iOS更貼近原生。不過都說Navigator更cool。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容