《腾讯大家》小程序开发总结

腾讯大家产品背景

《腾讯大家》是公司推出的中文互联网专栏写作服务产品。由于寻找有效信息的成本是非常大的,一些真正具有传播价值的内容,却往往淹没于信息洪流之中。如何将最有价值的信息以最快的速度呈现给用户,正是《大家》产品设计的初衷。《大家》更关注互联网用户更深入、更持久的思考与表达。我们希望呈现给用户的,是经得起时间考验的文章,是时代最前沿的思想。它的表现,可能是一个专栏、一部电子书、一个属于个人的频道,甚至是一款小程序。

所谓“大家”,意在集华语写作之大家手笔,为中文互联网用户提供最具魅力的经典文字,打造最有力量的互联网言论阵地、最有价值的网络阅读品牌。

腾讯大家小程序使用场景

腾讯大家小程序根植于微信小程序功能,与公众号精密结合运营,通过传播引发网友关注,扩大腾讯大家内容的出口。

腾讯大家是腾讯内容出品部的优质栏目,内容以质量取胜,每日产出数篇精品内容,因此,腾讯大家更加注重每一篇文章的传播效率以及传播速度。 在小程序之前,腾讯大家内容主要通过单篇推荐的方式在腾讯新闻客户端、微信公众号等途径传播,而PC端的聚合页面(栏目、作者文章列表等)难以适应目前移动传播的形式,因此访问数据量较少,历史内容价值缺失。

基于聚合页面的传播需求,腾讯大家小程序解决了在移动端聚合及快速查找历史内容的需求。绑定小程序以后,在推送单篇文章时,可以配合推送作者文章列表、相关文章列表等定制页面。 用户可以通过微信文章入口、小程序收藏等功能,可以随时随地查看腾讯大家的最新内容及历史内容。

解决的实际问题

1.解决了基于移动端的内容聚合、历史内容查询,便于用户浏览;

2.增加了作者(栏目)关注定制(收藏感兴趣的作者/栏目),使得用户能持续接收高质量作者的内容,也便于产品侧对于用户关注的把握更精准;

3.解决了内容聚合页面与微信文章的互通,将特定专题的文章聚合页通过微信分享后,可以让用户快速地浏览到感兴趣专题的内容,也能够让历史文章得到再次浏览和传播;

4.增加了功能性H5的展示(比如在首页最上方的banner 大家之选内的开放编辑部玩法,号召用户参与内容制作),可以通过H5发起多样的用户活动,让产品与用户的交互更丰富;

5.将人工推荐变为用户主动查阅。腾讯大家的每天精品内容,通过web发出后,需要去各个媒体平台进行推荐(例如腾讯新闻客户端、QQ minisite、QQ.com首页,微信公众号),而且昨天的文章被推荐之后,今天会被新文章取代,网友们只能看到今天的新文章,而通过小程序的交互,用户点击最新的文章之后也查阅到历史文章,使得沉淀的内容得到再次曝光。

腾讯大家小程序呈现内容包含:首页首页聚合(tab)、作者列表(tab)、专栏聚合(tab)、个人中心(tab)、内容底层、作者底层、栏目底层、活动底层(战队底层,专题底层)。

一、功能分析

1.1 多端数据共享

