仿写bilibili小程序 —— 我的第一个微信小程序项目

6,422 阅读11分钟

前言

每一个程序员,最开始的时候都是一个小白,一点一点积累,一步一个脚印,慢慢成长起来的。相信对于大佬们来说,写一个小程序并不难,但对与我这种学习没多久的人来说,每一步都是在泥泞中前进,不知道泥中哪里有坑,当然这对于新手来说,是一种成长,踩的坑多了,路就逐渐平坦起来。

所以还是要加油奋斗。

20210822153913.gif

这是我写的第一个小程序项目,由于是和别人合作,我就写了其中一部分,我想通过写这篇文章,记录下我的第一个小程序项目,同时把自己在写小程序的所得,与大家分享,希望能帮到在努力中的人。

小程序分享

先贴上源代码地址 bilibili_weapp

总体效果展示

来看看效果:

rjkix-68nmc.gif

这些是写小程序中用到的一些工具,网站:

有赞vant组件

微信小程序官方文档

fiddler抓包工具

阿里巴巴字体库

页面

整体

开始写小程序,第一个就是切页面,首先看看首页

QQ截图20210822170428.png

QQ截图20210822171655.png

我们写页面的时候,会用到哪些东西?

  1. 外部的样式,比如weui样式

  2. 外部的组件,比如vant

  3. 自写组件

  4. 自写样式

  5. 字体库,如 阿里巴巴字体库

     像一些搜索框,导航栏等一些通用样式,或者组件,都可以引入外部的直接用,用法都有文档,看看就会用了
    
     而没有类似的组件和样式的话,那就要自己写了
    
     页面很多地方都要用到图标,图标也是引用外部字体库
    

页面架构

-van-search
-van-tabs
     -热门
         - hot_item
     -追番
         - chase_date_item
             - day_of_week
         - chase_index
         - chase_recommend
         - chase_recommend

用vant组件 searchvan-tabs快速搭好整体页面

QQ截图20210822180915.png

然后在热门这个tab下,全部都是一个样式的视频列表,所以直接用一个wx:for循环就行了,tab用scroll-view包起来,便于滑动

QQ截图20210822191450.png

追番这个tab就比较多的东西了,首先 chase_date_item 显然又是一个 van-tabs ,但是每个tab样式都差不多,于时也可以用wx:for 循环,循环里再套一个番剧列表的循环

QQ截图20210822192033.png

剩下的都一样,用wx:for循环列表

QQ截图20210822192401.png

下面是视频播放页和番剧播放页页面,搜索页只有一个van-search,所以就不展示了

视频播放页:

QQ截图20210822193342.png

QQ截图20210822193517.png

番剧播放页:

QQ截图20210822193613.png

QQ截图20210822193737.png

样式(经验踩坑分享)

简单的样式大家都会写,所以我就不贴出来了,有需要的可以去我的gitee上去源代码,链接放在文章最后面,喜欢的话可以点个收藏

这里我会介绍一下我的经验和踩过的坑

1. 弹性布局

大多数的布局都可以弹性布局解决,弹性布局的justify-content无法解决的水平布局,可以用margin代替,使用特殊的选择器,比如:

.chase_date_item__content:nth-child(4n+1){
  margin-left: 0;
}

选中的是4n加1的子元素

有些特殊需求的话,就用绝对定位,其他的时候少用定位,影响性能

2. 弹性布局有一个问题就是使用弹性布局的盒子上定义的margin和padding都会增加盒子的宽高,用宽高限制也没用,但是只要加上一条样式就可以解决这个问题:

    box-sizing: border-box;

3.一行或多行文本超出显示...

我们都会一行文本超出则显示...,样式为:

  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;

但是要实现多行文本超出则显示...怎么实现?:

  text-overflow: ellipsis;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;

QQ截图20210823095742.png

4. transition实现动态高度不定的下拉效果 我们知道transition可以通过过渡实现动画一样的效果,下面是视频简介的下拉效果

jax2z-bqbph.gif

