Weex 从无到有开发一款上线应用 1

iOS调试Demo
WeexDemo

初始化APPFrame

终端创建 Weex工程
weex init xxx_weex
安装IDE插件

WebStorm安装Weex语法支持和插件。由于最开始Weex文件为.we,现在Weex已经完美支持Vue,所以现在也是用Vue开发。
具体安装步骤如下


打开偏好设置.png
plugins.png

同样的下载Vue.js
安装后重启WebStorm。
由于WebStorm还不能直接创建Vue文件,所以需要先添加一下Vue文件模板。操作如下

添加Vue文件模板.png

模板内容如下

<template>
    <div class="view">
    </div>
</template>

<script>
    let stream = weex.requireModule('stream')
    let modal = weex.requireModule('modal')
    let navigator = weex.requireModule('navigator')
    let globalEvent = weex.requireModule('globalEvent');
    let apiHost = ''
    export default {
        data () {
            return {

            }
        },
        methods: {

        },
        created () {

        },
        mounted()
        {

        },
        components: {
            
        }
    }
</script>

<style scoped>
    .view {
        width: 750px;
        height: 1334px;
        background-color:#fff ;
    }
</style>
新建AppFrame.Vue文件
AppFrame.png

到这里Weex开发环境已经完成。接下来我们就要开始扩展组件和模块了。

扩展原生AppFrame Component

由于Weex开发的是单个页面,也没有系统的ViewController 和 NavigationController以及TabBarController,那么我们怎么开始一个架设一个动态的应用框架呢?这就需要我们书写一个原生的AppFrame组件。
因为是从无到有开发,我们就新建一个Xcode工程。
如果是已有工程那么就创建一个新的Target或者Project,或者新建工程通过pod导入。
创建一个 WXComponent子类。
然后重写-(instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance方法,
方法实现如下

-(instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
{
    if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
        
        [self firstInitAPPFrameWithAttributes:attributes];
        
    }
    return self;
}

类文件如下图


AppFrameComponent.png
这样做的思路:

tabbarItem的数据通过服务端下发的形式进行创建。
以及tabbarViewController.ViewControllers也通过服务端下发的形式进行创建。
-------------2017.05.24 明晚再写-----------------
如果App设计是最常用的TabbarUI,那么下发的tabbarItem的数据结构如下:

 {
//                            标题
                            title:'',
//                            没选中字体颜色
                            normalTitleColor:'999999',
//                            选中字体颜色
                            selectedTitleColor:'FF3C00',
//                            背景颜色
                            tintColor:'FF3C00',
//                            选中图片
                            selectedImage:'',
//                            没选中图片
                            image:''
}

当然有非常规的UI那么同样需要配置的数据有这些,其他就需要针对应用来自己设计数据结构。
导航栏也同样需要考虑系统的是否满足需求,不满足的那么就在当前页面做一个假的导航栏(这样有一定的缺陷,比如我们再通话中使用app那么假的导航栏需要处理成新的高度)。下边是针对系统导航栏的下发数据

{
//                            导航栏标题
                            title:'',
//                            透明导航栏的字体颜色
                            clearTitleColor:'ffffff',
//                            高斯模糊导航栏的字体颜色
                            blurTitleColor:'000000',
//                            左边选项按钮
                            leftItemsInfo:
                                [
                                    {
//                                        原生调取 weex方法名
                                        aciton:'',
//                                        渲染 按钮的链接  如果以http开头 会渲染网络JSBundle 反之 渲染本地JSBundle
                                        itemURL:''
                                    }
                                ],
//                            右边选项按钮
                            rightItemsInfo:
                                [
                                    {
                                        aciton:'',
                                        itemURL:host + dirctoryPath + 'DemoRefreshRightItem.js',
//                                        设计图中container的大小
                                        frame:'{{0, 0}, {32, 16}}'
                                    }
                                ],
//                            把导航栏变成透明的
                            clearNavigationBar:true,
//                            把导航栏隐藏
                            hiddenNavgitionBar:false,
//                            导航栏背景颜色
                            navigationBarBackgroundColor:'',
//                            导航栏背景图片
                            navgationBarBackgroundImage:'',
//                            自定义标题视图的JSBundle地址 如果以http开头 会渲染网络JSBundle 反之 渲染本地JSBundle
                            customTitleViewURL:'',
//                        这个是当前导航栏栈顶的JSBundleURL
                            rootViewURL:host + dirctoryPath + 'index.js',
                        }
渲染过程

根据应用的设计我们需要作出不同的AppFrame调整,这个是毋庸置疑。再次回到原生APPFrameComponent类中。这里大体说一下WXSDKInstance在iOS端的渲染过程,具体的还是要看源码,每个人读的时候都有自己的见解认识。
1.从- (void)renderWithURL:(NSURL *)url开始,[WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy]处理URLRequest,_mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request]; [_mainBundleLoader start];下载JSBundle源。不赘述如何处理的URLRequest,看一下在下载之前都处理什么(代码有缺失,详尽请看源码)。

- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data
{
    WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy];
    [self _renderWithRequest:request options:options data:data];
}
- (void)_renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data;
{
    NSURL *url = request.URL;
    _scriptURL = url;
    _jsData = data;
    NSMutableDictionary *newOptions = [options mutableCopy] ?: [NSMutableDictionary new];
    
    if (!newOptions[bundleUrlOptionKey]) {
        newOptions[bundleUrlOptionKey] = url.absoluteString;
    }
    // compatible with some wrong type, remove this hopefully in the future.
    if ([newOptions[bundleUrlOptionKey] isKindOfClass:[NSURL class]]) {
        WXLogWarning(@"Error type in options with key:bundleUrl, should be of type NSString, not NSURL!");
        newOptions[bundleUrlOptionKey] = ((NSURL*)newOptions[bundleUrlOptionKey]).absoluteString;
    }
    _options = [newOptions copy];
  //获取网站主页地址
    if (!self.pageName || [self.pageName isEqualToString:@""]) {
        self.pageName = [WXUtility urlByDeletingParameters:url].absoluteString ? : @"";
    }
    //这里会配置一些请求信息 比如手机系统 手机型号 app名字 屏幕信息等
    request.userAgent = [WXUtility userAgent];
    //告诉监视器WXMonitor开始下载JSBundle
    WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, self);
    __weak typeof(self) weakSelf = self;
    _mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request];;
    _mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        //这里省略源码,此处验证请求回调数据是否有效 并告知WXMonitor请求结果
        NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        if (!jsBundleString) {
            WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_STRING_CONVERT, @"data converting to string failed.", strongSelf.pageName)
            return;
        }
        WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName);
        WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf);
        [strongSelf _renderWithMainBundleString:jsBundleString];
    };
    _mainBundleLoader.onFailed = ^(NSError *loadError) {
//此处省略源码 处理报错信息
    };
    [_mainBundleLoader start];
}

2.开始渲染JSBundle,
首先会验证WXSDKInstance的一些信息是否有误,并打出相应Log。
接下来会创建WXRootView调用instanceonCreate(UIView * view){ } block。
重新调用一下引擎默认配置(

{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self _registerDefaultComponents];
        [self _registerDefaultModules];
        [self _registerDefaultHandlers];
    });
}```)
通过```WXSDKManager```取得```WXBridgeManager```实例,调用```- (void)createInstance:(NSString *)instance template:(NSString *)temp   options:(NSDictionary *)options data:(id)data
```还是会验证参数是否有效,看渲染堆```instanceIdStack```中是否含有该```instanceId```,有的话看是否有在渲染的```instanceId```,有就添加任务,没有就把该id置顶创建,让```WXBridgeContext```实例去渲染(该操作验证并必须在```WXBridgeThread```)。到此就开始了JS引擎的渲染,可想而知会生成DOM树,然后逐个解析UI Component(js call native 创建以及渲染UI)会从```WXComponent```的```initWithRefxxxxx```开始。
######应用BasicWeexViewController的实现
不多赘述,按照官方给的思路,很容易就能封装出。这是我项目中的[XMWXViewController](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/ViewControllers/XMWXViewController.m)
######AppFrameComponent 实现
接上,AppFrame要做的就是将下发的tabbarItem 和 navigationItem形成框架 和UI。不同UI设计对用不同的AppFrameComponent 实现。
下面是最常用最简单(我项目中的[XMWXAPPFrameComponte](https://github.com/jiaowoxiaoming/XMWeex/blob/master/XMWeex/XMWeex/WeexComponent/APPFrame/Basic/XMWXAPPFrameComponte.m))的实现:

pragma mark - private method

/**
初始化APP框架

@param attributes 返回的RenderInfo
*/
-(void)firstInitAPPFrameWithAttributes:(NSDictionary *)attributes
{
dispatch_async(dispatch_get_main_queue(), ^{
//设置APP
UIApplication * application = [UIApplication sharedApplication];
UITabBarController * tabarViewController = [[UITabBarController alloc] init];
// tabarViewController.view.alpha = 0;
UIWindow * window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[((UIResponder *)application.delegate) setValue:window forKey:@"window"];

    window.rootViewController = tabarViewController;
    
    window.backgroundColor = [UIColor whiteColor];
    
    [window makeKeyAndVisible];
    
    [self handleTabbarViewControllers:attributes tabarController:tabarViewController];
});

}
/**
创建tabbar.items
@param attributes component 下发数据
@param tabarController [UIApplication sharedApplication].keyWindow.rootViewController
@return 创建的UITabBarItem集合
*/
-(NSMutableArray <UITabBarItem *> *)handleTabbarItems:(NSDictionary *)attributes tabarController:(UITabBarController __kindof * )tabarController
{
NSString * tabItemsDictJsonString = [WXConvert NSString:attributes[XMWXAPPFrameComponteTabbarItemsKey]];

NSArray * tabItemsInfoArray = [NSJSONSerialization JSONObjectWithData:[tabItemsDictJsonString dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingAllowFragments) error:nil];

NSMutableArray * tabarItems = [NSMutableArray array];

[tabItemsInfoArray enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    XMWXTabbarItem * xmItem = [XMWXTabbarItem itemWithDict:obj];
    UITabBarItem * item = [[UITabBarItem alloc] init];
    
    item.title = xmItem.title;
    
    if (xmItem.tintColor.length) {
        [tabarController.tabBar setTintColor:colorWithHexString(xmItem.tintColor, 1.f)];
    }
    
    if (xmItem.normalTitleColor.length) {
        [item setTitleTextAttributes:@{NSForegroundColorAttributeName:colorWithHexString(xmItem.normalTitleColor, 1.f)} forState:(UIControlStateNormal)];
    }
    if (xmItem.selectedTitleColor.length) {
        [item setTitleTextAttributes:@{NSForegroundColorAttributeName:colorWithHexString(xmItem.selectedTitleColor, 1.f)} forState:(UIControlStateSelected)];
    }
    if ([xmItem.image hasPrefix:@"http"]) {
        
        [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:xmItem.image] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            
        } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
            item.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
            [tabarController.tabBar setItems:tabarItems];
        }];
    }else
    {
        item.image = xmwx_imageForSetting(xmItem.image);
    }
    
    if ([xmItem.selectedImage hasPrefix:@"http"]) {
        
        [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:xmItem.selectedImage] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
            
        } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
            item.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

            [tabarController.tabBar setItems:tabarItems];
        }];
        
    }else
    {
        item.selectedImage = xmwx_imageForSetting(xmItem.selectedImage);
    }
    
    [tabarItems addObject:item];
}];
return tabarItems;

}
/**
渲染TabbarViewController

@param attributes component 下发数据
@param tabarController [UIApplication sharedApplication].keyWindow.rootViewController
*/
-(void)handleTabbarViewControllers:(NSDictionary *)attributes tabarController:(UITabBarController __kindof * )tabarController
{
NSMutableArray <UITabBarItem *> * tabbarItems = [self handleTabbarItems:attributes tabarController:tabarController];
NSArray * viewControllerItems = [NSJSONSerialization JSONObjectWithData:[[WXConvert NSString:[attributes objectForKey:XMWXAPPFrameComponteViewControllerItemsKey]] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];

NSMutableArray * viewControllers = [NSMutableArray array];

[viewControllerItems enumerateObjectsUsingBlock:^(NSDictionary *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
    XMWXNavigationItem * navigationItem = [XMWXNavigationItem infoWithDict:obj];
    
    XMWXViewController * viewController = [[XMWXViewController alloc] init];
    
    viewController.renderInfo = navigationItem;
    if (navigationItem.rootViewURL.length > 0) {
        if ([navigationItem.rootViewURL hasPrefix:@"http"]) {
            viewController.renderURL = [NSURL URLWithString:navigationItem.rootViewURL];
        }else
        {
            NSString * path = [[NSBundle mainBundle] pathForResource:navigationItem.rootViewURL ofType:@""];
            if (path) {
                viewController.renderURL = [NSURL fileURLWithPath:path];
            }
        }
    }
    
    XMWXViewController * __weak weakViewController = viewController;
    viewController.instance.onCreate = ^(UIView * view)
    {
        XMWXViewController * __strong vc = weakViewController;
        [vc.view addSubview:view];
    };
    viewController.instance.frame = viewController.view.bounds;
    viewController.instance.onLayoutChange = ^(UIView *view)
    {
        XMWXViewController * __strong vc = weakViewController;
        vc.instance.frame = vc.view.bounds;
    };
    
    UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:viewController];
    nav.tabBarItem = [tabbarItems objectAtIndex:idx];
    [viewControllers addObject:nav];
}];

[tabarController setViewControllers:viewControllers animated:YES];

}
/**
数据更改的时候调用
@param attributes component属性数据
*/
-(void)updateAttributes:(NSDictionary *)attributes
{
if ([UIApplication sharedApplication].keyWindow.rootViewController) {
[self handleTabbarItems:attributes tabarController:(UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController];
[self handleTabbarViewControllers:attributes tabarController:(UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController];
}else
{
[self firstInitAPPFrameWithAttributes:attributes];
}

}

到此原生Component已经完成。
那么我们需要回到我们初始化Weex环境的方法中加入```    //通过配置这个Component参数来配置程序框架HTML标签名
    [WXSDKEngine registerComponent:@"AppFrame" withClass:NSClassFromString(@"xxxAPPFrameComponte")];```
######开始书写[AppFrame.Vue](https://github.com/jiaowoxiaoming/app_weex/blob/master/app_weex/src/Components/Frame/AppFrame.vue)
回到WebStorm,加入如下代码

<template>
<AppFrame id='AppFrame' :tabarItems="tabbarItemsJsonString" :viewControllerItems="viewControllerItemsString"></AppFrame>
</template>这样就可以使用我们扩展的AppFrameComponenttabbarItemsJsonStringviewControllerItemsString```需要添加下面代码

