仿王者荣耀小程序-攻略页面
前言
"上号上号!!!你在干什么呢"
无论在何时何地身边总是会响起这熟悉的话语,这貌似在大学生生活中很普遍。
可惜可惜,本人什么游戏都玩,什么都不精,那我就必须在学习、工作方面努力了。
作为一名在校生,小程序是一种对全栈项目最好的练习,也是一个必备的知识点。正好王者荣耀小程序是一个不错的小程序,其中也有一个攻略页面。
开冲!
- 更新记录 2021/8/22更新新闻点赞效果
IDE
总体架构
- 该项目是微信小程序云开发项目,使用wxml+wxss+js+component+云数据库结合,命名格式采用BEM格式。
|-wzry 项目名
|-cloudfunctions 云函数模块
|-miniprogram 项目模块
|-components 自定义组件
|-herosType 英雄信息排列组件
|-navBarTitle 自定义topbar组件
|-newsPoster 新闻文章组件
|-data 自定义数据
|-allHeros.js 封装获取云数据
|-acticleData.js 用户文章数据
|-circleList.js 新闻文章数据和新闻类型列表
|-herosMap.js 英雄图鉴中最新英雄
|-herosRank.js 3个上分英雄
|-segment.js 段位排行
|-upScoreData.js 上分页面数据
|-miniprogram_npm 构建有赞组件库
|-pages 页面
|-introduction 攻略主页面
|-upScore 上分宝典页面
|-heros 全部英雄页面
|-search 搜索页面
|-style 样式库
weui.wxss weui样式库
app.js 全局js
app.json 全局json配置
app.wxss 全局wxss
其中的data文件下全为固定数据,采用module.exports
暴露,在需要使用的地方,可以使用const {getHeroOrderByType} = require("../../data/allHeros")
进行引入,这里{}
使用的是es6 的解构
,当然也可以直接用一个常量接受使用。
关于云数据库,我采用的是csv
格式进行导入小程序云数据库表
中,csv
格式制作,可以先创建一个excel文件
进行编辑数据,然后另存为csv格式
,需要注意的一点,文件中的中文需要转换为utf-8编码
,可以在另存为csv格式前,先修改文件格式为txt,然后通过vscode进行修改编码格式,接着在改回excel格式,最后另存为csv格式。
项目规划
将项目的架构搭建后,分析各页面的功能,思考相互页面之间的联系,清楚如何进行动态数据绑定,如何获取数据,各页面有哪些细节需要注意等等。
项目解构
以下就是我的所有页面
tabbar
由于微信自带的tabbar格式就可以满足王者荣耀小程序的tabbar就没有再去自定义tabbar,而是直接使用默认格式了。写在app.json
中,pagePath只有一个页面,其他页面由团队其他人负责,我这就没有修改成他们的。
"tabBar": {
"selectedColor": "#a68f5c",
"borderStyle": "white",
"backgroundColor": "#fff",
"list": [
{
"text": "主页",
"iconPath": "images/tabbar/home.png",
"pagePath": "pages/Introduction/Introduction",
"selectedIconPath": "images/tabbar/home_on.png"
},
{
"text": "精选",
"iconPath": "images/tabbar/jingxuan.png",
"pagePath": "pages/Introduction/Introduction",
"selectedIconPath": "images/tabbar/jingxuan_on.png"
},
{
"text": "攻略",
"iconPath": "images/tabbar/hero.png",
"pagePath": "pages/Introduction/Introduction",
"selectedIconPath": "images/tabbar/hero_on.png"
},
{
"text": "我",
"iconPath": "images/tabbar/wo.png",
"pagePath": "pages/Introduction/Introduction",
"selectedIconPath": "images/tabbar/wo_on.png"
}
]
},
精选主页
- 对页面分析
topbar
最上面的是topbar,王者荣耀小程序和默认的topbar格式不一样,所有我选择自定义组件, 这里取名字有点错误,当时以为topbar也属于navBar的一种。o(╥﹏╥)o
<!-- introduction.wxml -->
<navBar isback="{{false}}" title="王者荣耀"></navBar>
<!-- navBarTitle.wxml 组件名为navBar-->
<view class="topbar-wrapper">
<view class="topbar-body">
<view wx:if="{{isback}}" bindtap="_goBack" class="topbar-back">
<van-icon name="arrow-left" />
<view class="verticalLine"></view>
</view>
<view class="topbar-title">{{title}}</view>
</view>
</view>
这里让王者荣耀四个字和右边胶囊平齐没有使用类似wx.setNavigationBarTitle
这样的接口,而是直接使用css
中padding-top
一点一点调。从父组件中传入两个参数,isback
控制是否显示回退按钮(<),title
为页面标题,_goback
函数控制回到上一个页面,使用的是 wx.navigateBack
API。
中间部分
新英雄排列我做成了一个组件,因为在全部英雄中复用了两次。但组件是为全部英雄分类而做的,新英雄排列没有进行修改。
<view class="heros__type">{{type}}</view>
<view class="heros__headImg heros-type_line">
<view class="heros" wx:for="{{arr}}" wx:key="index">
<image src="{{item.img_url}}"></image>
<text>{{item.name}}</text>
</view>
</view>
其结构很简单,使用了弹性布局,开启了自动换行flex-wrap: wrap;
,在css布局中对于各英雄之间的空白,我使用的是margin-right
,但给第5个.heros:nth-child(5)
设置为0。
上分排行榜: 给整个盒子绑定了一个点击事件跳转到上分界面
,同样使用的是弹性布局,使用了3次弹性布局,整个大盒子是一个弹性盒,左边的文字也是一个,右边图片也是,图片右边的箭头是用伪类来做的,通过transform: rotate(45deg);
旋转45度达到效果
.heros-upImg::after{
content: "";
width: 16rpx;
height: 16rpx;
position: absolute;
right: -16rpx;
border-top:1px solid grey ;
border-right:1px solid grey;
transform: rotate(45deg);
}
点击全部英雄可以跳转到全部英雄界面
用户文章和直播推荐
文章类型有三种情况,我都使用假数据完成了效果
- 文章没有图片
- 文章只有一张图片
- 文章有3张及以上的图片
其中我将有图片和无图片分为两种情况书写(建议可以看看注释)
<!-- 带图片的文章 -->
<view class="acticle" wx:for="{{article}}" wx:key="id" wx:for-item="act">
<view class="title">{{act.title}}</view>
<!-- 判断文章的类型,有两类,一类是文章,一类是直播 -->
<block wx:if="{{act.type=='pic'}}">
<!-- 文章的内容 -->
<view class="content">{{act.content}}</view>
<!-- 判断文章的图片是否大于1-->
<block wx:if="{{act.imgUrl.length>1}}">
<view class="imgList">
<!--点击事件:开启遮罩层,效果为图片放大 -->
<view class="img" wx:for="{{act.imgUrl}}" wx:key="id" catch:tap="onClickShow" data-id="{{act.id}}" data-index="{{item.id}}">
<image src="{{item.src}}" mode='aspectFit' />
<!-- 判断图片的数量 -->
<block wx:if="{{index+1>=3}}">
<view class="img_total">共{{act.imgUrl.length}}张</view>
</block>
</view>
</view>
</block>
<!-- 图片为1张的情况 和上边不同之处 mode='widthFix' widthFix:缩放模式,宽度不变,高度自动变化,保持原图宽高比不变-->
<block wx:else>
<view class="img__one" wx:for="{{act.imgUrl}}" wx:key="id" data-id="{{act.id}}" data-index="{{item.id}}" catch:tap="onClickShow" >
<image src="{{item.src}}" mode='widthFix' />
</view>
</block>
<!-- 遮罩层 -->
<!-- custom-style为自定义样式 -->
<van-overlay show="{{ show }}" bind:click="onClickHide" custom-style="background-color:black" lock-scroll="flase" z-index="200">
<!-- 使用transparent 为了让swiper 自带的指示点隐藏 -->
<swiper indicator-dots="false" indicator-color="transparent" indicator-active-color="transparent"
class="overlay-swiper" current="{{current}}">
<swiper-item class="overlay-swiper-item" wx:for="{{nowArry}}" wx:key="id">
<view class="swiper-current">{{item.id}}/{{nowArry.length}}</view>
<view class="overlay-img">
<image src="{{item.src}}" mode='aspectFit' />
</view>
</swiper-item>
</swiper>
</van-overlay>
</block>
</view>
- 文字部分,使用了文字超出省略,如果是文字是单行省略,则可以使用
white-space: nowrap;overflow: hidden;text-overflow:ellipsis;
如果是固定行数省略,则可以使用overflow: hidden;display: -webkit-box;-webkit-line-clamp: 2; -webkit-box-orient: vertical;
- 图片是排列用的是flex布局,为了获取到图片中件部分,在这里还使用了定位,
left: 50%;top:50%;transform: translate(-50%,-50%);
做到绝对居中。
-
有图片的文章设置了有赞组件遮罩层,通过点击事件修改
show
数据为true
将图片放大,在组件基础上添加可以进行滑动图片,缺点是没发将右上角胶囊覆盖住。 -
遮罩层如何完成交互的呢~~~ 我给每张图片的父容器绑定了一个捕获事件(防止冒泡)
onClickShow
,然后使用data-id="{{act.id},data-index="{{item.id}}
将数据中每篇文章的唯一标识id
和每个图片的唯一标识id
传入函数中(id为文章标识,index为图片标识),可以通过函数的默认参数event
,打印event参数可以得到下图。传过来的id放在event.currentTarget.dataset
中,显示图片和滑动图片并不难,但要保证所选的文章和图片要保持一致,(如最上方的数字)这是就需要用到传入的数据,通过this.setData
方法将index-1
作为swiper
的current
属性、id-1
作为文章数组的下标。(这里减一的原因是我数据中都是从1开始的)。
<!-- 带直播的文章 -->
<block wx:if="{{act.type=='video'}}">
<video poster="{{act.image}}" objectFit="cover" duration="{{act.duration_raw}}" src="{{act.video}}"></video>
<view class="video_bottom">
<view class="video__zhubo">
<view class="video__avatar">
<image src="{{act.avatar}}"></image>
</view>
<text>{{act.name}}</text>
</view>
<view class="video_type">{{act.desc}}</view>
</view>
</block>
- 直播推荐:这里本来是点击跳转到另一个小程序的,但我不知道其他小程序的appid,所有就放弃了跳转而是使用了一个视频代替,使用了
video
标签
上分宝典页面
值得一讲的地方就是这里也使用了遮罩层,同样
这里还使用了radio单选框组件,对于交互功能,也通过引入data中segment.js
段位数据,这里点击相应段位就会切换段位,通过对radio
绑定点击事件传入name
值修改data{}展示到页面的相关数据。
||
|
|
最下面使用了一个小动画,使用方法为css动画@keyframes
,修改定位中的right
起到移动的效果,期间慢慢的修改字体图标
的opacity
透明度
最强上分榜
最强上分榜、输出狂魔榜、开始上分都是上分宝典页面的一部分,写在siwper中的swiper-item
。两个页面布局是一样的,写两个是为了展示展示。他们的布局很普通,使用了大量的弹性布局,然后在讲数据引入,使用它主要是因为好用,当然使用其他布局手段也可以。
全部英雄
-
搜索框使用的是
weui
自带的搜索框,做了一点修改,点击搜索框会跳转到搜索页面进行搜索,减少全部英雄页面的负担。 -
该页面重点是下面的英雄分类,使用了云数据库。
- 建立好数据表
- 引入数据表,进行筛选
- 将筛选好的数据分类好分别插入wxml
- 建立数据表,我在开头已经写了方法,可以使用导入的方式,英雄数据.csv,也可以自己一条一套记录添加
- 引入数据表,筛选分类,我将代码封装在
data文件
下的allHeros.js
,注释很重要!!!!
//引入云数据库
const db = wx.cloud.database()
const allHeros = db.collection("allHeros")
// 将db.command设置为一个字符,方便使用
const _ = db.command
// 设置每次获取数据记录的限制20条
const limit = 20
// 将获取数据封装为一个函数,然后只需要暴露函数就可
const getHeroOrderByType = async (type) => {
// where 对云数据库进行筛选
let {data} = await allHeros.where(_.or([{ // _.or()或语句,不同字段不同值,符合一个就ok
hero_type: _.eq(type)
},
{
hero_type2: _.eq(type)
}
])).orderBy("hero_id", "desc").get() // orderBy() 排序,desc表示降序,asc表示升序
// 因为知道所有类型的数据都不超过40条 所以只要重复一次
// 如果超过40条,可以修改判断语句改为 while(data.length==limit*index) 需要定义一个变量index储存筛选次数
if(data.length ==20){
let arr = await allHeros.where(_.or([{
hero_type: _.eq(type)
},
{
hero_type2: _.eq(type)
}
])).skip(limit).orderBy("hero_id", "desc").get();
data = [...data,...arr.data] // 将两次查询到的结果合并为一个数组
}
//返回数组
return data
}
const allOccupation = {
getHeroOrderByType
}
module.exports = allOccupation
- 在heros.js 引入封装好的筛选js,使用es7 的
async
和await
来处理异步
const {getHeroOrderByType} = require("../../data/allHeros")
const type=["战士","法师","坦克","刺客","射手","辅助"]
// 设置数据封装为函数
async getHeroType(){
this.setData({
zhanshi:await getHeroOrderByType(type[0]),
fashi:await getHeroOrderByType(type[1]),
tanke:await getHeroOrderByType(type[2]),
cike:await getHeroOrderByType(type[3]),
sheshou:await getHeroOrderByType(type[4]),
fuzhu:await getHeroOrderByType(type[5]),
})
},
onLoad(options) {
this.getNewHeros()
this.getHeroType()
this.getnewsPost()
},
赋值后的数组(zhanshi...)可以直接在wxml通过wx:for="{{zhanshi}}"
使用
新闻点赞
上图也使用了云数据库,点击修改云数据库中的support(点赞数)和supported(是否点赞过)
- 上图使用
newsPoster组件
,交互功能:点击点赞数加1,颜色变红,在点减1,颜色恢复 将更新数据代码
封装在allHeros.js,
先在父组件页面
引入allHeros.js获取新闻数据
,然后传入子组件
,通过子组件
点击后将数据传入父组件
,接着在父组件页面引入allHeros.js的更新数据函数
:使用where
筛选出需要修改的记录,然后使用update
更新数据,在调用更新数据函数
。
//allHeros.js
// 获取数据
const getNewsPost = async ()=>{
let {data} = await newsPost.orderBy("id","asc").get()
return data
}
// 更新数据,修改点赞数
const updataNewsPost= async (id,support,supported)=>{
await newsPost.where({//根据自己设置的id筛选,也可以通过别的唯一字段筛选
id:_.eq(id)
}).update({
data: { //需要修改的数据
support:support,
supported:!supported
}
})
}
// 子组件
methods: {
onSupport(e){
let {support,id,supported}= e.currentTarget.dataset
// 将数据传入父组件
this.triggerEvent("support",{support,id,supported})
}
}
// 父组件页面js引入
const {updataNewsPost} = require("../../data/allHeros")
// 修改点赞数
async onSupport(e){
//获取数据
let {id,supported,support} = e.detail
//判断是否点赞过 support在数据库中默认值为false
if(!supported){
//点赞+1
await updataNewsPost(id,support+1,supported)
}else{
//点击-1
await updataNewsPost(id,support-1,supported)
}
//重新让页面获取所有数据,达到渲染页面效果
this.getnewsPost()
},
踩坑系列
- 自定义topbar的时候回会导致整个页面会往上移动,需要设置topbar的高度,同样也要修改页面相关的高度。
- 遮罩层确定点击的当前文章以及滑动的图片,需要区分它们的唯一标识,this.setData要对应好数据。
- 只有一张图片的文章,当时用css搞了很久,一直都不对劲,一直查找资料,才发现修改image中mode属性就ok,心态直接起飞。
- 云数据库筛选的时候,let {data} 一定要加{},因为函数返回的是一个promise对象,对象之中才是我们想要的数组。
个人体会
写完这些页面,个人感觉挺累的,虽然我负责的这些页面比较简单,交互功能较少,(云函数没用上o(╥﹏╥)o),第一次写小程序的精力,希望以后越来越好。
源码
源码 欢迎大家给项目一个star, 鼓励开源更多的项目, 谢谢。