腾讯大家产品背景《腾讯大家》是公司推出的中文互联网专栏写作服务产品。由于寻找有效信息的成本是非常大的,一些真正具有传播价值的内容,却往往淹没于信息洪流之中。如何将最有价值的信息以最快的速度呈现给用户,正是《大家》产品设计的初衷。《大家》更关注互联网用户更深入、更持久的思考与表达。我们希望呈现给用户的,是经得起时间考验的文章,是时代最前沿的思想。它的表现,可能是一个专栏、一部电子书、一个属于个人的频道,甚至是一款小程序。 所谓“大家”,意在集华语写作之大家手笔,为中文互联网用户提供最具魅力的经典文字,打造最有力量的互联网言论阵地、最有价值的网络阅读品牌。 腾讯大家小程序使用场景腾讯大家小程序根植于微信小程序功能,与公众号精密结合运营,通过传播引发网友关注,扩大腾讯大家内容的出口。
腾讯大家是腾讯内容出品部的优质栏目,内容以质量取胜,每日产出数篇精品内容,因此,腾讯大家更加注重每一篇文章的传播效率以及传播速度。 基于聚合页面的传播需求,腾讯大家小程序解决了在移动端聚合及快速查找历史内容的需求。绑定小程序以后,在推送单篇文章时,可以配合推送作者文章列表、相关文章列表等定制页面。 用户可以通过微信文章入口、小程序收藏等功能,可以随时随地查看腾讯大家的最新内容及历史内容。 解决的实际问题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.定时抓取已有作者全部数据,然后进行缓存,输出带分页参数接口。 在作者列表展示上做如下处理:
1.首次进入作者页加载数名作者数据。 响应到的数据格式:
作者模板结构: <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.小程序有以用户为个人中心的功能应用,比如作者栏目收藏、关注等。 获取用户数据示例: 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会判断是否已经收藏此作者 模板结构: |