UIWebView與JavaScript的交互及JavaScript調試技巧

UIWebView與JS的交互,說白了就是Objective-C和JavaScript的相互調用。Objective-C調用JavaScript代碼的方法,是通過UIWebView的 - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;的方法來實現的。該方法向UIWebView傳遞一段需要執行的JavaScript代碼最后獲取執行結果。

JavaScript調用Objective-C的方法,并沒有現成的API,但是有些方法可以達到相應的效果。具體是利用UIWebView的特性:在UIWebView的內發起的所有網絡請求,都可以通過delegate函數得到通知。

說明:

  • 本文是一個小白記錄OC與JS交互的學習歷程,適合跟我一樣的小白,大神若要噴,請輕噴_

  • 學習UIWebView與JS的交互之前,建議熟悉下HTML和Javascript的基本語法,不用看太多,在w3school看一到兩天HTML,再看一到兩天JS就行。

OC調用JS方法、JS調用OC方法(不使用第三方開源庫的情況下)

準備工作:

1.新建一個Single View Application,
再新建一個ViewController(eg:BasicUsageViewController),然后在StoryBoard新建一個ViewController,拖一個UIWebView和UILabel以備用,關聯webView及代理

@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property (weak, nonatomic) IBOutlet UILabel *testLabel;

2.在工程中新建一個web1.html文件(Commend+N、OtherEmptyNext、輸入、create),代碼如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
            <title>我是HTML標題</title>
            </head>
    <body bgcolor="#9aff9a">
        <div id="addNewNodeTest">
            <p id="p1"> 這是段落A。</p>
            <p id="p2"> 這是段落B。</p>
        </div>
        <div class="page">
            <button onclick="changeUILabelText()"> 改變UILabel文字 </button>
            <button onclick="logText()"> NSLog打印文字 </button>
        </div>
    </body>
    
</html>

3.再在工程中新建一個test.js文件,代碼如下:

//添加子節點
function addNewNodeTest () {
    var para = document.createElement("p");
    var node = document.createTextNode("這是新段落。");
    para.appendChild(node);
    var element = document.getElementById("addNewNodeTest");
    element.appendChild(para);
    console.log("添加子節點成功");
}

//改變UILabel的文本
function changeUILabelText() {
    //"changelabeltext"是你自己定的一個協議。
    //注url不要含大寫字母,就算是大寫字母,在`webView:shouldStartLoadWithRequest:navigationType:`代理方法里也會被替換成小寫字母
    var url = "changelabeltext:" + "我是改變后的文字";
    //給document.location重新賦值就相當于webView加載一個新的URL,所以又會調用`webView:shouldStartLoadWithRequest:navigationType:`方法,然后就可以在這個代理方法里截獲這個重定向請求
    document.location = url;
}

//也可以自己封裝個傳參數的方法
function sendCommand(cmd,param){
    var url = "yourprotocol:" + cmd + ":" + param;
    document.location = url;
}
//打印測試
function logText(){
    sendCommand("log","Hi,I'm In logText Function");
}

好了,現在可以開擼了

4.加載webView并插入測試js

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSString* path = [[NSBundle mainBundle] pathForResource:@"web1" ofType:@"html"];
    [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"js"];
    NSString *jsString = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    [self.webView stringByEvaluatingJavaScriptFromString:jsString];
}

5、加載結束,獲取HTML頁面title元素,賦值給self.title

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // 獲取HTML頁面title元素,賦值給self.title
    self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
}

6、建幾個按鈕,體驗插入js的幾種方式

模擬器
//分別對應上圖3個按鈕
- (IBAction)insertJavaScript1:(UIButton *)sender {
    //方法1:預加載的test.js內部已經寫了addNewNodeTest()方法,這里只需注入"addNewNodeTest()"字符串即可
    [self.webView stringByEvaluatingJavaScriptFromString:@"addNewNodeTest()"];
}

- (IBAction)insertJavaScript2:(UIButton *)sender {
    //方法2:把test.js內部的addNewNodeTest()方法復制過來,去掉行與行之間的空格
    //字符串雙引號要么前面加轉義符"\",要么變成單引號,例如:
    NSString *addNewNode = @"var para = document.createElement(\"p\");var node=document.createTextNode('這是新段落。');para.appendChild(node);var element=document.getElementById('addNewNodeTest');element.appendChild(para);";
    [self.webView stringByEvaluatingJavaScriptFromString:addNewNode];
}

