阅读 849

没朋友(MPVUE),就仿个职人小程序吧~

前言

前言不知道说什么,但是感觉大家都有前言,我也不能输,那就随便唠点好了。本人是一名大三学生,是一个前端小菜鸡,这是我的第一篇掘金文章。之前和两个队友组队写了一个原创小程序——猿简历(已上线,但还有很多需要完善的)。最近初学了vue,又涉及了一下mpvue,于是就找了个小项目来练练手,既能再找找小程序的感觉,又能练练vue的写法。起初是打算一周练手完成主要功能,所以这真的是一个很小很小的小项目,没有什么技术难点,只能分享一些小tips了。还希望各位看官能看的高兴。

部分项目截图

项目准备

因为我第一次写mpvue的时候,起初无法下手,不确定要怎么构建项目,所以就把自己构建项目的步骤一并写了上来,希望能帮助到你。

初始化项目

初始化项目前,请确保本机已经正确装好node环境

  • 下载vue脚手架

    npm install vue-cli -g
    复制代码
  • 初始化项目

    vue init mpvue/mpvue-quickstart mpvue-zhirent
    复制代码

    progarm

    1. 是否使用vuex状态管理模式+库),no。原因:vuex并不是时候都需要,这只是个小型的仿写项目,所有没有用vuex。

    2. 是否使用ESLint(适用于JavaScript和JSX 的可插入linting 实用程序),no。因为使用了ESLint将会非常麻烦。

  • 进入项目根目录

    cd mpvue-zhirent
    复制代码
  • 根据package.json安装项目依赖包

    npm install
    复制代码
  • 启动初始化项目

    npm start || npm run dev
    复制代码

定义数据及数据请求的封装

定义数据

当看到职人小程序时,脑子里第一反应便是基于MVVM(Model-View-ViewModel)系统架构思想。那么定义数据便成了项目开发最重要的事情。首先搜索了一下职人的相关网站,没有找到可以爬取数据的网站(也可能是我没有看到),就只能定义假数据了。而说到定义假数据,第一想法就是用EasyMock(一个可视化,并且能快速生成 模拟数据的持久化服务)定义假数据了。于是手撸假数据撸到怀疑人生,这时候终于懂了程序员那句最美的情话了——“我给你写接口好吗?”。

我定义数据时添加了一个"errno":0,为的是在请求数据时判断接口请求的状态。active和sponsor分别是沙龙数据和主办方数据。

mock-data

数据请求与数据请求的封装

这次开发我用了axios来请求数据。

  1. 首先,安装axios

    $ npm install axios
    复制代码
  2. build > webpack.base.config.js文件中找到alias为axios添加别名

     alias: {
      'vue': 'mpvue',
      'axios':'axios/dist/axios',
      '@': resolve('src')
     }
    复制代码
  3. src > utils下添加一个axios.js文件,封装axios

    import axios from 'axios'
    import qs from 'qs'
    
    axios.defaults.timeout = 30000;
    axios.defaults.baseURL ='';
    axios.defaults.headers.post[ 'Content-Type' ] = 'application/x-www-form-urlencoded;charset=UTF-8';
    axios.defaults.adapter = function (config) {
      return new Promise((resolve, reject) => {
        let data = config.method === 'get' ? config.params : qs.stringify(config.data)
        wx.request({
          url:config.url,
          method:config.method,
          data:data,
          success:(res)=>{ 
            return resolve(res)
          },
          fail:(err)=>{
            return reject(err)
          }
        })
      })
    }
    //请求拦截器
    axios.interceptors.request.use(function ( request ) {
      // console.log(request) //请求成功
      return request
    }, function ( error ) {
      // console.log(error); //请求失败
      return Promise.reject(error);
    });
    
    // 添加响应拦截器
    axios.interceptors.response.use(function ( response ) {
      // console.log(response.data.data) //响应成功
      return response;
    }, function ( error ) {
      // console.log(error); //响应失败
      return Promise.reject(error);
    });
    
    
    export function get (url,params) {
      return axios({
        method:'get',
        url:url,
        params:params
      })
    }
    复制代码
  4. main.js文件中封装数据请求

    import Vue from 'vue'
    import App from './App'
    import axios from 'axios'
    // 将get请求引入
    import {get} from './utils/axios'
    
    // 将get请求挂载上去
    Vue.prototype.$get=get;
    
    Vue.prototype.getList = function (){
      wx.showLoading({
        title:'加载中'
      }),
        this.$get("https://www.easy-mock.com/mock/5d17149edc925c312db9c9ea/zhirent/zhirent")
        .then((res) => {
          if (res.data.errno === 0) {
            this.active = res.data.data.active;
            this.sponsors = res.data.data.sponsors
          }
        })
        wx.hideLoading()
    }
    
    Vue.config.productionTip = false
    App.mpType = 'app'
    
    const app = new Vue(App)
    app.$mount()
    
    复制代码

stylus编译

  1. 安装stylus、stylus-loader包
    $ npm i stylus stylus-loader -D
    复制代码
  2. build > webpack.base.config.js文件中添加找到rules,添加文件处理规则
    {
        test: /\.css$/,
        loader: 'style-loader!css-loader!stylus-loader'
      }
    复制代码
    如下图:
    stylus
  3. 配置完成后重启服务,一定要重启服务!!!!

mpvue踩坑之旅Start

好了,该安装的也安装了,该配置的也配置了,终于可以开始愉快又令人激动的Coding之旅了。职人小程序包含了沙龙、主办方、我的 三个主要页面,以及沙龙详情和主办方详情两个页面,我的页面包含了个人的活动报名、主办方收藏、活动收藏等信息。接下来我将分享一些项目中遇到的坑和一些主要功能的实现。

一、添加页面及自定义tabBar

添加页面

mpvue添加页面并不像小程序那样添加页面方便。需要疯狂手动添加文件,手动配置页面信息,手动添加页面路径。每次添加新页面一定要记得去app.json里添加路径信息!!!

1.创建页面文件

我们需要在src/pages目录下手动创建页面文件夹index,并在index文件夹下添加 index.vuemain.js两个文件,index.vue则是我们的html页面(可将index改为其它,只需在main.js中引入即可),main.js不可改名,如果将main改为其他名字(相关位置均做修改),都将会出现报错。

  • index.vue在页面还未写任何页面结构时 需要编写如下基本结构,否则将会出现

    pages/mine/main.js 出现脚本错误或者未正确调用 Page() 的报错信息

    <template>
      <div></div>
    </template>
    
    <script>
    export default {
    
    }
    </script>
    
    <style>
    
    </style>
    
    复制代码
  • main.js的基本结构则为

    import Vue from 'vue'
    import App from './index'  //如果index.vue改名为home.vue,这里则将index改为home
    
    const app = new Vue(App)
    app.$mount()
    复制代码

2.添加页面

每创建一个新页面都需在src/app.json文件中的pages手动 添加页面路径,否则将会出现

navigateTo:fail page "pages/index/main" is not found的报错信息

"pages": [
    "pages/index/main"
   ]
复制代码

3.配置分页面

可以通过在onLoad(){}生命周期中调用wx.setNavigationBarTitle({})api动态设置分页面的navigationBarTitle

onLoad(){
    wx.setNavigationBarTitle({
      title: '授权'
    })
  }
复制代码

也可以手动添加main.json文件,静态设置分页面的navigationBarTitle

{
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#224fa4",
    "navigationBarTitleText": "职人",
    "navigationBarTextStyle": "white"
}
复制代码

自定义tabBar

src/app.json下的tabBar下做相应更改即可,和小程序的tabBar自定义过程无异。这里要注意的是:一般会将tabBar的icon放在static/tabs目录下,其他页面图标则放在images目录下,方便管理。页面的icon可以选择svg格式的话就选择svg的图片,这样有利于节省小程序的空间,但小程序的tabBar-icon暂时还不支持svg格式的图片。我在webpack.base.config.js中设置了图片路径别名,故图片路径可直接写static/tabs/car1.png,如下图:

static

"tabBar": {
    "color": "#999",
    "backgroundColor": "#fff",
    "selectedColor": "#0c53ab",
    "borderStyle": "black",

    "list": [{
      "text": "沙龙",
      "pagePath": "pages/salon/main",
      "iconPath": "static/tabs/car1.png",
      "selectedIconPath": "static/tabs/car.png"
    }, {
      "text": "主办方",
      "pagePath": "pages/sponsor/main",
      "iconPath": "static/tabs/home1.png",
      "selectedIconPath": "static/tabs/home.png"
    }, {
      "text": "我的",
      "pagePath": "pages/mine/main",
      "iconPath": "static/tabs/mine1.png",
      "selectedIconPath": "static/tabs/mine.png"
    }
  ],
    "position": "bottom"
  }
复制代码

二、授权页面

进入小程序,跳转到授权页面,授权后跳转到沙龙页面,如果用户点击拒绝则会停留在该页面,用户下次进入小程序时,还是能跳转到授权页面。

授权
那么如何实现授权页面呢?

  • 首先,我们需要在进入小程序时通过调用wx.getSetting()来判断用户是否已经授权。如果已经授权,那么我们就可以通过调用wx.getUserInfo()来获取用户信息,如果没有授权,则跳转到写好的授权页面。那么我们就需要在App.vue中的onShow或onLaunch生命周期中进行判断

    // 获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
    wx.getSetting({
      success(res){
        if (res.authSetting['scope.userInfo']) {
          // 如果已经授权,可以直接调用 getUserInfo获取用户信息
          wx.getUserInfo({
            success: function(res) {
              console.log(res.userInfo)
            }
          })
        }else{
          // 如果未授权,则跳转到授权页面
          wx.reLaunch({
            url: '/pages/authorize/main',
          })
        }
      }
    })
    复制代码
  • 然后,通过小程序<button>自带的open-type属性开放getUserInfo能力,进行授权。再通过bindgetuserinfo属性返回获取到的用户信息,通过判断是否获取到用户信息进行判断是否授权成功,如果成功,则跳转到沙龙首页

    <button class="authorize" open-type="getUserInfo" @getuserinfo="getUserInfo">同意授权</button>
    复制代码
    getUserInfo(){
      wx.getSetting({
        success:(res) => {
          if(res.authSetting['scope.userInfo']){
            wx.showLoading({
              title:'加载中'
            })
            wx.reLaunch({
              url:'/pages/salon/main'
            })
            wx.hideLoading()
          }
        }
      })
    }
    复制代码
  • 授权成功后,刷新页面,就可以看到打印出来的用户信息啦

userInfo
就这样,一个小程序授权页面就完成啦,是不是很简单?写完之后就想起,之前组队写一个参赛的小程序,两个队友写小程序授权界面,一个队友捣鼓了半天说好难写啊,于是另一个队友将授权页面揽了下来,写完之后授权页面总是会在我意想不到的时候出现在我面前,就像疯狂在对我说

当时因为自己要做的事情真的太多了,产品经理从0到1,UI从0到1,切图仔从0到1,加上两个大佬都出问题了,觉得小菜鸡的自己就更不可能会了,也就专心做自己份内的事情。当自己尝试授权界面后,就很想手动艾特我两个队友进来挨打。咋肥事啊小老弟?

所以遇见任何难题时,都应该自己动手去尝试一下,不能觉得别人不会你就一定也不会,一定要自己去尝试写一写!!!

三、沙龙/主办方页面

这两个页面十分简单,很直观的两个数据渲染页面。两个页面写法几乎一样,我就挑一个代码少一点的来说了。

数据加载

首先,我们区别一下onLoad & onShow

  • onLoad 一个页面只会触发一次,页面第一次加载时触发,一般放一些不需要实时更新的数据
  • onShow 每次打开页面都会执行一次,页面加载就触发,一般放置实时更新的数据

综上,我们加载这两个页面数据,应该在onLoad生命周期中请求数据。由于之前我们已经将数据请求封装到main.js文件中了,故直接调用封装的方法即可。

onLoad(){
    this.getList();
  }
复制代码

数据渲染

很明显,这两个页面都需要用到数据循环,需要使用v-for嵌套时。

// 主办方页面
 <div class="sponsor">
    <div v-for="(item,index) in sponsors"  :key="index"  @click="toSponsorInfo(index)" class="card-container">
      <div class="card-content">
        <div class="card-icon">
          <image class="icon" :src="item.avatar"></image>
        </div>
        <div class="card-info">
          <div class="card-title">{{item.name}}</div>
          <div class="card-desc">{{item.info}}</div>
        </div>
        <button @click.stop="collect(index)" :class="collectList[index] ? 'like' : 'unlike'">{{collectList[index] ? '已关注' : '关注'}}</button>
      </div>
      <div class="card-footer">
        <div class="card-salon-num">共举办{{item.salonNum}}场沙龙</div>
        <div v-if="item.salonNum>0" class="card-recently">最近沙龙:{{item.salons[0].title}}</div>
      </div>
    </div>
  </div>
复制代码

而使用v-for嵌套时需要注意:在mpvue中外循环和内循环不能使用同一个index,需要更改一个其中一个的索引值标识,否则将出现

同一组件内嵌套的 v-for 不能连续使用相同的索引,目前为: index,index 的报错信息

stylus的使用

因为我们在项目准备时已经将stylus配置好了,便可以直接在index.vue中使用stylus

<style lang="stylus" scoped>
</style>
复制代码

这里scoped表示该样式是该页面的私有样式,不会污染全局

四、详情页跳转

页面跳转传参

页面随机点击一个列表,都会跳转到对应详情页,这涉及到了页面传值的问题。首先,我们需要获取当前点击的列表的index值,而index值我们在写v-for循环时,有用到v-for="(item,index) in sponsors 每一个列表的index代表着当前数据的下标值,我们可以通过将该值传递到详情页,再请求数据并通过传递的下标值找到对应的数据,最后将拿到的数据渲染到详情页就好啦。具体做法如下:

  1. 首先找到循环所在的div标签,绑定一个事件,并将当前列表的index传递过去
    <div v-for="(item,index) in sponsors"  :key="index"  @click="toSponsorInfo(index)" class="card-container">
    复制代码
  2. <script>中添加methods属性,在methods属性中创建写方法。
    // 接收传递进来的index值
    toSponsorInfo(index){
      // 拼接url,并将接收的index值传到要跳转的页面
      const url = `/pages/sponsorInfo/main?index=${index}`
      wx.navigateTo({
        url
      });
    复制代码
  3. 在详情页面中的onLoad生命周期中通过options接收传递过来的参数
    onLoad(options){
     let i = options.index;
     wx.showLoading({
      title:'加载中'
    }),
    this.$http
      .get("https://www.easy-mock.com/mock/5d17149edc925c312db9c9ea/zhirent/zhirent")
      .then((res) => {
        // 拿到对应index的主办方详情信息
        this.sponsor = res.data.data.sponsors[i];
         wx.hideLoading()
      });
    }
    
    复制代码

五、数据查询

沙龙详情的页面中,包含了举办该沙龙的主办方列表,而主办方详情里面,又包含了举办的沙龙列表。首先,定义假数据不可能在沙龙数据里面,将举办该沙龙的主办方详情也写上,这样定义数据太麻烦了,而且会重复很多数据。因为我在一个数据请求中,分了两条数据,一条是沙龙数据,一条是主办方数据。这两条数据就相当于数据库中的两个表,但由于没有建立数据库,所以where字段无法使用,但我们可以通过遍历进行数据查找。
定义数据时,我在avtive(沙龙)数据中,给出一个sponsors数组,数组中代表着举办该沙龙的主办方列表,每个主办方给出一个name字段。在sponsors(主办方)数据中,给出一个salons数组,数组代表着该主办方举办过的沙龙列表,每个沙龙给出一个title字段。

  • 沙龙详情中的主办方列表,这里给出一个sponsorsNum字段,便于判断,当主办方数字大于0时,才需要进行数据遍历查找。
    沙龙详情中的主办方列表
  • 主办方详情中的沙龙列表,这里也给出一个salonNum字段,当举办的沙龙数量大于0时,才进行数据遍历查找。
    主办方详情中的沙龙列表

以主办方详情为例

  • 首先,在数据请求时,拿到所有的沙龙数据,
    this.$http
      .get("https://www.easy-mock.com/mock/5d17149edc925c312db9c9ea/zhirent/zhirent")
      .then((res) => {
        // 拿到对应index的主办方详情
        this.sponsor = res.data.data.sponsors[i];
        // 拿到所有的沙龙数据
        let salonsInfos = res.data.data.active
    
        // 通过数组遍历拿到该主办方详情中的举办的沙龙列表
        // 如果主办方举办的沙龙数量大于0,进行数组遍历
        if(this.sponsor.salonNum>0){
          // 遍历该主办方的举办过的沙龙列表
          this.salonList = this.sponsor.salons.map(item=>{
            let sal
            // 遍历拿到的所有沙龙数据
            salonsInfos.forEach(salon => {
             // 当沙龙的主题与主办方举办的沙龙列表中的主题相同时,拿到对应的沙龙信息,并将沙龙信息替换进沙龙列表的title字段
              if(salon.title === item.title) sal = salon
            })
            return sal
          })
        }
        
         wx.hideLoading()
      });
    },
    复制代码
    这样,我们就拿到了对应title的沙龙列表的详细数据了,再将列表渲染到页面即可。

六、我的页面

这个gif截出来就成了这个鬼样子,我也不知道为什么。。。大概在暗示我要换手机了吧T-T。

从gif图中可以看出:我的页面由三部分组成,头部展示用户信息、客服和设置,tab栏选择选项,tab选项对应的展示页面。

用户头像和昵称

展示用户头像和昵称有两种写法:

  • 通过调用wx.getUserInfo()来获取用户头像和昵称,但是需要用户授权,授权后才能成功获取用户头像和昵称。

  • 使用官方的<open-data>标签直接显示用户头像和昵称(无需授权)

    <open-data type="userAvatarUrl"></open-data>    //获取用户头像直接显示在小程序中
    <open-data type="userNickName" ></open-data>    //获取用户昵称直接显示在小程序中
    复制代码

    修改<open-data>标签中的样式时需注意,这里不能像写img/image的样式一样,直接给图片一个width="50px";height="50px";border-radius="50%"的样式,这样并不会生效。图片会如下图一样,没有任何改变。

    那么怎样才能修改 <open-data>标签的样式呢?
    我们可以添加一个display:flex属性使图片大小能够改变,再添加一个overflow:hidden属性使圆角能够出现。

    <open-data class="thumb" type="userAvatarUrl"></open-data>
    复制代码
    .thumb
        width 100%
        height 100%
        border-radius 50%
        overflow hidden
        display flex
    复制代码

    这样,一个好看的头像显示就完成啦。

客服

因为这里只是一个小demo项目,加上客服功能还需要配置开发者信息以及添加客服账号,实在是麻烦,就懒得弄了。但是有个icon在这里,总感觉不能点击会很难受,于是就添加了个点击事件,当你好奇是啥的时候弹出一个暂不支持的提示框哈哈哈哈哈。别问、问就是懒,别说、说就是没必要。
如果有想要了解一下客服功能怎么实现的盆友可以自行查看一些小程序官方文档:客服消息使用指南客服账号管理

设置

点击小齿轮就会跳转到一个设置的页面,页面分为两项,第一项是职人小程序的%¥%¥?咱也不知道咋描述,他说是关于职人就是关于职人吧。第二项是主办方中心。这两项点击又是一个跳转页面,两个页面都没啥操作,就是一个页面展示。关于职人的页面我就直接截了个图上去,毕竟咱也没有UI小姐姐,只能偷偷懒了,不会偷懒的程序猿不是好程序猿hhhh。

选项卡页面切换(划重点划重点!)

当点击任意tab时,样式和展示的内容都会随之变化。
嘶,这个样式咋动态显示啊?通过点击事件给dom节点添加类名吗?有点太麻烦了吧。添加一个数据进行判断吗?也好麻烦啊。
嘶,这个又要咋操作才能根据我点击的的内容展示该展示的页面啊?点击一个tab就给一个数据,通过数据来判断是否展示页面吗?这也太麻烦了。

这个看似简单,但又有些许难度的小tab栏应该怎么实现呢?

  • 首先,我们在数据源里定义一个navList,让tab栏由数据渲染出来,这样我们可以获取它的index值,再通过index来赋值。

    data(){
        return{
            //给一个默认的changeNav值,
            changeNav:0,
            navList: [{name:'参加'},{name:'感兴趣'},{name:'关注'}]
        }
    }
    复制代码
  • 然后,我们可以通过html的data-属性来自定义数据,并将自定义数据与点击的index值进行绑定。

    <div class="tab-item"  v-for="(item,index) in navList" :key="index" :data-current="index" @click="swichNav">
    复制代码
  • 最后,通过定义的点击事件来获取自定义的数据

    methods:{
        swichNav(e){
          const current = e.currentTarget.dataset.current
          this.changeNav = current
        }
    }
    复制代码
  • 而样式问题我们则通过判断改变的changeNav值是否与index值相等来动态添加一个active的类名

    :class="{active:changeNav == index}"
    复制代码
  • 页面的转换则通过v-if来判断展示哪一个页面

    <Nothing v-if="changeNav==0" :tips="salonTip"></Nothing>
    <Interest v-if="changeNav==1" :salon="interestList" @goDetail="goSalonDetail"></Interest>
    <Collect v-if="changeNav==2" :sponsors="collectList" @go="goSponDetail"></Collect>
    复制代码

这个小程序的报名要填信息啥啥的,以及毕竟是个活动,感觉随意报名有点不太好,所以就没有写报名的小分页面,就直接展示一个没有参加的沙龙页面了。(还不是因为懒)

七、本地缓存实现主办方关注功能

通过动图我们可以看到,每个列表都有一个关注的按钮,点击关注按钮即可成功关注,点击列表查看主办方详情页也能看见关注成功的状态,点击已关注会弹出一个操作菜单,点击取消关注才能取消成功。且两个页面的关注状态都是共享的,不管在哪个页面操作关注与否,在另一个页面都会共享状态。状态共享的话,一般会想到vuex状态管理或者本地缓存,这里我选择基于本地缓存实现列表关注功能,别问,问就是没有建vuex。
那么,如何通过本地缓存来实现一个通过数据渲染的列表,点击任意列表,都可以使得对应的列表进行关注与否操作呢?

  • 第一步:给按钮添加一个点击事件,并阻止冒泡事件。
    首先,点击按钮进行关注与否操作后,紧接着会跳转到详情页,是因为按钮的父级元素中还有一个点击跳转至详情页的点击事件,如果不做任何处理,目标元素的事件会冒泡到父级元素。所以我们在写按钮的点击事件时,需阻止冒泡事件。
     <button @click.stop="collect(index)" :class="collectList[index] ? 'like' : 'unlike'">{{collectList[index] ? '已关注' : '关注'}}</button>
    复制代码
  • 第二步:在数据源定义一个默认数组collectList:[],在页面加载时读取之前本地缓存的状态查看是否有关注,如果不存在本地缓存,就把默认数组collectList添加到本地缓存。如果存在本地缓存,就将本地缓存的数据赋值给collectList
    onShow(){
        var cache = wx.getStorageSync('collectList')
        if(!cache){
          wx.setStorage({
            key:"collectList",
            data:this.collectList
          })
        }else{
          this.collectList = cache
        }
      }
    复制代码
  • 第三步:在methods中编写点击事件collect(){}
    methods:{
        collect(index){
          // 防止this指向改变
          let self = this
          // 拿到缓存区的collectList数组
          var cache = wx.getStorageSync('collectList')
          // 获取当前主办方是否被关注
          var currentCache = cache[index]
          // 如果没有被关注
          if(!currentCache){
            // 将当前列表的关注状态设置为关注
            currentCache = true
            wx.showLoading({
              title: '加载中',
            })
            //将当前列表的关注状态赋值给本地缓存
            cache[index] = currentCache
            //重新设置缓存
            wx.setStorage({
              key:'collectList',
              data:cache
            })
            // 将缓存赋值给数据源
            self.collectList = cache
            wx.hideLoading()
          }else{
            //如果存在缓存状态,则调用操作菜单
            wx.showActionSheet({
              itemList: ['取消关注'],
              success (res) {
                wx.showLoading({
                  title: '加载中',
                })
                currentCache = false
                cache[index] = currentCache
                wx.setStorage({
                  key:'collectList',
                  data:cache
                })
                self.collectList = cache
                wx.hideLoading()
              }
            })
          }
    }
    复制代码
    这样,就实现了点击哪一个主办方的关注按钮,哪一个主办方便发生状态改变的功能了,那么接下来我们则需要在详情页做类似的处理。
  • 第四步:在详情页的数据源定义一个isCollected:'',在页面加载时读取缓存状态并赋值给数据源中的isCollected
    onShow(){
        // 拿到缓存区的关注信息
        var cache = wx.getStorageSync('collectList')
        //在页面接收参数index时将option.index赋值给数据源中的index了,所以这里直接调用了this.index
        this.isCollected = cache[this.index]
    },
    复制代码
  • 第五步:给详情页的按钮添加点击事件
    <button @click="collect" :class="isCollected ? 'like' : 'unlike'">{{isCollected ? '已关注' : '关注'}}</button>
    复制代码
     methods: {
        // 接下来的操作和上面的操作没啥区别。。。我就不写了。。。
        collect(){
          var cache = wx.getStorageSync('collectList')
          let self = this
          var currentCache = cache[self.index]
          if(!currentCache){
            wx.showLoading({
              title:'加载中'
            })
            currentCache = true
            cache[self.index] = currentCache
            wx.setStorage({
              key:'collectList',
              data:cache
            })
            self.isCollected = cache[self.index]
            wx.hideLoading()
          }else{
            wx.showActionSheet({
              itemList: ['取消关注'],
              success(res){
                wx.showLoading({
                  title:'加载中'
                })
                currentCache = false
                cache[self.index] = currentCache
                wx.setStorage({
                  key:'collectList',
                  data:cache
                })
                self.isCollected = cache[self.index]
                wx.hideLoading()
              }
            })
          }
        },
    }
    复制代码

这样,就实现了基于本地缓存实现列表关注功能惹,细心的盆友可能会发现,在判断是否存在关注状态时,if和else都有共同的操作,但是我没有合在一起写,而是各自都写了一遍。是因为,合在一起写时会产生异步,当else操作还没执行完时,就执行接下来的赋值操作,导致操作不成功。解决异步可以用Promise,但是promise也要重复两次代码,能解决问题为啥不用最简单直白的方式呢?所以懒人本懒直接在if判断里重复两次代码了。

八、感兴趣/关注列表展示

由于之前我们利用本地缓存实现了将感兴趣/关注的功能,那么我们如何拿到感兴趣与关注的列表详情数据呢?同样的,我们还是可以通过数组遍历来获取到详细数据。就拿主办方关注来说,我们可以在数据加载时先拿到缓存区的主办方收藏列表,通过数组遍历拿到关注了的主办方的下角标数组,再通过遍历将对应下角标的主办方关注详情拿到。将数据渲染上去即可。

onShow(){
this.$get("https://www.easy-mock.com/mock/5d17149edc925c312db9c9ea/zhirent/zhirent")
      .then((res) => {
        // 所有的主办方
        wx.showLoading()
        this.sponsors = res.data.data.sponsors;

        // 拿到缓存中的主办方收藏列表
        var cache = wx.getStorageSync('collectList')
        // 拿到收藏的主办方的下角标数组
        this.targetList = []
        cache.forEach((item,i) => {
          if(item) this.targetList.push(i)
        });

        // 拿到收藏的主办方信息列表
        this.collectList = this.targetList.map(index => {
          let spon
          this.sponsors.forEach((item,i) => {
            if(index === i) spon = item
          })
          return spon
        })
        // console.log(this.collectList)
}
复制代码

此外,点击我的页面上的任意关注/感兴趣列表,都可以跳转到对应的主办方/沙龙详情页面,我们只需要在编写页面跳转至详情页时,将跳转的index值修改一下即可。

goSponDetail(index){
  const url = `/pages/sponsorInfo/main?index=${this.targetList[index]}`
  wx.navigateTo({
    url
  })
}
复制代码

九、沙龙详情页面的一些小功能

点击感兴趣同步用户信息

和主办方列表关注一样,列表实现感兴趣功能我就不详细描述了。与主办方页面不同的是,页面加载时需要判断一下是否感兴趣,如果感兴趣的话,页面加载时,个人信息就应该出现在头像列表上。当点击取消感兴趣时,则删除个人信息。由于我们写了授权页面,给小程序进行了授权操作,所以可以直接调用wx.getUserInfo()来获取用户信息

onShow(){
    self = this
    // 获取用户信息
    wx.getUserInfo({
      success(res){
        var userInfo = res.userInfo
        var nickName = userInfo.nickName
        var avatarUrl = userInfo.avatarUrl
        // 将用户信息赋值给数据源中的user
        self.user = {
          name:nickName,
          avatar:avatarUrl
        }
        var cache = wx.getStorageSync('interestList')
        self.isInterested = cache[self.index]
        if(self.isInterested){
          self.interestInfo.unshift(self.user)
        }
      }
    })
  },
复制代码

点击预览图片

这个调用小程序官方apiwx.previewImage就好啦。

自定义操作菜单组件(父子组件通信)

由于这个操作菜单并不是紧挨着底部的,所以我们需要自己定义一个share的组件,并在数据源给定一个isShare:false的状态,通过状态来决定share组件是否展示。首先,我们点击分享按钮,将数据源中的状态改变为true,然后再在share组件中的取消按钮定义一个点击事件,并通过$emit将事件传给父组件,从而实现父子组件间通信问题。

  • 编写share组件,并给对应的item添加对应的点击事件。分享给好友或群直接应用微信官方的button组件中open-type属性,并将open-type的值设置为share,即可实现分享给好友功能。保存沙龙海报会自动生成对应活动的二维码,没有上线就没有二维码。所以这个功能我就没有去写了。这里有一点要提的是,小程序的button如果背景色为白色,就会出现自带的边框,给button添加一个plain="true"属性就好了。
    <div class="container">
        <div class="tips" >分享该沙龙</div>
        <button plain="true" open-type="share" class="type">分享给好友或群</button>
        <button plain="true" class="type" @click="save">保存沙龙海报</button>
        <div class="cancel" @click="cancel">取消</div>
      </div>
    复制代码
  • 给share组件的数据源里添加一个share:false的数据
    data() {
        return {
          share:false
        }
      }
    复制代码
  • 点击取消按钮,子组件通过$emit触发事件来向父组件传递数据
    cancel(){
      this.$emit('cancel',this.share)
    }
    复制代码
  • 在父组件中引入share组件,并在数据源中定义isShare:false,通过isShare来判断是否展示操作菜单,并通过v-on(可简写为@)绑定了一个cancel事件来监听子组件的触发事件,
    <Share v-if="isShare"  @cancel="cancelShare"></Share>
    复制代码
    <script>
    import Share from '@/components/share/share'
    export default{
        data(){
            return:{
                isShare:false,
            }
        },
        components:{
            Share
        },
        methods:{
            //接收子组件传递过来的数据:this.share(false),并将值赋值给isShare
            cancelShare(msg){
              this.isShare = msg
            }
        }
    }
    </script>
    复制代码

就这样,一个简单的自定义操作菜单组件就完成了。既然说到了父子组件通信问题,那我就再说一下vue中,父组件如何向子组件传值,以及兄弟组件间通信问题好了。

父组件向子组件传值

由于沙龙详情页面结构实在是太多模块了,所以我每个展示模块都封装成了组件,提高代码可读性。以及有两个页面都涉及到了点击展开详情,于是我也封装成了一个组件,提高代码复用性,这里就以点击展开详情的组件来说一说父组件如何向子组件传值的问题好了。

  • 自定义一个文本伸缩组件(?&%……¥我也不知道怎么描述这个组件,就这么叫好了。)
    <template>
      <div>
        <div class="info-desc" :class="isEllipsis ?'ellipsis':'unellipsis'">{{info}}</div>
        <div class="text-expander" @click="ellipsis" >
            <text class="text-expander__content">{{isEllipsis?'展开全部':'全部收起'}}</text>
            <image class="text-expander__icon" :src="isEllipsis?'/static/images/down.svg':'/static/images/up.svg'"></image>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          isEllipsis:true
        }
      },
      methods: {
        ellipsis(){
          this.isEllipsis =!this.isEllipsis
        }
      },
      props:[
        'info'
      ]
    }
    </script>
    
    <style lang="stylus" scoped>
    .info-desc
      font-size 14px
      display -webkit-box
      -webkit-box-orient vertical
      text-overflow ellipsis
      overflow hidden
      line-height 26px
      text-align left
    .ellipsis
      -webkit-line-clamp 3
    .unellipsis
      -webkit-line-clamp 0
    .text-expander
      margin 0 auto
      width 80px
      height 20px
      .text-expander__content
        color #c4c8c7
        font-size 16px
      .text-expander__icon
        width 15px
        height 15px
    </style>
    复制代码

在不同页面上,文本中展示的内容不一样,所以我们需要向父组件接收数据,父组件向子组件传递数据可以通过props属性来传递。子组件像父组件要info数据,通过props来索取。

  • 在父组件中引用子组件,并通过v-bind(可以简写为@)来绑定数据。
    <Expander :info="salonInfo"></Expander>
    复制代码

就这样,子组件就可以拿到父组件中定义的salonInfo数据了。有兴趣的盆友还可以了解一下$on(子组件接收数据),以及非父子组件的兄弟组件间通信方式(建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件)。

结语

结语也不知道说些啥,但别人也都有,我也不能输!那还是随便唠点吧QAQ。
本来是打算找个界面简洁功能稍多的小项目来练手,结果最后还是百分之八十是在切图,不得不说,切图真的很快乐,所以我的同桌老王经常调侃我:“纯粹的前端”,当然这里指的是纯粹的切图。但一只酱绝不服输,这次采用的是easy-mock,下次写小项目就尝试jsonp请求数据,或者使用万能的爬虫爬取数据,或者全栈开发一个小项目。写项目也是一件非常快乐的一件事情,当你实现一个小功能时,或者自己想到 一个解决的办法时,真的很有成就感(虽然这个项目真的没有什么技术难点T-T)。最后奉上源码,希望能帮助到大家一小丢丢。

关注下面的标签,发现更多相似文章
评论