• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

微信小程序开发(二)首页

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

轮播

小程序内部有现成的 swiper 组件可以用:

<swiper class="banner" indicator-dots="{{BannerIndicatorDots}}" autoplay="{{BannerAutoPlay}}" interval="{{BannerInterval}}" duration="{{BannerDuration}}" circular="{{BannerCircular}}">
	<block wx:for="{{SwiperImageUrls}}" wx:key="*this">
		<swiper-item>
			<image src="{{item}}" mode="widthFix" style="width:100%"></image>
		</swiper-item>
	</block>
</swiper>

indicator-dots 取值可以是 true 或 false,表示是否显示轮播图下方的用来代表第几张图片的圆点;autoplay 自动播放;interval 是切换的间隔时间,以毫秒为单位;duration 是切换的持续时间,也是以毫秒为单位;circular 是否循环播放。

这里的 是 wxml 语言特有的语法,效果类似于 view,但不会加载到实际网页中。后面的 wx:for 也是小程序特有的机制,意思是把整个 block 中的内容做循环展示。双重大括号 {{ }} 中包含的内容是 js 代码中 data 字段的某个变量,这种赋值方式叫做数据绑定,即把 js 文档中的 data 字段中某一个变量绑定到 wxml 中以供调用显示。wx:key 表示循环的索引,如果不需要索引就可以省略或者像我一样写个 *this。

swiper-item 是轮播图的一个单位,此处代表一张图片,我在里面放了一个 标签,然后用数据绑定的方式把后台的图片链接绑定到这里,从而实现数据和前端分离便于后期运维。


选项卡导航栏

导航栏需要用 标签来实现,导航栏的每一项都是一个 view,以 flex 布局排列并平均分布长度,或根据具体需求自定义每一项的长度也可以。

<view class="tab {{TabFixed? \'tab-position-fixed\' : \'\'}}" id=\'tab\'>
	<view wx:for="{{TabItems}}" wx:key="id" class="tab-item {{CurrentTab == item.id ? \'tab-item-active\' : \'\'}}" data-current="{{item.id}}" bindtap="clickTab">{{item.title}}</view>
</view>

这里给导航栏设置了一个吸顶效果,根据 data 字段中 TabFixed 标志的取值来给导航栏施加一个吸顶的类 tab-position-fixed,这里我采用的吸顶方法是监听页面滑动,如果滑动距离超过了初始时导航栏距离顶部的距离,就把 TabFixed 标志设置为 true,也就吸顶了。js 代码如下:

// 获取初始状态时导航栏距离页面顶部的距离
getInitTab2Top: function() {
  wx.createSelectorQuery().select(\'#tab\').boundingClientRect((res) => {
    this.setData({
      InitTab2Top: res.top
    })
  }).exec()
},

// 监听页面滑动事件
onPageScroll: function(e) {
  if (this.data.TabFixed != (e.scrollTop > this.data.InitTab2Top - 1)) {
    this.setData({
      TabFixed: (e.scrollTop > this.data.InitTab2Top)
    })
  }
},

先创建一个请求绑定前台的 id 为 tab 的元素,然后调用 boundingClientRect().exec() 方法得到此元素距离用户手机显示屏顶部的距离,存到 InitTab2Top 中,之后监听页面滑动即可,如果滑动的位置距离顶部的距离大于 InitTab2Top,说明需要吸顶,否则就不需要吸顶。但这种做法存在一些问题,我用一个巧妙的招数解决了,但不知道实际环境中的解决方案。此时我们虽然能实现导航栏的吸顶,但导航栏下方的内容会在导航栏吸顶后顶上去,占据原来属于导航栏的内容,这样就会导致在滑动页面时不连贯,会看到内容突然平移了一下,用户体验不好。为了解决这个问题,我设置导航栏下面的内容根据导航栏是否固定进行平移 translate,这样暂时抵消掉了导航栏吸顶带来的 bug,可以在后面的代码中看到这里的操作。

导航栏内部的每一项都是一个 view,需要根据当前选项卡是否处于激活状态来设置其样式,所以要对每一个选项卡都进行编号,再设置一个 data-current 来传递当前选项卡的编号是啥,在后台用一个变量 CurrentTab 来表示当前显示的选项卡的编号,再绑定点击事件 bindtap 从而实现前台更改编号传递数据给后台,后台更改变量值控制前台显示内容。后台的控制导航栏点击事件的 js 代码如下:

// 单击选项卡切换
clickTab: function(e) {
  if (this.data.CurrentTab == e.target.dataset.current) {
    return false
  } else {
    this.setData({
      CurrentTab: e.target.dataset.current
    })
  }
},

e.target.dataset.current 是我在前台设置的 data-current 传递过来的参数,这样就拿到了当前选项卡的编号,再把它赋值给 this.data.CurrentTab 即可实现选项卡的切换。此处暂时还没到切换选项卡内容的部分,仅仅是到达了根据 CurrentTab 控制哪个选项卡处于激活状态而已,真正的切换在下面一节。


