文本編輯器---iOS本地與JavaScript交互解決方案

iOS原生OC與JS互調(diào)

本文demo:git地址

? ? ? ? 由于前段時間剛剛完成一個比較復(fù)雜的本地與原生的交互應(yīng)用,需求要做一個類似有道云筆記或簡書這樣的文本編輯器牺弄,在本地網(wǎng)頁編輯各種復(fù)雜的文章然后保存到后臺,需要隨時從后臺提取展現(xiàn)。現(xiàn)對這種混合應(yīng)用如何實現(xiàn)缀辩,以及開發(fā)過程中遇到的坑進(jìn)行一個總結(jié)。基于JavaScriptCore封裝了一個新的sdk雌澄,文章開頭給出了解決oc與js混合調(diào)用的demo斋泄,如果你覺得有所幫助,請給個星星镐牺。如果覺得代碼有問題或者有疑惑地方請在評論區(qū)留言炫掐,我會第一時間回復(fù)。


文本編輯器功能示例

一睬涧、理論基礎(chǔ)

1. iOS跟JavaScript中的對象募胃、方法

1.1? ? ?對象:OC我們已經(jīng)非常熟悉了,這里稍微啰嗦下畦浓,OC是一門面向?qū)ο蟮膭討B(tài)語言痹束,基于c語言增加一層面向?qū)ο蟮恼Z法,讓oc能滿足所有面向?qū)ο蟮乃刑攸c(封裝(成員變量)讶请、繼承和多態(tài))祷嘶。oc中除了對象外還有一些基本數(shù)據(jù)類型,通過一些轉(zhuǎn)換方法也可以將基本數(shù)據(jù)類型轉(zhuǎn)換成對象類型夺溢,例如:NSNumber *number = [NSNumber numberWithInt:1]论巍。

JavaScript :JS中事物都是對象:字符串、數(shù)值风响、數(shù)組嘉汰、函數(shù)等等,JSt的對象是一種無序的集合數(shù)據(jù)類型状勤,它由若干鍵值對組成鞋怀。詳細(xì)可參考:JS對象

1.2? ? ?方法:oc中的方法分為類方法跟對象方法,方法本身并不屬于對象持搜,而且無法當(dāng)作參數(shù)傳遞密似,但是oc中可以用block當(dāng)參數(shù)來傳遞代碼塊,實現(xiàn)回調(diào)葫盼。相對于oc辛友,js方便很多,js中方法直接可以當(dāng)作參數(shù)進(jìn)行傳遞實現(xiàn)代碼塊的傳遞剪返。

2.本地跟JS交互手段

2.1? ?iOS7前:OC調(diào)用JS只能通過函數(shù)stringByEvaluatingJavaScriptFromString废累,往JS里面注入一段代碼,這里只能注入字符串脱盲,一般我們會把調(diào)用的方法寫在里面邑滨,例如:

NSString *jsString = [[NSString alloc]initWithString:@"initStyle()"];[(UIWebView*)self.realWebView stringByEvaluatingJavaScriptFromString:jsString];

JS又是如何實現(xiàn)調(diào)用OC方法呢?其實iOS SDK 并沒有原生提供 js 調(diào)用 native 代碼的 API钱反,但是 UIWebView 的一個 delegate 方法使我們可以做到讓 js 需要調(diào)用時掖看,通知 native匣距。

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

這個方法可以攔截網(wǎng)頁端發(fā)出的網(wǎng)絡(luò)請求。具體做法就是讓?js 通知 native 的方法是讓 js 發(fā)起一次特殊的網(wǎng)絡(luò)請求哎壳,PhoneGap 之前是使用加載一個隱藏的 iframe 來實現(xiàn)的毅待,通過將 iframe 的 src 指定為一個特殊的 URL,實現(xiàn)在 delegate 方法中截獲這次請求归榕。通過分析截獲的URL我們可以知道JS想干些什么基于URL的攔截進(jìn)行的操作尸红。大家現(xiàn)在用得比較多的是WebViewJavascriptBridgeEasyJSWebView這兩個開源庫,很多混合都采用的這種方式刹泄。這種方式調(diào)用OC方法外里,有篇文章有過介紹:關(guān)于UIWebView的總結(jié)