腾讯大家小程序要与大家官网(http://dajia.qq.com)呈现给用户内容保持一致,新的开发在不影响原有内容原创平台(http://ninja.webdev.com)的基础上,增加小程序用户中心,开发用户对作者(栏目)的收藏、对文章评分、对文章历史浏览记录等功能。

感谢Ninja团队在开发过程中的支持,感谢saturnzhao(赵冬明)、杰哥jillywang(王杰)对接口数据及标准文档的指导。

1.2 首页精选详情内容展示

三处小技巧:

1.为了保证精选列表和web站点数据一致,将列表json数据缓存,这样做的好处1是避免了多次重复请求,二是缓存了的数据可以根据实际运营要求做数据插入,满足日常运营。

2.善用image组件,组件mode 有 13 种模式,其中 4 种是缩放模式,9 种是裁剪模式。利用组件特性,可以让原有内容配图完美的展现。

3.列表下拉加载过程利用数组特性concat进行数组拼接(利用小程序数据驱动特征)。

<image class="choice-image" mode="aspectFill" src="{{item.n_image}}"></image>

mode=”aspectFill”模式纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。

Api.fetchGet(dajaMorEchoiceUrl, (err, res) => {
    ...
    vm.$set({ firstData: vm.$data().firstData.concat(res.data) })
    ...
});

父组件注册onReachBottom方法使用下拉加载后,将每次请求的返回数据使用concat与原数据拼接。

1.3 内容底层展示

小程序的核心是一个响应的数据绑定系统,所以我们要展示一篇资讯详情,需要有一份数据,通过这份数据来判断这篇资讯是要渲染段落、表格、列表、图片、还是视频。

腾讯大家的内容原创发布系统对底层的文章属性有良好的标准json数据输出能力。举例一篇正文部分输出:

{tag: "text", value: "对于不认识Ayawawa的人,很难用一两句话介绍她的理论。"}, {tag: "text", value: "简单来说,她是个情感网红,真名叫杨冰阳。"},
{tag: "text", value: "对于不认识Ayawawa的人,很难用一两句话介绍她的理论。"}
{tag: "text", value: "前些天她接受姜思达采访的视频播出后, 激进派女权主义者们对她开始了新一轮的批评:说她跪舔“男权”、固化性别不平等、“物化”婚恋关系、“直男癌”。"}
{tag: "text", value: "但我觉得Ayawawa不应该被骂得那么惨,而且重点是:她被骂的角度也不够准。"}
{tag: "image", title: "", src: "//img1.gtimg.com/cul/pics/hv1/92/70/2270/147624692.jpg"}
{tag: "title", level: "H2", value: "婚姻对不同阶层的女性,根本不是一回事"}

对应展示模板:

<block wx:if="{{detail.length > 0}}">
  <block wx:for="{{detail}}"  wx:key="item">
      <view wx:if="{{item.tag == 'title' || item.tag == 'text'}}" class="{{item.tag}} {{item['level'] ? 'h2' : ''}}">{{item.value}}</view>
    <block wx:if="{{item.tag == 'image'}}">
        <image class="{{item.tag}}" mode="widthFix" src="{{item.src}}"></image>
        <view class="imgalt">{{item.title}}</view>
    </block>
  </block>
</block>
<block wx:else>
  <view class="p"></view>
</block>

另外,常规模式下资讯内容从技术角度看是带有html标签的富文本内容。在小程序中是不能将这些带有html标签的富文本内容直接展示的。

1.4 作者展示

腾讯大家作者有近千名,而作者的新增并不频繁,在数据端做以下处理:

1.定时抓取已有作者全部数据,然后进行缓存,输出带分页参数接口。 2.在拉取数据时传入用户id,检测用户与作者对应关系(如收藏,打分,分别对应的栏目等。

在作者列表展示上做如下处理:

1.首次进入作者页加载数名作者数据。 2.上拉加载做分页传参处理。 3.重新回到作者页面会刷新用户收藏数据(包含已经收藏及列表中的标记)。

响应到的数据格式:

作者模板结构:

 <view class="oloading" wx:if="{{ready}}" style="height:{{wHeight}}px">
    </view>
    <view class="body" wx:if="{{body}}">
        <view class="num_index" wx:if="{{colAutData.length !== 0}}">
            我的收藏
        </view>
        <view class="box_author" wx:if="{{colAutData.length !== 0}}">
            <view class="no_author" wx:if="{{!dataReady}}">暂无收藏</view>
            <block wx:for="{{colAutDatas}}" wx:key="item" wx:if="{{colAutData.length !== 0}}">
                <view class="colltloading" wx:if="{{!dataReady}}"></view>
                <view class="num_author" bindtap="onAuthor" data-id="{{item.id}}">
                    <image class="num-image" src="{{item.image}}"></image>
                    <view class="num_name">{{item.name}}</view>
                    <view class="box_start {{item.have == 1 ? 'on':'out'}}" catchtap="onRecommStart" data-id="{{item.id}}" data-parentid="{{item.first_letter}}" data-have="{{item.have}}"></view>
                </view>
            </block>
        </view>
        <block wx:for="{{authors}}" wx:key="item">
            <view class="num_index">
                {{index}}
            </view>
            <view class="box_author">
                <block wx:for="{{item}}" wx:key="author">
                    <view class="num_author" bindtap="onAuthor" data-id="{{item.id}}">
                        <image class="num_image" src="{{item.image}}"></image>
                        <view class="num_name">{{item.name}}</view>
                        <view class="box_start {{item.have == 1 ? 'on':'out'}}" catchtap="onRecommStart" data-id="{{item.id}}" data-parentid="{{item.first_letter}}" data-have="{{item.have}}"></view>
                    </view>
                </block>
            </view>
        </block>
    </view>

下拉加载时数据数组处理方法

extendKey:function(array) {
    let arr = [];
    for(let p in array){
        arr.push(p);
    };
        return arr;
},
getAuthorDataMore: function() {
    ...
    wx.showNavigationBarLoading();
    Api.fetchPost(Api.getAllAuthor, {userid: user.openid, perpage: 20, page: (vm.$data().page += 1) }, (err, res) => {        
         if (res.ret == 200) {
            let list = res.data;
            let alist = vm.$data().
            let keyArr = that.extendKey(list);authors;
            let akeyArr = that.extendKey(alist);
            keyArr.map(item => {
                akeyArr.map(eitem => {                    

                    if (item == eitem) {
                        alist[eitem] = alist[eitem].concat(list[item]);
                        keyArr.shift();
                    } else {}
                });
                keyArr.map(_item => {
                    alist[_item] = list[_item]
                })
            });
            vm.$set({ authors: alist });
            wx.hideNavigationBarLoading();
        } else {}
    });
    ...
}

1.5 用户登录态获取

引用小程序官方文档的登录流程图

流程简而言之:

1.在小程序上通过wx.login()获取code。

2.将code传到自己的服务器,然后将小程序的secret和appid与微信服务器交换openid和session_key。

3.将session_key加上随机数生成sessionId,然后openid和session_key存在session里。

4.小程序将sessionId存起来,每次访问都带上这个sessionId。

5.小程序请求登陆区内接口,通过wx.checksession检查登陆态,如果失效重新走上登录流程,否则待上3rd_session到后台进行登陆验证。

为什么有用户登录态:

1.小程序有以用户为个人中心的功能应用,比如作者栏目收藏、关注等。 2.用户敏感数据,只对用户可见。

获取用户数据示例:

getUsrAppId: function() {
    let user = wx.getStorageSync('user') || {};
    let userInfo = wx.getStorageSync('userInfo') || {};
    wx.getUserInfo({
        success: function(res) {
            let objz = {};
            objz.avatarUrl = res.userInfo.avatarUrl;
            objz.nickName = res.userInfo.nickName;
            wx.setStorageSync('userInfo', objz); 
            //userInfo  
        },
        fail: function() {
            console.log('用户拒绝');
            wx.setStorageSync('allow', { 'user': 'notallow' });
        }
    });
    wx.login({
        success: function(res) {
            if (res.code) {
                Api.fetchPost(Api.getOpenid, { code: res.code }, (err, res) => {
                    let _userObj = JSON.parse(res.data);
                    let obj = {};
                    obj.openid = _userObj.openid;
                    obj.expires_in = _userObj.expires_in;
                    wx.setStorageSync('user', obj);
                });
            } else {
                console.log('获取用户登录态失败!' + res.errMsg)
            }
        }
    });    
    if ((!user.openid || (user.expires_in || Date.now()) < (Date.now() + 600)) && (!userInfo.nickName)) {
        ...
    };
}

1.6 用户信息获取

因为文章的评分、个人中心的头像和昵称都需要用到用户信息。所以大家小程序在第一次打开后会自动弹出授权窗口。当用户授权后,信息缓存在Storage里,缓存的过期时间由具体的功能场景来控制。

1.7 收藏功能(含作者及栏目收藏)

首先将含有收藏功能的地方标注出来:

1处收藏按钮为作者收藏与栏目收藏,当用户按下按钮后会变为已收藏、再按下去则为取消收藏。在开发过程中,主要是对按钮状态的判断

模板结构:

<view class="writer-collet {{have == false ? 'edd':'cdd'}}" bindtap="writerColletButton" data-have="{{have}}" data-id="{{load.wid}}" data-parentid="{{author.first_letter}}" hover-class="writer-collet-hover"><view class="add"  wx:if="{{!have}}"></view>{{allReady}}</view>

处理逻辑:

ColletButton: function(e) {
    let id = e.currentTarget.dataset.id;
    let have = e.currentTarget.dataset.have;
    let parentid = e.currentTarget.dataset.parentid;
    let user = wx.getStorageSync('user') || {};
    wx.showLoading({
        title: '正在处理',
    });
    if (!have) {
        Api.fetchPost(Api.collection, { userid: user.openid, id: id, type: 1, have: 1 }, (err, res) => {
            if (res.ret == 200) {
                wx.hideLoading();
                vm.$set({ allReady: "已经收藏", have: true })
            } else {...}
        });
    } else {
        Api.fetchPost(Api.collection, { userid: user.openid, id: id, type: 1, have: 0 }, (err, res) => { 
                if (res.ret == 200) {
                wx.hideLoading();
                vm.$set({ allReady: "收藏", have: false })
            } else {...}
        });
    };
}

2处3处的收藏逻辑基本相同,只是3会判断是否已经收藏此作者

模板结构:

<view class="box_start {{item.have == 1 ? 'on':'out'}}" catchtap="onRecommStart" data-id="{{item.id}}" data-parentid="{{item.first_letter}}" data-have="{{item.have}}"></view>

收藏按钮处理方法:

onRecommStart:function(e){
    let that = this;
    let user = wx.getStorageSync('user') || {};
    let id = e.currentTarget.dataset.id;
    let parentid = e.currentTarget.dataset.parentid;
    let have = e.currentTarget.dataset.have;
    let list = vm.$data().authors;    //新增
    let alist = {"pid":parentid,"aid":id}
    wx.showLoading({
      title: '正在处理',
    });    
    if (!have){
        Api.fetchPost(Api.collection,{userid:user.openid,id:id,type:1,have:1}, (err, res) => {            
    if (res.ret == 200){
                wx.hideLoading();
            } else {...}
        });
    }else{
        Api.fetchPost(Api.collection,{userid:user.openid,id:id,type:1,have:0}, (err, res) => {            
    if (res.ret == 200){
                wx.hideLoading();
            } else {...}
        });
    }
    //此处找到操作的元素位置
    list[parentid].map(item => {       
       if(item.id == id){
           item.have  = !item.have;
       } 
      return item
    });
    vm.$set({authors:list});
    setTimeout(function(){
        //此处为刷新顶部收藏栏数据
        that.getColAutData();
    },1000)
},

4处为栏目收藏区域,使用了scroll-view组件,左右滑动方式方便用户查看自己已经收藏的栏目。需要注意的是需要在小程序onLoad或onShow时,取到栏目的个数,再计算组件整体宽度。

模板:

<scroll-view scroll-x class="scrollcolumns">
    <view class="scroll-view" style="width:{{wWidhth}}rpx">
            <view class="first"></view>
        <block wx:for="{{columnColtDatas}}" wx:key="item">
            ....
        </block>
    </view>
</scroll-view>

逻辑(请注意wWidhth值的计算):

...
colData.data.map(item => {
    Api.fetchGet(Api.column + item, (err, res) => {        
        if (res.data) {
            columnAutData.push(res.data.channel);            
            if (columnAutData.length == colData.data.length) {
                vm.$set({ columnColtDatas: columnAutData, wWidhth: (colData.data.length * 694) + 44, dataReady: true });
            };
        };
    });
});
...

1.8 个人中心功能

个人中心主简单呈现个人信息、用户收藏的作者/栏目统计、用户已浏览的文章记录。值得注意的是,页面onShow周期时需要刷新用户的收藏统计信息。

1.9 浏览记录功能

浏览记录模块在个人中心页面中:

1.数据来源为用户浏览文章时的上报,服务端做时间戳记录(浏览去重)等工作。

2.在开发列表加载逻辑时,需要注意验证一下拿到数据的一致性。因为运营端可能已经删掉某篇文章,而用户的上报的浏览记录又是过去时,所以对于这种情况的发生,需要在数据字段做标记、或者在删稿流程上形成通知机制。

1.10 评论功能

因为信息审核和登录态的问题,腾讯大家小程序评论功能折中选择调用【珊瑚评论】记录接口,仅做评论内容展示。

1.11 分享功能(含首页)

分享功能都在onShareAppMessage()函数里,不同于右上角分享按钮,如果在页面中某个地方添加分享功能,需要button绑定属性open-type=”share”。除此之外,还需要相关分享属性如:

<button class="choice-share-b" catchtap="onShareAppMessage" open-type="share" data-title="{{item.title}}" data-tid="{{item.tid}}"></button>

1.12 评分功能

评分功能在文章底层页中,用户对文章的评分操作会形成:

1.这一篇文章的评分数据依据。 2.这一篇文章在栏目的栏目评分依据 3.这一篇文章作者的评分依据

在开发中,评分功能由多个功能函数组成,大致可以分为渲染、用户操作、服务器操作回调、还有数据换算等一些函数方法。

1.13 海报生成功能

此功能报用于单篇文章及作者朋友圈传播海报生成。

生成功能需要注意以下:

1.海报的生成使用小程序canvas组件(canvas功能及api能力详见官网文档)。

2.对于图片素材,例如背景,二维码等图标,需要wx.downloadFile()函数支持(详见文后封装的常用函数)。

3.图片保存使用wx.canvasToTempFilePath()方法,调试阶段建议使用wx.previewImage()来调试。

4.对于二维码及素材的加载时机,根据自己业务场景来处理。

5.不同机型每行的文字大小及换行,需要用函数来处理。

6.熟悉理解scene参数,理解小程序不同方式(如扫码)打开场景值。

7.理解wx.createSelectorQuery()接口。

8.对于圆角的头像处理,最好交给后端进行图像处理。前端canvas处理的话需要考虑内存开销,当图片太大时不适合。

9.文中的小程序码为B码,微信官方给到的为图片二进制流,需要做接口类型指定处理。

10.适当将素材进行base64,并进行本地缓存。

对于文字类型的canvas绘图,需要经常计算字符多少,换行计算。分享一下这两个函数:

getTrueLength: function(str) {
     let len = str.length,
         truelen = 0;     
     for (let x = 0; x < len; x++) {         
        if (str.charCodeAt(x) > 128) {
             truelen += 2;
         } else {
             truelen += 1;
         }
     }     
    return truelen;
 },
 cutString: function(str, leng) {
     let len = str.length,
         tlen = len,
         nlen = 0;     
    for (let x = 0; x < len; x++) {         
        if (str.charCodeAt(x) > 128) {             
        if (nlen + 2 < leng) {
                 nlen += 2;
             } else {
                 tlen = x;                 
                 break;
             }
         } else {             
                 if (nlen + 1 < leng) {
                 nlen += 1;
             } else {
                 tlen = x;                 
                 break;
             }
         }
     }     
     return tlen;
 }

1.14 消息模板(暂未上线)

消息模板根据产品的实际业务来做开发,建议低频的推送用户。

小程序模板功能中需要向接口传递formId。在发送给用户id上,建议合理的进行分组(如用户订阅栏目或者作者openid进行分组)。如果发生文章更新,推送文章更新的消息模板。

二、样式表现

2.1 雪碧图合并技巧

小程序中出现了一个新单位rpx(responsive pixel),官方规定屏幕宽度为20rem,规定屏幕宽为750rpx。(在开发前尽量和视觉设计老师约定好设计文稿,例如750像素宽的设计稿能方便我们开展工作)。

雪碧图:

雪碧图自动生成图片及代码(建议灵活使用,本工具用于移动端项目雪碧图生成):

工具链接:https://code.ahthw.com/tools/csssprite/

2.2 tabBar导航栏图标大小建议

tabBar常规为图标搭配标题,具体配置可参考官方文档:链接 https://developers.weixin.qq.com/miniprogram/dev/framework/config.html

对比官方参考,大家小程序略去了tabBar.list.text配置,这样的处理方式主要是为还原设计稿。每个图标素材的像素大小为81px*81px,通过尝试:文字区域建议35px。

2.3 wxml数据绑定中巧用三元运算

合理的使用三元运算,使代码更简洁。

样式模板举例:

<view class="box_start {{item.have == 1 ? 'on':'out'}}" catchtap="onRecommStart">...</view>
<view wx:if="{{item.tag == 'title' || item.tag == 'text'}}" class="{{item.tag}} {{item['level'] ? 'h2' : ''}}">...</view>
<view class="choice-dajia-view" style='height:{{viewIsshow == false ? windowHeight:"auto"}}' wx:if="{{!choiceWarp}}">...</view>

内容显示:

<view ...>{{item.have == 1 ? '取消收藏:'收藏''}}</view>

2.4 wxss技巧

1.text-align:justify;可以将内容左右对齐,使内容外观更整齐。

2.原生组件层级特别高,例如canvas,在长页面时会留下阴影,巧用position:fixed属性,在父层级可以使页面整体长度等于视窗高度,避免阴影出现。

3.巧用-webkit-line-clamp属性,如

word-break:break-all;
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical;
overflow:hidden;

表示当一段文字超过2行时,出现省略号。这样在开发时不担心文字多少,也不要求接口数据对字符串长度限制,并且不需要前端进行函数截取。

4.当页面未加载成功时,loading展示尽量以样式、本地的base64文件及css3动画组成,提高页面性能。

5.对于可以预处理的数据,可以先提前加载渲染好,用样式操控显示隐藏。

6.rpx可以用在背景元素等css less属性上。

7.@import “*.wxss”的使用能更好的进行样式复用。

8.为显示的图片view做一个背景样式,容错图片打不开等意外因素。

9.使用image组件的mode=”widthFix”,可以保证文章底层中配图宽度不变的情况下高度自适应。

三、代码开发维护

3.1 Wxpage框架

腾讯大家小程序选用wxpage框架。【链接】https://github.com/tvfe/wxpage

WXPage 是一个极其轻量的微信小程序开发框架,其中的API蕴含了“极致页面打开速度的思想”,为可维护性与开发效率而设计的功能,框架来自“腾讯视频”小程序的项目沉淀。

框架对小程序生命周期的扩展(如onNavigate、onAppLaunch等很多有意思的扩展)、组件的依赖、实例方法(如emit、$put)、实用函数等都有一系列独特的包装,适用于组件开发。

特别感谢sendguan(关开设)在大家小程序开发中无私支持。

3.2 工具方法模块化管理

这里的工具方法指的是一些公用的方法或代码。通常根据业务的需要,我们可以建立一到多个模块,在模块里封装一些公用方法,一来方便调用,二来方便维护,如:

//Api.js let Api = {
  fun1: function() {
    ...
  },
  fun2: function() {
    ...
  },
};//接口module.exports = {
  fun1: fun1
};

