利益相关
无
说明
该小程序代码已开源,点击可查看源码,可随意 star。也可以先扫描下方的小程序码直接体验。
写在前面
前段时间写了一个简单的小程序 QuietWeather,源码在这里,具体实现相关可查看这篇文章:两天撸一个天气应用微信小程序。但是这个 小程序 和 QuietWeather 完全不是一个数量级的。so,该文章梳理内容会有那么一点儿多,想跳过的可以直接拉到最下面。。。
这里先上效果图,感兴趣的也可以 查看源码 。实际体验可扫描👆上面的小程序码。
效果图
PC
开发者工具录制,会有些卡顿
文章数据入口调整了,也保留了动画,请酌情忽略 gif
卡顿
实现
过滤器
在 filter
目录下创建了一个 wxs
,里面是用到的过滤器,需要注意的是,wxs
的语法只能是 es5
,而且部分 js
语法是不支持的,具体支持的语法可查看微信小程序开发文档。
组件
细分的话,细分的话目前总共写了 8 个组件:
- 页面为空组件
empty
- 沸点
item
组件feidianItem
:分为上半部分feidianItemTop
和下半部分组件feidianItemBottom
,会在沸点、赞过的沸点等相关页面复用 - 文章
item
:有两种展现样式,这里写了独立的两个组件:postItemOne
、postItemTwo
- 标签展现
item
tagItem
:会在标签管理等相关页面复用 - 小册
item
xiaoceItem
:会在小册tab
、购买的小册等相关页面复用
组件相对比较简单,基本上是以展现为主,并没有太多交互,可下载源码 在微信开发者工具调试查看相关效果。
页面
HOME 页
HOME
页主要展现两部分:顶部热门文章推荐和下面的推荐文章列表,使用了两个上面提到的组件: postItemOne
、postItemTwo
。且未登录时,会有一个登陆提示。实现如下:
<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
<navigator url='/pages/login/login' wx:if='{{!logined}}'>
<view class='card guide'>
<view class='l'>
<view class='t'>登录账号</view>
<view class='c'>收藏文章,同步阅读记录,数据永不丢失</view>
</view>
<view class='r'>登录</view>
</view>
</navigator>
<view class='hot card' wx:if='{{hotRecomment.length && hotRrecommendShow}}'>
<view class='btitle'>
<view class='l'>
<image class='icon' src='/img/ic_hot_home.png'></image>
<view>热门推荐</view>
</view>
<view class='r'>
<image catchtap='refreshHot' class='refresh {{rotate}}' src='/img/refresh_icon.png'></image>
<image catchtap='closeHot' class='close' src='/img/chart_close.png'></image>
</view>
</view>
<postItemOne list='{{hotRecomment}}' graphics='{{true}}'></postItemOne>
</view>
<view class='timeline'>
<postItemTwo list='{{timeline}}'></postItemTwo>
</view>
</view>
其中,顶部热门推荐的刷新会有以下的实现效果,这里需要稍微注意下:
热门推荐点击刷新,将当前的 3 条文章 objectId 以 id|id|id 的格式发送请求,然后重新拉取热门推荐列表看抓包,热门推荐只返回 20 条,刷新一次移除三条,所以简单处理的话,user_filter_entry 之后直接将热门推荐数组的前三条移除即可;上面方式更精确,以防服务端之后又有什么返回呢
同时,搜索 tab
的顶部 banner
列表也是在这个页面预先请求,然后保存到本地。实现如下:
getBannerImgList() {
const auth = this.data.auth
wx.request({
url: `${config.bannerRequestUrl}/get_banner`,
data: {
position: 'explore',
page: 0,
pageSize: 20,
platform: 'android',
device_id: auth.clientId,
client_id: auth.clientId,
token: auth.token,
src: 'android',
},
success: (res) => {
let data = res.data
if (data.s === 1) {
let bannerImgList = (data.d && data.d.banner) || []
wx.setStorage({
key: 'bannerImgList',
data: bannerImgList,
})
} else {
wx.showToast({
title: data.m.toString(),
icon: 'none',
})
}
},
fail: () => {
wx.showToast({
title: '网路开小差,请稍后再试',
icon: 'none',
})
},
})
},
然后配合下拉刷新 onPullDownRefresh
重新获取数据。
其他具体细节这里不再赘述可查看源码。
搜索 TAB
搜索 tab
页两部分组成:顶部的 swiper
和下面的热门文章列表,这里复用 postItemOne
组件即可。顶部的 swiper
在首页已经预先获取过了(见上面),这里直接读取即可。实现如下:
<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
<swiper autoplay circular interval="3500" duration="500" wx:if='{{bannerImgList.length}}' style='height:{{swiperHeight}}'>
<block wx:for="{{bannerImgList}}" wx:key="{{index}}">
<swiper-item>
<image src='{{item.screenshot}}' class="banner" mode='widthFix'></image>
</swiper-item>
</block>
</swiper>
<view class='hot card'>
<view class='btitle'>
<view class='l'>
<image class='icon' src='/img/pin_hot.png'></image>
<view>热门文章</view>
</view>
</view>
<recommendItem list='{{rankList}}' graphics='{{false}}'></recommendItem>
</view>
</view>
沸点 TAB
该页面复用的组件是 feidianItem
,顶部的热门沸点是使用的 swiper
实现的。
该页面有一个小细节需要稍微注意下:如果页面保持在顶部,那么切换 tab
后需要重新刷新获取新数据,如果页面已经往下滑动了,那么切换 tab
后就不需要刷新获取新数据。实现如下:
wxml
:
<view class='container'>
<view class='recommendList'>
<swiper autoplay='{{false}}' circular='{{false}}' duration="500" wx:if='{{recommendList.length}}' next-margin='100rpx' style='height:{{swiperHeight}}'>
<block wx:for="{{recommendList}}" wx:key="{{index}}">
<swiper-item>
<view class='item'>
<view class='title' wx:if='{{item.isRecommend}}'>
<image class='icon' src='/img/ic_topic_star.png'></image>
<text>编辑推荐</text>
</view>
<view class='title' wx:else>
<image class='icon' src='/img/pin_hot.png'></image>
<text>热门沸点</text>
</view>
<view class='content'>
<view class='text'>{{item.content}}</view>
<view class='img' wx:if='{{item.pictures && item.pictures.length}}'>
<image mode='aspectFill' src='{{item.pictures[0]}}'></image>
</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
<view class='pinList'>
<feidianItem item='{{item}}' wx:for='{{list}}' wx:key='{{index}}'></feidianItem>
</view>
</view>
js
:
const config = getApp().globalData.config
const utils = require('../../utils/utils.js')
Page({
data: {
COUNT: 30,
swiperHeight: 'auto',
recommendList: [],
list: [],
auth: {},
scrollTop: 0,
},
onShow () {
// 如果 scrollTop 为 0,也 reload
if (utils.pageReload(this.data.auth, [this.data.list]) || !this.data.scrollTop) {
this.init()
}
},
onPullDownRefresh() {
this.init()
},
init() {
wx.showLoading({
title: '数据加载中',
})
this.setData({
auth: {},
})
let auth = utils.ifLogined()
this.setData({
auth,
})
this.initSwiper()
this.getHotRecommendList()
this.pinListRecommend(true)
},
initSwiper() {
wx.getSystemInfo({
success: (res) => {
this.setData({
swiperHeight: `${(res.windowWidth || res.screenWidth) / 375 * 135}px`
})
},
})
},
// 热门推荐列表
getHotRecommendList() {
const auth = this.data.auth
wx.request({
url: `${config.shortMsgMsRequestUrl}/getHotRecommendList`,
data: {
uid: auth.uid,
device_id: auth.clientId,
client_id: auth.client_id,
token: auth.token,
src: 'web',
},
success: (res) => {
let data = res.data
if (data.s === 1) {
this.setData({
recommendList: (data.d && data.d.list) || [],
})
} else {
wx.showToast({
title: data.m.toString(),
icon: 'none',
})
}
},
fail: () => {
wx.showToast({
title: '网路开小差,请稍后再试',
icon: 'none',
})
},
})
},
// 沸点列表
pinListRecommend(reload) {
const auth = this.data.auth
let list = this.data.list
if (utils.isEmptyObject(list) || reload) {
list = [{ createdAt: '' }]
}
let createdAt = (list.slice(-1)[0].createdAt) || ''
wx.request({
url: `${config.shortMsgMsRequestUrl}/pinList/recommend`,
data: {
uid: auth.uid,
device_id: auth.clientId,
token: auth.token,
src: 'web',
limit: this.data.COUNT,
before: createdAt,
},
success: (res) => {
let data = res.data
if (data.s === 1) {
wx.hideLoading()
let list = (data.d && data.d.list) || []
this.setData({
list: reload ? list : this.data.list.concat(list),
})
} else {
wx.showToast({
title: data.m.toString(),
icon: 'none',
})
}
},
fail: () => {
wx.showToast({
title: '网路开小差,请稍后再试',
icon: 'none',
})
},
})
},
onReachBottom() {
this.pinListRecommend()
},
onPageScroll (e) {
this.setData({
scrollTop: e.scrollTop,
})
},
onShareAppMessage(res) {
return {}
},
})
小册 TAB
小册页面基本上没有什么可说的,只有小册展现。。。组件复用 xiaoceItem
,实现如下:
<view class='lists'>
<xiaoceItem list='{{xiaoceList}}'></xiaoceItem>
</view>
我的 TAB
该页面以简单的数目展现(未读消息条数、收藏集数目、阅读过的文章数)和跳转为主,调用相关的 API
即可,没有什么难度。实现如下:
wxml
:
<view class='wrapper'>
<view class='card profile' catchtap='navigatItem' data-url='/pages/personal/personal'>
<view class='info'>
<image class='avatar' src='{{userInfo.avatarLarge}}' wx:if='{{userInfo.avatarLarge}}'></image>
<image class='avatar' src='/img/empty_avatar_user.png' wx:else></image>
<view class='text'>
<view class='name'>{{userInfo.username || '登录/注册'}}</view>
<view>{{userInfo.jobTitle || '添加职位'}} @ {{userInfo.company || '添加公司'}}</view>
</view>
</view>
<view class='more'>
<view class='reddot' wx:if='{{auth && !userInfo.company}}'></view>
<image src='/img/profile_arrow.png'></image>
</view>
</view>
<view class='card items'>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/infoCenter/infoCenter'>
<view class='title'>
<image src='/img/ic_notification.png'></image>
<view>消息中心</view>
</view>
<view class='count reddot' wx:if='{{userNotificationNum}}'>{{userNotificationNum}}</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/favorate/favorate'>
<view class='title'>
<image src='/img/ic_heart_entry_bottom_full.png'></image>
<view>我喜欢的</view>
</view>
<view class='count'>{{userInfo.collectedEntriesCount || 0}}篇</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/collectionSet/collectionSet'>
<view class='title'>
<image src='/img/ic_collection_set.png'></image>
<view>收藏集</view>
</view>
<view class='count'>{{userInfo.collectionSetCount || 0}}个</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/purchasedXiaoce/purchasedXiaoce'>
<view class='title'>
<image src='/img/user_buy.png'></image>
<view>已购小册</view>
</view>
<view class='count'>{{userInfo.purchasedBookletCount
|| 0}}本</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/myPins/myPins?liked=1'>
<view class='title'>
<image src='/img/user_liked_pin.png'></image>
<view>赞过的沸点</view>
</view>
<view class='count'>{{userInfo.likedPinCount || 0}}个</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/readHistory/readHistory'>
<view class='title'>
<image src='/img/view.png'></image>
<view>阅读过的文章</view>
</view>
<view class='count'>{{userInfo.viewedEntriesCount || 0}}篇</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/manageTag/manageTag'>
<view class='title'>
<image src='/img/tag.png'></image>
<view>标签管理</view>
</view>
<view class='count'>{{userInfo.subscribedTagsCount || 0}}个</view>
</view>
</view>
<view class='card items'>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/feedback/feedback' data-open='true'>
<view class='title'>
<image src='/img/icon_feed_back.png'></image>
<view>意见反馈</view>
</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/setting/setting' data-open='true'>
<view class='title'>
<image src='/img/settings.png'></image>
<view>设置</view>
</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/miniqrcode/miniqrcode' data-open='true'>
<view class='title'>
<image src='/img/qrcode.png' style='width:28rpx;height:28rpx;padding:10rpx'></image>
<view>小程序码</view>
</view>
</view>
</view>
</view>
js
:
const utils = require('../../utils/utils.js')
const config = getApp().globalData.config
Page({
data: {
userInfo: {},
userNotificationNum: 0,
auth: {},
},
onShow () {
let auth = utils.ifLogined()
this.setData({
auth,
})
if (auth) {
this.getUserInfo()
this.userNotificationNum()
} else {
this.setData({
userInfo: {},
userNotificationNum: 0,
})
}
},
navigatItem (e) {
return utils.navigatItem(e)
},
// 获取用户信息
getUserInfo() {
const auth = this.data.auth
wx.request({
url: `${config.apiRequestUrl}/getUserInfo`,
data: {
src: 'web',
device_id: auth.clientId,
uid: auth.uid,
token: auth.token,
current_uid: auth.uid,
},
success: (res) => {
let data = res.data
if (data.s === 1) {
this.setData({
userInfo: data.d,
})
} else {
wx.showToast({
title: data.m.toString(),
icon: 'none',
})
}
},
fail: () => {
wx.showToast({
title: '网路开小差,请稍后再试',
icon: 'none',
})
},
})
},
// 消息中心消息条数
userNotificationNum() {
const auth = this.data.auth
wx.request({
url: `${config.notifyRequestUrl}/getUserNotificationNum`,
data: {
src: 'web',
uid: auth.uid,
token: auth.token,
},
success: (res) => {
let data = res.data
if (data.s === 1) {
this.setData({
userNotificationNum: data.d && data.d.notification_num,
})
} else {
wx.showToast({
title: data.m.toString(),
icon: 'none',
})
}
},
fail: () => {
wx.showToast({
title: '网路开小差,请稍后再试',
icon: 'none',
})
},
})
},
})
文章详情页
文章详情页返回的数据是整篇文章的 html
格式,如果是浏览器的话,直接显示即可,小程序里这里使用的是 wxParse
解析的。实现如下:
<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<import src="../../wxParse/wxParse.wxml"/>
<view class='container'>
<image wx:if='{{postInfo.screenshot}}' style='width:100%;height:340rpx;' mode='aspectFill' src='{{postInfo.screenshot}}'></image>
<view class='content'>
<view class='user'>
<view class='avatar' catchtap='toPersonal'>
<image mode='aspectFill' src='{{(postInfo.user && postInfo.user.avatarLarge) || "/img/default_avatar.png"}}'></image>
</view>
<view class='info'>
<view class='name'>{{postInfo.user && postInfo.user.username}}</view>
<view class='others'>
<text class='time'>{{postInfo.createdAt}}</text>
<text>阅读 {{postInfo.viewsCount}}</text>
</view>
</view>
</view>
<view class='title'>{{postInfo.title}}</view>
<template is="wxParse" data="{{wxParseData:article.nodes}}"/>
</view>
</view>
至于评论相关的,还没有写。。。
个人中心页
个人中心页和我的页面展现差不多,也是显示条目和页面跳转为主,调用相关的 API
即可,不再赘述。实现如下:
<view class='wrapper'>
<view class='card profile'>
<view class='info'>
<image class='avatar' mode='aspectFill' src='{{userInfo.avatarLarge}}' wx:if='{{userInfo.avatarLarge}}'></image>
<image class='avatar' src='/img/empty_avatar_user.png' wx:else></image>
<view class='text'>
<view class='name'>{{userInfo.username}}</view>
<view class='jobtitle'>{{userInfo.jobTitle}}</view>
<view class='others'>{{userInfo.selfDescription }}</view>
</view>
</view>
<view class='bottom'>
<view class='l'>
<view class='action'>
<view>{{userInfo.followeesCount}}</view>
<view class='key'>关注</view>
</view>
<view class='action'>
<view>{{userInfo.followersCount}}</view>
<view class='key'>关注者</view>
</view>
</view>
<!-- <view class='edit'>编辑</view> -->
<image src='/img/ic_dynamic_vote.png' class='trend' catchtap='showDataTrend'></image>
</view>
</view>
<view class='card items'>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/dynamic/dynamic?thirduid={{thirduid}}' data-open='true'>
<view class='title'>
<view>动态</view>
</view>
</view>
</view>
<view class='card items'>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/myPins/myPins?thirduid={{thirduid}}' data-open='true'>
<view class='title'>
<view>沸点</view>
</view>
<view class='count'>{{userInfo.pinCount || 0}}</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/originalPost/originalPost?thirduid={{thirduid}}' data-open='true'>
<view class='title'>
<view>原创文章</view>
</view>
<view class='count'>{{userInfo.postedPostsCount}}</view>
</view>
<view class='item' wx:if='{{userInfo.postedEntriesCount}}' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/sharePost/sharePost?thirduid={{thirduid}}' data-open='true'>
<view class='title'>
<view>分享文章</view>
</view>
<view class='count'>{{userInfo.postedEntriesCount}}</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/collectionSet/collectionSet?thirduid={{thirduid}}' data-open='true'>
<view class='title'>
<view>收藏集</view>
</view>
<view class='count'>{{userInfo.collectionSetCount}}</view>
</view>
</view>
<view class='card items'>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/favorate/favorate?thirduid={{thirduid}}' data-open='true'>
<view class='title'>
<view>喜欢的文章</view>
</view>
<view class='count'>{{userInfo.collectedEntriesCount}}</view>
</view>
<view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/subscribedTag/subscribedTag?thirduid={{thirduid}}' data-open='true'>
<view class='title'>
<view>关注的标签</view>
</view>
<view class='count'>{{userInfo.subscribedTagsCount}}</view>
</view>
</view>
<view class='card items'>
<view class='item' wx:if='{{userInfo.community && userInfo.community.weibo && userInfo.community.weibo.username}}'>
<view class='title'>
<image src='/img/icon_profile_weibo.png'></image>
<view class='val'>{{userInfo.community.weibo.username}}</view>
</view>
</view>
<view class='item' wx:if='{{userInfo.blogAddress}}'>
<view class='title'>
<image src='/img/icon_profile_blog.png'></image>
<view class='val'>{{userInfo.blogAddress}}</view>
</view>
</view>
</view>
</view>
文章数据页
文章数据页就是显示你的文章获得了多少收藏、多少评论、多少阅读相关的数据,该页面主要是数字滚动动画的实现。这里的实现思路是这样的:
将数字从 0 到 N 纵向排列,然后 translateY
到相应的数字即可,主要的实现如下:
<view class='countInner' style='transform:translateY(-{{100*(item.length-1)/item.length}}%)' wx:for='{{filters.strToNumArr(userInfo.totalCollectionsCount)}}' wx:key='{{index}}' wx:for-item='item' wx:for-index='index'>
<view wx:for='{{item}}' wx:key='{{idx}}' wx:for-item='i' wx:for-index='idx'>{{i}}</view>
</view>
// 按照长度生成 0 字符串
generateZeroArr (len) {
Array.apply(null, Array(len)).map(function (item, i) {
return 0
})
}
其他细节不再赘述,可查看源码。
消息中心页
消息中心有两个 tab
,可以点击、滑动切换,这里的实现是 swiper
。这里需要注意的是:消息分为不同的 category
,不同的 category
展现的内容是不一样的,所以这里需要区分下,目前我获取到的 category
只有几种,是否全部覆盖所有的 category
只能等遇到没有覆盖的时候随手补上了。。。实现如下:
<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
<view class='top tabs'>
<view class='inner'>
<view class='tab {{currentSwiper === "0" ? "active" : ""}}' data-index='0' catchtap='switchSwiper'>用户消息</view>
<view class='tab {{currentSwiper === "1" ? "active" : ""}}' data-index='1' catchtap='switchSwiper'>系统消息</view>
</view>
<view class='bar' style='left:{{currentSwiper*50}}%'></view>
</view>
<swiper class='swiper' autoplay='{{false}}' indicator-dots='{{false}}' bindchange='swiperChanged' current='{{currentSwiper}}'>
<swiper-item item-id='0'>
<scroll-view scroll-y bindscrolltolower='getMoreUserNotification'>
<view class='item' wx:for='{{list}}' wx:key='{{index}}'>
<view class='avatar' data-id='{{item.users[0].objectId}}' catchtap='toPersonal'>
<image mode='aspectFill' src='{{item.users[0].avatarLarge || "/img/default_avatar.png"}}'></image>
</view>
<view class='content' wx:if='{{item.category==="collection"}}'>
<view>{{item.users[0].username}}等{{item.count}}人 喜欢了你的文章 <text data-id='{{item.entry && item.entry.objectId}}' catchtap='toPostDetail'>{{item.entry && item.entry.title}}</text></view>
<view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
</view>
<view class='content' wx:elif='{{item.category==="comment"}}'>
<view>{{item.users[0].username}}回复了你在文章 <text data-id='{{item.entry && item.entry.objectId}}' catchtap='toPostDetail'>{{item.entry && item.entry.title}}</text> 的评论</view>
<view class='comment'>{{(item.reply && item.reply.content) || (item.comment && item.comment.content)}}</view>
<view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
</view>
<view class='content' wx:elif='{{item.category==="follow"}}'>
<view>{{item.users[0] && item.users[0].username}} 关注了你</view>
<view class='time'>{{item.users[0] && item.users[0].jobTitle}}</view>
</view>
<view class='content' wx:elif='{{item.category==="comment-like"}}'>
<view>{{item.users[0] && item.users[0].username}} 赞了你在 <text>{{item.entry && item.entry.title}}</text> 的评论</view>
</view>
<view class='content' wx:elif='{{item.category==="pin-like"}}'>
<view>{{item.users[0] && item.users[0].username}} 赞了你的 <text>沸点</text></view>
<view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
</view>
<view class='content' wx:elif='{{item.category==="pin-comment"}}'>
<view>{{item.users[0] && item.users[0].username}} 回复了你的 <text>沸点</text></view>
<view class='comment'>{{(item.reply && item.reply.content) || (item.pinComment && item.pinComment.content)}}</view>
<view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
</view>
<view class='content' wx:else>未知状态,可提交给开发者</view>
</view>
</scroll-view>
<empty wx:if='{{!list.length}}' tip='暂无消息'></empty>
</swiper-item>
<swiper-item item-id='1'>
<view wx:if='{{systemInfoList.length}}'>
不好意思,我没有系统消息,所以看不到系统消息 API 的数据结构,也看不到样式。。。
</view>
<empty wx:if='{{!systemInfoList.length}}' tip='暂时没有系统通知'></empty>
</swiper-item>
</swiper>
</view>
动态页
动态页也需要稍微注意一点,动态页分不同的 category
,这里覆盖的有 follow
、collection
、subscribe
,如果遇到未覆盖的只能顺手补上。实现如下:
<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
<view class='item' wx:for='{{list}}' wx:key='{{index}}'>
<view class='inner' wx:if='{{item.category === "follow"}}'>
<view class='top'>
<view class='l'>
<image mode='widthFix' src='/img/ic_dynamic_user.png'></image>
<view>关注了:</view>
</view>
<view class='r'>{{filters.timeBefore(item.createdAtString)}}</view>
</view>
<view class='bottom'>
<view class='tagcard'>
<image mode='aspectFill' src='{{item.users[0].avatarLarge || "/img/entry_image_default.png"}}'></image>
<view class='others'>
<view class='title'>{{item.users && item.users[0].username}}</view>
<view class='info' wx:if='{{item.users && item.users[0].jobTitle && item.users[0].company}}'>{{item.users && item.users[0].jobTitle}} @ {{item.users && item.users[0].company}}</view>
</view>
</view>
</view>
</view>
<view class='inner' wx:if='{{item.category === "collection"}}'>
<view class='top'>
<view class='l'>
<image mode='widthFix' src='/img/ic_dynamic_collect.png'></image>
<view>喜欢了:</view>
</view>
<view class='r'>{{filters.timeBefore(item.createdAtString)}}</view>
</view>
<view class='bottom'>
<view class='tagcard' data-id='{{item.entry && item.entry.objectId}}' catchtap='toPostDetail'>
<image mode='aspectFill' src='{{item.entry.screenshotUrl || "/img/entry_image_default.png"}}'></image>
<view class='others'>
<view class='title'>{{item.entry && item.entry.title}}</view>
</view>
</view>
</view>
</view>
<view class='inner' wx:if='{{item.category === "subscribe"}}'>
<view class='top'>
<view class='l'>
<image mode='widthFix' src='/img/ic_dynamic_tag.png'></image>
<view>关注了 {{item.tags && item.tags[0].title}} 等 {{item.tags && item.tags.length}} 个标签</view>
</view>
<view class='r'>{{filters.timeBefore(item.createdAtString)}}</view>
</view>
<view class='bottom'>
<view class='imglist'>
<image mode='aspectFill' wx:for='{{item.tags}}' wx:key='{{idx}}' wx:for-index='idx' wx:for-item='i' src='{{i.icon}}'></image>
</view>
</view>
</view>
</view>
</view>
标签管理页
这个页面的 tab
也是用 swiper
实现的,复用的组件是 tagItem
。实现如下:
<view class='container'>
<view class='top tabs'>
<view class='inner'>
<view class='tab {{currentSwiper === "0" ? "active" : ""}}' data-index='0' catchtap='switchSwiper'>已关注标签</view>
<view class='tab {{currentSwiper === "1" ? "active" : ""}}' data-index='1' catchtap='switchSwiper'>所有标签</view>
</view>
<view class='bar' style='left:{{currentSwiper*50}}%'></view>
</view>
<swiper class='swiper' autoplay='{{false}}' indicator-dots='{{false}}' bindchange='swiperChanged' current='{{currentSwiper}}'>
<swiper-item item-id='0'>
<view>
<tagItem list='{{tagList}}'></tagItem>
</view>
<empty wx:if='{{!tagList.length}}' tip='暂无消息'></empty>
</swiper-item>
<swiper-item item-id='1'>
<scroll-view scroll-y bindscrolltolower='getMoreRecommendTags'>
<view class='hot' wx:if='{{hotTagList.length}}'>
<view class='title'>推荐标签</view>
<tagItem list='{{hotTagList}}'></tagItem>
</view>
<view class='suggest' wx:if='{{recommendTagList.length}}'>
<view class='title'>你可能感兴趣的标签</view>
<tagItem list='{{recommendTagList}}'></tagItem>
</view>
</scroll-view>
<empty wx:if='{{!hotTagList.length && recommendTagList.length}}' tip='暂时没有系统通知'></empty>
</swiper-item>
</swiper>
</view>
原创文章页、喜欢的文章页、阅读过的文章页、赞过的沸点页、关注的标签页
这几个页面都是组件的复用,没有太多要说的。
意见反馈页、设置页
这两个页面只是一个关于页面而已。。。
完成度
APP
里面的东西实在是不少,包括页面和交互,要完全照抄实现确实需要一些时间和精力,UI
之类的都是简单测量+肉眼调试实现的,下面列出页面和交互的完成度,这里应该只是列出了绝大部分(还是上面那句话,APP
里面的东西实在是不少),未列出、未实现的后续会根据时间、精力来实现。
实际完成度请以代码为主(线上小程序也会持续更新)。
页面完成度
貌似不支持 markdown
待办事宜写法?QAQ
- [x] 启动页
- [x] 登录、未登录跳转逻辑和页面数据刷新逻辑等
- [x] HOME、搜索、沸点、小册 TAB 涉及到的上拉、下拉刷新
- [x] POST、ENTRY(文章类型不同) 详情页
-
[ ] HOME TAB
-
[x] 首页
- [x] 热门推荐
- [x] 下部列表
- [ ] 标签展示相关
-
-
[ ] 搜索 TAB
- [x] 顶部轮播
- [x] 热门文章
- [ ] 搜索功能相关
- [ ] 本周最热
-
[ ] 收藏集
- [ ] ...
-
[ ] 活动
- [ ] ...
-
[ ] 沸点 TAB
-
[ ] 推荐
- [x] 顶部热门沸点
- [x] 沸点列表
- [x] 沸点详情
- [ ] 话题
- [ ] 动态
- [ ] 发布沸点
-
-
[ ] 小册 TAB
- [x] 小册列表
- [ ] 小册详情
-
[ ] 我的 TAB
-
[ ] 个人主页
- [x] 文章数据
- [ ] 编辑
- [ ] 关注、被关注列表
- [x] 动态页
- [x] 沸点页
- [x] 原创文章页
-
[x] 收藏集
- [ ] 收藏集详情页
- [x] 喜欢的文章
-
[x] 关注的标签
- [ ] 标签详情页
- [x] 我喜欢的
- [x] 收藏集
- [ ] 已购小册
- [x] 赞过的沸点
- [x] 阅读过的文章
-
[x] 标签管理
- [x] 已关注标签
-
[x] 所有标签
- [x] 推荐标签
- [x] 所有标签
- [ ] 夜间模式
- [x] 意见反馈(和官方 APP 有差异,这里是个简单的关于页)
-
[ ] 设置
- [ ] ...
-
- [x] 登录页
- [ ] 注册页
- [ ] 修改密码页
- [x] 其他完成部分...
- [ ] 未完待续部分...
交互完成度
评论、留言、关注、添加到收藏集、喜欢、发表沸点等暂时均没有实现,因为 APP 里面的东西实在是不少......
- [ ] 评论
- [ ] 留言
- [ ] 关注
- [ ] 喜欢
- [ ] 未完待续部分...
说明
- 1、话说
API
域名(二级)真是多啊,小程序后台域名白名单最多只能配20
个,现在已经占了16
个了,感觉要完整抄完实现APP
版小程序,配额不够啊。不行的话,就只能搭个server
代理了; - 2、个别接口只有
APP
用到了,请求字段需要按照web
的略作调整;个别接口也要设置对应的header
; - 3、文章详情页返回的是整片文章的
html
格式的content
,这里使用的是开源的 wxParse 进行富文本解析; - 4、由于小程序的限制,第三方的
url
不能在webview
中打开,所以文章里面的外链能点开算我输; - 5、开发时,个别细节需要稍微注意,比如:沸点
tab
页,如果已经滑到了顶部,onShow
获取新数据,否则,不刷新;未登录时,首页APP
调用的 API 是get_recommended_entry
,就是懒为了方便小程序里仍然使用get_entry_by_timeline
;其他的不一一赘述,详情可 查看源码; - 6、由于
账号权限等问题有些API
需要天时地利人和,部分API
返回的数据格式没有拿到,所以对应的页面也没有写,比如:系统消息页面(最近一直没有系统消息)等; - 7、部分数据可能未完全覆盖,比如:用户消息这块,目前列举出的
category
有collection
、comment
、follow
、comment-like
、pin-like
、pin-comment
,可能还会有其他消息类型,遇到了会一一补上;还有动态页,也是同样的问题;可能还有其他没有完全覆盖的数据; - 8、点击某些文章进入详情页会提示
illegal token
,亦或文章没有正常显示出来,应该是请求参数需要略作调整,或者文章类型需要判断。类似这样的小问题,后续会调整补充; - 9、小程序(非小游戏)在于一个 小 字,应该是一个应用的浓缩精华版,而不应该是一个内容丰富多彩的
APP
的100%
的复制版,这样会显得比较臃肿,此处应该有but
,该小程序仅仅是出于学习交流的目的,所以这个问题不在我们的考虑范围内; - 10、登录现在只能手机号登录,邮箱登录给忘记了,回头一并加上;
- 11、我也是有人生梦想的人;
转载请注明出处
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。