iOS中UIWebview蹬癌、JavaScript與OC交互、Cookie管理

注,本文轉(zhuǎn)載自:http://www.cocoachina.com/ios/20170612/19485.html

WebView

  • 創(chuàng)建與用法
- (void)loadRequest:(NSURLRequest *)request;
- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
  • UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(UIWebView *)webView didFailLoadWithError:(nullable NSError *)error;
  • UIWebView OC調(diào)用JS
  1. stringByEvaluatingJavaScriptFromString:
self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
  • 該方法不能判斷調(diào)用了一個(gè)js方法之后镀梭,是否發(fā)生了錯(cuò)誤刀森。當(dāng)錯(cuò)誤發(fā)生時(shí),返回值為nil报账,而當(dāng)調(diào)用一個(gè)方法本身沒有返回值時(shí)研底,返回值也為nil,所以無(wú)法判斷是否調(diào)用成功了透罢。
  • 返回值類型為nullable NSString *榜晦,就意味著當(dāng)調(diào)用的js方法有返回值時(shí),都以字符串返回羽圃,不夠靈活乾胶。當(dāng)返回值是一個(gè)js的Array時(shí),還需要解析字符串朽寞,比較麻煩识窿。
  1. JavaScriptCore(iOS 7.0 +)
  • 其實(shí)WebKit都有一個(gè)內(nèi)嵌的js環(huán)境,一般我們?cè)陧?yè)面加載完成之后脑融,獲取js上下文喻频,然后通過(guò)JSContext的evaluateScript:方法來(lái)獲取返回值。因?yàn)樵摲椒ǖ玫降氖且粋€(gè)JSValue對(duì)象肘迎,所以支持JavaScript的Array甥温、Number、String妓布、對(duì)象等數(shù)據(jù)類型
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    //更新標(biāo)題窿侈,這是上面的講過(guò)的方法
    //self.navigationItem.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
 
    //獲取該UIWebView的javascript上下文
    JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
 
    //這也是一種獲取標(biāo)題的方法。
    JSValue *value = [self.jsContext evaluateScript:@"document.title"];
    //更新標(biāo)題
    self.navigationItem.title = value.toString;
}
  • 可以通過(guò)@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);秋茫,設(shè)置該block來(lái)獲取異常
//在調(diào)用前史简,設(shè)置異常回調(diào)
[self.jsContext setExceptionHandler:^(JSContext *context, JSValue *exception){
        NSLog(@"%@", exception);
}];
//執(zhí)行方法
JSValue *value = [self.jsContext evaluateScript:@"document.titlexxxx"];
  • UIWebView JS調(diào)用OC
  1. Custom URL Scheme(攔截URL)
  • 比如darkangel://。方法是在html或者js中圆兵,點(diǎn)擊某個(gè)按鈕觸發(fā)事件時(shí)跺讯,跳轉(zhuǎn)到自定義URL Scheme構(gòu)成的鏈接,而Objective-C中捕獲該鏈接殉农,從中解析必要的參數(shù)刀脏,實(shí)現(xiàn)JS到OC的一次交互
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    //標(biāo)準(zhǔn)的URL包含scheme、host超凳、port愈污、path、query轮傍、fragment等
    NSURL *URL = request.URL;    
    if ([URL.scheme isEqualToString:@"darkangel"]) {
        if ([URL.host isEqualToString:@"smsLogin"]) {
            NSLog(@"短信驗(yàn)證碼登錄暂雹,參數(shù)為 %@", URL.query);
            return NO;
        }
    }
    return YES;
}
  • 優(yōu)點(diǎn):泛用性強(qiáng),可以配合h5實(shí)現(xiàn)頁(yè)面動(dòng)態(tài)化
  • 缺點(diǎn):無(wú)法直接獲取本次交互的返回值创夜,比較適合單向傳參杭跪,且不關(guān)心回調(diào)的情景
  1. JavaScriptCore(iOS 7.0 +)
function share(title, imgUrl, link) {
     //這里需要OC實(shí)現(xiàn)
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    //將js的function映射到OC的方法
    [self convertJSFunctionsToOCMethods];
}
 