这里分享一些大家小程序开发中封装的方法:

function downFile(url, callback) {
    wx.downloadFile({
        url: url,
        success: function(res) {
            callback(res.tempFilePath)
        }
    })
}// get请求方法function fetchGet(url, callback) {
    wx.request({
        url: url,
        header: { 'Content-Type': 'application/json' },
        success(res) {
            callback(null, res.data)
        },
        fail(e) {
            console.error(e)
            callback(e)
        }
    })
}// post请求方法function fetchPost(url, data, callback) {
    wx.request({
        url: url,
        method: 'POST',        
        header: { "Content-Type": "application/x-www-form-urlencoded" },
        data: data,
        success(res) {
            callback(null, res.data)
        },
        fail(e) {
            console.error(e)
            callback(e)
        }
    })
}function fetchData(url, data, callback) {
    wx.request({
        method: 'GET',
        url: url,
        data: data,
        success(res) {
            callback(res.data)
        },
        fail(e) {
            console.error(e)
            callback(e)
        }
    })
}
function removeBlock(s) {
    let regex = "\\((.+?)\\)";    
    return s.match(regex)[1]
};
function removeAt(target, index) {    
    return !!target.splice(index, 1).length;
}
function remove(target, item) {
    let index = target.indexOf(item);    
    return index > -1 ? removeAt(target, index) : false;
}
function getDateDay(str) {
    let string = str.toString().substr(0, 10)    
    return string.replace(/-/g, '.');
}
function sliceArray(array, size) {
    let result = [];    
    for (let x = 0; x < Math.ceil(array.length / size); x++) {
        let start = x * size;
        let end = start + size;
        result.push(array.slice(start, end));
    }    
    return result;
};
function getArrayItems(arr, num) {
    let temp_array = new Array();    
    for (let index in arr) {
        temp_array.push(arr[index]);
    }
    let return_array = new Array();    
    for (let i = 0; i < num; i++) {        
    //判断如果数组还有可以取出的元素,以防下标越界
        if (temp_array.length > 0) {
            let arrIndex = Math.floor(Math.random() * temp_array.length);
            return_array[i] = temp_array[arrIndex];
            temp_array.splice(arrIndex, 1);
        } else { break; }
    }    
    return return_array;
}
    