<script>
    export default {
        data () {
            return {
                tabbarItemsJsonString:JSON.stringify(
                    [{上边的TabbarItem数据}]),
                viewControllerItemsString:JSON.stringify(
                    [{上边的navigationBarItem数据}]),
            };
        },
        methods: {}
    }
</script>

完整的AppFrame.Vue
到这里其实就可以编译我们的Xcode工程了。

开发中的联动调试

那么如何看我们的效果呢?可能从一开始就应该跟大家说如何联动调试,但是我觉得读到这里大家并没有开始着手做,只是看,所以也就没有中间如何联动调试的步骤,大家真的上手做的时候,中间是少不了的,到时候大家再看下面内容。
建议大家还是使用WebStorm,本文就是基于此IDE开发的Weex Project。
还有这里涉及到单页面的调试还是应用的整体调试。单页面调试还是用Weex官方提供的Playground,如何进行单页面的调试,Weex文档说明了。那么如何进行整个应用的联动调试呢?
其实就是实时将Weex project 打包,然后用部署到本机服务器。通过扫码或者本地写死AppFrame的renderURL。
Weex没有提及这样的调试,这里就详细说明。
在Weex Project中找到package.json文件更改serve后的端口(或者直接使用8080,可以更改为8081什么的)。操作如图

更改端口.png