- (void)convertJSFunctionsToOCMethods
{
    //獲取該UIWebview的javascript上下文
    //self持有jsContext
    //@property (nonatomic, strong) JSContext *jsContext;
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
 
    //js調(diào)用oc
    //其中share就是js的方法名稱,賦給是一個(gè)block 里面是oc代碼
    //此方法最終將打印出所有接收到的參數(shù)驰吓,js參數(shù)是不固定的
    self.jsContext[@"share"] = ^() {
        NSArray *args = [JSContext currentArguments];//獲取到share里的所有參數(shù)
        //args中的元素是JSValue涧尿,需要轉(zhuǎn)成OC的對(duì)象
        NSMutableArray *messages = [NSMutableArray array];
        for (JSValue *obj in args) {
            [messages addObject:[obj toObject]];
        }
        NSLog(@"點(diǎn)擊分享js傳回的參數(shù):\n%@", messages);
    };
}
  • 上面的代碼實(shí)現(xiàn)了OC方法替換JS實(shí)現(xiàn)。它十分靈活檬贰,主要依賴這些Api
@interface JSContext (SubscriptSupport)
/*!
@method
@abstract Get a particular property on the global object.
@result The JSValue for the global object's property.
*/
- (JSValue *)objectForKeyedSubscript:(id)key;
/*!
@method
@abstract Set a particular property on the global object.
*/
- (void)setObject:(id)object forKeyedSubscript:(NSObject*)key;

UIWebView的Cookie管理

Cookie在Web利用的最多的地方姑廉,是用來(lái)記錄各種狀態(tài)。比如你在Safari中打開百度翁涤,然后登陸自己的賬號(hào)桥言,之后打開所有百度相關(guān)的頁(yè)面,都會(huì)是登陸狀態(tài)迷雪,而且當(dāng)你關(guān)了電腦,下次開機(jī)再次打開Safari打開百度虫蝶,會(huì)發(fā)現(xiàn)還是登陸狀態(tài)章咧,其實(shí)這個(gè)就利用了Cookie。Cookie中記錄了你百度賬號(hào)的一些信息能真、有效期等赁严,也維持了跨域請(qǐng)求時(shí)登錄狀態(tài)的統(tǒng)計(jì)性

  • Cookie管理
    UIWebView的Cookie管理很簡(jiǎn)單,一般不需要我們手動(dòng)操作Cookie粉铐,因?yàn)樗蠧ookie都會(huì)被[NSHTTPCookieStorage sharedHTTPCookieStorage]這個(gè)單例管理疼约,而且UIWebView會(huì)自動(dòng)同步CookieStorage中的Cookie,所以只要我們?cè)贜ative端蝙泼,正常登陸退出程剥,h5在適當(dāng)時(shí)候刷新,就可以正確的維持登錄狀態(tài)汤踏,不需要做多余的操作
// 可能有一些情況下织鲸,我們需要在訪問(wèn)某個(gè)鏈接時(shí)舔腾,添加一個(gè)固定Cookie用來(lái)做區(qū)分,那么就可以通過(guò)header來(lái)實(shí)現(xiàn)
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];
[request addValue:@"customCookieName=1314521;" forHTTPHeaderField:@"Set-Cookie"];
[self.webView loadRequest:request];
// 也可以主動(dòng)操作NSHTTPCookieStorage搂擦,添加一個(gè)自定義Cookie
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:@{
    NSHTTPCookieName: @"customCookieName", 
    NSHTTPCookieValue: @"1314521", 
    NSHTTPCookieDomain: @".baidu.com",
    NSHTTPCookiePath: @"/"
}];
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];    //Cookie存在則覆蓋稳诚,不存在添加
// 還有一些常用的方法,如讀取所有Cookie
NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies;
// Cookie轉(zhuǎn)換成HTTPHeaderFields瀑踢,并添加到request的header中
//Cookies數(shù)組轉(zhuǎn)換為requestHeaderFields
NSDictionary *requestHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
//設(shè)置請(qǐng)求頭
request.allHTTPHeaderFields = requestHeaderFields;

WKWebView