function reWirteUrl(url) {    
//console.log(url);
    if (url !== null) {        
    if (!/^(http:\/\/)/i.exec(url)) {            
        return url.replace(/(http:)?(?=\/\/img1\.gtimg\.com\/)/g, 'https:');
        } else if (url.indexOf("https://") == -1) {            
        return url.replace("http://", "https://");
        }
    } else {        
        return 'https://mat1.gtimg.com/news/images/static/weixin/wxss/basicprofile_r3.png';
    }
}

3.3 巧用wxml模板

wxml支持import,在大家小程序开发过程中,实际结合了Wxpage对子模板、组件的定义方法。

模板示例(代码来源:何润锋工作室小程序):

<!-- 引入子组件模板 -->
<import src="/comps/header.wxml" />
<import src="/comps/player.wxml" />
<import src="/comps/playerintro.wxml" />
<import src="/comps/recommvideo.wxml" />
<import src="/comps/recommnote.wxml" />
<import src="/comps/comment.wxml" />
<view class="wxpage" style="height:{{windowHeight}}px" disable-scroll="false">
    <!-- 使用子组件并传递数据 -->
    <template is="header" data="{{...header}}"></template>
    <!-- 播放 -->
    <template is="player" data="{{...player}}"></template>
    <!-- 播放 -->