2.1? ?iOS7后:蘋果開放了一個新的框架:JavaScriptCore框架

關(guān)于這類的文章網(wǎng)上寫的很多,也很不錯特石,這里就不重復(fù)介紹盅蝗。具體可以參考:iOS7新JavaScriptCore框架介紹JavaScriptCore使用姆蘸。這里就不過多講述了墩莫。本文主要目的是介紹基于JavaScriptCore封裝新的第三方開源庫:XHWebViewBridge

交互框架圖

二、開源庫XHWebViewBridge使用及介紹

1.OC調(diào)用JS

????1.1)實現(xiàn)方法

? ? ? ? ? ?框架初始化針對網(wǎng)頁類型進(jìn)行處理逞敷,通過初始化參數(shù)usingUIWebView:來確認(rèn)初始化的網(wǎng)頁初始化的是屬于WKWebView還是UIWebView贼穆。不管是哪種類型框架對js調(diào)用只提供一個方法:

- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void(^)(id,NSError*))completionHandler;

javaScriptString:需要注入的js代碼? ? ?completionHandler:注入完成的回調(diào)

????1.2 ) 使用場景----oc觸發(fā)狀態(tài)js調(diào)用方法作出響應(yīng)

????????????一般來講我們會有這些場景:1、頁面剛加載完成后傳一些參數(shù)到j(luò)s兰粉,網(wǎng)頁拿到參數(shù)可以自己去請求數(shù)據(jù)或者做其他展示幫組js完成自己的初始化。2顶瞳、點擊本地的按鈕需要網(wǎng)頁作出相應(yīng)的反應(yīng)即調(diào)用js的方法玖姑。3、本地網(wǎng)絡(luò)請求成功后慨菱,得到一些參數(shù)數(shù)據(jù)需要給網(wǎng)頁讓網(wǎng)頁能夠作出相應(yīng)的回應(yīng)焰络。

使用場景

1.3 )evaluateJavaScript方法解析

????????????一般在方法里面注入一段js為一個js方法的調(diào)用(因為方法可以帶參數(shù),這樣就能把oc的一下數(shù)據(jù)傳遞到j(luò)s里面)符喝。這里我們可能需要轉(zhuǎn)變一下闪彼,一般我們oc調(diào)用某個方法必須是直接由一個類或者一個對象去調(diào)用相應(yīng)的類方法或者對象方法,但js不僅可以這樣還可以像c語言里面的內(nèi)聯(lián)函數(shù)一樣直接調(diào)用(前提是這個js方法是全局方法)例如案例二直接調(diào)用js的“ocCalljsfun1”方法协饲,

? ? ? ? ? ? 很多時候由于頁面剛加載完成很多對象都還沒有初始化需要oc傳遞過去的參數(shù)完成初始化畏腕,所以會讓JS把傳遞初始化參數(shù)的方法寫成全局的,這樣就可以像案例二所示直接調(diào)用茉稠,然后帶上oc需要給js的參數(shù)描馅。

1.4 )? 傳遞參數(shù)注意事項?

? ? ? ? ? ? a、要注意雙引號沖突而线,可以用單引號(JS中單引號跟雙引號效果是一樣的)@"initStyle('done')"或者對雙引號轉(zhuǎn)義一下@"initStyle(\"done\")"铭污。

? ? ? ? ? ? b恋日、多個參數(shù): 用逗號隔開例如:evaluateJavaScript:@"ocCalljsfun2('OC調(diào)用網(wǎng)頁打印','傳遞一個參數(shù)')"

? ? ? ? ? ? c、傳遞一個或者多個字典(字典在js里面也是對象):由于evaluateJavaScript只能注入字符串嘹狞,所以我們需要辦字典轉(zhuǎn)變成字符串岂膳,最好的方法就是序列化,將字典專成json串然后js拿到j(luò)s串后對json進(jìn)行解析就能拿到準(zhǔn)確的數(shù)據(jù)磅网√附兀可能是由于解析的原因字典轉(zhuǎn)的json傳遞到j(luò)s解析不錯來,所以統(tǒng)一處理了下知市,不管是一個字典還是多個字典都添加進(jìn)NSMutableArray里面傻盟,然后對數(shù)組進(jìn)行轉(zhuǎn)json這樣處理后的的json是沒問題的(已驗證過)

