Weex实战开发

俗话说光说不练假把式。那就先来感受下Weex的魅力:

效果图.png

目前项目已上传至GitHub上,需要的可自行前去下载:
原生项目(Xcode)
Weex项目(WebStorm)

一.目的

        Weex虽然从阿里爸爸把它生出来也有两年时间了,但我想对于广大开发者来说,可能对它的了解少之又少。对于这种新鲜的事物,我们总是要持有敬畏的态度的,因为当你在进一步了解它的时候,你会发现它有无穷的魅力在吸引你。我当初就是被它的魅力深深吸引,想更深入的了解,但我在度娘那里没有找到多少真正的项目实战,即使有也是比较笼统的讲了一下大概,没有详细的介绍。因此我写此篇文章的目的是帮助那些刚刚开始接触Weex又急于想找个项目练手的新手玩家们,我会以一个初学者的角度,尽可能的讲解项目的每个细节。

二.声明

        对于小白:如果在这之前你没听说过Weex,那很好,这篇文章你可以读一读。啥?没兴趣?那我可以把Weex的广告语透露给你,Write Once, Run Everywhere。点我

        对于新手:如果你是刚刚开始接触并且跃跃欲试的新手玩家,正想找个真实项目练练手。那更好,这篇文章对你在合适不过了。跟我一起,边学边练。我会一步一步的介绍整个项目的流程。

        对于大佬:如果您是Weex大佬,那更好了。不要走,留下联系方式,小弟我有点问题想跟您请教请教。

学习门槛:

1.Vue:Vue语言基础

2.ES6:ECMAScript 6 入门

3.iOS或者安卓开发语言和编辑器基本使用

        还有一点我觉得有必要先声明一下,由于本人的文字能力有限,有些地方语言表达可能不太清楚,文章排版可能不太清晰,但是这又有什么关系呢,再大的困难也挡不住大家的热情啊!由于内容很长而本人时间有限,只能在工作之余写一些东西,所以打算不定时更新,不便之处还请谅解。好了废话不多说了,开始入正题吧。。。


三.那就开始吧

开发环境:macOS 10.13.4

开发工具:WebStorm 2018.1 Xcode 9.3

        本项目采用的是集成的方式,即将 Weex 集成到已有的应用。为什么呢?原因有两点,一:Weex TabBar(标签栏)和NavigationBar(导航栏)不太好用,需要用到第三方的组件,我想与其用第三方的不如直接用原生代码写了。二:本人认为项目中还是需要用原生的代码的,毕竟像Weex这种新生事物,很多地方有待完善,完全依赖未免在有些地方会存在力不从心,所以为了增强代码可控性,我建议使用集成的方式。(个人看法,不喜勿喷)

新建原生工程:

1.新建一个Xcode工程,建立目录结构如下:(里面的类文件暂时先不要建,之后会慢慢的一一说明)
原生项目结构.png

2.通过cocoaPods向项目中导入最新版本的WeexSDK,在 Podfile 文件中添加如下内容:(至于cocoaPods怎么使用就在这就不多赘述了,不会的可以去问问度娘)

target 'demo' do
  # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
  # use_frameworks!

  # Pods for demo
  pod 'WeexSDK'

  target 'demoTests' do
    inherit! :search_paths
    # Pods for testing
  end
  target 'demoUITests' do
    inherit! :search_paths
    # Pods for testing
  end
end

        打开命令行,切换到你已有项目 Podfile 这个文件存在的目录,执行 pod install,没有出现任何错误表示已经完成环境配置。至此,原生的工程就算大功告成了。

新建Weex工程:

1.新建一个Weex工程,建立目录结构如下:(里面的类文件暂时先不要建,之后会慢慢的一一说明)

Weex项目结构.png

啥?你告诉我你不会!那不可能吧,既然都来实战了,我默认你会这些基本的操作了啊。哪有都上阵打仗了不会用枪的道理。。。(不会请看这里

2.下一步就是进入刚刚创建的文件夹,并且安装依赖,然后执行 npm start:

cd your`s project file
npm install
npm start

        然后工具会启动一个本地的 web 服务,监听 8081 端口。

3.使用WebStorm打开Weex项目(Weex编译器有很多比如还有Sublime等等,使用哪个看个人爱好了)。在WebStorm里面打开2个终端,依次执行npm run build,npm run serve两条命令。
npm run build.png
npm run serve.png
        然后在项目下会自动生成一个叫dist文件夹,里面的index.js文件就是我们需要放到服务器上的。当执行完npm run serve命令后,浏览器会自动打开一个窗口,名叫Weex Preview,可以动态查看页面在Web 下的渲染效果。 源代码在 src/ 目录中,你可以像一个普通的 Vue.js 项目一样来开发。
Weex Preview.png

        当你出现这种页面的时候,那么恭喜你,你的Weex工程算是新建好了。


开始写代码

1.打开原生工程
(1)创建GlobalDefine文件,里面加1条宏,其值就是Weex工程中index.js文件的路径。这样做的好处就是当我们在Weex项目中修改好代码之后,原生项目只需要重新加载一次js文件就可以同步看到修改之后的效果,不需要每次都拷贝过来,然后在build一次原生项目。

#define HomeJS @"/Users/peter/Desktop/weexCode/weexDemo/dist/index.js"

(2)创建.pch文件,为以后类文件引用做准备。

#import <WeexSDK/WeexSDK.h>
#import "GlobalDefine.h"

(3)在info.plist中添加Allow Arbitrary Loads并设置值为YES(不会就点我)。如果不设置会无法进行http请求哦,当然也加载不了网络图片咯。
(4)由于weexSDK 目前没有提供图片下载的能力,在WXImgLoaderProtocol 定义了一些获取图片的接口, image 组件正是通过 WXImgLoaderProtocol 获得并展示图片,我们可以实现该 protocol 中的接口方法,这样 image 标签才能正常展示图片。这就需要我们自定义handler并注册了。在WeexCustom目录下创建WXImgLoaderDefaultImpl类,实现WXImgLoaderProtocol协议里面的方法。

@implementation WXImgLoaderDefaultImpl
#pragma mark -
#pragma mark WXImgLoaderProtocol
- (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image,  NSError *error, BOOL finished))completedBlock
{
    if ([url hasPrefix:@"//"]) {
        url = [@"http:" stringByAppendingString:url];
    }
    return (id<WXImageOperationProtocol>)[[[SDWebImageManager sharedManager] imageDownloader]downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
        if (completedBlock) {
            completedBlock(image, error, finished);
        }
    }];
}
@end

(5)在NativeFile下面创建四个Controller分别对应底部四个标签栏。并且自定义TabBarController文件继承系统的UITabBarController作为项目的根控制器。

@implementation TabBarController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor] ;
    [self setViewControllers:self.allControllers animated:NO];
}
- (NSArray *)allControllers{
    if (_allControllers == nil) {
        HomeViewController *home = [[HomeViewController alloc] init] ;
        ShopViewController *shop = [[ShopViewController alloc] init] ;
        MineViewController *mine = [[MineViewController alloc] init] ;
        MoreViewController *more = [[MoreViewController alloc] init] ;
        
        NSArray *array = @[[self navWithRoot:home title:@"首页" image:@"icon_tabbar_homepage" selectedImage:@"icon_tabbar_homepage_selected"],
                          [self navWithRoot:shop title:@"商家" image:@"icon_tabbar_merchant_normal" selectedImage:@"icon_tabbar_merchant_selected"],
                          [self navWithRoot:mine title:@"我的" image:@"icon_tabbar_mine" selectedImage:@"icon_tabbar_mine_selected"],
                          [self navWithRoot:more title:@"更多" image:@"icon_tabbar_misc" selectedImage:@"icon_tabbar_misc_selected"]];
        _allControllers = [[NSArray alloc] initWithArray:array];
    }
    return _allControllers;
}
- (UINavigationController *)navWithRoot:(UIViewController *)vc title:(NSString *)title image:(NSString *)image selectedImage:(NSString *)selectedImage {
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
    UIImage *imageNormal = [UIImage imageNamed:image];
    UIImage *imageSelected = [UIImage imageNamed:selectedImage];
    UITabBarItem *tabBarItem = [[UITabBarItem alloc] initWithTitle:title image:[[imageNormal bp_scaleWithSize:CGSizeMake(30, 30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] selectedImage:[[imageSelected bp_scaleWithSize:CGSizeMake(30, 30)] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
    tabBarItem.titlePositionAdjustment = UIOffsetMake(0, -2) ;
    [tabBarItem setTitleTextAttributes:[NSDictionary dictionaryWithObject:[UIColor orangeColor] forKey:NSForegroundColorAttributeName] forState:UIControlStateSelected] ;
    nav.tabBarItem = tabBarItem ;
    return nav;
}
@end

(6)在WeexConfig目录下创建WeexSDKManager类,用来对WeexSDK的初始化,以及相关自定义组件的注册都可以放在该类里面(我们前面自定义的图片下载WXImgLoaderDefaultImpl就放在这里面注册)。

+ (void)setup;
{
    [self initWeexSDK];
    [self loadCustomContain];
}

+ (void)initWeexSDK
{
    [WXAppConfiguration setAppGroup:@"AliApp"];
    [WXAppConfiguration setAppName:@"WeexDemo"];
    [WXAppConfiguration setAppVersion:@"1.8.3"];
    [WXAppConfiguration setExternalUserAgent:@"ExternalUA"];
    [WXSDKEngine initSDKEnvironment];
#ifdef DEBUG
    [WXLog setLogLevel:WXLogLevelLog];
#endif
    //自定义组件的注册
    [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
    [WXSDKEngine registerModule:@"HomeViewController" withClass:NSClassFromString(@"HomeViewController")];
    [WXSDKEngine registerComponent:@"PeterSwitch" withClass:NSClassFromString(@"PeterSwitch")];
}
+ (void)loadCustomContain
{
    [[UIApplication sharedApplication] delegate].window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    [[UIApplication sharedApplication] delegate].window.window.backgroundColor = [UIColor whiteColor];
    TabBarController *demo = [[TabBarController alloc] init];
    [[UIApplication sharedApplication] delegate].window.rootViewController = demo;
    [[[UIApplication sharedApplication] delegate].window makeKeyAndVisible];
}

(7)WeexSDKManager对外提供setup的类方法,在AppDelegate的didFinishLaunchingWithOptions方法里面调用。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [WeexSDKManager setup];
    return YES;
}

(8)万事俱备,接下来需要我们把WeexSDK用起来啊,这就是使用SDK将打包生成的js文件解析成各平台原生组件的过程。进入HomeViewController(首页),代码如下。

@interface HomeViewController ()
@property (nonatomic, strong) WXSDKInstance *instance;
@property (nonatomic, strong) UIView *weexView;
@end
@implementation HomeViewController
WX_EXPORT_METHOD(@selector(weexRender))
WX_EXPORT_METHOD(@selector(iosRender))

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //隐藏系统导航栏
    [self.navigationController setNavigationBarHidden:YES animated:NO];
    [self iosRender];
}
- (void)iosRender
{
    CGFloat width = self.view.frame.size.width;
    CGFloat height = self.view.frame.size.height;
    [_instance destroyInstance];
    _instance = [[WXSDKInstance alloc] init];
    _instance.viewController = self;
    _instance.frame = CGRectMake(0, 0, width, height-49);

    __weak typeof(self) weakSelf = self;
    _instance.onCreate = ^(UIView *view) {
        [weakSelf.weexView removeFromSuperview];
        weakSelf.weexView = view;
        [weakSelf.view addSubview:weakSelf.weexView];
        UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.weexView);
    };
    _instance.onFailed = ^(NSError *error) {
        WXLogDebug(@"%@", @"Render onFailed...");
    };
    _instance.renderFinish = ^(UIView *view) {
        WXLogDebug(@"%@", @"Render Finish...");
    };
    _instance.updateFinish = ^(UIView *view) {
        WXLogDebug(@"%@", @"Update Finish...");
    };
    //这里的HomeJS就是全局的宏定义
    NSURL *URL = [NSURL fileURLWithPath:HomeJS];
    [_instance renderWithURL:URL options:@{@"bundleUrl":URL.absoluteString} data:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //只要点击屏幕就会调用这个方法,重新解析一次js文件,这样做的好处就是不需要重新build项目就能刷新js
    [self iosRender];
}
- (UIStatusBarStyle)preferredStatusBarStyle
{   //修改顶部状态栏(电池栏)颜色为白色
    return UIStatusBarStyleLightContent;
}
- (void)dealloc
{   //控制器销毁的时候要做相应处理
    [_instance destroyInstance];
}
@end

(9)这些完成之后build一下原生项目,之后自动启动Xcode自带模拟器,神奇的一幕出现了。哇!成就感爆棚有木有,之前的一切努力都是值得的,这就是前端开发的魅力所在。
start.png

        至此我们原生部分代码就可以告一段落了,之后在写“商家”、“我的”和“更多”的时候还需要再回来,接下里我们大部分工作都会在Weex项目中完成。
2.打开Weex项目
(1)在Home目录下创建Home.vue文件,用来写首页。
(2)进入index.vue,将Home.vue引入进来。

<template>
    <home></home>
</template>
<script>
    //用这种方式引入vue组件
    import home from '../src/demo/Home/Home';
    export default {
        name: 'App',
        data () {
            return {
            }
        },
        //在components里面声明然后才能使用
        components:{
            home
        }
    }
</script>
//scoped-以表示它的样式作用于当下的模块,很好的实现了样式私有化的目的
<style scoped>
</style>

(3)新建globalDefine.js文件,放一些全局的变量

exports.apiUrl = {
    resUrl:'http://192.168.0.225:8081/images/'
}

首页

1.顶部导航栏:

<template>
    <!--Weex的template里面有且只能有一个div标签作为跟标签-->
    <div class="container">
        <!--导航栏-->
        <div class="navgationContainer">
            <div class="navigation">
                <!--地理位置-->
                <div class="locationContainer">
                    <text style="color: white">上海△</text>
                </div>
                <!--搜索框-->
                <div class="search">
                    <!--图标-->
                    <image :src="searchIcon" style="width: 44px;height: 44px;margin-left: 10px"></image>
                    <input  style="margin-right: 10px;margin-left: 10px;font-size: 30px;flex: 1" placeholder="输入商家、品类、商圈"/>
                    <image :src="scanIcon" style="width: 44px;height: 44px;margin-right: 10px"></image>
                </div>
                <div style="flex-direction: row; flex: 0.3;justify-content: center;align-items: center">
                    <!--地图-->
                    <image :src="mapIcon" style="width: 44px;height: 44px;margin-right: 5px"></image>
                    <text style="color: white">地图</text>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    //图片地址采用基础地址加名称的方式拼接
    var globalDefine = require('../../globalDefine');
    export default {
        data(){
            return{
                searchIcon:globalDefine.apiUrl.resUrl + 'search.png',
                scanIcon:globalDefine.apiUrl.resUrl + 'scan.png',
                mapIcon:globalDefine.apiUrl.resUrl + 'map.png',
            }
        }
    }
</script>
<style scoped>
    .navgationContainer{
        height: 128px;
        background-color: rgba(255,96,0,1.0);
    }
    .navigation{
        flex-direction: row;
        height: 88px;
        margin-top: 40px;
        align-items: center;
    }
    .search{
        flex: 1;
        flex-direction: row;
        background-color: white;
        justify-content: space-between;
        align-items: center;
        margin-left: 20px;
        margin-right: 20px;
        border-radius: 8px;
        height: 60px;
    }
</style>

代码注解:
1.为了简化页面设计和实现, 屏幕的宽度统一为750像素,不同屏幕会按照比例转化为这一尺寸。
2.标准CSS支持很多样式选择器, 但Weex目前只支持单个类的选择器。
3.标准CSS支持很多的长度单位,Weex目前只支持像素,并且px在样式中可以忽略不写, 直接使用对应的数值。
4.标准CSS包含了非常多的样式属性,但Weex只支持了其中的一部分,包括盒模型,flexbox,position等布局属性。以及font-size,color等样式。
5.v-bind动态绑定指令,默认情况下标签自带属性的值是固定的,在为了能够动态的给这些属性添加值,可以使用v-bind:你要动态变化的值="表达式"。
6.v-bind用于绑定属性和数据 ,其缩写为“ : ” 也就是v-bind:src = :src。
7.项目中图片地址均采用基础地址+名称的方式拼接,如果出现图片加载不出来的情况可以在globalDefine.js将resUrl更换成自己本机的ip地址即可。(至于Weex的图片导入方式我建议看一下这篇文章:Weex导入图片)
8.你或许会问为什么我把样式直接写在行内了,个人习惯而已,我喜欢把样式代码比较少的或者不是公共样式采用内联样式,其他的用页内样式。不然一个标签一个class得把我累死。

        当我们执行了npm run serve命令之后,我们每一次改变都会自动在Weex Preview渲染,相应的我们点击iOS模拟器重新加载index.js文件会得到最新的页面渲染效果。


首页导航栏.png

2.导航栏做好了接下来就是正文的列表页,整个列表用一个scroller组件包装,里面的每一个cell分开来写,这样可以减轻首页的代码量。

顶部分页视图


首页顶部视图.png
<template>
    <div class="tab" style="background-color: white;flex: 1;height: 380px">
        <slider class="slider" auto-play="true" interval="3000" @change="onchange">
            <div style="width: 750px">
                <div v-for="(v,i) in items2" style="flex-direction: row;margin-top: 36px;width: 750px">
                    <div v-for="(item,k) in v" style="flex: 1;justify-content: center;align-items: center">
                        <image :src="item.icon" style="width: 88px;height: 88px"></image>
                        <text style="font-size: 30px">{{item.name}}</text>
                    </div>
                </div>
            </div>
            <div style="width: 750px">
                <div v-for="(v,i) in items3" style="flex-direction: row;margin-top: 36px;width: 750px">
                    <div v-for="(item,k) in v" style="flex: 1;justify-content: center;align-items: center">
                        <image :src="item.icon" style="width: 88px;height: 88px"></image>
                        <text style="font-size: 30px">{{item.name}}</text>
                    </div>
                </div>
            </div>
            <indicator class="indicatorClass"></indicator>
        </slider>
    </div>
</template>
<script>
        methods: {
            onchange (event) {
                console.log('changed:', event.index)
            }
        }
    }
</script>

代码注解:
1.由于篇幅的原因我就不把所有代码都截上来了,这里只选取相对重要的部分,需要的童鞋请前去下载完整项目。
2.为什么用slider而不用scroller?slider组件用于在一个页面中展示多个图片,在前端,这种效果被称为轮播图。它支持任意类型的Weex组件作为其子组件,而且它有一个专属子组件—indicator用于显示轮播图指示器效果,这个indicator必须充当slider组件的子组件使用才有效果。
3.@change="onchange",slider的事件,当轮播索引改变时,触发该事件。
4.<div v-for="(v,i) in items">
        <div v-for="(item,k) in v" >
        </div>
   </div>
    循环创建每一个item,注意v-for语句的写法。如果只是一重循环直接v-for="item in items"就可以了,其中item就是items里面的每一个元素,在其子组件中可以直接使用item赋值。
5.indicator作为子组件之间写在slider里面就可以了,他会自动随着slider的滑动而改变指示器。
6.text组件只能包含文本值,你可以使用 {{}} 标记插入变量值作为文本内容。不支持子组件。

首页中间视图


首页中间的view.png
<template>
    <div class="container">
        <!--左边view-->
        <div class="leftView">
            <image :src="leftViewTopImage" style="width: 240px;height: 60px;margin-top: 40px"></image>
            <image :src="leftViewMiddleImage" style="width: 240px;height: 120px"></image>
            <text style="color: darkgray;font-size: 34px">探路组碳烤鱼</text>
            <div style="flex-direction: row">
                <text style="color: cyan;font-size: 28px">¥9.5</text>
                <text style="color: darkorange;background-color: khaki;font-size: 28px">再减3元</text>
            </div>
        </div>

        <!--右边view-->
        <div class="rightView">
            <div class="rightViewTopView">
                <div>
                    <text style="color: darkorange;font-size: 32px;margin-left: 20px">天天特价</text>
                    <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">特惠不打烊</text>
                </div>
                <image :src="rightViewTopImage" style="width: 150px;height: 120px;"></image>
            </div>
            <div class="rightViewBottomView">
                <div>
                    <text style="color: crimson;font-size: 32px;margin-left: 20px">一元吃</text>
                    <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">一元吃美食</text>
                </div>
                <image :src="rightViewBtttomImage" style="width: 150px;height: 120px"></image>
            </div>
        </div>
    </div>
</template>

代码注解:
此处没啥好说的,常规UI布局,注意Flexbox布局技巧。

首页促销视图


首页促销视图.png
<template>
    <div class="container">
        <!--上面的view-->
        <div class="topView">
            <div class="topLeftView">
                <text style="color: magenta;font-size: 38px;margin-left: 30px;margin-top: 25px">最高立减25</text>
                <text style="color: #717171;font-size: 32px;margin-left: 30px">美味享不停,赶快行动吧</text>
            </div>
            <div class="topRightView">
                <image :src="topViewRightImage" style="width: 250px;height: 120px;"></image>
            </div>
        </div>
        <div class="bottomView">
            <!--左边view-->
            <div class="leftView">
                <div class="leftViewTopView">
                    <div>
                        <text style="color: darkorange;font-size: 32px;margin-left: 20px">1元肯德基</text>
                        <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">1元能吃肯德基</text>
                    </div>
                    <image :src="leftViewTopImage" style="width: 150px;height: 120px;"></image>
                </div>
                <div class="leftViewBottomView">
                    <div>
                        <text style="color: crimson;font-size: 32px;margin-left: 20px">4月开春大促</text>
                        <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">领21元红包</text>
                    </div>
                    <image :src="leftViewBtttomImage" style="width: 150px;height: 120px"></image>
                </div>
            </div>

            <!--右边view-->
            <div class="rightView">
                <div class="rightViewTopView">
                    <div>
                        <text style="color: darkorange;font-size: 32px;margin-left: 20px">新用户专享</text>
                        <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">小长假美美哒</text>
                    </div>
                    <image :src="rightViewTopImage" style="width: 150px;height: 120px;"></image>
                </div>
                <div class="rightViewBottomView">
                    <div>
                        <text style="color: crimson;font-size: 32px;margin-left: 20px">一元抢吧</text>
                        <text style="color: #717171;font-size: 32px;margin-left: 20px;margin-top: 10px">爆品抢到手软</text>
                    </div>
                    <image :src="rightViewBtttomImage" style="width: 150px;height: 120px"></image>
                </div>
            </div>
        </div>
    </div>
</template>

首页购物中心


首页购物中心.png
购物中心代码:
<template>
    <div class="container">
        <homeBottomCommonCell
          :rightViewBtttomImage = rightViewBtttomImage
          leftTitle = "购物中心"
          rightTitle = "全部4家"
        ></homeBottomCommonCell>

        <scroller class="scrollerClass" scroll-direction="horizontal" >
            <home-shop-center-item v-for="obj in homeShopCenterData.data"
                                   :imageStr = obj.img
                                   :title = obj.name
                                   :tagTitle = obj.showtext.text
            ></home-shop-center-item>
        </scroller>
    </div>
</template>
<script>
    var globalDefine = require('../../globalDefine');
    var homeBottomCommonCell = require('./homeBottomCommonCell');
    var homeShopCenterItem = require('./homeShopCenterItem');
    var homeShopCenterData = require('../resource/homeShopCenter');
    export default {
        data () {
            return{
                rightViewBtttomImage:globalDefine.apiUrl.resUrl + 'gw.png',
                homeShopCenterData:homeShopCenterData,
            }
        },
        components:{
            homeBottomCommonCell,
            homeShopCenterItem
        }
    }
</script>
homeBottomCommonCell组件代码:
<template>
    <div class="container">
        <div class="innerView">
            <div class="leftView">
                <image :src="rightViewBtttomImage" style="width: 50px;height: 50px;margin-left: 20px"></image>
                <text style="color: black;font-size: 34px;margin-left: 15px;">{{leftTitle}}</text>
            </div>
            <div class="rightView">
                <text style="color: #717171;font-size: 28px;">{{rightTitle}}</text>
                <image :src="rightarrow" style="width: 20px;height: 25px;margin-left: 10px;margin-right: 20px"></image>
            </div>
        </div>
    </div>
</template>
<script>
    var globalDefine = require('../../globalDefine');
    export default {
        props:{
            rightViewBtttomImage:'',
            leftTitle: '',
            rightTitle: '',
        },
        data () {
            return{
                rightarrow:globalDefine.apiUrl.resUrl + 'icon_cell_rightarrow.png',
            }
        }
    }
</script>
homeShopCenterItem组件代码:
<template>
    <div class="container">
        <image :src="imageStr" style="width: 300px;height: 200px;margin-top: 20px;border-radius: 5px"></image>
        <text style="color: black;font-size: 34px;margin-left: 15px;margin-top: 10px">{{title}}</text>
        <text style="color: black;font-size: 34px;position: absolute;top: 150px;background-color: darkorange;color: white;padding-left: 5px;padding-right: 5px">{{tagTitle}}</text>
    </div>
</template>

<script>
    export default {
        props:{
            imageStr:'',
            title:'',
            tagTitle:''
        },
        data () {
        }
    }
</script>

代码注解:
1.头部购物中心由于在很多地方都可以用到,且样式差不多,所以可以单独抽出一个组件来用<homeBottomCommonCell>,这里我觉得有必要注意一下正向传值。正向传值的变量名写在组件的props里面,使用的时候需先在script里面引用该组件,然后在components里声明该组件,然后就可以当做正常标签一样来使用了。给组件赋值的语句写在第一个尖括号里面(如果赋的值是一个变量,则需在前面加:,比如
:rightViewBtttomImage = rightViewBtttomImage)
2.内容部分用横向滚动的<scroller>,里面每个item采用循环创建的方式填充,所以需写一个item组<homeShopCenterItem>。
3.设置<scroller>的滚动方向,scroll-direction定义了 scroller 的滚动方向,样式表属性 flex-direction 定义了 <scroller >的布局方向,两个方向必须一致。当需要一个水平方向的 <scroller >时,使用 scroll-direction:horizontal 和 flex-direction: row。当需要一个竖直方向的 <scroller >时,由于这两个值均是默认值,这两个值可以不设置。
4.拿本地json数据,在本地建好json文件,在用的时候引用即可(var homeShopCenterData = require('../resource/homeShopCenter')),注意json的格式和路径是否正确。
5.引用路径:看想引用的文件是否和所在文件处在同一文件夹下,如果在是则只需一个点,如果不在则需两个点然后接对应的文件夹名称。

首页热门频道


首页热门频道.png

这里由于没有什么新的东西就不贴代码了,挺占地方的,注意一下布局技巧,需要的去下载下来看就可以了。
首页猜你喜欢


首页猜你喜欢.png
<template>
    <div class="container">
        <homeBottomCommonCell ref="homeBottomCommonCell"
                :rightViewBtttomImage = rightViewBtttomImage
                leftTitle = "猜你喜欢"
        ></homeBottomCommonCell>

        <list class="list" ref="guessList" show-scrollbar='false'  v-bind:style="{height:listHeight}" offset-accuracy="10" @appear="onappear" @scroll="scrollHandler" @scrollstart="scrollStart">
            <cell class="cell" v-for="(shop, index) in lists" ref="item">
                <div class="panel">
                    <div style="flex-direction: row">
                        <div class="panelLeftView">
                            <image :src="dealWithImgUrl(shop.imageUrl)" style="width: 240px;height: 180px;margin-left: 20px"></image>
                        </div>
                        <div class="panelRightView">
                            <div style="flex-direction: row;justify-content: space-between">
                                <text style="color: black;font-size: 28px;margin-right: 20px;lines:1;text-overflow:ellipsis;flex: 0.8">{{shop.title}}</text>
                                <text style="color: black;font-size: 28px;flex: 0.3">{{shop.topRightInfo}}</text>
                            </div>
                            <text style="color: darkgray;font-size: 28px;margin-right: 20px;margin-top: 10px;lines:2;text-overflow:ellipsis">{{shop.subTitle}}</text>
                            <div style="flex-direction: row;justify-content: space-between;margin-top: 10px">
                                <text style="color: crimson;font-size: 28px">{{shop.subMessage}}</text>
                                <text style="color: black;font-size: 28px;margin-right: 20px">{{shop.bottomRightInfo}}</text>
                            </div>
                        </div>
                    </div>
                    <div style="flex: 1;height: 1px;background-color: #c4c4c4;margin-top: 20px"></div>
                </div>
            </cell>
        </list>
    </div>
</template>
<script>
    var globalDefine = require('../../globalDefine');
    var homeBottomCommonCell = require('./homeBottomCommonCell');
    var stream = weex.requireModule('stream');
    const modal = weex.requireModule('modal');
    const dom = weex.requireModule('dom');

    export default {
        data () {
            return{
                rightViewBtttomImage:globalDefine.apiUrl.resUrl + 'cnxh.png',
                lists: [],
                listHeight:'',
            }
        },
        components:{
            homeBottomCommonCell,
        },
        created(){
            const self = this;
            let url = 'http://api.demo.com/group/v2/recommend/homepage/city/20?userId=160495643&userid=160495643&__vhost=api.mobile.demo.com&position=23.134643%2C113.373951&movieBundleVersion=100&utm_term=6.6&limit=40&wifi-mac=64%3A09%3A80%3A10%3A15%3A27&ci=20&__skcy=X6Jxu5SCaijU80yX5ioQuvCDKj4%3D&__skua=5657981d60b5e2d83e9c64b453063ada&__skts=1459731016.350255&wifi-name=Xiaomi_1526&client=iphone&uuid=5C7B6342814C7B496D836A69C872202B5DE8DB689A2D777DFC717E10FC0B4271&__skno=FEB757F5-6120-49EC-85B0-D1444A2C2E7B&utm_content=5C7B6342814C7B496D836A69C872202B5DE8DB689A2D777DFC717E10FC0B4271&utm_source=AppStore&utm_medium=iphone&version_name=6.6&wifi-cur=0&wifi-strength=&offset=0&utm_campaign=AgroupBgroupD100H0&__skck=3c0cf64e4b039997339ed8fec4cddf05&msid=0FA91DDF-BF5B-4DA2-B05D-FA2032F30C6C2016-04-04-08-38594';
            this.getNews(url,res => {
                this.lists = res.data.data;
                this.listHeight = res.data.data.length * 220 + 3 +'px';
                // modal.toast({message:res.ok,duration:1.0});
            });
        },
        methods: {
            getNews(url,callback){
                return stream.fetch({
                    method:'GET',
                    type:'json',
                    url:url
                },callback);
            },
            // 处理图像的尺寸
            dealWithImgUrl(url){
                if (url.search('w.h') == -1){ // 没有找到,正常返回
                    return url;
                }else{
                    return url.replace('w.h', '240.180');
                }
            }
        }
    }
</script>

代码注解:
1.公共部分采用上面一样的办法,内容部分使用<list>组件。
2.数据来源,这部分的数据来源自网络,因此网络请求是这块比不可少的,好在Weex本身提供网络请求模块(fetch)。首先引入stream(var stream = weex.requireModule('stream')),在methods周期函数里定义getNews方法,然后created周期函数里调用getNews,传入url,这里fetch请求如果在网页端可能会出现跨域的问题,但是在真机就不会。
3.<list>的高度,本来想等<list>设置内容并布局成功后拿到内容高度来设置<list>的高度,然而想法是美好的现实是残酷的。搞了半天没成功,最后只能用等高的cell,用cell的高度乘以数量来设置list高度(有哪位大佬知道怎么做麻烦告诉我下哈)。由于这里是动态的改变组件的高度,所以v-bind就必不可少了。在list标签里面绑定高度样式(v-bind:style="{height:listHeight}"),随后在网络数据请求成功后计算好总高度赋值给listHeight就可以动态改变高度了。
4.<list>的滚动事件,在<list>标签里写上需要监听的事件名称(@scroll="scrollHandler"),然后在methods周期函数重写scrollHandler方法,该方法有一个参数,里面有滚动时的一些属性值。
5.标签里面函数调用,由于数据返回时图片的链接需要我们处理,所以得写一个专门处理图片链接的函数(dealWithImgUrl),在设置image的src是调用,调用格式(:src="dealWithImgUrl(shop.imageUrl)")。

刷新控件


刷新控件.png
<refresh class="refresh" @refresh="onrefresh" :display="refreshing ? 'show' : 'hide'">
   <text class="indicator-text">Refreshing ...</text>
   <loading-indicator class="indicator"></loading-indicator>
</refresh>
methods:{
            onrefresh (event) {
                if (this.refreshing == false){
                    modal.toast({ message: 'Refreshing', duration: 0.2 })
                    this.refreshing = true
                    setTimeout(() => {
                        this.refreshing = false;
                    }, 500)
                }
            }
        }

代码注解:
1.Weex提供了一个刷新组件<refresh>,<refresh> 是 <scroller>、<list>、<hlist>、<vlist>、<waterfall> 的子组件,只能在被它们包含时才能被正确渲染。
2.<refresh>组件里面可以添加子组件,例如可以添加<text>。

商家

商家.png
<template>
    <div class="container">
        <!--导航栏-->
        <div class="navgationContainer">
            <div class="navigation">
                <image :src="mapIcon" style="position: absolute;width: 60px;height:60px;left: 25px;top: 13px"></image>
                <text class="pageTitle">商家</text>
                <image :src="searchIcon" style="position: absolute;width: 60px;height:60px;right: 25px;top: 10px"></image>
            </div>
        </div>
        <!--网页-->
        <web class="webClass" :src="detailUrl" @pagestart="onPageStart" @pagefinish="onPageFinish" @error="onError">
        </web>
    </div>
</template>

代码注解:
1.这个页面很简单,一个<web>组件搞定,主要是熟悉Weex的web组件是使用,<web> 不支持任何嵌套的子组件,并且必须指定 width 和 height 的样式属性,否则将不起作用。
2.要加载的网页内容的 URL。必须指定一个基于 bundle URL 的相对 URL,它将被重写为真实资源 URL(本地或远程)。
3.支持公共事件,绑定自己特有的事件pagestart、pagefinish、error。详情请查阅<web>组件的使用

我的

我的.png
<template>
    <div class="container">
        <!--头部view-->
        <mineHeaderView></mineHeaderView>
        <scroller style="background-color: #e8e8e8">
            <div>
                <mineCommonCell
                        :leftIcon = "globalDefine.apiUrl.resUrl + 'collect.png'"
                        leftTitle = "我的订单"
                        rightTitle = "查看全部订单"
                        rightIcon= ""
                ></mineCommonCell>
                <mineOrderCell></mineOrderCell>
            </div>
            <div>
                <mineCommonCell style="margin-top: 20px"
                        :leftIcon = "globalDefine.apiUrl.resUrl + 'draft.png'"
                        leftTitle = "我的钱包"
                        rightTitle = "账户余额:¥100"
                        rightIcon= ""
                ></mineCommonCell>
                <mineCommonCell
                        :leftIcon = "globalDefine.apiUrl.resUrl + 'like.png'"
                        leftTitle = "抵用券"
                        rightTitle = "0"
                        rightIcon= ""
                ></mineCommonCell>
            </div>
        </scroller>
    </div>
</template>

更多

更多.png

        页面就不多说了,这里重点讲一下怎么使用原生控件来自定义组件。<switch>组件已不推荐业务上使用,因为各端实现不一致且端上定制能力较弱,不适合作为内置组件实现,因此建议开发者通过 weex 上层能力自行定制该组件。
        1.先来看原生部分的代码:

//创建一个类,一定要继承自WXComponent
#import "WXComponent.h"
@interface PeterSwitch : WXComponent
@end

#import "PeterSwitch.h"
@interface PeterSwitch ()
//自定义组件的属性,可以在weex里面绑定修改
@property (nonatomic, assign) BOOL isOn ;
@property (nonatomic, strong) UIColor *tintColor ;
@property (nonatomic, strong) UIColor *onTintColor ;
@property (nonatomic, strong) UIColor *thumbTintColor ;
@end

@implementation PeterSwitch
//一个 component 默认对应于一个 view,如果未覆盖 loadView 提供自定义 view, 会使用 WXComponent 基类中的 WXView, WXView 是继承自 UIView 的一个派生 view。
- (UIView *)loadView {
    UISwitch *mySwitch = [[UISwitch alloc]init];
    [mySwitch addTarget:self action:@selector(switchAction:) forControlEvents:UIControlEventValueChanged];
    return mySwitch;
}

//对组件 view 需要做一些配置,比如设置 delegate, 可以在 viewDidLoad 生命周期做,如果当前 view 没有添加 subview 的话,不要设置 view 的 frame,WeexSDK 会根据 style 设置。
- (void)viewDidLoad {
    UISwitch *mySwitch = ((UISwitch *)self.view);
    //tintColor 关状态下的背景颜色
    mySwitch.tintColor = _tintColor;
    //onTintColor 开状态下的背景颜色
    mySwitch.onTintColor = _onTintColor;
    //thumbTintColor 滑块的背景颜色
    mySwitch.thumbTintColor = _thumbTintColor;
    mySwitch.on = _isOn;
}

//支持自定义事件,点击switch发送事件,可以带参数字典,字典将传导weex页面
- (void)switchAction:(UISwitch *)mySwitch{
    [self fireEvent:@"onSwitch" params:@{@"isSwitchOn":@(mySwitch.isOn)} domChanges:nil];
}

//支持自定义属性,在 viewDidLoad 中设置属性
- (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]) {
        
        if (attributes[@"tintColor"]) {
            _tintColor = [WXConvert UIColor:attributes[@"tintColor"]];
        }
        if (attributes[@"onTintColor"]) {
            _onTintColor = [WXConvert UIColor:attributes[@"onTintColor"]];
        }
        if (attributes[@"thumbTintColor"]) {
            _thumbTintColor = [WXConvert UIColor:attributes[@"thumbTintColor"]];
        }
        if (attributes[@"isOn"]) {
            _isOn = [WXConvert BOOL:attributes[@"isOn"]] ;
        }
    }
    return self;
}

//属性更新
- (void)updateAttributes:(NSDictionary *)attributes
{
    if (attributes[@"tintColor"]) {
        _tintColor = [WXConvert UIColor:attributes[@"tintColor"]];
        ((UISwitch *)self.view).tintColor = _tintColor;
    }
    if (attributes[@"onTintColor"]) {
        _onTintColor = [WXConvert UIColor:attributes[@"onTintColor"]];
        ((UISwitch *)self.view).onTintColor = _onTintColor;
    }
    if (attributes[@"thumbTintColor"]) {
        _thumbTintColor = [WXConvert UIColor:attributes[@"thumbTintColor"]];
        ((UISwitch *)self.view).thumbTintColor = _thumbTintColor;
    }
    if (attributes[@"isOn"]) {
        _isOn = [WXConvert BOOL:attributes[@"isOn"]];
        ((UISwitch *)self.view).on = _isOn;
    }
}
@end

//记得要在manager里面注册一下
[WXSDKEngine registerComponent:@"PeterSwitch" withClass:NSClassFromString(@"PeterSwitch")];

        2.Weex部分的代码:

<PeterSwitch class="PeterSwitch" v-if="isSwitch" @onSwitch="onSwitch" tintColor="#0088fb" onTintColor="#bfed5a" :thumbTintColor=thumbTintColor :isOn=isOn></PeterSwitch>
<div v-else style="flex-direction: row">
   <text v-if="renderRightTitle()" style="color: #404040;font-size: 34px;margin-left: 25px;">{{rightTitle}}</text>
   <image :src="arrowImg" style="width: 22px;height: 35px;margin-right: 25px;margin-left: 15px;margin-top: 3px"></image>
</div>

<script>
        methods: {
            onSwitch (e) {
                this.isOn = e.isSwitchOn;
                var r = Math.floor(Math.random()*256);
                var g = Math.floor(Math.random()*256);
                var b = Math.floor(Math.random()*256);
                var color = '#'+r.toString(16)+g.toString(16)+b.toString(16);
                this.thumbTintColor = color;
            }
        }
</script>

代码注解:
1.v-if ,v-else,这是vue的条件语句,如果v-if的条件判断成立就创建if的组件,否则创建v-else组件。
2.给自定义组件< PeterSwitch > 绑定onSwitch方法,在methods里实现,实现方法里改变属性thumbTintColor的值,可以实现原生组件的属性更改。

作者简介: 就职于甜橙金融信息技术部,负责iOS前端开发工作。对于业内的新技术比较感兴趣,在我看来,新的东西必然是在旧的基础上优化而来,这对我们提高开发效率很有帮助。

如需转载,请注明出处,谢谢~~~

推荐阅读更多精彩内容