<scroll-view class="scroll-posts-list {{select == false ? 'up':'down'}} {{upper == false ? 'in':'on'}}"  scroll-y="true" style="height:800rpx;" bindscrolltolower="lower" scroll-top="{{scrollviewTop}}" bindscroll="upper">
    <!-- 播放信息 -->
    <template is="playerintro" data="{{...playerintro}}"></template>
    <!-- 播放信息 -->
    <!-- 推荐视频 -->
    <template is="recommvideo" data="{{...recommvideo}}"></template>
    <!-- 推荐视频 -->
    <!-- 手记 -->
    <template is="recommnote" data="{{...recommnote}}"></template>
    <!-- 手记 -->
    <!-- 评论 -->
    <template is="comment" data="{{...comment}}"></template>
    <!-- 评论 -->
</scroll-view>
</view>

子组件示例:

<template name="header">
    <!-- 子组件事件绑定 -->
    <view data-c="{{$id}}" bindtap="onTap" class="header">{{title}}</view>
</template>
let Api = require('../lib/api');
let Txv = require("../lib/video");
let C = require('../lib/wxpage').C
module.exports = C('player', function(vm) {            
        return {                
                /**
                 * 子组件独立的数据
                 */
                data: {
                    tvpVid: "",
                    tvpUrl: "",
                    tvpVideoError: "",
                    tvpState: "",
                    tvpIsAd: "",
                    tvpReportUrl: "",
                    select: false,
                    autoplaytype: false
                },
                getNetWork: function() {
                    wx.getNetworkType({
                        success: function(res) {                            
                            // 返回网络类型, 有效值:
                            // wifi/2g/3g/4g/unknown(Android下不常见的网络类型)/none(无网络)
                             networkType = res.networkType;
                            console.log(networkType);                            
                            if (networkType == 'wifi') {
                                vm.$set({
                                    autoplaytype: true
                                })
                            }
                        }
                    })
                },                /**
                 * 子组件独立的生命周期方法
                 */
                onLoad: function(query) {
                     that = this;                    
                     this.getNetWork();
                }
            }
});