这里看着就是过渡高度,但是简介信息的不定长的,所以,他不能固定高度。height为auto,transition无法发挥效果,于是只能通过过渡max-height来代替,max-height的值设置为height不可能超过的大小,但是也不能太大,会导致过渡效果不明显

.drop_content{
  max-height: 40vh;
  overflow: hidden;
  transition: all 0.5s;
}
.drop_content_on{
  max-height: 0;
  overflow: hidden;
  transition: all 0.5s;
}

5. 不听话的button按钮

button在小程序中有很多的作用,但是我们在用到它的时候,它的样式总是无法让我们称心如意,所以我们要将他的样式重置

button::after { border: none; }
button {
  display: inline;
  padding: 0 !important;
  margin: 0 !important;
  font-weight: normal !important;
  color: #000 !important;
  background-color: #fff !important;
  }

这里我是需要button作为一个承载功能的容器,所以我把样式都加上 !important,有些属性不提高等级是不会生效的,这也是微信小程序button组件内部的一些操作,没有办法。如果有需要的话,这些属性还可以通过再写一个类覆盖

6. 微信小程序的动画实现

小程序中的css3动画效果不会生效,想要实现动画效果,必须借助js,和小程序的animation属性和api

先看看效果

nptu9-g8etg.gif

wxml

QQ截图20210823105357.png

js

let animation = wx.createAnimation({   //创建一个animation
      duration: 300,
      timingFunction: 'linear',
      delay: 0
    });
    animation.rotate(180).step();    //添加动画
    this.setData({
      ani:animation.export()       //将animation添加到data中,数据驱动页面
    });

完成后你会发现,这个动画只会运行一次???虽是bandtap事件,但是他不会每次点完就执行一次

当然你可以在加一个动画让他回退到之前的状态,再次点击就有用了

    animation.rotate(180).step();
    animation.rotate(0).step();  // 加一个动画回到之前的状态

但是这其实不好看,可以看到我的动画是点一次转一次,看看实现

Rotate(n){         // 传入一个参数
    let animation = wx.createAnimation({
      duration: 300,
      timingFunction: 'linear',
      delay: 0
    });
    animation.rotate(180*n).step();   // 每次点击根据传入的n改变动画旋转角度
    this.setData({
      ani:animation.export()
    });
  },

因为这个按钮还有更改页面数据的功能,后面在功能介绍时还会再细说

7. 组件样式:组件是别人写的,他会给你提供样式接口,但是往往达不到你想要的效果,其实这也是跟button一样的,适当的加 !important 提高优先级就能生效了,比如:

QQ截图20210823113144.png

这是van-tab组件,我想设计选中的样式,他提供了接口,但是一旦写样式,其本来的样式就被破坏了。
你想自己写原来的样式,但是组件内置的样式又会阻碍你,  适当的用!important让自己写的样式生效                  
.tab_active{
  background-color: #FB7299;
  margin-top: 10rpx !important;
  border-radius: 60rpx;
  color: #fff ! important;
  height: 70rpx;
  line-height: 70rpx !important;
}

外置组件有很多属性来设置不同的样式,这个要多看文档,我用到的组件: van-search van-tab

-功能

页面都处理完了那就开始写功能吧,处理页面其实也挺累人,但是接下来会更累

20210822154118.gif

数据

要写一个完整的页面,很重要的一个就是数据,没有数据看不到效果,我们在写页面的时候也会用一写静态数据更好看到效果,也没办法设计一些功能,而且微信小程序的页面是数据驱动式的,这也很重要

话虽这样说,我也没有做成真的数据,用的是一些假数据,我尝试去找了bilibili的api接口,但是并没有找到简单好用的接口,而其他的一些复杂接口也不会用,所以只能寻找其他方法。如果各位大佬有好的点子也希望能留言赐教

20210822122805.gif

我用的是fiddler抓包工具抓取数据,具体的使用方法可以参考这篇文章

抓取的json数据还可以直接导入云数据库

QQ截图20210823141005.png

此外一些没抓到的手写的数据,和一些不固定的数据,即随时间变化的数据,一起放在了data文件夹下,

QQ截图20210823141314.png