WKWebView,是Apple于iOS 8.0推出的WebKit中的核心控件扳还,用來(lái)替代UIWebView, WKWebView比UIWebView的優(yōu)勢(shì)在于:

  • 更多的支持HTML5的特性
  • 高達(dá)60fps的滾動(dòng)刷新率以及內(nèi)置手勢(shì)
  • 與Safari相同的JavaScript引擎
  • 將UIWebViewDelegate與UIWebView拆分成了14類與3個(gè)協(xié)議官方文檔說(shuō)明
  • 可以獲取加載進(jìn)度:estimatedProgress(UIWebView需要調(diào)用私有Api)
  1. 創(chuàng)建
/*-initWithFrame: to initialize an instance with the default configuration. 如果使用initWithFrame方法將使用默認(rèn)的configuration
The initializer copies the specified configuration, so mutating the configuration after invoking the initializer has no effect on the web view. 我們需要先設(shè)置configuration,再調(diào)用init橱夭,在init之后修改configuration則無(wú)效
*/
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
  1. 重要屬性
// WKWebView 擁有自己的私有存儲(chǔ)氨距,它的一些緩存等數(shù)據(jù)都存在websiteDataStore中
@property (nonatomic, strong) WKWebsiteDataStore *websiteDataStore API_AVAILABLE(macosx(10.11), ios(9.0));
// js->oc的交互,以及注入js代碼都會(huì)用到它
@interface WKUserContentController : NSObject <NSCoding>
//讀取添加過(guò)的腳本
@property (nonatomic, readonly, copy) NSArray<WKUserScript *> *userScripts;
//添加腳本
- (void)addUserScript:(WKUserScript *)userScript;
//刪除所有添加的腳本
- (void)removeAllUserScripts;
//通過(guò)window.webkit.messageHandlers.<name>.postMessage(<messageBody>) 來(lái)實(shí)現(xiàn)js->oc傳遞消息徘钥,并添加handler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
//刪除handler
- (void)removeScriptMessageHandlerForName:(NSString *)name;
@end
  1. 具體創(chuàng)建
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc] init];
configuration.userContentController = controller;
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
self.webView.allowsBackForwardNavigationGestures = YES;    //允許右滑返回上個(gè)鏈接衔蹲,左滑前進(jìn)
self.webView.allowsLinkPreview = YES; //允許鏈接3D Touch
self.webView.customUserAgent = @"WebViewDemo/1.0.0"; //自定義UA,UIWebView就沒有此功能呈础,后面會(huì)講到通過(guò)其他方式實(shí)現(xiàn)
self.webView.UIDelegate = self;
self.webView.navigationDelegate = self;
[self.view addSubview:self.webView];
  1. 動(dòng)態(tài)注入js
WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:@"document.cookie = 'DarkAngelCookie=DarkAngel;'" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[controller addUserScript:newCookieScript];
//創(chuàng)建腳本
WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:@"alert(document.cookie);" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
//添加腳本
[controller addUserScript:script];
  1. 加載
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
- (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString *)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL API_AVAILABLE(macosx(10.11), ios(9.0));
  1. 代理
@protocol WKNavigationDelegate;    //類似于UIWebView的加載成功舆驶、失敗、是否允許跳轉(zhuǎn)等
@protocol WKUIDelegate;    //主要是一些alert而钞、打開新窗口之類的
//下面這2個(gè)方法共同對(duì)應(yīng)了UIWebView的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//先:針對(duì)一次action來(lái)決定是否允許跳轉(zhuǎn)沙廉,action中可以獲取request,允許與否都需要調(diào)用decisionHandler臼节,比如decisionHandler(WKNavigationActionPolicyCancel);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler撬陵;
//后:根據(jù)response來(lái)決定,是否允許跳轉(zhuǎn)网缝,允許與否都需要調(diào)用decisionHandler巨税,如decisionHandler(WKNavigationResponsePolicyAllow);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

//開始加載,對(duì)應(yīng)UIWebView的- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;

//加載成功粉臊,對(duì)應(yīng)UIWebView的- (void)webViewDidFinishLoad:(UIWebView *)webView;
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;

//加載失敗草添,對(duì)應(yīng)UIWebView的- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
  1. 新屬性