- (IBAction)insertJavaScript3:(UIButton *)sender {
    //方法3:把test.js內部的addNewNodeTest()方法復制過來,并在每一行首尾加上雙引號(跟方法2差不多)
    NSString *addNewNode =
    @"var para = document.createElement('p');"
    "var node = document.createTextNode('這是新段落。');"
    "para.appendChild(node);"
    "var element = document.getElementById('addNewNodeTest');"
    "element.appendChild(para);";
    [self.webView stringByEvaluatingJavaScriptFromString:addNewNode];
}

說明:addNewNodeTest()方法執行的操作是創建了一個節點<p> 這是新段落。</p>,添加到了位置1,然后webView上就會新增一行,不懂的同學請自行腦補(看不懂也沒關系,這里只是演示怎么用OC調js代碼)

    <div id="addNewNodeTest">
        <p id="p1"> 這是段落A。</p>
        <p id="p2"> 這是段落B。</p>
        //位置1
    </div>

7.好了,現在讓js調OC的方法:在ViewController里添加如下代碼:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSLog(@"開始加載請求");
    //當點擊按鈕時,navigationType = UIWebViewNavigationTypeOther
    NSString *requestString = [[request URL] absoluteString];
    NSArray *components = [requestString componentsSeparatedByString:@":"];
    if ([components[0] isEqualToString:@"changelabeltext"] && components.count > 1) {
        //這種通過URL傳參數的方式貌似不是太好,因為參數如果含中文還得URL解碼,eg:
        self.testLabel.text = [components[1] stringByRemovingPercentEncoding];
        return NO;
    }
    //也可以這樣判斷
    else if([request.URL.scheme isEqualToString:@"yourprotocol"]) {
        NSLog(@"%@",[components[2] stringByRemovingPercentEncoding]);
        return NO;
    }
    return YES;
}

點擊webView里的改變UILabel文字按鈕會發現testLabel的文字變了,這里解釋下原因:web1.html代碼中
<button onclick="changeUILabelText()"> 改變UILabel文字 </button>這個按鈕綁定了一個方法,名字叫changeUILabelText(),點擊就會調用changeUILabelText()方法(當然包含這個方法的test.js已經加載了),然后webView的URL變了就會重新加載,這樣在回調方法webView:shouldStartLoadWithRequest:navigationType:會再次調用,然后就可以在這個代理方法里截獲這個重定向請求的request.URL.absoluteString來處理OC代碼了

說明:

(1)Objective-C調用JavaScript代碼的時候是同步的

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

(2)JavaScript調用Objective-C代碼的時候是異步的

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

調試模擬器或真機里的WebView的技巧

  • 模擬器

模擬器加載網頁后,打開電腦端Safari(確保偏好設置里的高級 - 在菜單中顯示“開發”菜單選項已打開),然后選擇開發Simulator,就會看見模擬器正在運行的web1.html,點擊web1.html就來到了控制臺。
點擊方式一按鈕,就會發現控制臺標簽頁最先面有輸出,這是因為在test.jsaddNewNodeTest ()的最后一行有這么一句話:console.log("添加子節點成功");,在這里,console.log()相當于NSLog,括號內可以直接加變量。

當然,你也可以在控制臺插入js代碼,如下圖:在左下角輸入一句js代碼alert('666');就能在模擬器上得到反饋,當然,此時你輸入addNewNodeTest();效果跟點擊方式一按鈕是一樣的

模擬器調試之控制臺

你也可以切換到調試器標簽,然后打個斷點,點擊方式一按鈕,就可以單步調試了。有興趣的同學可以切換到元素標簽頁看看

模擬器調試之調試器
  • 真機

首先在手機的設置 - Safari - 高級 - 啟用Web檢查器,然后用數據線連接電腦,Xcode運行你的項目,打開一個含webView的頁面,就可以在電腦端Safari的開發菜單下看到你的設備了,調試方法同上

高級用法(WebViewJavascriptBridge)