选项卡内容

选项卡内容其实就是 swiper 加 swiper-item 的嵌套,我在其中增加了一个 scroll-view 来实现新闻栏目的内部滚动,每一栏都是用几个 view 实现的,循环产出即可:

<swiper style="height: {{ClientHeight? ClientHeight + \'px\' : \'auto\'}}" class="{{TabFixed? \'swiper-translate\' : \'\'}}" current="{{CurrentTab}}" duration="{{TabSwiperDuration}}" bindchange="swiperTab">
	<swiper-item wx:for="{{TabItems}}" wx:key="id">
		<scroll-view id="scroll" scroll-y="true" style="height: {{ClientHeight? ClientHeight + \'px\' : \'auto\'}}" bindscrolltolower="loadMore">
			<view class="swiper-item-container">
			<block wx:for="{{item.content}}" wx:key="index">
				<view class="content" bindtap="contentTapped" data-id="{{item._id}}">
					<view class="content-text">
						<view class="title"><text>{{item.title}}</text></view>
						<view class="subtitle"><text>{{item.subtitle}}</text></view>
						<view class="date"><text>{{item.date}}</text></view>
					</view>
				</view>
			</block>
			</view>
		</scroll-view>
	</swiper-item>
</swiper>

由于 swiper 默认不能像 view 一样进行自动延申,所以首先需要对它进行高度自适应的设置,这里我给它一个内嵌的 style,设置高度根据 ClientHeight 变量的取值而改变,后台这里的 ClientHeight 是根据用户手机屏幕高度自动变化的,并减去了轮播图占据的高度,代码如下:

// 设置swiper自适应高度
onReady: function() {
  wx.getSystemInfo({
    success: (res) => {
      this.setData({
        ClientHeight: res.windowHeight - this.data.InitTab2Top
      })
    },
  })
},

之后根据导航栏是否固定在顶部,对选项卡内容进行高度补偿,修复了上文提到的 bug。

swiper 的 current 属性表示当前选项卡的编号,从 0 开始,我把它绑定到 CurrentTab 上,从而实现后台控制前台选项卡的切换。同时我给它设置了 bindchange 事件,后台处理的 js 代码 如下:

// 滑动切换选项卡
swiperTab: function(e) {
  this.setData({
    CurrentTab: e.detail.current
  })
  wx.pageScrollTo({
    duration: 500,
    scrollTop: this.data.InitTab2Top
  })
},

这样就实现了切换选项卡的所有功能,还进行了一点视觉优化,切换时自动滑动到导航栏吸顶的位置,跳过轮播图,给一个 500ms 的切换动画时长,实测效果不错。

swiper-item 也是批量生成的,我一共设置了三大块内容,所以就是三个 swiper-item,内部嵌套 scroll-view 进行滚动,触底事件 bindscrolltolower 绑定好,作为加载更多文章的入口。高度也需要和 swiper 一样做成自适应的,因为小程序的 scroll-view 暂时还不支持自动扩展高度,有点难顶。注意这里 scroll-y 一定要设置为 true,否则滚不动。

再往里就没什么说的了,绑定点击事件并传递文章的 id 以供阅读,再根据标题、副标题和日期区分一下字体即可。下面介绍一下加载文章部分,触底加载 loadMore 放在下一节介绍。

我设置在页面 onload 时加载一次文章,数据库用的是小程序自带的免费云开发数据库(穷冒烟了),所以一次最多只能加载 20 个记录,也就是我首次加载的文章个数,其他文章在触底时会自动加载,直到加载完毕。加载文章的 js 代码:

// 从云数据库中获取每个选项卡的内容
getTabItemContent: function(tabindex, loadnum) {
  let CollectionName = "Index_TabItem" + tabindex.toString() + "_Content"
  let ContentTarget = "TabItems[" + tabindex.toString() + "].content"
  let BottomTarget = "TabItems[" + tabindex.toString() + "].reachBottom"
  const db = wx.cloud.database()

  db.collection(CollectionName).count().then(res => {
    let TotalArticles = res.total
    // 需要加载的文章数 >= 已有的文章总数,必是初次加载,且加载后没有更多文章
    if (loadnum >= TotalArticles) {
      db.collection(CollectionName).orderBy(\'date\', \'desc\').get().then(res => {  // 最新文章放在前面
        for (let i = 0; i < res.data.length; i++) {
          res.data[i].date = util.formatTime(res.data[i].date)  // 格式化时间
        }
        this.setData({
          [ContentTarget]: res.data,
          [BottomTarget]: true
        })
      })
    // 需要加载的文章数 < 已有的文章总数
    } else {
      let curArticleNum = this.data.TabItems[tabindex].content.length
      if (!curArticleNum) {  // 初次加载(无需skip)
        db.collection(CollectionName).orderBy(\'date\', \'desc\').get().then(res => {
          for (let i = 0; i < res.data.length; i++) {
            res.data[i].date = util.formatTime(res.data[i].date)
          }
          this.setData({
            [ContentTarget]: res.data
          })
        })
      } else {  // 非初次加载
        db.collection(CollectionName).orderBy(\'date\', \'desc\').skip(curArticleNum).get().then(res => {
          let originContent = this.data.TabItems[tabindex].content
          for (let i = 0; i < res.data.length; i++) {
            res.data[i].date = util.formatTime(res.data[i].date)
            originContent.push(res.data[i])
          }
          this.setData({
            [ContentTarget]: originContent
          })
          if (res.data.length + curArticleNum >= TotalArticles) {  // 没有更多的文章了
            this.setData({
              [BottomTarget]: true
            })
          }
        })
      }
    }
  })
},