? ? ? ? ?array轉(zhuǎn)json字符串:

//數(shù)組轉(zhuǎn)json

+ (NSString*)arrayToJSONString:(NSMutableArray*)array

{

? ? NSError*error =nil;

? ? NSData *jsonData = [NSJSONSerialization dataWithJSONObject:array options:NSJSONWritingPrettyPrinted error:&error];

? ? NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

? ? returnjsonString;

}

轉(zhuǎn)換完成后json會出現(xiàn)與html沖突的一些標(biāo)簽或特殊字符,所以我們對轉(zhuǎn)換完成后的json需要再次處理嫂丙,調(diào)用下面方法

+ (NSString*)removeQuotesFromHTML:(NSString*)html {

? ? html = [htmlstringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];

? ? html = [htmlstringByReplacingOccurrencesOfString:@"“" withString:@"""];

? ? html = [htmlstringByReplacingOccurrencesOfString:@"”" withString:@"""];

? ? html = [htmlstringByReplacingOccurrencesOfString:@"\r"? withString:@"\\r"];

? ? html = [htmlstringByReplacingOccurrencesOfString:@"\n"? withString:@"\\n"];

? ? returnhtml;

}

這樣處理后的json就可以直接傳到j(luò)s里面而且能保證數(shù)據(jù)的完整瞻鹏,js拿到后只需解析json就能得到一個數(shù)組對象,數(shù)組對象里面裝一個或者多個字典對象诡蜓。

? ? ? ? 至此oc調(diào)用js就沒有什么問題了起趾,不管是什么參數(shù)都能處理。

2.JS調(diào)用OC

? ? ? ? 2.1)? ? 實現(xiàn)方法?

? ? ? ? ? ? ? ? ? ? 由于iOS7以前的攔截URL方法過于麻煩隅肥,所以框架采用的是JavaScriptCore框架來實現(xiàn)js調(diào)用oc竿奏。

? ? ? ? ? ? ? ? ? ? 新建一個繼承XHFunctionModule管理類,假設(shè)取名XHWebViewBridgeManager腥放,這個新的類需要有一個繼承自<JSExport>的代理泛啸,然后你可以寫自己的代理方法(方法需要在.m文件里面實現(xiàn)),這個代理里面的方法將會自動變成你注入對象的對象方法秃症,有同學(xué)會問那我從哪里注入對象呢候址?新建的類必須實現(xiàn)+(NSString*)moduleName這個類方法,框架會把返回的對象注入到網(wǎng)頁种柑。例如:

+(NSString*)moduleName{

? ? return @"XHWebViewBridgeManager";

}

這樣你就往網(wǎng)頁注入了XHWebViewBridgeManager對象岗仑,然后你在代理里面寫的方法就是這個對象的方法例如你的.h文件是這樣的

@protocolXHWebManagerProtocol

- (void)jsCallOC;

@end

@interfaceXHWebViewBridgeManager : XHFunctionModule

@end

在js里面就可以這樣使用?XHWebViewBridgeManager.jsCallOC();這樣就完成了js調(diào)用oc代碼。如果是要傳參數(shù)或者回調(diào)怎么辦聚请?請見本文開頭demo有示例荠雕;

? ? ? ? 2.2 ) 使用場景

? ? ? ? ? ? ? ? ? ? ? ?a.點擊頁面里面的按鈕然后讓本地處理事件,例如驶赏,點擊按鈕需要插一張本地圖片或者需要調(diào)起本地相機(jī)等

? ? ? ? ? ? ? ? ? ? ? ? b.網(wǎng)頁經(jīng)過某些操作獲取的數(shù)據(jù)需要給本地進(jìn)行后續(xù)的處理炸卑。

? ? ? ? 2.3)使用說明及注意事項

? ? ? ? ? ? ? ? ? ? a.數(shù)據(jù)傳遞區(qū)別

OC-JS類型對照表

上門就是類型對照,所以oc方法參數(shù)里面只能是上圖左邊類型煤傍,js調(diào)用代理里面的方法時傳遞上圖右邊相應(yīng)的數(shù)據(jù)類型矾兜,大多數(shù)情況下js傳給本地的字符或者對象,字符oc用NSString患久,對象用NSDictionary椅寺,由于js可以將方法作為參數(shù)傳遞浑槽,如果是js傳遞的是function OC里面需要用JSValue類型,例如:

傳遞function參數(shù)

上面為JS里面我們之前注入的XHWebViewBridgeManager對象調(diào)用OC代理里面的一個方法

帶回調(diào)實現(xiàn)

??OC在.m里面的實現(xiàn)用JSValue接收function類型參數(shù)返帕,由于function里面還有參數(shù)桐玻,那么我們該如何回傳這個參數(shù)?JavaScriptCore框架提供了callWithArguments這個方法荆萤,方法只能傳NSArray類型镊靴,所以在js里面接收到的參數(shù)也是一個array或者叫對象(js除了基本類型都可以看著是對象)。

三链韭、坑處理

1偏竟、在js調(diào)oc方法時,如果js傳function作為參數(shù)有時候會卡屏敞峭。原因是在oc是多線程踊谋,但是刷新UI一定是在主線程里面,而js正常情況下一般只有一個線程旋讹,一旦出現(xiàn)js在回調(diào)里面的方法實現(xiàn)出現(xiàn)延遲(例如上傳下載)殖蚕,而oc需要js立即返回數(shù)據(jù)否則就卡自己主線程了。解決方法:js實現(xiàn)里面加setTimeout方法沉迹,例如:

處理卡屏

2.如果想注入多個對象怎么辦睦疫?類似XHWebViewBridgeManager再新建新的類然后實現(xiàn)moduleName方法,然后返回新的對象名即可鞭呕,框架會幫你注入進(jìn)js的content上下文蛤育。

3.如果是頁面想把所有編輯好的樣式保存下來給后臺,然后下次本地網(wǎng)頁直接加載后臺給的數(shù)據(jù)就能實現(xiàn)草稿續(xù)編功能葫松。然而之前編輯的中文或者一些特殊符號在json序列化后會與html一些標(biāo)簽重合導(dǎo)致本地重新加載解析后的數(shù)據(jù)出現(xiàn)截斷瓦糕。解決方法:網(wǎng)頁先保存html所有的樣式,然后對樣式進(jìn)行編碼(編碼作用是對一些特殊字符进宝、中文進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換成與html不沖突的字符)枷恕,編碼后然后進(jìn)行json序列化存入后臺党晋。

下面是編碼與解碼方法

整體思路:將符號轉(zhuǎn)換為ascii碼,將中文進(jìn)行兩次encodeURI()編碼徐块。?

