说点儿闲话

上周一,技术主管忽然分给我一个任务,是微信页面的开发任务,专门指明了让用 iView Weapp来做。

意思很明显了,是开发微信小程序呢,之前从没做过小程序项目,不过很久之前自己学过一点儿,上手起来不难,这里分享一下实现的过程。

最终效果 先睹为快

安装小程序开发者工具 并 新建一个小程序代码

首先,需要安装小程序开发者工具,如果已经装过了,直接用就可以了。

按照微信开放文档 你的第一个小程序 新建一个小程序,开发过程中,小程序的 AppID可以先试用测试号。

vs code 编写小程序代码

在vs code中进行代码编写,vs code需要安装以下插件:
1. minapp

支持微信小程序标签、属性的智能补全,并且提示中包含文档内容(同时支持原生小程序、mpvue 和 wepy 框架,并提供 snippets)。  

2.wechat-snippet

这个插件主要的功能就是代码辅助,代码片段自动完成,可以作为上个插件的补充。  

3.wxml

这款插件用于将wxml代码进行高亮显示,并且提供代码格式化的功能,可将代码格式化为较易阅读的样式。  

4.vscode weapp api 

为 VSCode 提供微信小程序 API 提示及代码片段。

搭建项目

整合目录结构

小程序目录结构 这里不再赘述,看官方文档就好。

新建的小程序默认已经有index和logs,那么,我这里是将index,复制一份改为mine,新建list,删除logs,list的数据将直接使用logs的数据做为例子,所以,logs可以先留着自己看看。在根目录下新建images,用于存放项目中的图片。

小程序配置

小程序配置 按照文档来配置小程序, 全局配置和页面配置等。

然后,做如下修改:
app.json
pages:

"pages": [
    "pages/index/index",
    "pages/list/list",
    "pages/mine/mine"
  ],

引入iView Weapp

快速上手 - iView Weapp按照文档,到 GitHub 下载 iView Weapp 的代码,将 dist 目录拷贝到自己的项目中。然后按需引用组件。

底部导航菜单

将需要用的图标放在images/tabBar/下,然后在app.json增加 tabBar:

"tabBar": {
    "color": "#333333",
    "selectedColor": "#2e75b6",
    "list": [
      {
        "pagePath": "pages/index/index",
        "selectedIconPath": "images/tabBar/tabBar-s1.png",
        "iconPath": "images/tabBar/tabBar-s2.png",
        "text": "首页"
      },
      {
        "pagePath": "pages/list/list",
        "selectedIconPath": "images/tabBar/tabBar-k1.png",
        "iconPath": "images/tabBar/tabBar-k2.png",
        "text": "列表"
      },
      {
        "pagePath": "pages/mine/mine",
        "selectedIconPath": "images/tabBar/tabBar-w1.png",
        "iconPath": "images/tabBar/tabBar-w2.png",
        "text": "我的"
      }
    ]
  },

效果如图:


首页index页面实现

轮播图

首页上方先放一个轮播图,直接使用 组件中的视图容器—— swiper 滑块视图容器 即可实现轮播图,还是先把图片放置在images中,然后在index.js中配置data,并设置swiper的属性。

index.js:

Page({
  data: {
    imgUrls: [
      '../../images/index/pic1.jpg',
      '../../images/index/pic2.png',
      '../../images/index/pic3.jpg'
    ],
    indicatorDots: true,  // 是否显示面板指示点
    autoplay: true,  // 是否自动切换
    interval: 2000,  // 自动切换时间间隔
    duration: 1000,   // 滑动动画时长
  },
})

在index.wxml中:

<swiper indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}" class="swiper">
  <block wx:for="{{imgUrls}}" wx:key="index">
    <swiper-item>
      <image src="{{item}}" class="slide-image" />
    </swiper-item>
  </block>
</swiper>

index.wxss:

/**index.wxss**/

.swiper {
  height: 300rpx;
  border-bottom: 1rpx solid #ccc;
}

.slide-image {
  width: 100%;
  height: 100%;
}

图标宫格、通告栏、信息卡片

图标宫格、通告栏、信息卡片这三个模块,直接使用 iView Weapp 的组件。
在index.json中:

{
  "usingComponents": {
    "i-grid": "../../dist/grid/index",
    "i-grid-item": "../../dist/grid-item/index",
    "i-grid-icon": "../../dist/grid-icon/index",
    "i-grid-label": "../../dist/grid-label/index",
    "i-icon": "../../dist/icon/index",
    "i-row": "../../dist/row/index",
    "i-card": "../../dist/card/index",
    "i-panel": "../../dist/panel/index",
    "i-notice-bar": "../../dist/notice-bar/index"
  }
}

在index.wxml中,swiper下继续写:

<i-grid>
    <i-row>
        <i-grid-item>
            <i-grid-icon>
                <i-icon size="24" type="activity" />
                <i-grid-label>宫格</i-grid-label>
            </i-grid-icon>
        </i-grid-item>
        <i-grid-item>
            <i-grid-icon>
                <i-icon size="24" type="addressbook" />
                <i-grid-label>宫格</i-grid-label>
            </i-grid-icon>
        </i-grid-item>
        <i-grid-item>
            <i-grid-icon>
                <i-icon size="24" type="barrage" />
                <i-grid-label>宫格</i-grid-label>
            </i-grid-icon>
        </i-grid-item>
    </i-row>
    <i-row>
        <i-grid-item>
            <i-grid-icon>
                <i-icon size="24" type="collection" />
                <i-grid-label>宫格</i-grid-label>
            </i-grid-icon>
        </i-grid-item>
        <i-grid-item>
            <i-grid-icon>
                <i-icon size="24" type="computer" />
                <i-grid-label>宫格</i-grid-label>
            </i-grid-icon>
        </i-grid-item>
        <i-grid-item>
            <i-grid-icon>
                <i-icon size="24" type="coupons" />
                <i-grid-label>宫格</i-grid-label>
            </i-grid-icon>
        </i-grid-item>
    </i-row>
</i-grid>

<i-panel title="滚动 通告栏">
    <i-notice-bar icon="systemprompt" loop speed="800">
        2018年世界杯,将于6月14日至7月15日举行;2018年世界杯,将于6月14日至7月15日举行;
    </i-notice-bar>
</i-panel>

<view style="margin: 16px">标题</view>
<i-card full title="卡片标题" extra="额外内容" thumb="![](file:///C:\Users\SSJ\AppData\Roaming\Tencent\QQTempSys\%W@GJ$ACOF(TYDYECOKVDYB.png)https://i.loli.net/2017/08/21/599a521472424.jpg">
    <view slot="content">内容不错</view>
    <view slot="footer">尾部内容</view>
</i-card>

首页布局就算是完成了,是不是还挺简单的呢?跟着做下去,你将得到一个简单的微信小程序Demo。

我的mine页面实现

获取用户信息:名字、头像,及 功能列表,直接上代码:
mine.js:

//mine.js
//获取应用实例
const app = getApp()

Page({
  data: {
    motto: 'Hello World',
    userInfo: {},
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo')
  },
  //事件处理函数
  bindViewTap: function() {
    // 点击头像跳转
    wx.navigateTo({
      url: '../mine/mine'
    })
  },
  onLoad: function () {
    if (app.globalData.userInfo) {
      this.setData({
        userInfo: app.globalData.userInfo,
        hasUserInfo: true
      })
    } else if (this.data.canIUse){
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    } else {
      // 在没有 open-type=getUserInfo 版本的兼容处理
      wx.getUserInfo({
        success: res => {
          app.globalData.userInfo = res.userInfo
          this.setData({
            userInfo: res.userInfo,
            hasUserInfo: true
          })
        },
        fail: function () {
          //获取用户信息失败后。请跳转授权页面
          wx.showModal({
            title: '警告',
            content: '尚未进行授权,请点击确定跳转到授权页面进行授权。',
            success: function (res) {
              if (res.confirm) {
                console.log('用户点击确定')
                wx.navigateTo({
                  url: '../tologin/tologin',
                })
              }
            }
          })
        }
      })
    }
  },
  getUserInfo: function(e) {
    // console.log(e)
    app.globalData.userInfo = e.detail.userInfo
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  }
})

mine.json:

{
  "navigationBarTitleText": "个人中心",
  "usingComponents": {
    "i-panel": "../../dist/panel/index",
    "i-cell-group": "../../dist/cell-group/index",
    "i-cell": "../../dist/cell/index"
  }
}

mine.wxml :

<!--mine.wxml-->
<view class="container">
  <view class="userinfo">
    <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
    <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
      <text class="userinfo-nickname">{{userInfo.nickName}}</text>
    </block>
  </view>
  <view class="usermotto">
    <text class="user-motto">{{motto}}</text>
  </view>
</view>

<i-panel title="功能">
    <i-cell-group>
        <i-cell title="我的xx" is-link url="pages/mine/mine"></i-cell>
        <i-cell title="xxxxxxx" is-link url="pages/mine/mine"></i-cell>
        <i-cell title="xxxxxxx" is-link url="pages/mine/mine"></i-cell>
        <i-cell title="消费记录" is-link url="pages/mine/mine"></i-cell>
    </i-cell-group>
</i-panel>

mine.wxss:

/**mine.wxss**/
.container {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: 20rpx 0;
  box-sizing: border-box;
}

.userinfo {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.userinfo-avatar {
  width: 128rpx;
  height: 128rpx;
  margin: 20rpx;
  border-radius: 50%;
}

.userinfo-nickname {
  color: #aaa;
}

.usermotto {
  margin: 20px;
}

列表list页面实现

list.json:

{
  "navigationBarTitleText": "列表页",
  "enablePullDownRefresh": true, // 下拉刷新
  "usingComponents": {
    "i-card": "../../dist/card/index",
    "i-spin": "../../dist/spin/index",
    "i-load-more": "../../dist/load-more/index"
  }
}

list实现

list.js:

//list.js
const util = require('../../utils/util.js')
Page({
  data: {
    logs: [],
  },
  onLoad: function() {
    this.clearCache(); //清本页缓存
    this.getArticles(0); //第一次加载数据
  },
  getArticles: function(pageno) {
    this.setData({
      logs: (wx.getStorageSync('logs') || []).map(log => {
        return util.formatTime(new Date(log))
      })
    })
  },
})

list.wxml:

<!-- list.wxml -->
<view class="container data-list">
  <block wx:for="{{logs}}" wx:for-item="log" wx:key="index">
    <i-card full title="{{index + 1}}.卡片标题" extra="额外内容" thumb="https://i.loli.net/2017/08/21/599a521472424.jpg">
      <view slot="content">{{index + 1}}.内容不错</view>
      <view slot="footer">{{log}}</view>
    </i-card>
  </block>
</view>

下拉刷新、上拉加载更多

主要参考了以上思路。
实现代码:
在list.wxml里view的视图容器内的底部增加:

    <i-spin size="large" fix wx:if="{{ spinShow }}"></i-spin>
    <i-load-more tip="{{loadMoreTip}}" loading="{{ loadMore }}" />

list.js修改为如下:

//list.js
const util = require('../../utils/util.js')
var page = 0; //当前页
var totalPages = 3; //总页数
Page({
  data: {
    logs: [],
    //加载样式是否显示
    spinShow: true,
    loadMore: true,
    loadMoreTip: '加载中',
    allLoaded: false
  },
  onLoad: function() {
    this.clearCache(); //清本页缓存
    this.getArticles(0); //第一次加载数据
  },
  getArticles: function(pageno) {
    let vm = this
    setTimeout(function() {
      //要延时执行的代码
      vm.setData({
        spinShow: false
      })
      var res = (wx.getStorageSync('logs') || []).map(log => {
        return util.formatTime(new Date(log))
      });
      var tmpArr = vm.data.logs;
      tmpArr.push.apply(tmpArr, res);
      vm.setData({
        logs: tmpArr
      })
      vm.loadMore = true
    }, 3000) //延迟时间 这里是3秒
  },
  // 下拉刷新
  onPullDownRefresh: function() {
    this.setData({
      spinShow: true
    })
    this.clearCache();
    this.getArticles(0); //第一次加载数据
  },
  // 页面上拉触底事件(上拉加载更多)
  onReachBottom: function() {
    page++;
    if (!this.allLoaded) {
      if (page > totalPages - 1) {
        this.setData({
          loadMore: false,
          loadMoreTip: '没有更多了',
          allLoaded: true // 若数据已全部获取完毕
        })
      } else {
        this.setData({
          loadMore: true,
          loadMoreTip: '加载中',
          allLoaded: false // 若数据未获取完毕
        })
        this.getArticles(page); //后台获取新数据并追加渲染
      }
    } else {
      this.setData({
        loadMore: false,
        loadMoreTip: '没有更多了',
        allLoaded: false // 若数据未获取完毕
      })
    }
  },
  // 清缓存
  clearCache: function() {
    page = 0; //分页标识归零
    this.setData({
      logs: [] //文章列表数组清空
    });
  },
})

list.wxss:

.loading{
    display: inline-block;
    margin-right: 12px;
    vertical-align: middle;
    width: 20px;
    height: 20px;
    background: transparent;
    border-radius: 50%;
    border: 2px solid #2d8cf0;
    border-color: #2d8cf0 #2d8cf0 #2d8cf0 transparent;
    animation: loading-spin 0.6s linear;
    animation-iteration-count: infinite;
}

这里下拉的效果没做处理,上拉加载效果也比较粗糙。可以根据项目要求再进行美观优化,例如你可以自己在下拉时增加下拉到底、松开刷新、刷新中....等动态效果。

如果,最开始,你在看本篇文章时,一步一步跟着做,那么你也将得到一个小程序,实现的最终效果,如下图:

参考资料

微信开放文档 你的第一个小程序
微信开放文档 小程序配置
微信开放文档 小程序目录结构
微信开放文档 组件
微信开放文档 swiper 滑块视图容器

快速上手 - iView Weapp

安利向:用Visual Studio Code编写微信小程序
vscode 小程序开发插件

小程序入坑(一)list列表以及item详情页,json传送时间戳转换成年月日

小程序-列表渲染

微信小程序 下拉刷新/上拉加载更多 (上拉加载更多怎么实现)

微信小程序实现列表页面及上拉加载功能

03-05 23:48