然后在WebStorm中打开终端(使用终端App的话需要先cd到项目目录下)执行

npm install

npm run serve

成功如下图


run serve.png

创建一个dist目录
然后在终端输入

weex compile src dist

上边命令是将src目录下全部生成JSBundle文件
下面命令是针对某一个Vue文件生成JSBundle

weex compile src/xxx.vue dist

Weex打包JSBundle.png

这个时候我们就可以将
http://本机IP:8083/dist/components/Frame/AppFrame.js
转化成二维码,用XMWeex编译的App扫描生成的二维码或者将你自己现在开发的加一个二维码扫描,甚至你也可以写死,直接渲染上述地址。
如此方式实现AppDelegate
代码摘要:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    UITabBarController * tabarViewController = [[UITabBarController alloc] init];
    
    UIWindow * window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window = window;
    window.rootViewController = tabarViewController;
    
    
    window.backgroundColor = [UIColor whiteColor];
    [window makeKeyAndVisible];
    [WXAppConfiguration setAppGroup:@"application"];
    [WXAppConfiguration setAppName:@"application"];
    [WXAppConfiguration setAppVersion:@"1.0"];
    
    //init sdk enviroment
    [WXSDKEngine initSDKEnvironment];
    [WXSDKEngine registerModule:@"XMWXModule" withClass:NSClassFromString(@"XMWXModule")];
    [WXSDKEngine registerHandler:[XMWXWebImage new] withProtocol:@protocol(WXImgLoaderProtocol)];
    //通过配置这个Component参数来配置程序框架HTML标签名
    [WXSDKEngine registerComponent:@"AppFrame" withClass:NSClassFromString(@"XMWXAPPFrameComponte")];
    
#if TARGET_IPHONE_SIMULATOR//模拟器
    NSString * renderURL = @"http://192.167.0.3:8083/dist/components/Frame/AppFrame.js";
    //    NSString * renderURL = [NSString stringWithFormat:@"%@%@",host,@"AppFrame.weex.js"];
    [self instance:renderURL];
    
#elif TARGET_OS_IPHONE//真机
    XMWXScanViewController * scanVC = [[XMWXScanViewController alloc] init];
    tabarViewController.viewControllers = @[scanVC];
#endif
    
    [WXLog setLogLevel:WXLogLevelError];
    return YES;
}
-(WXSDKInstance *)instance:(NSString *)renderURLString
{
    if (!_instance) {
        _instance = [[WXSDKInstance alloc] init];
        [[NSURLCache sharedURLCache] removeAllCachedResponses];
//
        [_instance renderWithURL:[NSURL URLWithString:renderURLString]];
        
    }
    return _instance;
}

这个时候你已经能得到类似如下的App框架。

AppFrame.jpg

如果读到这,你会发现其实我们的这个AppFrame的页面并没有开发。其实渲染出的就是一个ViewController。
那么下面我们要做的就是开发每一个模块。循序渐进,从
下一篇Weex 从无到有开发一款上线应用 2

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容