@property (nullable, nonatomic, readonly, copy) NSString *title;    //頁(yè)面的title,終于可以直接獲取了
@property (nullable, nonatomic, readonly, copy) NSURL *URL;        //當(dāng)前webView的URL
@property (nonatomic, readonly, getter=isLoading) BOOL loading;    //是否正在加載
@property (nonatomic, readonly) double estimatedProgress;    //加載的進(jìn)度
@property (nonatomic, readonly) BOOL canGoBack;    //是否可以后退扼仲,跟UIWebView相同
@property (nonatomic, readonly) BOOL canGoForward;    //是否可以前進(jìn)远寸,跟UIWebView相同
  • OC -> JS
//執(zhí)行一段js,并將結(jié)果返回屠凶,如果出錯(cuò)驰后,error則不為空
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id result, NSError * _Nullable error))completionHandler;
  • JS -> OC
<a href="darkangel://smsLogin?username=12323123&code=892845">短信驗(yàn)證登錄</a>
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    //可以通過(guò)navigationAction.navigationType獲取跳轉(zhuǎn)類型,如新鏈接矗愧、后退等
    NSURL *URL = navigationAction.request.URL;
    //判斷URL是否符合自定義的URL Scheme
    if ([URL.scheme isEqualToString:@"darkangel"]) {
        //根據(jù)不同的業(yè)務(wù)灶芝,來(lái)執(zhí)行對(duì)應(yīng)的操作,且獲取參數(shù)
        if ([URL.host isEqualToString:@"smsLogin"]) {
            NSString *param = URL.query;
            NSLog(@"短信驗(yàn)證碼登錄, 參數(shù)為%@", param);
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }
    decisionHandler(WKNavigationActionPolicyAllow);
    NSLog(@"%@", NSStringFromSelector(_cmd));
}
  • scriptMessageHandler
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
- (void)removeScriptMessageHandlerForName:(NSString *)name;
// 使用
[controller addScriptMessageHandler:self name:@"currentCookies"]; //這里self要遵循協(xié) WKScriptMessageHandler
window.webkit.messageHandlers.currentCookies.postMessage(document.cookie);
// 我在OC中將會(huì)收到WKScriptMessageHandler的回調(diào)
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"currentCookies"]) {
        NSString *cookiesStr = message.body;    //message.body返回的是一個(gè)id類型的對(duì)象,所以可以支持很多種js的參數(shù)類型(js的function除外)
        NSLog(@"當(dāng)前的cookie為: %@", cookiesStr);
    }
}
- (void)dealloc {
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"currentCookies"];
}

推薦,關(guān)注

1570756780.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末监署,一起剝皮案震驚了整個(gè)濱河市颤专,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钠乏,老刑警劉巖栖秕,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異晓避,居然都是意外死亡簇捍,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門俏拱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)暑塑,“玉大人,你說(shuō)我怎么就攤上這事锅必∈赂瘢” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵搞隐,是天一觀的道長(zhǎng)驹愚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)劣纲,這世上最難降的妖魔是什么逢捺? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮癞季,結(jié)果婚禮上劫瞳,老公的妹妹穿的比我還像新娘。我一直安慰自己绷柒,他們只是感情好志于,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著废睦,像睡著了一般伺绽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上郊楣,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天憔恳,我揣著相機(jī)與錄音瓤荔,去河邊找鬼净蚤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛输硝,可吹牛的內(nèi)容都是我干的今瀑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼橘荠!你這毒婦竟也來(lái)了屿附?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤哥童,失蹤者是張志新(化名)和其女友劉穎挺份,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贮懈,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匀泊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朵你。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片各聘。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖抡医,靈堂內(nèi)的尸體忽然破棺而出躲因,到底是詐尸還是另有隱情,我是刑警寧澤忌傻,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布大脉,位于F島的核電站,受9級(jí)特大地震影響芯勘,放射性物質(zhì)發(fā)生泄漏箱靴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一荷愕、第九天 我趴在偏房一處隱蔽的房頂上張望衡怀。 院中可真熱鬧,春花似錦安疗、人聲如沸抛杨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怖现。三九已至,卻和暖如春玉罐,著一層夾襖步出監(jiān)牢的瞬間屈嗤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工吊输, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饶号,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓季蚂,卻偏偏與公主長(zhǎng)得像茫船,于是被迫代替她去往敵國(guó)和親琅束。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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