WebViewJavascriptBridge 是一個用于UIWebView / WKWebViews和JS交互的封裝庫,連Facebook Messenger都在使用。

這里我就引用一下楊騎滔的這篇博客的內容,也就是通過實現以下功能來學習WebViewJavascriptBridge的使用(侵刪)。

原文已經比較詳盡了,但是有一些地方對于我等小白來說可能不夠詳細,所以折騰了不少時間,所以在這里對原文做了一點修改,更加清晰易懂。

要實現的功能

  • WebView展示一段HTML,禁止HTML文本中自帶的 <img> 標簽自動加載,也就是說下載圖片的操作放在native端來處理,并通過JS將圖片在Cache中的地址返回給UIWebview。

  • 實現點擊WebView圖片放大、保存圖片到相冊等操作。

之所以要把圖片操作放在native端做的好處在于:1、可以進行本地緩存,下次進入這篇文章可以直接從緩存讀取,提高響應速度并且節省用戶流量。2、可以實現點擊圖片放大、保存圖片到相冊等操作。

技術難點也有兩個:

  • 如何讓HTML文本onLoad的時候,禁用自身的圖片加載而是從本地獲取圖片?
  • 如何把native端下載好的圖片返回給網頁?

先來看看基本用法

在WebViewJavascriptBridge中,交互的方式只有兩種:send 和 callHandler,JS和OC都有這兩個方法,所以對應的四種關系是(很重要):

四種關系圖表

以上表中的對應關系的解讀是,例如第一條:在JS中如果調用了bridge.send(),那么將觸發OC端_bridge初始化方法中的回調。

同理,第二條,在JS中調用了bridge.callHandler('testJavascriptHandler'),它將觸發OC端注冊的同名方法:

也就是說,一種語言register了Handler(回調或者block),另一種語言callHandler就會執行回調或者block,還能傳遞數據;不理解不要緊,下面的Demo這四種方式全都有例子。

了解了使用規則,下面來看看在我們這個實際需求中應用的整體思路:


整體思路

廢話不說,直接開擼:

1、導入WebViewJavascriptBridge,新建一個ViewController,聲明一個WebViewJavascriptBridge實例:

@property WebViewJavascriptBridge* bridge;

2、找一個含圖片的html,比如這一篇(源碼已做刪減),導入到項目中

3、在項目中新建一個js文件,比如imageCache.js,貼上如下代碼:

//一加載這個js就會調用下面自己寫的onLoaded() 方法
window.onload = function() {
    onLoaded();
}

//使用WebViewJavascriptBridge的話,這一段是必須的(固定寫法)
function connectWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) {
        callback(WebViewJavascriptBridge)
    } else {
        document.addEventListener('WebViewJavascriptBridgeReady', function() {
                                  callback(WebViewJavascriptBridge)
                                  }, false)
    }
}

//上面已經說了,一插入js,這個方法就開始執行
function onLoaded() {
    connectWebViewJavascriptBridge(function(bridge) {
                                   //document.querySelectorAll:按文檔順序返回指定元素節點的子樹中匹配選擇器的元素集合,如果沒有匹配返回空集合
                                   //下面這幾句是提取所有img標簽的esrc屬性值(圖片的URL),并存到imageUrlsArray這個數組中
                                   var allImage = document.querySelectorAll("img");
                                   allImage = Array.prototype.slice.call(allImage, 0);
                                   var imageUrlsArray = new Array();
                                   allImage.forEach(function(image) {
                                                    var esrc = image.getAttribute("esrc");
                                                    var newLength = imageUrlsArray.push(esrc);
                                                    });
                                   //將imageUrlsArray這個數組發送到OC的block
                                   bridge.send(imageUrlsArray);////四種關系圖表之第1種
                                   
                                   bridge.init(function(message, responseCallback) {
                                               alert(message);
                                               if (responseCallback) {
                                               responseCallback("Message1已收到,送你個Message2")
                                               }
                                    })
                                   //這里先注冊下,等待OC代碼的_bridge調用([_bridge callHandler:....])
                                   bridge.registerHandler('imagesDownloadComplete', function(data, responseCallback) {
                                                          var allImage = document.querySelectorAll("img");
                                                          allImage = Array.prototype.slice.call(allImage, 0);
                                                          allImage.forEach(function(image) {
                                                                           if (image.getAttribute("esrc") == data[0] || image.getAttribute("esrc") == decodeURIComponent(data[0])) {
                                                                           image.src = data[1];
                                                                           }
                                                                           
                                                                           });
                                                          responseCallback("圖片"+data[0]+"已加載")
                                                          })
                                   
                                   //使用WebViewJavascriptBridge的話,這一段是必須的,不然上面的imageUrlsArray傳不過去
                                   bridge.send('Please respond to this', function responseCallback(responseData) {
                                               console.log("Javascript got its response", responseData)
                                               })
                                   });
    
}