;!function () {

? ? ? ? String.prototype.AsciiEncode = function () {

? ? ? ? ? ? var fh = "", dg = "", asc = 0, perfix = arguments[0] || '~', str = this;

? ? ? ? ? ? for (i = 0; i < str.length; i++) {

? ? ? ? ? ? ? ? dg = str.substring(i, i + 1);

? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? asc = parseInt(str.charCodeAt(i));

? ? ? ? ? ? ? ? ? ? if ((asc < 48) || (asc > 90 && asc < 97) || (asc > 122 && asc < 127) || (asc > 57 && asc < 65)) {

? ? ? ? ? ? ? ? ? ? ? ? var s000 = asc.toString();

? ? ? ? ? ? ? ? ? ? ? ? if (asc < 100) { s000 = "0" + s000; }

? ? ? ? ? ? ? ? ? ? ? ? fh += perfix + s000;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? else {

? ? ? ? ? ? ? ? ? ? ? ? fh += dg;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? } catch (e) {

? ? ? ? ? ? ? ? ? ? fh += dg;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? return fh.replace(/[\u4E00-\u9FA5\uF900-\uFA2D]/g, function () {

? ? ? ? ? ? ? ? return encodeURI(encodeURI(arguments[0]));

? ? ? ? ? ? });

? ? ? ? }

JS解碼:

? ? ? ? String.prototype.AsciiDecode = function () {

? ? ? ? ? ? var fh = "", youb = "", str = decodeURI(decodeURI(this));

? ? ? ? ? ? var array = str.split(arguments[0] || '~');

? ? ? ? ? ? for (i = 0; i < array.length; i++) {

? ? ? ? ? ? ? ? if (i > 0) {

? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? youb = array[i].substring(0, 3);

? ? ? ? ? ? ? ? ? ? ? ? array[i] = array[i].replace(youb, String.fromCharCode(youb));

? ? ? ? ? ? ? ? ? ? } catch (e) { }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? fh += array[i];

? ? ? ? ? ? }

? ? ? ? ? ? return fh;

? ? ? ? }

? ? } ();

OC解碼:

+ (NSString*)addQuotesFromHTML:(NSString*)html {

? ? NSString*fh =@"";

? ? NSString*youb;

? ? NSString*decodeS = [self? ?URLDecodedString:html];

? ? NSString*newD = [self??URLDecodedString:decodeS];

? ? NSArray *array = [newD componentsSeparatedByString:@"~"];

? ? NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];

? ? for(NSIntegeri =0; i < array.count; i++) {

? ? ? ? if(i >0) {

? ? ? ? ? ? NSString*string = array[i];

? ? ? ? ? ? if(string.length==0) {

? ? ? ? ? ? ? ? return??string;

? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? youb = [string? substringToIndex:3];

? ? ? ? ? ? }

? ? ? ? ? ??intintyoub = [youb? intValue];

? ? ? ? ? ? NSString*newuu = [NSString? stringWithFormat:@"%c",intyoub];

? ? ? ? ? ? NSString *wee = [string stringByReplacingOccurrencesOfString:youb withString:newuu];

? ? ? ? ? ? [newArray? replaceObjectAtIndex:iwithObject:wee];

? ? ? ? }

? ? }

? ? if(newArray.count>1) {

? ? ? ? for(NSString*ssinnewArray) {

? ? ? ? ? ? NSString*k = [fh? stringByAppendingString:ss];

? ? ? ? ? ? fh = k;

? ? ? ? }

? ? ? ? //對特殊字符處理未玻,還原在html中形態(tài)

? ? ? ? fh = [fhstringByReplacingOccurrencesOfString:@"&amp;" withString:@"&"];

? ? ? ? fh = [fhstringByReplacingOccurrencesOfString:@"&lt;" withString:@" <"];

? ? ? ? fh = [fhstringByReplacingOccurrencesOfString:@"&gt;" withString:@">"];

? ? ? ? returnfh;

? ? }else{

? ? ? ? fh = newD;

? ? ? ? returnfh;

? ? }

}

+(NSString*)URLDecodedString:(NSString*)str

{

? ? NSString*decodedString=(__bridge_transfer NSString*)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, (__bridge CFStringRef)str,CFSTR(""),CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));

? ? returndecodedString;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胡控,隨后出現(xiàn)的幾起案子扳剿,更是在濱河造成了極大的恐慌,老刑警劉巖昼激,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庇绽,死亡現(xiàn)場離奇詭異锡搜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瞧掺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門耕餐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辟狈,你說我怎么就攤上這事肠缔。” “怎么了哼转?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵明未,是天一觀的道長。 經(jīng)常有香客問我壹蔓,道長趟妥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任庶溶,我火速辦了婚禮煮纵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘偏螺。我一直安慰自己行疏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布套像。 她就那樣靜靜地躺著酿联,像睡著了一般。 火紅的嫁衣襯著肌膚如雪夺巩。 梳的紋絲不亂的頭發(fā)上贞让,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機(jī)與錄音柳譬,去河邊找鬼喳张。 笑死,一個胖子當(dāng)著我的面吹牛美澳,可吹牛的內(nèi)容都是我干的销部。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼制跟,長吁一口氣:“原來是場噩夢啊……” “哼舅桩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雨膨,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤擂涛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后聊记,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撒妈,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡恢暖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了踩身。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胀茵。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挟阻,靈堂內(nèi)的尸體忽然破棺而出琼娘,到底是詐尸還是另有隱情,我是刑警寧澤附鸽,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布脱拼,位于F島的核電站,受9級特大地震影響坷备,放射性物質(zhì)發(fā)生泄漏熄浓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一省撑、第九天 我趴在偏房一處隱蔽的房頂上張望赌蔑。 院中可真熱鬧,春花似錦竟秫、人聲如沸娃惯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趾浅。三九已至,卻和暖如春馒稍,著一層夾襖步出監(jiān)牢的瞬間皿哨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工纽谒, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留证膨,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓鼓黔,卻偏偏與公主長得像央勒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子请祖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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