来一波成品图:

结语:
因为时间比较短,知乎也是个大项目,页面、功能还需要慢慢完善,发布了的功能也有一些待改进的地方,后续慢慢把这个项目做下去,慢慢打磨技术。爱代码,爱知乎! 欢迎同样志同道合的码友们多多指教和交流。ヾ(❀╹◡╹)ノ~
顺便附上我的项目地址:仿“知乎”微信小程序
icon“赞助地”:icon
不知道有多少人跟我一样爱上“知乎”的呢?一直喜欢“知乎”所带来的用户体验效果和一些新颖的信息传送。所以在最近的小程序学习中,开始的第一个项目实训,就是“知乎”了。
开始做了才知道,“知乎”小程序的工程量之大,我这个前端新手不知应该需要多少个工作日,才能把“知乎”完整做成小程序。不过程序员的路本就是边学习边成长的,学无止境。小人不才,最近做了一些,忍不住先开始一波经验和问题的分享了。
首页由三个tab项组成,”关注”,“推荐”,“热榜”,这几个页面的切换功能分别设置了“点击切换”和“滑屏切换”:
home.js部分代码: switchTab (e) {//tab点击事件 let index = parseInt(e.target.dataset.index) this.setData({ currentIndex: index }) }, handleChangeTab (e) {//tab滑屏事件 const p = 33.3 this.setData({ lineStyle: `left: ${p * e.detail.current}%` }) }
直接将index值parseInt,赋值到每次转到的当前tab的index值:currentIndex,数值的比较相对于字符串的比较就轻松许多了不是吗? 而后滑屏事件里自动会引用点击事件中的结果,这个时候不得不又为parseInt点个赞,我只需要将那条“选中线”在每次触发该事件时自动乘当前的currentIndex,得到它在不同当前tab下的位置值。
没错就没用if…else!用烦了,还好还有点余地让我换口味,没办法的时候该low还不是得low,这个时候就得安慰自己是‘走走基层’体验生活了hhhhh
在进行首页的三个tab页时,都存在一个问题,页面中有些分享的文中,插入了图片,而有些则没有插入。但在我们的代码中,一般初始都是加了这个图片元素的,至于数据中加不加入图片,是发文用户自己的事儿了吧。
但是呢,小程序起步,我就遇到这样一个问题,不管有没有加入图片,页面上仍然会给这个图片元素留了空白占位,导致其它内容全部被挤下去了。
所以我就在想:怎么能让这里有图片就显示,没图片就不留占位呢?
解决:
wx:if => 条件渲染
加入wx:if的元素,页面会自动判断是否该渲染该元素所包含的代码块,wx:if:”{{绑定的数据}}”,绑定的数据有值时渲染,无值时不渲染。
看到热榜页面的样式就感觉它就像个多行三列的网格,所以撇开了繁琐的页面设计与标签类名间的位置样式值设置,果断用了栅格布局:
home.wxml部分代码: <view class="container-item" wx:for="{{hotList}}" wx:key="{{item.id}}"> <view class="row"> <view class="col"> <view class="col-1">{{item.rank}}</view> <view class="col-7"> <view class="title">{{item.title}}</view> <view class="status">{{item.status}}</view> </view> <view class="col-4"> <image wx:if="{{item.titleImage}}" class="title-image" src="{{item.titleImage}}"></image> </view> </view> </view> </view>
当然,小程序暂时还没有col-number的固定值,所以直接设置类名并不会发生变化,没办法直接像使用api一样直接使用,所以,就在app.wxss全局样式中定义了每个col块的宽度:
.col>.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12{ overflow: hidden; } .col-1{ width: 8.33333333333333%; } .col-2{ width: 16.66666666666666%; } .col-3{ width: 25%; } .col-4{ width: 33.33333333333333%; } .col-5{ width: 41.66666666666666%; } .col-6{ width: 50%; } .col-7{ width: 58.33333333333333%; } .col-8{ width: 66.66666666666666%; } .col-9{ width: 75%; } .col-10{ width: 83.33333333333333%; } .col-11{ width: 91.66666666666666%; } .col-12{ width: 100%; }
这样后面这个程序中若还想再建这种布局就能直接简便地引用了。
当然,这个页面中,在设置这个之前,还少不了flex布局的实现,“弹性布局”真是我这段时间学习css中最喜欢的东西了。相关知识大家可以看看阮一峰老师的文章。
该界面完成了一个个人觉得平时刷知乎的时候比较妙的用户体验,:头部nav在下滑到一定距离时仍然fix其主要“功能转到点”在头部,如果用户在下滑到比较长距离的时候想要去到头部的导航,不用重新上滑很长的距离到顶部去实现这个动作。
先看一波效果:
前端框架semantic ui
在这方面的效果就做得好棒,看人家主页semantic ui。
所以在做这个项目的时候特意也挑了这个效果去实现。
作为一个前端初学者,还是得依靠我们的“主角”scroll-view,
配置项 | 作用 |
---|---|
scroll-top | 设置竖向滚动条位置 |
scroll-y | 允许纵向滚动 |
bindscroll | 滚动时触发的回调函数 |
我在这里用了scroll事件进行两个小页面(也就是页面滚动前与滚动到 一定距离显示市显示的不同top nav顶部导航条)的切换,配合小程序的”wx:if else “框架进行条件渲染,当页面滚动到设置的目标距离时,切换新生成的顶部导航。
thought.wxml部分代码: 切换前的nav: <view wx:if="{{!display}}" class="nav"> <view class="title">想法</view> <view class="message"> <image class="messageImg" src="{{messageImg}}" wx:if="{{messageImg}}"></image> <text class="messageTitle" >消息</text> </view> <view class="myThought"> <image class="thoughtImg" src="{{thoughtImg}}" wx:if="{{thoughtImg}}"></image> <text class="thoughtTitle">我的想法</text> </view> </view> 切换后的nav: <view wx:else class="nav1"> <view class="title">想法</view> <view class="message"> <image class="messageImg" src="{{messageImg}}" wx:if="{{messageImg}}"></image> </view> <view class="myThought"> <image class="thoughtImg" src="{{thoughtImg}}" wx:if="{{thoughtImg}}"></image> </view> </view>
分别给两个nav设置不同样式。
scroll事件触发nav转换:
Scroll事件接收两个参数:
1.scroll-top;
2.display属性
scroll: function(e){ if (e.detail.scrollTop > 200) { this.setData({ display: true }) } else { this.setData({ display: false }) } }
在这个地方就有几个开始动手时就踩到的坑:
1.使用竖向滚动条时必须为组件设置一个固定高度,否则bindscroll事件无法触发;
2.scroll-top在设置竖向滚动条位置时,如果设置的值没有变化,组件不会渲染!
这种功能呢,用我们老师的话来说… 没错,就是很有“质感”!
h5写过轮播的都只有写过的知道,相对还是比较麻烦的,并没有一个轮播图组件,有个ViewPage也需要自己定制,小程序中swiper组件封装的相对还是方便的,使用方式也相对容易些。
主要属性:
配置项 | 作用 |
---|---|
indicator-dots | 是否显示面板指示点 |
autoplay | 是否自动切换 |
current | 当前所在页面的index |
interval | 自动切换时间间隔 |
duration | 滑动动画时长 |
bindchange | current改变时触发change事件 |
属性只需要设置就行了 也可以抽到js文件的data中进行数据绑定,监听使用bindchange,在js中做业务处理。
wxml代码块:
<swiper class="swiper" autoplay="true" interval="2000" duration="1000" circular="true" > <block wx:for="{{discussion}}" wx:for-index="index"> <swiper-item class="discuss"> <image wx:if="{{item.url}}" src="{{item.url}}" class="swiper-url"/> <view class="discuss-desc"> <view class="discussing">{{item.discussing}}</view> <view class="desc-title">{{item.title}}</view> <view class="desc-question"> {{item.descQuestion}} <!-- <swiper class="desc-question" autoplay="true" interval="3000" duration="1000" vertical="true"> <block wx:for="{{item.descQuestion}}" wx:if="{{item.descQuestion}}"> <swiper-item class="question"> <view class="question-item">{{item.questionItem}}</view> </swiper-item> </block> </swiper> --> </view> </view> </swiper-item> </block> </swiper>
其实这个页面里的轮播中,还嵌套了其中一个项的纵向轮播,刚开始我直接在swiper中另嵌套了一个swiper,然后设置verticle=“true”,但只能将外层swiper的轮播切换时间增加,才能给里面的纵向轮播预留时间显示出来,所以体验效果并不好,有知道怎么做的可以传授下一下(๑`・ᴗ・´๑)ヾ(●´∇`●)ノ哇~。
每次输入搜索内容后,点击回车确定,转到详情页,搜索历史栏也随之新增一条新记录,同时将记录保存到本地缓存,页面刷新之后仍然存在历史记录。
search.wxml <view class="search-history"> <text class="zhhs">搜索历史</text> <view class="search-history-item" wx:for="{{historyRecord}}" wx:key="item.id"> <image class="search-history-icon" src="/assets/icons/shizhong.png"></image> <text>{{item.recordItem}}</text> </view> </view>
在进行bindconfirm事件处理上,刚开始就生生踩雷了,刚开始学小程序都得了解的事,我就犯了错,我们都应该知道,要改变data里的数据,只能用setData,刚开始我直接在外部使用了this.data.historyRecord.push({id:’’,recordItem:e.detail.value}); 然后就报错显示没有push这个方法,然后我当然就去找度娘问清楚啦,被提示data里的数据只能用setData改变,然后一敲脑袋,最后就是下面的样子了:
search.is bindconfirm: function(e){ console.log(e); var historyRecord = this.data.historyRecord; var recordItem = e.detail.value; historyRecord.unshift({ id:'0', recordItem: recordItem }); this.setData({ historyRecord:historyRecord }); }
要使用setData之外的方法,就要借用变量来赋值啦,将数组赋到外部定义的变量,就可以使用setdata之外的方法了。
而该变量本身就是数组,数组的方法之多,够用的了!起先我用的是push()方法,后来又去摸了下知乎搜索页,发现历史记录的最新记录都是直接插入首行,好的,数组方法unshift()满足你!
每次输入搜索内容后,出现搜索结果条页,点击结果条,转到详情页,搜索历史栏也随之新增一条新记录。
Search.wxml部分代码 <view wx:else class="search-like"> <view class="search-like-item" data-param="{{item.text}}" wx:for="{{searchLikeList}}" wx:key="{{index}}" bindtap="turnTo"> <image class="search-like-icon" src="/assets/icons/sousuo.png"></image> <text>{{item.text}}</text> <image data-index="{{index}}" class="turn" src="/assets/icons/turn.png"></image> </view> </view>
Search.js部分代码 changeSearch (e) { let value = e.detail.value if (value === '') { this.setData({ haveSerachLike: false }) return } let arr = this.data.searchLikeAllList.filter(item => item.text.indexOf(value) > -1) console.log(arr) this.setData({ haveSerachLike: true, searchLikeList: arr, }) }, turnTo: function(e){ this.saveHistory({ id: 0, recordItem: e.target.dataset.param }) wx.navigateTo({ url: '../searchDetail/searchDetail' }) },
选择结果条目上的某条内容后,点击进入详情页,同样,在历史记录中新增一条记录内容,记录保存在本地缓存中。
效果图:
感觉自己抱紧了数组方法的大腿,这里再次用到过滤filter,先给要赋予清除事件的元素通过设置data - index 的方法来标识要传递的值,然后在deleteRecord事件中将历史记录中被该事件选中的index值进行过滤,即删除所传递的index值,然后返回过滤后的数组,即没被过滤掉的记录。
Search.wxml部分代码: <view class="search-history"> <text class="zhhs">搜索历史</text> <view class="search-history-item" wx:for="{{historyRecord}}" wx:key="{{index}}"> <image class="search-history-icon" src="/assets/icons/shizhong.png"></image> <text>{{item.recordItem}}</text> <image data-index="{{index}}" class="delete" src="/assets/icons/delete.png" bindtap="deleteRecord"></image> </view>
search.js部分代码: deleteRecord: function(e){ console.log(e); let filterArr = this.data.historyRecord.filter((item, index) => { return index !== e.target.dataset.index }) this.setData({ historyRecord: filterArr }) wx.setStorage({ key: 'historyRecord', data: filterArr }) },
效果图:
(1)布局:
布局方式采用弹性布局,热搜词横向排序,并且在设置固定百分比宽度的情况下用了弹性布局下flex-wrap:wrap;进行超出则换行操作。
(2)功能实现:
在绑定的热搜词数据中给它加入hotstatus参数,表示热度情况,类型为number:
<view class="search-item"> <view class="hot-search-item" wx:for="{{hots}}" wx:key="{{item.id}}"> <view class="hot-item"> <view class="text"> <image class="hot-img" src="{{item.hotImg}}" wx:if="{{item.hotImg}}"></image> <text>{{item.text}}</text> </view> <view class="hot-status" >{{hotStatus}}</view> </view> </view> </view>
然后在页面加载事件onload中添加排序方法,检索热词数组中每个hotstatus值,按从大到小排序排列在“知乎热搜”块中:
onLoad: function (options) { var hots = this.data.hots; var hots2 = hots.sort((x, y) => y.hotStatus - x.hotStatus); // reverse()方法会反转数组项的顺序 // hots.reverse(); console.log(hots2); this.setData({ hots: hots2 })
做了好几个搜索页的功能,发现“保存历史记录并加入本地缓存”这个功能在好几个地方都用到了,每个事件中都写一遍,代码繁琐,逻辑可读性略差,所以我将该功能封装成一个方法,每次需要用到的时候,直接带着相应参数引用即可:
saveHistory (param) { let arr = this.data.historyRecord arr.unshift(param) wx.setStorage({ key: 'historyRecord', data: arr }) this.setData({ historyRecord: arr }) }
随即,上面第一条的bindconfirm事件函数则变为:
bindconfirm: function(e){ console.log(e); var recordItem = e.detail.value; this.saveHistory({ id: 0, recordItem }) turnTo事件函数: turnTo: function(e){ this.saveHistory({ id: 0, recordItem: e.target.dataset.param }) wx.navigateTo({ url: '../searchDetail/searchDetail' }) },
代码是不是变得更加简洁了?逻辑更加清晰了?这就是我们一直追求的“用最短的代码写最棒的功能!”
原生App开发中,下拉刷新和上拉加载是使用得比较多的一个功能了。
小程序开发中,小程序只提供了下拉刷新的接口。
Bug & Tip:
在滚动 scroll-view 时会阻止页面回弹,所以在 scroll-view 中滚动,是无法触发 onPullDownRefresh
若要使用下拉刷新,请使用页面的滚动,而不是 scroll-view ,这样也能通过点击顶部状态栏回到页面顶部,在这里其实也就说了在使用scroll-view时是不能使用onPullDownRefresh了。
我这里直接用了scroll-view实现下拉刷新上拉加载更多,scroll-view有三个event事件:
配置项 | 作用 |
---|---|
bindscrolltoupper | 滚动到顶部触发的回调函数 |
bindscrolltolower | 滚动到底部触发回调函数 |
bindscroll | 滚动时触发的回调函数 |
这里js代码里面其实就是处理逻辑:
上拉:我们需要在数组container-list的后面拼接数据和处理请求的页码;
首先需要封装一个获取页码数据的方法
getPage: getPage: function(){ var that = this; var pageIndex = that.data.currentPage; wx.request({ url: '', data: { page: pageIndex }, success: function(res){ if(pageIndex != 1){ // 加载更多 console.log('加载更多'); var tempArray = that.data.articles; tempArray = tempArray.concat(that.data.articles); that.setData({ allPages: that.data.allPages, articles: tempArray, hideBottom: true }) } }
然后判断当前页是否是最后一页:
if (that.data.currentPage == that.data.allPages){ that.setData({ loadMoreData: '已经到顶' }) return; }
这里加了一个定时,是为了延长上拉下拉视图的显示时间:
setTimeout(function(){ console.log('上拉加载更多'); var currentPage = that.data.currentPage; currentPage = currentPage + 1; that.setData({ currentPage: currentPage, hideBottom: false }) that.getPage(); },300);
下拉:我们需要把当前页码设置成1,articles取当前网络请求的数据。网络请求getData函数上拉下拉的区分是通过当前页码值区分的。
refresh: function(event){ var that = this; page = 1; that.setData({ articles: [{这里加入传入的刷新的数据}] scrollTop: 0, hidden:true }); that.getPage(); // GetList(this) },
来一波成品图: 因为时间比较短,知乎也是个大项目,页面、功能还需要慢慢完善,发布了的功能也有一些待改进的地方,后续慢慢把这个项目做下去,慢慢打磨技术。爱代码,爱知乎! 欢迎同样志同道合的码友们多多指教和交流。ヾ(❀╹◡╹)ノ~ 顺便附上我的项目地址:仿“知乎”微信小程序结语:
icon“赞助地”:icon
作者:mosa
链接:https://juejin.im/post/5b1cc7c5f265da6e225cedbf