主页面

为了测试与开发方便,暂时不区分客户端与服务端。增设一个主页面判断用户身份。根据身份的不同,显示不同的功能入口。如果是管理员,则显示管理端、客户端两个入口(方便测试)。如果非管理员,只能进入客户端。如下图所示:

我们需要做以下工作:

  • 增加一个页面并将其作为主页面
  • 增加判断用户身份的云端函数
  • 设置存储用户信息的数据表

通过增设这个页面功能,我们将接触熟悉几乎所有小程序开发涉及的知识。万事开头难,我们将会最大化的利用开发工具生成的Demo。所以,请先保留工程那些工具自动生成的碍眼的各种文件。

入口页面

虽然我们可以通过在工程浏览器的文件夹上的右键菜单中选择直接创建新页面,还是建议大家先为该页面新建一个文件夹,然后在该文件夹中创建页面。暂且将新加入的页面命名为mainPage,在该文件夹下新建页面index。

有些时候怎么做都可以的时候,观察下官方给的demo能让你更好的做出选择。

在这里插入图片描述
程序所有的页面都在app.json中的pages字段注册。这里我们加入将自己前面建立的页面路径(mainPage/index),并将其放在第一的位置,它将作为程序启动后第一个页面出现。
在这里插入图片描述

云函数与云数据库

程序所有的数据都保存在云端数据库中。即便没有从事过web开发,对于XML、JSOn之类的数据也应该不陌生。云数据库集合中的每条记录都是一个 JSON 格式的对象。对于有过关系型数据库使用经验,但第一次接触该数据的人,可以对照文档快速理解,下图给出几处常见术语的对比:
在这里插入图片描述

为了实现用户的身份识别与必要信息存储,我们在云开发控制台中新建两个集合:
在这里插入图片描述

  • admin 程序初始配置,由开发人员控制。其中的admins字段保存了所有管理员的身份ID(手动录入)
  • users 存储已登陆过的用户信息

为记录增加字段时,可以指定类型。比如,管理员字段就是包含了用户openid的数组
在这里插入图片描述
demo包含了获取用户openid的示例,将其值填入admins字段。这样openid在这个数组中的用户就能作为管理员使用该小程序了。
在这里插入图片描述

代码实现

小程序不是完整的js运行环境,具体支持情况请参考腾讯的官方文档JavaScript 支持情况(我没有javascript语言的背景,也不打算系统学习它。如果报错,换一种代码实现方式对我来说更快捷)。WXMLWXSS和HTML、CSS虽然很像,但确实不能等同。因为我没有其中任何一种语言的基础,反而没有什么困扰。文章给出关键代码实现,完整的项目请参考本软件在github的提交。

云端实现

这里我们通过修改cloudfuntions目录下的login函数来实现。该函数实现从admin集合读取管理员的openid列表,判断当前用户是否管理员。然后检查users集合,如果没有该用户的记录,就补充创建。

// 云函数模板
// 部署:在 cloud-functions/login 文件夹右击选择 “上传并部署”

const cloud = require('wx-server-sdk')

// 初始化 cloud
cloud.init({
  // API 调用都保持和云函数当前所在环境一致
  env: cloud.DYNAMIC_CURRENT_ENV
})

const db = cloud.database()
exports.main = async (request) => {
  console.log(request)
  const wxContext = cloud.getWXContext()
  //判断用户类型
  var res = await db.collection('admin').limit(1).get()
  var data = res.data[0]
  var index = data['admins'].findIndex(item => item == wxContext.OPENID)
  var user = index != -1 ? 'admin' : 'user'
  //用户信息注册
  const userCol = db.collection('users')
  res = await userCol.where({
    openid: wxContext.OPENID
  }).get()
  if (res.data.length == 0) {//新用户
    await userCol.add({
      data: {
        info: {
          avatarUrl: request.userInfo.avatarUrl,
          nickName: request.userInfo.nickName
        },
        openid: wxContext.OPENID
      }
    }).catch(console.error)
  } else {
    if (res.data[0].info.avatarUrl != request.userInfo.avatarUrl
      || res.data[0].info.nickName != request.userInfo.nickName) {
      await userCol.where({
        openid: wxContext.OPENID
      }).update({
        info: {
          avatarUrl: request.userInfo.avatarUrl,
          nickName: request.userInfo.nickName
        }
      }).catch(console.error)
    }
  }
  //返回信息
  return {
    success: true,
    data: {
      user: user,
      openid: wxContext.OPENID
    }
  }
}