4、viewDidLoad里加載 webView

NSString *path = [[NSBundle mainBundle] pathForResource:@"article1" ofType:@"html"];
    //原網頁html代碼
    NSString *_content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    //我們要做的第一步是替換獲取的HTML文本中默認的src,避免webView自動加載圖片
    _content = [_content stringByReplacingOccurrencesOfString:@"src" withString:@"esrc"];
    //正則替換,給每個圖片添加一個onImageClick點擊方法
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(<img[^>]+esrc=\")(\\S+)\"" options:0 error:nil];
    //終于得到我想要的html了!!!
    _content = [regex stringByReplacingMatchesInString:_content options:0 range:NSMakeRange(0, _content.length) withTemplate:@"<img esrc=\"$2\" onClick=\"javascript:onImageClick('$2')\""];
    [self.webView loadHTMLString:_content baseURL:nil];
    
    //插入js
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"imageCache" ofType:@"js"];
    NSString *jsString = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    [self.webView stringByEvaluatingJavaScriptFromString:jsString];
    
    //初始化一個WebViewJavascript橋梁,方便imageCache.js把數據傳過來
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView webViewDelegate:self handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"###來自imageCache.js的圖片URL數組: %@", data);
        //利用SDWebImageManager下載圖片到本地
        [self downloadAllImagesInNative:data];
        _imageURLs = data;
        responseCallback(@"###Right back atcha");
    }];
#pragma mark -- 下載全部圖片
-(void)downloadAllImagesInNative:(NSArray *)imageUrls{
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
    //初始化一個數組用于存image
    _allImagesOfThisArticle = [NSMutableArray arrayWithCapacity:imageUrls.count];
    for (NSUInteger i = 0; i < imageUrls.count; i++) {
        NSString *_url = imageUrls[i];
        [manager downloadImageWithURL:[NSURL URLWithString:_url] options:SDWebImageHighPriority progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            
            if (image) {
                [_allImagesOfThisArticle addObject:image];
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    NSString *imgB64 = [UIImageJPEGRepresentation(image, 1.0) base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
                    //把圖片在磁盤中的地址傳回給JS
                    NSString *key = [manager cacheKeyForURL:imageURL];
                    
                    NSString *source = [NSString stringWithFormat:@"data:image/png;base64,%@", imgB64];
                    //四種關系圖表之第4種
                    [_bridge callHandler:@"imagesDownloadComplete" data:@[key,source] responseCallback:^(id responseData) {
                        NSLog(@"js把img標簽的esrc屬性替換后-->%@<",responseData);
                    }];
                    
                });
                
            }
            
        }];
        
    }
    
}

好了,到這里為止就webView就可以加載本地緩存的圖片了,如果要實現圖片的點擊放大,請接著往下看

5、imageCache.js里添加圖片點擊事件:

//圖片點擊會觸發
function onImageClick(picUrl){
    connectWebViewJavascriptBridge(function(bridge) {
                                   //作者用的是"p img[esrc]",意思是獲取p標簽里的img的src值
                                   //我這里的圖片是div,所以要改成"div img[esrc]"
                                   //var allImage = document.getElementsByTagName('img');//這樣比較通用
                                   var allImage = document.querySelectorAll("div img[esrc]");
                                   allImage = Array.prototype.slice.call(allImage, 0);
                                   var urls = new Array();
                                   var index = -1;
                                   var x = 0;
                                   var y = 0;
                                   var width = 0;
                                   var height = 0;
                                   //獲取點擊圖片在所有圖片中的編號以及在圖片相對于webView左上角的位置、寬高,并把這些信息返回給OC
                                   allImage.forEach(function(image) {
                                                    var imgUrl = image.getAttribute("esrc");
                                                    var newLength = urls.push(imgUrl);
                                                    if(imgUrl == picUrl || imgUrl == decodeURIComponent(picUrl)){
                                                    index = newLength-1;
                                                    x = image.getBoundingClientRect().left;
                                                    y = image.getBoundingClientRect().top;
                                                    x = x + document.documentElement.scrollLeft;
                                                    y = y + document.documentElement.scrollTop;
                                                    width = image.width;
                                                    height = image.height;
                                                    console.log("x:"+x +";y:" + y+";width:"+image.width +";height:"+image.height);
                                                    }
                                                    });
                                   
                                   console.log("檢測到點擊"+"x="+x+"y="+y+"width="+width+"height="+height);
                                   //四種關系圖表之第2種
                                   bridge.callHandler('imageDidClicked', {'index':index,'x':x,'y':y,'width':width,'height':height}, function(response) {
                                                      console.log("JS已經發出imgurl和index,同時收到回調,說明OC已經收到數據");
                                                      });
                                   });
    
}

6、viewDidLoad里注冊js圖片點擊事件回調,這里我用了一個簡單的圖片瀏覽器HZPhotoBrowser,修改了部分代碼使能夠適用于webView

//這里注冊一下,imageCache.js里的`bridge.callHandler('imageDidClicked', {'index':index,'x':x,'y':y,'width':width,'height':height}, function(response)`就會傳數據過來
    [_bridge registerHandler:@"imageDidClicked" handler:^(id data, WVJBResponseCallback responseCallback) {
        
        NSInteger index = [[data objectForKey:@"index"] integerValue];
        
        CGFloat originX = [[data objectForKey:@"x"] floatValue];
        CGFloat originY = [[data objectForKey:@"y"] floatValue];
        CGFloat width   = [[data objectForKey:@"width"] floatValue];
        CGFloat height  = [[data objectForKey:@"height"] floatValue];
        
        //啟動圖片瀏覽器
        HZPhotoBrowser *browserVc = [[HZPhotoBrowser alloc] init];
        // browserVc.sourceImagesContainerView = cell.webView; // 原圖的父控件
        browserVc.imageCount = _allImagesOfThisArticle.count; // 圖片總數
        browserVc.currentImageIndex = index;
        browserVc.delegate = self;
        browserVc.imageFrameinWebView = CGRectMake(originX, originY+64, width, height);
        [browserVc show];
        
        NSLog(@"OC已經收到JS的imageDidClicked了: %@", data);
        responseCallback(@"OC已經收到JS的imageDidClicked了");
    }];
    //四種關系圖表之第3種(測試)
//    [_bridge send:@"###Message1:我將會被發送到imageCache.js里bridge.init()的回調里"];
    
    //四種關系圖表之第3種(測試)
//    [_bridge send:@"###Message1:我將會被發送到imageCache.js里bridge.init()的回調里,imageCache.js還會給我回調,不信你可能下面的Log" responseCallback:^(id responseData) {
//        NSLog(@"###%@", responseData);
//    }];
#pragma mark - HZPhotoBrowser的代理方法
//這里沒有占位小圖,所以就讓大圖代替
- (UIImage *)photoBrowser:(HZPhotoBrowser *)browser placeholderImageForIndex:(NSInteger)index {
    return _allImagesOfThisArticle[index];
}

- (NSURL *)photoBrowser:(HZPhotoBrowser *)browser highQualityImageURLForIndex:(NSInteger)index {
    return [NSURL URLWithString:_imageURLs[index]];
}

最終效果

最終效果

好了,到此為止WebViewJavascriptBridge的基本用法已基本說完了,雖然很簡單,但是也花了我一天的時間,寫的同時又發現了不少新東西,還是很值的。這里是這個小Demo的源碼。渣渣代碼,就不上傳github了。

  • 參考及推薦文章:

UIWebView與JS的深度交互

UIWebView與JavaScript的那些事兒

iOS開發之Objective-C與JavaScript的交互

關于UIWebView和PhoneGap的總結

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

推薦閱讀更多精彩內容