在需要时使用module.exports + require在js中引入

QQ截图20210823141708.png

QQ截图20210823141739.png

功能介绍

1. 页面,样式切换

小程序经常需要切换页面,当然实际上并没有跳转页面,只是隐藏了,这时我们一般使用wx:if:

<view wx:if="{{...}}"><view>

但是这种方法不利于频繁切换,因为wx:if会在需要的时候渲染,不需要的时候就会销毁,频繁切换的话就会频繁渲染销毁,作为代替可以用hidden,真正的隐藏:

<view hidden="{{currentId!=0}}"></view>
<view hidden="{{currentId!=1}}"></view>

样式切换可以使用三元运算符:

<view class="play-information__title {{isDrop ? 'detail_drop':'detail_nodrop'}}"></view>

2. 上拉到顶下拉到底加载数据

其实这个并不难,因为scroll-view组件自身带有这两个事件,可查看[文档](https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html)
<scroll-view class="hot_item-scroll" scroll-y
    bindscrolltolower="addmoreAfter"
    bindscrolltoupper="addmoreBefore"
    upper-threshold="0"
    lower-threshold="20"
    >

最后两个属性表示距离多少时触发事件,顶部设置为0,底部设置为20

链接数据库中指定集合,只取其中20条放到data中,同时取得集合全部数据长度

const db=wx.cloud.database();
const plays=db.collection('plays');
async setvoicedata(){
    let res=await plays
    .limit(20)
    .get();
    this.voiceList=res.data;
    this.total=(await plays.count()).total;
    this.setData({
      voiceList:this.voiceList
    })
  },

两个事件函数

判断页面数据长度,如果超过所有数据总长度,则显示没有更多然后返回,否则,再从所有数据中拉取20条数据(使用skip(),跳过已经拉取的数据,取到新数据),使用展开运算符放到data中,注意异步

async addmoreAfter(){
    const index=this.data.voiceList.length;
    if(index>=this.total){
      wx.showToast({
        title: '没有更多数据',
        icon:'none'
      })
      return ;
    }
    wx.showToast({
      title: '加载中',
      icon:'loading',
      duration:1000
    })
    let {data:voiceList}=await plays
    .skip(index)
    .limit(20)
    .get()
    this.setData({
      voiceList:[...this.data.voiceList,...voiceList]
    })
  },
  async addmoreBefore(){
    const index=this.data.voiceList.length;
    if(index>=this.total){
      wx.showToast({
        title: '没有更多数据',
        icon:'none'
      })
      return ;
    }
    wx.showToast({
      title: '加载中',
      icon:'loading',
      duration:1000
    })
    let {data:voiceList}=await plays
    .skip(index)
    .limit(20)
    .get()
    this.setData({
      voiceList:[...voiceList,...this.data.voiceList]
    })
  },

3. 分享功能

微信小程序有内置api,button的open-type设置为share;

<button open-type="share"><text class="iconfont icon-weixin1"></text>分享给好友</button>

有必要的话再设置一些数据,用来传递视频信息

<button open-type="share"><text class="iconfont icon-weixin1" data-id="{{...}}"></text>分享给好友</button>

124l0-wnj9u.gif

设置分享的样式,js中有一个onShareAppMessage函数,处理这个事件,返回一个对象,其中包含标题,图片,打开时跳往的地址,地址可以传递这个视频的id

onShareAppMessage: function () {
    return{
    title:this.data.playInformation.title,
    imageUrl:this.data.playInformation.video_cover,
    path:"/pages/play/play?id="+this.data.playInformation.playid
    }
  }

4. 收藏功能

3gdep-8p5e9.gif

同样是连接数据库集合,更改数据,这里还更用户数据有关系,但因为我不负责用户这一块,便没有写,原理一样,同时更改数据库数据 和 本地数据

const db=wx.cloud.database();
const _=db.command;     // 云函数
async collectPlay(){
    if(this.data.isCollect){
      this.playInformation.collect--;
      this.setData({
        isCollect:false,     // 用来判断是否收藏的数据
        playInformation:this.playInformation
      });
      playCollection
      .where({
        playid:this.data.playInformation.playid
      })
      .update({
        data:{
          collect:_.inc(-1)    // 自减
        }
      })
    }
    else{
      this.playInformation.collect++;
      this.setData({
        isCollect:true,
        playInformation:this.playInformation
      })
      playCollection
      .where({
        playid:this.data.playInformation.playid
      })
      .update({
        data:{
          collect:_.inc(1)   // 自增
        }
      })
    }
  },

5. 搜索功能 搜索是对数据库搜索,所以和上个功能差不多,不过要用到正则,模糊搜索

<van-search value="{{value}}"
 show-action
  shape="round"
  bind:search="onSearch"
  clearable="true"
  />
onSearch(e){
    wx.showToast({
      title: '查询中',
      icon:'loading'
    });
    let keyword=e.detail;       // search 组件上触发事件时传来的关键字
    if(!keyword.trim()){       // trim 去除空格方法
      wx.showToast({
        title: '请输入要输入的关键字',
        icon:'error'
      })
      return ;
    }
    posterCollection
    .where({
      title:db.RegExp({      //正则表达式
        regexp:keyword,
        options:'i'        // 忽略大小写
      })
    })
      .orderBy('ts','desc')  // 排序
      .get()
      .then(res=>{
        console.log(res);
        this.setData({
          result:res.data
        })
      })

  },

6. 换一换

nptu9-g8etg.gif

换一换的动画样式在前面已经介绍了,换一换的数据也是有限的,换到最后又会回到第一个推荐,所以这是一个轮循,点击去往下一个,到最后又返回第一个

const {result}=require('../../data/recommend');  //  全局引入需要的数据,这个文件可以在需要的替换掉
const recommend = require('../../data/recommend');
changeRecommend(e){
    let id=e.currentTarget.dataset.id;
    let [n1,n2]=this.data.n; 
    // n 是计数用的数组,因为有两个一样的换一换,所以定义了一个数组分别计数,把两个合并在一起
    let n=this.data.n[id-1];  // 用传入的Id确定是触发了哪个换一换
    if(n>6){
      n=0;
    }            // 判断轮循是否到最后一个,如果是,则跳往第一个
    const recommend=modules[id].items;  // 数据是数组形式存储,根据id取出当前所需数据
    let recommendShow=recommend.slice(n*3,n*3+3); // 取当前轮循数据
    this.Rotate(n);                //  触发动画
    if(id==1){
      this.setData({
        recommendShow,
        n:[n+1,n2]             // 更新数据
      })
    }
    else{
      this.setData({
        recommendShowMy:recommendShow,          // 更新数据
        n:[n1,n+1]
      })
    }
  },

7. 根据日期显示数据 zh0va-j98fm.gif

虽说这个不难,但当时花了不少时间,就是设计数据,代码如下

    chaseWeek:['一','二','三','四','五','六','日'],
    episodes:[{dayofweek:1,data:{...},...] //同样是一个数组,按星期排序
    //准备数据
// 根据日期改变数据的排序
datebar(){
    const firstepisodes=this.data.episodes;  // 得到初始数据
    let episodes=[];
    let todayOfWeek=new Date().getDay();  // 得到今天星期
    for(let i=0;i<7;i++){               //  循环改变数据顺序
      let j=(i+todayOfWeek+4)%7;
      episodes.push(firstepisodes[j]);
    }
    this.setData({
        episodes
      })                   // 设置数据
  },

由于异步的原因,我把这个函数放在了onready周期里,页面中使用wx:for输出就会根据日期来变化了

其他

关于组件和通过画布生成海报的功能,目前还未完全解决,还需待我再练个九九八十一天,或许就会有下一篇文章了吧,如果大佬们有分享的话,还请赐教

20210822154059.gif

结语

以上便是我全部的分享内容了,我这小小的一步,不知是否给你带来帮助或者是消遣呢,希望大家能喜欢,喜欢的话就点个赞哦

源码

最后希望也支持一下我的源代码 bilibili_weapp

20210823183907.gif