对于异步函数,常见有callback、promise两种调用方式。小程序的官方文档也多采用这两种方式举例。但如果有多层调用的话,这种嵌套就比多层for嵌套的可读性还要差。因此,除非有需要函数要以非阻塞方式被调用,一般使用await来代替callback、promise。

小程序端实现

建议大致浏览下官方关于框架的文档中关于注册页面、页面生命周期、WXML、简易双向绑定的说明。

一个页面的实现代码大致如下:

Page({
  data: {
    text: "This is page data."
  },
  onLoad: function(options) {
    // Do some initialize when page load.
  },
  onShow: function() {
    // Do something when page show.
  },
  onReady: function() {
    // Do something when page ready.
  },
  onHide: function() {
    // Do something when page hide.
  },
  onUnload: function() {
    // Do something when page close.
  },
  onPullDownRefresh: function() {
    // Do something when pull down.
  },
  onReachBottom: function() {
    // Do something when page reach bottom.
  },
  onShareAppMessage: function () {
    // return custom share data when user share.
  },
  onPageScroll: function() {
    // Do something when page scroll
  },
  onResize: function() {
    // Do something when page resize
  },
  onTabItemTap(item) {
    console.log(item.index)
    console.log(item.pagePath)
    console.log(item.text)
  },
  // Event handler.
  viewTap: function() {
    this.setData({
      text: 'Set some data for updating view.'
    }, function() {
      // this is setData callback
    })
  },
  customData: {
    hi: 'MINA'
  }
})

页面渲染或者加载都会使用data作为数据源。data中的数据必须是可以转成JSON的类型,如字符串,数字,布尔值,对象,数组等。具体说明请参考官方关于页面的文档

通过Button组件调用wx.getUserInfo会弹出用户权限申请:
在这里插入图片描述

注意我们不能通过直接调用该接口弹出授权请求。参考官方文档:在用户未授权过的情况下调用此接口,将不再出现授权弹窗,会直接进入 fail 回调(详见《公告》)。在用户已授权的情况下调用此接口,可成功获取用户信息。要使用 button 组件,并将 open-type 指定为 getUserInfo 类型,获取用户基本信息

这里我们保存通过该函数获取的头像图片链接和昵称。通过wx.cloud.callFunction来调用我们前面实现的login云函数。参数中的name字段的值为云函数的名称,这是通过云函数的package.json文件中的name字段来设定的(虽然一般情况下云函数文件夹的名称跟云函数名称保持一致):
在这里插入图片描述

     // 获取用户信息
    let self = this
    wx.getUserInfo({
      success: async function (res) {
        console.debug(res)
        var userInfo = res.userInfo
        var nickName = userInfo.nickName
        var avatarUrl = userInfo.avatarUrl
        self.setData({
          login: true,
          avatarUrl: avatarUrl,
          userInfo: userInfo
        })
        var ret = await wx.cloud.callFunction({
          name: 'login',
          data: {
            userInfo: {
              nickName: nickName,
              avatarUrl: avatarUrl
            }
          }
        })
        console.debug(ret)
        self.setData({
          _openid : ret.result.data.openid
        })

        if (ret.result.data.user == "admin") {//user
          self.setData({
            isAdmin: true
          })
        } 
      }
    })

页面的数据保存在页面js文件的data数据段中。

  • 如果要使UI能反应这些数据变化,要以this.setData()的方式更改数据。直接赋值(比如:this.data.nickName='昵称更改了)是不能刷新UI的,还会造成UI和实际数据不一致 。另外要注意的是,如果要在以callback或者promise风格的匿名函数中调用setData,要提前保留this的值。因为该处的this已经并非指代该页面对象了。
    在这里插入图片描述
  • 对于那些不影响界面的变量,命名时要以下划线开头,比如 _openid

授权后的程序界面如下图所示:
在这里插入图片描述
index.wxml:

关于WXML文件,请参考微信开放文档

<view>
  <view class="userinfo">
    <button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo" class="userinfo-avatar"
      style="background-image: url({{avatarUrl}})" size="default"></button>
    <view class="userinfo-nickname-wrapper">
      <button class="userinfo-nickname">点击头像登陆</button>
    </view>
  </view>
  <button class="simple-button" wx:if="{{isAdmin}}" bindtap="onSelectedAdmin">管理员</button>
  <button class="simple-button" bindtap="onSelectedCustomer">客户</button>
</view>

index.wxss:

.userinfo {
  margin-top: 40rpx;
  height: 140rpx;
  width: 100%;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-left: none;
  border-right: none;
  display: flex;
  flex-direction: row;
  align-items: center;
  transition: all 300ms ease;
}

.userinfo {
  padding-left: 120rpx;
}

.userinfo-avatar {
  width: 100rpx;
  height: 100rpx;
  margin: 20rpx;
  border-radius: 50%;
  background-size: cover;
  background-color: white;
}

.userinfo-avatar[size] {
  width: 100rpx;
}

.userinfo-avatar:after { 
  border: none;
}
.userinfo-nickname {
  font-size: 32rpx;
  color: #007aff;
  background-color: white;
  background-size: cover;
  text-align: left;
  padding-left: 0;
  margin-left: 10px;
}

.userinfo-nickname::after {
  border: none;
}

.userinfo-nickname-wrapper {
  flex: 1;
}
.simple-button{
  border : 2px solid #007aff;
  margin-top: 10px;
}

虽然只是加入了一个简单的页面,但几乎涉及了小程序开发所需要的所有知识(可把我这个web开发白板折腾的不轻)

  • 知道如何去查官方文档,而且知道官方文档没那么严谨,好几个地方会涉及同一个知识点,却没有一处能说完整。
  • 了解web开发的常识,比如HTML、CSS、Javascript的基础知识。
  • 通过云函数及其调用,实现前后端(客户端、服务端)交互。
  • 使用Json云数据库。

选择UI组件库

从上面的实现一个简单页面的代码可以看出,对于可视化程序,相当大的难度和工作量都在界面上。我没打算成为CSS的专家,甚至没打算去系统学习它。因此迫切希望能找到合适的UI库,这不但能降低UI设计门槛,减轻负担,还能减少编码量。选择的原则:

  • 尽量使用腾讯官方提供的界面库
  • 如果非要选择第三方界面库,那就选个尽量小的

WeUI

官方名片:这是一套基于样式库weui-wxss开发的小程序扩展组件库,同微信原生视觉体验一致的UI组件库,由微信官方设计团队和小程序团队为微信小程序量身设计,令用户的使用感知更加统一。项目地址
在这里插入图片描述

它还有个好处就是支持扩展库引入,不占用小程序包体积

Vant

官方名片:Vant 是有赞前端团队开源的移动端组件库,于 2016 年开源,已持续维护 4 年时间。Vant 对内承载了有赞所有核心业务,对外服务十多万开发者,是业界主流的移动端组件库之一。
在这里插入图片描述
以上UI组件库,大家可通过扫描二维码后体验。

整合UI组件库

微信小程序开发支持使用npm安装第三方库

但是实际操作时有问题,比如,如果使用npm安装了vant、以扩展库方式引入weui,无论怎么设置都有冲突,百度了一圈子,不少人遇到类似问题,但试用了不少别人解决问题的方法,我这里没解决,这可能跟不熟悉npm有关。

但为了避免不必要的麻烦,以copy文件的方式(官方第二种方式)使用vant:
在这里插入图片描述
vant 的源代码可以从这里下载。这里我们将其放在工程的miniprogram/components文件夹中:
在这里插入图片描述

以扩展包的方式引入weui

这种方式引入weui非常简单,只需要在app.json中增加:
“useExtendedLib”: {
“weui”: true
}
在这里插入图片描述

接下来

我们会利用本次的知识和导入的UI组件库实现管理端的主界面,并实现商品信息的管理功能,如下:
在这里插入图片描述
在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