备注:在开发过程或者调试过程中,可以使用使用全局函数getApp()、getCurrentPages()等获取栈内的所有页面,然后根据业务需求进行页面数据设置。

四、数据统计

4.1 停留时长统计

目前有两种办法

1.使用官方接口文件【链接】https://developers.weixin.qq.com/miniprogram/dev/api/analysis-visit.html,个性化的分析和探究可以参考【链接】https://github.com/ireeoome/reeoome/issues/3。

2.利用小程序的生命周期onLoad及onHide等控制条件实现时间戳上报。实现思路:onHide 时new Date().getTime()时间戳减去onLoad时间戳。

4.2 分享次数统计

用户分享次数的统计功能主要利用onShareAppMessage()回调,通过绑定页面元素后对e.from !== ‘menu’方式判断是否为右上角分享功能或页面自定义分享。

对于栏目和人群、时间段等数据的上报可以根据自己的实际业务要求来处理。

4.3 渠道来源统计

日常业务的渠道多来源于公众号打开、扫描小程序B码等。在开发的时候,对在onLoad后进行对sence进行解析。例如:

onLoad: function(options) {
    let scene = decodeURIComponent(options.scene);    
        if (scene.indexOf("tid") !== -1) {
            tidNum = scene.match(/\d+/);        
            if (tidNum && tidNum[0]) {
            options.tid = tidNum[0]
        }
    }
    vm.$set({ load: options,... });
}

对于特别的统计需求,我们可以利用接口给B码增加参数的形式去完成,在onLoad阶段进行参数解析。

例如:通过以下的传参生成的二维码我们就可以解析form:house方式扫码形成的访问渠道。

...QRCode&pages=pages/view&scene=tid%2C62585&form%2C=house

4.4 小程序后台自带的数据分析功能

五、其他

5.1 调试深究

1.在 iOS、Android、开发工具上,我们开发的代码分别运行在JavaScriptCore(https://github.com/zloirock/core-js)、X5内核、nwjs内核上,对于相同代码在不同机器上的差异,我们可以深究一下。链接:jsCore / X5前端(https://x5.tencent.com/tbs/guide/tech.html#/)

2.日常pc开发,小程序在X5开发工具内运行,在小程序远程调试或者无法复现问题可以深究一下。 链接:X5debug调试(https://x5.tencent.com/tbs/document/doc-inspector-debug.html) / 工具简介(https://x5.tencent.com/tbs/document/debug-detail-wifi.html)。

3.对于特殊功模块的测试,可以做前端日志的上报进行观测,另外小程序后台运维中心可以对脚本错误进行监控,对错误的次数设置阈值会以微信群消息通知。

5.2 webview

除url携带参数外,小程序与webview暂时没其它通信方法。因此,在小程序和webview的跳转中,如何同步登录状态、地址信息状态等,是一个难以解决的问题。微信小程序的webview支持的功能是比较完善的,与浏览器环境差距不大。目前看来,webview更适合完成一些静态展示页面或更新迭代较频繁的运营活动页面的需求(src参数value控制可以动态刷新webview)。未来功能完善后,可能会作为解决小程序跳转层级不够用、小程序代码过大等问题的解决方案。

5.3 开发之外

小时候写作文,老师总让我们先列提纲。对于项目开发,先收集各种相关资料,选定框架、理清自己的项目结构、项目中可能的难点、可优化的地方等等,这对我们对开发的风险把控、开发进度估算都非常有益。

如果您觉得我们的内容还不错,就请转发到朋友圈,和小伙伴一起分享吧~

原文发布于微信公众号 - 腾讯Bugly(weixinBugly)

原文发表时间:2018-05-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小樱的经验随笔

【批处理学习笔记】第二十八课:声音和控制

声音     呵呵,是不是注意到了批处理没有声音呐?闲话不说,直接做实验吧 ^_^ ======================================...

2794
来自专栏花叔的专栏

多端统一开发框架 Taro 1.0 正式发布

Taro 是一个多端统一开发框架,它支持使用 React 的开发方式来编写可以同时在微信小程序、Web 、React Native 等多个平台上运行的应用,帮助...

2222
来自专栏达摩兵的技术空间

重新思考数据输入

在目前的产品交互中,输入数据然后程序或者产品对数据进行验证是非常常见的需求,而产品进行验证的目的性也很明确,就是为了避免脏数据进入数据库。但是从产品交互本身来讲...

1042
来自专栏王二麻子IT技术交流园地

一、VueJs 填坑日记之基础概念知识解释

概述 在最开始听说vuejs这个词是在2016年,当时天真的认为自己是个后端开发工程师不需要学习太多的前端知识,不过紧接着在2017年在公司就用到了vuejs。...

2248
来自专栏腾讯移动品质中心TMQ的专栏

腾讯TMQ在线沙龙回顾|UI自动化中阶思考与实践

UI自动化中阶思考与实践 活动时间:2017年4月17日 QQ群视频交流 活动介绍:TMQ在线沙龙第十九期分享活动 本次分享的主题是:UI自动化中阶思考与实践 ...

2909
来自专栏生信宝典

在线浏览器,在线PS,在线AI,在线编程 ...

现在越来越习惯使用在线工具,拿来即用,用完即走。只要有网,在哪都可以用。比如我们推出的在线绘图 (http://www.ehbio.com/ImageGP)已经...

4924
来自专栏web编程技术分享

手把手的SpringBoot教程,SpringBoot创建web项目(一)

41912
来自专栏全栈工程师成长之路

网站项目开发学习手册

4226
来自专栏FreeBuf

Apple iOS 9.3 S/Plus – 触摸密码绕过漏洞

? 介绍 iOS是苹果公司开发的手机操作系统,发布于2007年,使用在iPhone 和 iPod Touch上,并且已经开始延伸至其他苹果设备如iPad和A...

2055
来自专栏更流畅、简洁的软件开发方式

我写项目的思路和“自然架构”

我写项目的思路     三层的思路是要把页面(UI、数据显示)、业务逻辑、数据处理(也叫持久化)分离开来处理,思路自然是好的,但是一到了实际应用中,好多人...

2169

扫码关注云+社区

领取腾讯云代金券