我把数据库名写死在里面了,确实,并不是一个明智的做法,懒了。调用云数据库需要先 wx.cloud.init(),这里我在 onload 方法中已经 init 过了,然后创建一个数据库对象 db,根据 collection 名和 doc 名获取指定的数据库中的记录即可,一次最多20个记录,取文章的时候需要用 orderBy 根据时间进行排序,还需要考虑用 skip 方法跳过已经取出来的内容。这里我根据是否是初次加载来区分后续有无更多文章,如果文章数小于等于 20 的话,说明加载完了就没有更多文章了,设置 BottomTarget 标志位为 true;否则文章数大于 20,需要根据当前文章列表中有无文章来判断是不是初次加载,从而确定后续是否还有文章。

这里网上还有一种思路,我觉得应该比我这种方法要好。他们先把所有文章按照时间排序取出来放到 data 中的数组中,然后每次 loadMore 的时候就从中取出一部分给当前显示出来的数组,这样省得老是调数据库麻烦,下次再做的话我会使用这种方法试试。


下拉加载提示框

触底时需要给一些提示,这里我采用的是底部弹出的提示框:

<block wx:if="{{TabItems[CurrentTab].scrolltolower}}">
	<view class="load-more-container" bindtap="loadMoreExit" style="{{TabItems[CurrentTab].closePopupTextBox? \'display:none;\' : \'\'}}">
		<block wx:if="{{TabItems[CurrentTab].reachBottom}}">
			<view class="load-more-text">没有更多文章了</view>
		</block>
		<block wx:else>
			<view class="load-more-text">滑动屏幕浏览更多</view>
		</block>
	</view>
</block>

根据当前选项卡是否触底的标志位来决定是否显示此提示框,如果触底就显示出来,单击提示框可以关闭,提示框内部文字根据是否触底显示不同的内容。后台 js 代码如下:

// 监听触底事件
loadMore: function() {
  let scrolltolowerTarget = "TabItems[" + this.data.CurrentTab.toString() + "].scrolltolower"
  let closePopupTextBoxTarget = "TabItems[" + this.data.CurrentTab.toString() + "].closePopupTextBox"
  this.setData({
    [scrolltolowerTarget] : true,
    [closePopupTextBoxTarget] : false
  })
  if (!this.data.TabItems[this.data.CurrentTab].reachBottom) {
    this.getTabItemContent(this.data.CurrentTab, this.data.LoadArticleNum)
  }
},

// 监听消息提示框被点击事件
loadMoreExit: function() {
  let closePopupTextBoxTarget = "TabItems[" + this.data.CurrentTab.toString() + "].closePopupTextBox"
  this.setData({
    [closePopupTextBoxTarget] : true
  })
}

这里就基本没有要解释的部分了,都是一些我设置的标志位,如 scrolltolower 表示当前选项卡是否触底,closePopupTextBox 表示是否关闭提示框。

最后还要再提一下页面跳转 wx.navigateTo,即点击了新闻栏目之后应该跳转的操作,js:

// 点击列表中一个栏目触发事件
contentTapped: function(e) {
  let curTab = this.data.CurrentTab
  wx.navigateTo({
    url: \'../index_details/index_details\',
    success: function(res) {
      res.eventChannel.emit(\'acceptDataFromOpenerPage\', [curTab, e.currentTarget.dataset.id])
    }
  })
},

由于我在数据库中设置的就是选项卡 id + 文章 id = 文章唯一标识,所以这里要把选项卡也传递进来,用 emit 方法将参数传递到新的页面即可。在新的页面用 eventChannel.on() 方法实现参数的接收,新页面 js 代码如下:

// 接收从index传递过来的参数
getParamFromOpenerPage: function() {
	let that = this
	const eventChannel = this.getOpenerEventChannel()
	eventChannel.on(\'acceptDataFromOpenerPage\', function(param) {
		that.setData({
			tabid: param[0],
			articleid: param[1]
		})
		that.getDetails(param[0], param[1])
	})
},

that.getDetails() 方法是我后面写的自定义函数,从云数据库拿数据的。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap