哔哩哔哩( ゜- ゜)つロ 干杯* 仿bilibili小程序-个人中心页面

1,813 阅读4分钟

前言

  • 学习了一阵子微信小程序云开发,为了巩固所学的知识和提高实战经验,决定与同学一起合作整一款小程序。这里主要分享一下我负责的部分和一些的学习过程,希望对您有所帮助,新人小白一枚,各方面都有很多不足之处,请多指教(〜 ̄▽ ̄)〜

效果图

  • 先来看看部分效果图吧 ٩(๑^o^๑)۶
  • 个人中心效果图 1629465288225.gif
  • 个人空间效果图 1629465200230.gif
  • 好友列表效果图(可实现取消关注,加关注,分组功能) 1629465892368.gif
  • 好友模糊搜索效果 1629281478128.gif

开始前准备

说明

  • 该项目基于小程序云开发,使用的模板是云开发快速启动模板,开发过程使用了组件,云函数,数据库等相关知识,该项目所有数据均存在云端数据库中

注:数据权限要选择所有用户可读,仅创建者可读写 (´△`)

总体架构

-- cloudfunctions  云函数
   remove    删除
-- commponent  自定义组件
   list   默认分组
   special    特别关注
-- images   
   icon    图标
-- pages
   mine         个人中心
   mySpace         我的空间
   myFriend          我的关注
   friendSearch        搜索好友

云函数

云函数简单来说就是在云后端(Node.js)运行的代码,本地看不到这些代码的执行过程,全封闭式只暴露接口供本地调用执行,本地只需等待云端代码执行完毕后返回结果。这也是面向接口编程的思想体现。

remove 云函数

  • 在我的好友页面取消关注时需要调用remove云函数
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
const db=cloud.database()
const defaultList=db.collection('defaultList')
const specialConcern=db.collection('specialConcern')
// 云函数入口函数
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()
  
  const _id=event._id    //获取要取消关注传入的up主的 _id
  const dl=await defaultList.doc(_id).remove()    //从数据库集合中查找出并删除
  const sc=await specialConcern.doc(_id).remove()
  return {     //返回删除后的结果
   dl,
   sc
  } 
}

自定义组件

该组件会在我的好友页面用到,我是在commponent文件夹下新建自定义组件list和special,并且实现点击关注按钮弹出操作菜单,可取消关注,加关注,分组这些功能

  • special组件的wxml部分(list组件同理) [S098K]0Y6(W9$$L$BC83{9B.png
xml部分
<view class="special" >
<view class="special_hd">
<view class="avatar">
<image src="{{specialConcern.avatar}}"/>
</view>
</view>
<view class="special_bd">
<text class="up">{{specialConcern.up}}</text>
<span class="digest">{{specialConcern.digest}}</span>
</view>
<view class="special_ft" bindtap="getInfo">
<button wx:if="{{tapIndex==1}}" class="concern" bindtap="concern">+ 关注</button>
<button wx:else class="areadyconcern" bindtap="cancleConcern">特别关注</button>
</view>
</view>

获取信息

//获取up主的相关信息
 getInfo(){
      this.triggerEvent('getInfo',{
        avatar:this.data.specialConcern.avatar,
        up:this.data.specialConcern.up,
        digest:this.data.specialConcern.digest
      })
    }

操作菜单

  • 点击已关注按钮弹出操作菜单,自定义组件触发事件时,使用 triggerEvent 方法,指定事件名、detail对象和事件选项 [72N@@X@SHH@CV5@YF%QVL]V.png
 cancleConcern(){
     //弹出操作菜单,选择取消或移组
      wx.showActionSheet({
        itemList:['默认分组','取消关注'],
        success:res=>{
          console.log(res);
          this.setData({
            tapIndex:res.tapIndex   //选择默认分组,tapIndex为0;取消关注,tapIndex为1
          }) 
          // 自定义触发事件 cancle
          this.triggerEvent("cancle",{ 
            _id:this.data.specialConcern._id,
            tapIndex:this.data.tapIndex
          })
        }
      })
    },
    //重新关注
    concern(){
     this.setData({
        tapIndex:2    //让 +关注 按钮隐藏,已关注按钮出现
     })
     // 自定义触发事件 concern
     this.triggerEvent("concern",{ 
      _id:this.data.specialConcern._id,
      tapIndex:this.data.tapIndex
    })
    }

个人中心页

~EZ{$(DU_PS(SESS{AOFD.png [{ZO]FU8)DB8SS00FE~(N7Z8.png

授权登录

  • 个人中心页主要重点就是登录操作,所以我在这里只讲登录方面
  • 由于考虑到用户信息多个页面都将用到,为了方便,我将授权获得的信息存入全局,在app.js中做以下操作
 globalData : {},
  onLaunch: function () {
    const userInfo=wx.getStorageSync('userInfo')
    if(userInfo){
      this.globalData.userInfo=userInfo
    }
  • 登录前页面内容空白,头像为默认头像,授权后获取信息登录,内容展开,使用hasUserInfo判断是否为授权且为登录状态
<block wx:if="{{canIUse && !hasUserInfo}}">
<image src="../../images/icons/defaultTx.png" mode="aspectFill" class="owner-avatar"/>
<button class="owner-name" open-type="getUserInfo" bindgetuserinfo="getUserInfo">点击登录</button>
</block>
<block wx:else>
<image class="owner-avatar" src="{{userInfo.avatarUrl}}"  mode="aspectFill"></image>
<text class="owner-name">{{userInfo.nickName}}</text>
</block>
  data: {
    canIUse: wx.canIUse('button.open-type.getUserInfo'),   //判断小程序的API是否在当前版本可用
    hasUserInfo:false,
    userInfo: {},
  },
// 调用用户信息
async getUserInfo(e){
  wx.setStorageSync('userInfo', e.detail.userInfo)
  app.globalData.userInfo = e.detail.userInfo    //将信息放入全局app.js
  if(app.globalData.userInfo){        //这里是为了判断是否授权成功
    this.setData({
      userInfo:e.detail.userInfo,
      hasUserInfo:true,
      canIUseGetUserProfile:true
    })
  }
},
//记住授权登录,防止下次使用时又要重新登录
async onLoad(options) {
  const userInfo=app.globalData.userInfo     
  if(userInfo){
    this.setData({
      hasUserInfo:true,
      userInfo:userInfo,
    })
  }
},

我的空间页

SG5AF67BNH}L0JU8Y2HV@FB.png

页面布局

  • 整体页面布局简单,多采用flex布局
  • 关于背景图片,自定义头部导航栏,只需在json中添加一条代码即可
"navigationStyle": "custom"
  • 关于导航栏和滑块,current表示当前滑动的页面,index表示导航栏索引,使用它们让导航栏和页面联动,页面的html其他布局就不介绍了。
<!-- 导航栏 -->
<view class="nav">
  <view class="nav-content {{index==current?'active-nav':''}}" wx:for="{{navList}}" wx:key='{{index}}' data-index="{{index}}" bindtap="currentNav">
    <text>{{item.title}}</text>
  </view>
</view>
</view>
<!-- swiper -->
<swiper style="height:{{currentHeight}}px;background-color: white;" current="{{current}}" bindchange="currentPage">
// 当前滑块页
currentPage(e){
  this.setData({
  current:e.detail.current
  })
  },
// 当前选中导航栏
currentNav(e){
  this.setData({
    index:e.currentTarget.dataset.index,
    current:e.currentTarget.dataset.index
  })
  },

我的好友页

V(XXW2_QB4G(1S4L2}YO_49.png

导航栏

  • 页面用swiper将关注和粉丝做成两个滑块,可以来回滑动,点击导航栏也可切换,实现导航栏和滑块联动
wxml
<!-- 导航栏 -->
<view class="nav" >
<view class="concern-fans {{index==current?'active-nav':''}}" wx:for="{{concern_fans}}" wx:key="index" data-index="{{index}}"
bindtap="currentNav">{{item.title}}</view>
</view>                                                
<!-- 滑块内容 -->
<view class="swiper-content">
<swiper style="height: 100vh;" current="{{current}}" bindchange="currentPage">

js
//当前选中的滑块
 currentPage(e){
    this.setData({
      current:e.detail.current
      })
  },
 //当前选中的导航栏
  currentNav(e){
    this.setData({
      index:e.currentTarget.dataset.index,
      current:e.currentTarget.dataset.index
    })

组件声明

  • 搜索栏使用了vant组件库需声明,关注列表的自定义组件也需声明(在json中声明)
//myFriend.json
  "usingComponents": {
    "list":"/components/list/index",
    "special":"/components/special/index",
    "van-search":"../../miniprogram_npm/@vant/weapp/search/index"
  },

搜索栏

  • 搜索部分我是直接引入van-search组件,这里详情可查看 vant组件库,真正的搜索不需要在这个页面实现搜索功能,真正的搜索会单独建一个页面完成,它只需要完成跳转即可。所以我单独建了一个搜索页面friendSearch 放在pages文件夹下,在下面的搜索页面中会详细讲解。
<!-- 搜索栏 -->
<navigator url="../friendSearch/friendSearch">
<van-search focus="false" shape="round" placeholder="搜索我的关注">
</van-search>
</navigator>

关注列表栏

  • 列表标题栏,此处唯一比较细节的就是通过改变specialisShow的布尔值来控制列表栏的展开与否。 _Z%[A56ZKZWF{A{89{[E1YO.png]}}}]
//myFriend.wxml 
<!-- 关注列表 -->
<view class="content">
<view class="concern" bindtap="openClose1">
<image wx:if="{{specialisShow}}" class="arrows" src="../../images/icons/downarrow.png"/>
<image wx:else class="arrows" src="../../images/icons/uparrow.png"/>
<text class="text">特别关注</text>
<span class="count">{{specialCount}}</span>
</view>

//myFriend.js
openClose1(e){        //列表是否展开
    const specialisShow=this.data.specialisShow
    if(specialisShow==true){
      this.setData({
        specialisShow:false,
        
      })
    }else {
      this.setData({
        specialisShow:true,    
      })
    } 
  }

  • 关注列表栏,使用了自定义组件special(自定义组件在前面组件目录中有详细介绍)在myFriend.js中绑定了在组件方法中自定义的触发事件getInfo,cancle,concern,并传值 [S098K]0Y6(W9$$L$BC83{9B.png
//myFriend.wxml
<!-- 关注组件 -->
<block wx:for="{{specialConcern}}" wx:key="index" wx:if="{{specialisShow}}">
<special id="special" specialConcern="{{item}}" bind:getInfo="getInfo" bind:cancle="specialCancle" bind:concern="specialConcern"></special>
</block>

//myFriend.js
getInfo(e){           //将选中的up主信息缓存
    wx.setStorageSync('info',e.detail)
  },
specialConcern(){         //重新关注
    let info=wx.getStorageSync('info')        //获取选中up主信息
    specialConcern
    .add({
      data:{
        up:info.up,
        digest:info.digest,
        avatar:info.avatar
      }
    })
  }
specialCancle(event){
    let tapIndex=event.detail.tapIndex
    let _id=event.detail._id
    if(tapIndex==1){                    //tapIndex=1则表示操作菜单选中取消关注
       wx.cloud.callFunction({          //调用remove云函数实现取消关注功能
          name:'remove',
          data:{
            _id:_id
          }
        })
    }
    else if(tapIndex==0){             //tapIndex=0表示选中移分组
      let info=wx.getStorageSync('info')      //获取信息并在目标分组添加信息
      defaultList
      .add({
        data:{
          up:info.up,
          digest:info.digest,
          avatar:info.avatar
        }
      })
   wx.cloud.callFunction({        //在目标分组添加信息后,删除原先分组中该选中的信息
        name:'remove',
        data:{
          _id:_id
        }
      }) 
    }
  },

搜索页面

  • 该页面是通过点击好友页面中的搜索栏跳转的真正搜索页面,搜索功能将在这个页面实现。
//搜索框
<van-search value="{{value}}"
shape="round" 
focus="true"
placeholder="搜索我的关注"
action-text 
use-action-slot
bind:change="onChage"
bind:search="onSearch">
<view slot="action" bind:tap="onClick">搜索</view>
</van-search>
//搜索成功后的结果
<block wx:for="{{defaultList}}" wx:key="index">
<list defaultList="{{item}}"></list>
</block>
//搜索失败
<view class="blank" wx:if="{{searchError}}">
    <image class="blank-img" src="https://s1.hdslb.com/bfs/static/jinkela/space/assets/nodata02.png"/>
    <text class="blank-text">找不到该用户~</text>
 </view>

模糊查询

  • 搜索成功 TY_7M7HE(VN{`LR}[7MNR9R.png])

  • 搜索失败 U@NA}0CV59LNP@4QAHB)RMM.png

  • 监听输入值,通过onChange() 随时监听搜索框输入的值并将值赋给value

onChage(e){            
    // console.log(e);
    this.setData({
      value:e.detail
    })
  }
  • 封装查询函数queryArray()通过输入的value值实现查询,细节的是我为了实现模糊查询,使用indexOf查询字符串,若字符串中至少有一个匹配成功则输出结果
async queryArray(){
    let value=this.data.value
    let arrList=this.data.arrList
    for(let i=0;i<arrList.length;i++){
      if(arrList[i].up.indexOf(value)>=0){
        // console.log([arrList[i]]);
        this.setData({
          defaultList:[arrList[i]]
        })
      }
    }
    else{              //查询失败
      this.setData({
        searchError:true
      })
    }
  },
  • 合并数组,由于我是将特别关注的数据和默认关注的数据分别放在两个集合,但查询时我希望它们在一个数组里方便查询,所以需要将两数组合并,定义一个合并数组函数addArray(),需要的时候调用就ok
async addArray(){
  let arr1= await db.collection('defaultList')   //默认关注信息数组
.get()
.then(res=>{
  return res.data
})
let arr2= await db.collection('specialConcern')   //特别关注信息数组
.get()
.then(res=>{
  return res.data
})
let arr=[...arr1,...arr2]
this.setData({
 arrList:arr
})
},
  • 调用函数queryArray()
// 键盘查询
  onSearch(){
    this.queryArray()
  },
  // 点击查询
  onClick(){
    this.queryArray()
  },

源码

项目源码,还包含了另外两同学的成果,含首页等其它页面的源码

结语

终于写完啦,(ಡωಡ)hiahiahia 在此非常感谢给予帮助的老师同学,在我遇到bug时恼到薅头发的时候及时救我于水火之中。如果你喜欢这篇文章或者可以帮到你,不妨点个赞吧!(๑• . •๑) 同时也非常希望看到这篇文章的你在下方给出建议!