微信小程序原生+云开发小结(校友会小程序DEMO)
发布于 3 年前 作者 uhe 2509 次浏览 来自 分享

1、初识微信小程序

1)小程序为什么存在?

2)为企业或校友提供便利的用户连接工具;它可以在一定程度上可以替代掉部分手机APP的作用(用完即走)。

3)产品设计标准:小而美、开发周期较短。

4) 云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。使用腾讯云开发技术,免费资源配额,无需域名和服务器即可搭建

5)小程序在微信里打开,无须下载app,也无须再访问传统的PC站点,随时随地互动

2、微信小程序开发前准备

  1. 翻阅微信小程序官方文档:

https://developers.weixin.qq.com/miniprogram/dev/framework/

  1. 下载、安装“微信者开发工具” :

https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html#%E5%AE%89%E8%A3%85%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7

3)注册一个校友小程序账号(校友会管理后台MP)

https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html#%E7%94%B3%E8%AF%B7%E5%B8%90%E5%8F%B7

3、小程序管理后台的基本操作

(一)版本管理

小程序认证:填写基本信息、注意选择行业类目、备案付费300元。

小程序有三个版本:开发版、审核版、线上版(默认代码体积不能超过2M)。

小程序项目中用到的静态资源,可以放到CDN或者腾讯云上,以减小代码体积。

(二)成员管理

管理员(1人),是注册账号的微信用户。

项目成员(15人),可以登录小程序管理后台,开发者必须是项目成员。

体验成员(15人),只有体验的权限,没有开发的权限。

(三)开发管理

AppID,相当是小程序的身份证号码,创建项目、调试项目、小程序之间的跳转都要用到,还有比如支付等也要用到。

AppSecret,小程序密钥,一般要给后端,在登录、支付等功能中都要用到。

Request 地址,就是api 的 baseURL,本地开发时可以关闭https验证,上线时一定要小程序管理后台中添加上这个地址,并且要求https协议的。

4、微信开发者工具的基本使用

1)如何创建新项目?

https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html#%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%B0%8F%E7%A8%8B%E5%BA%8F

2)调试项目(真机调试、微信开发者工具调试)

https://developers.weixin.qq.com/miniprogram/dev/devtools/debug.html

3)注意小程序api 版本库和微信版本之间兼容性问题。

5、认识四种文件

.wxml,类似 HTML 的角色。

.wxss,具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改。

.js,编写脚本与用户交互,响应用户的点击、获取用户的位置等等。

.json,是一种数据格式,并不是编程语言,在小程序中,JSON扮演的静态配置的角色。

6、自带配置风格的小程序

微信小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。

{
  "pages": [
    "pages/index/index",
    "pages/books/books"    
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "校友会小程序",
    "navigationBarTextStyle": "black"
  },
  "tabBar": {
    "color": "#aaaaaa",
    "selectedColor": "#ff0000",
    "list": [
      {
        "pagePath": "pages/index/alumni",
        "text": "找校友",
        "iconPath": "/assets/tabbar/alumni.png",
        "selectedIconPath": "/assets/tabbar/alumni-on.png"
      },
      {
        "pagePath": "pages/reg/reg",
        "text": "校友登记",
        "iconPath": "/assets/tabbar/reg.png",
        "selectedIconPath": "/assets/tabbar/reg-on.png"
      }
    ]
  }
}

7、App类与应用级别的生命周期

注册校友小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。

App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。

App({
  // 整个校友会小程序应用程序的入口
  onLaunch() {
    wx.login({
      success: res => console.log('校友登录', res.code)
    })
  },
  globalData: {
    userInfo: null,
  }
})

8、Page类与页面级别的生命周期

Page({
  data: { },
  onLoad: function (options) { },
  onReady: function () {},
  onShow: function () { },
  onHide: function () {},
  onUnload: function () {},
  onPullDownRefresh: function () { },
  onReachBottom: function () { },
  onShareAppMessage: function () { }
})

9、Component类与组件级别的生命周期

Component({
  properties: { },  // 父组件传递过来的属性
  data: { },  // 自有的状态数据
  ready: function(){ },   // 生命周期
  methods: { }  // 自有的方法
})

10、自定义校友卡Card组件

# card.wxml

        {{item.card_zh}}
    

# card.wxss
.card_item {
    padding: 0 10rpx;
    display: inline-block;
    border: 1rpx solid #ccc;
    line-height: 45rpx;
}
.card_item.on {
    color: red;
}
# card.js
Component({
  properties: {
    list: {type: Array, value: [] },
    value: {type:String, value: ''}
  },
  methods: {
    tapClick(e) {
      // 父子组件事件通信
      // this.triggerEvent('input', e.target.dataset.cate)
      // model 双向绑定的写法
      this.setData({value: e.target.dataset.cate})
    }
  },
  // 生命周期
  lifetimes: {},
  pageLifetimes: {}
})
# card.json
{
  "component": true
}

11、使用自定义封装的Card组件

# alumni.json
{
  "usingComponents": {
    "qf-cate": "/components/card/card"
  }
}

12、列表渲染(校友名单列表)

<block
  wx:for="{{alumniList}}"
  wx:for-item="alumni"
  wx:key="alumni"
  wx:for-index="idx"
>
  <view class="row">
    <text>{{idx+1}}</text>
    <text>{{alumni.id*100}}</text>
    <text>{{alumni.name}}</text>
  </view>
</block>

13、条件渲染(校友个人主页)

<view wx:if='{{idx===1}}'>校友姓名</view>
<view wx:elif='{{idx===2}}'>校友学院班级</view>
<view wx:elif='{{idx===3}}'>校友工作单位</view>
<view wx:else>校友行业领域</view>
<button bindtap='toggle'>切换</button>

<!-- 显示与隐藏、类似v-show -->
<view hidden='{{bol}}'>更多校友信息</view>

14、动态样式

<view class="box {{bol?'box':'box2'}}"></view>
<view style='color:red;font-size:{{size+"rpx"}}'>动态样式文字</view>

15、事件绑定(bind / catch)

<!-- 事件绑定、冒泡、事件传参、事件对象 -->
<view bindtap='clickOuter'>
  <view
    data-msg='hello bind'
    bindtap='click1'>
    交换校友名片(bind)
  </view>
  <view catchtap='click2'>交换校友名片(catch绑定)</view>
</view>

Page({
  data: {},
  click1(e) {
    console.log('click1', e)
    console.log('校友名片', e.target.dataset.msg)
  },
  click2(e) {
    console.log('click2', e)
  },
  clickOuter(e) {
    console.log('outer', e)
  }
})

16、表单绑定(单向绑定、双向绑定)

<view>
  <input type="text" value='{{username}}' bindblur='usernameChange' />
  <input type="text" model:value='{{password}}' />
  <button bindtap='submit'>提交校友注册登记</button>
</view>
Page({
  data: {
    username: '刘敏',
    password: '123456',
    },
    // 手动取值
  usernameChange(e) {
    console.log('e', e.detail.value)
    this.setData({username: e.detail.value})
  },
  submit() {
    const data = {
      username: this.data.username,
      password: this.data.password
    }
    console.log('提交校友登记', data)
  }
})

17、微信小程序动画(最新写法)

<view class="abc"></view>
<button bindtap='startAnim'>开始校友列表瀑布</button>
Page({
  data: { },
    startAnim() {
    // 创建动画
    // 第1参数是节点选择器
    // 第2个参数是动态帧(数组)
    // 第3个是过滤时间
    // 第4个参数是回调参数,用于清除动画,这是一种性能优化方案
    this.animate('.abc', [
      { opacity: 1.0, rotate: 0, backgroundColor: '#FF0000', scale: [1,1] },
      { opacity: 0.5, rotate: 45, backgroundColor: '#00FF00', scale: [0.6,0.6]},
      { opacity: 0.2, rotate: 80, backgroundColor: '#FF0000',scale: [0.2,0.2] },
    ], 5000, ()=> {
      // 清除动画
      // 它的第二个参数,用于控制动画的样式是否保留最后一帧的样式,默认是保留的
      this.clearAnimation('.abc', {
        opacity: false,
        rotate: false,
        // backgroundColor: false
      }, ()=> {
        console.log("清除动画成功")
      })
    })
  }
})

18、使用Canvas画布(最新写法)

<canvas type="2d" class="ad" id="myCanvas"/>
<button bindtap='save'>保存到校友相册</button>
Page({
  data: { },
    rpx2px(arg) {
        const res = wx.getSystemInfoSync()
        return res.screenWidth * arg / 750
    },
    // 用于支持最新drawImage()写法
    async getImage(path) {
        const c = wx.createOffscreenCanvas({type: '2d'})
        const image = c.createImage()
        await new Promise(resolve => {
            image.onload = resolve
            image.src = path
        })
        return image
    },
    onReady() {
        const $ = wx.createSelectorQuery()
        $.select('.ad')
        .fields({ node: true, size: true })
        .exec((res) => {
            this.canvas = res[0].node
            const ctx = canvas.getContext('2d')

            // 第1步,绘制一个和画布一样大的矩形
            ctx.fillStyle = 'orange'
            ctx.fillRect(0, 0, this.rpx2px(750), this.rpx2px(500))

            // 第2步,绘制标题文字
            ctx.font = "bold 25px serif"
            ctx.fillStyle = 'white'
            ctx.fillText('校友名单', this.rpx2px(0), this.rpx2px(50))

            // 第3步,绘制图片
            this.getImage('/assets/alumni.png').then(img=>{
                ctx.drawImage(img,50,50)
            })
        })
    },
    // 保存到校友相册
    save() {
        // 把canvas画布转换成临时路径
        wx.canvasToTempFilePath({
            x: 0,
            y: 0,
            width: this.rpx2px(750),
            height: this.rpx2px(500),
            destWidth: 1500,
            destHeight: 1000,
            canvas: this.canvas,
            success(res) {
                // 把临时路径中的图片保存到相册
                wx.saveImageToPhotosAlbum({
                    filePath: res.tempFilePath
                })
            }
        })
    }
})

19、小程序初次启动时请求用户授权地理定位

# app.js
App({
  onLaunch() {
    // 获取用户的地理定位的授权
    wx.getSetting({
      success(res) {
        console.log('当前校友用户的授权列表', res.authSetting)
        // 如果用户未授权地理定位
        if (!res.authSetting['scope.userLocation']) {
          // 无须用户触发,直接弹框请求用户同意使用地理定位
          // 当用户拒绝过一次后,这段代码没用了
          wx.authorize({
            scope: 'scope.userLocation',
            success (res) {
              console.log('地理定位:校友点击了同意', res)
            },
            fail (err) {
              console.log('地理定位:校友点击了拒绝', res)
            }
          })
        }
      }
    })
  }
})
# app.json
{
  "pages": [],
  "permission": {
    "scope.userLocation": {
      "desc": "为了给你更好的校友会服务,希望使用你的地理定位"
    }
  }
}

20、使用地理定位

<button bindtap='getLngLat'>获取校友用户经纬度</button>
<map class="map"
  longitude='{{latLng.longitude}}'
  latitude='{{latLng.latitude}}'
></map>
<button bindtap='navTo'>导航去这里</button>
Page({
  data: {
    latLng: {}
  },
  getLngLat() {
    var that = this
    wx.getSetting({
        success(res){
            if(res.authSetting['scope.userLocation']) {
                // 如果校友用户已经同意过地理定位,直接获取经纬度
                wx.getLocation({
                    success(res) {
                        console.log('校友用户位置', res)
                        that.setData({latLng: res})
                    }
                })
            }else{
                // 如果校友用户已经拒绝过地理定位,我们要打开微信内置的设置页面,让用户自己选择授权
                wx.openSetting({
                    success(res) {
                        console.log('校友用户手动选择授权', res)
                    }
                })
            }
        }
    })
  },
  navTo() {
    wx.openLocation({
        latitude: this.data.latLng.latitude,
        longitude: this.data.latLng.longitude,
        name: '深圳',
        address: '广东省深圳市深圳大学校友会'
    })
  }
})

21、onShareAppMessage 实现转发

<button open-type='share'>
  <view>分享校友卡片</view>
</button>

Page({
  data: {},
  // 实现转发的两种方案(胶囊按钮、button.open-type='share')
  onShareAppMessage(e) {
    console.log('转发校友卡片', e)
    if(e.from==='menu') {
      return {
        title: '杰出校友',
        path: 'pages/good/good',
        imageUrl: 'https:good.jpg'
      }
    }else if(e.from==='button') {
      return {
        title: '我正在为我的母校代言...',
        path: 'pages/ad/ad',
        imageUrl: 'https://ad.jpg'
      }
    }
  }
})

22、globalData 全局数据

App({
  globalData: { msg: 'hello,校友您好!' }
})
const app = getApp()
Page({
    data: {
        msg: app.globalData.msg
    },
    updateMsg() {
        app.globalData.msg = '校友'
        this.setData({msg: app.globalData.msg})
    }
})
{{msg}}
修改msg

23、onPageScroll 监听页面滚动

<!-- 使用scrollTop -->
<button bindtap='backToTop'>回到指定位置</button>
App({
  // 页面滚动
  onPageScroll(e) {
    console.log('页面滚动', e.scrollTop)
  },
  // 使用scrollTop滚动到页面的任何位置
  backToTop() {
    wx.pageScrollTo({
      scrollTop: 133,
      duration: 2000
    })
  }
})

24、<match-media> 实现媒体查询

<!-- 媒体查询 -->
<match-media min-width="315" max-width="600">
  <view>根据媒体设备来决定我是否显示</view>
</match-media>

25、<movable-area> 实现拖拽

<movable-area class="area">
  <movable-view
    direction='all'
    x='{{point.x}}'
    y='{{point.y}}'
    bindchange='areaChange'>
    <view class="area-text">text</view>
  </movable-view>
</movable-area>

Page({
    data: {
        point: {x:0,y:0}
    },
    areaChange(e) {
    this.setData({point: e.detail})
  }
})

26、功能极其强大的 <button>表单组件

<!-- button是表单,功能极其丰富 -->
<button open-type='contact'>联系校友会客服</button>
<button open-type='getPhoneNumber' bindgetphonenumber='getMobile'>登录校友平台</button> 
<button bindtap='getUser'>获取校友信息</button> 
<button open-type='openSetting'>打开授权页面</button>
<button open-type='feedback'>校友投诉建议</button>
Page({
  data: {},
  getMobile(e) { 
    // 目前已不支持个人版本的小程序
    console.log('获取校友手机号', e)
  },
  // 获取用户信息,会弹框请求用户授权
  // 即将过时,建议使用wx.getUserProfile获取用户信息
  getUserInfo(e) {
    console.log('获取校友用户信息', e)
  },
  getUser() {
    wx.getUserProfile({
      desc: '用于完善校友会员资料',
      success(e) {
        console.log('最新的校友用户信息', e)
        // 拿到用户信息之后,要调接口发送给后端数据库
        // 把校友用户保存在业务数据库
      }
    })
  }
})

27、使用 <picker> 组件选择省市区

<view class="section">
  <view class="section__title">省市区选择器</view>
  <picker
    mode="region"
    bindchange="bindRegionChange"
    value="{{region}}"
    custom-item="{{customItem}}"
  >
    <view class="picker">
      当前选择:{{region[0]}},{{region[1]}},{{region[2]}}
    </view>
  </picker>
</view>
    

Page({
  data: {
    region: ['广东省', '深圳', '南山区'],
    customItem: '全部',
  },
  bindRegionChange(e) {
    console.log('region 校友', e)
    this.setData({region: e.detail.value})
  }
})

28、<picker> 组件使用再举例

<view class="section">
  <view class="section__title">选择行业类型</view>
  <picker
    mode="selector"
    bindchange="bindTradeChange"
    value="{{tradeIdx}}"
    range='{{tradeArr}}'
    range-key='trade_zh'
  >
    <view class="picker">
      当前选择:{{tradeArr[tradeIdx].trade_zh}}
    </view>
  </picker>
</view>
    

Page({
    data: {
        tradeArr: [
            {id:0,trade:'all',trade_zh:'全部'},
            {id:1,trade:"car",trade_zh:"互联网行业"},
            {id:1,trade:"office",trade_zh:"移动互联网"}
        ],
        tradeIdx: 0
    },
    bindTradeChange(e) {
    console.log('trade picker', e)
    this.setData({tradeIdx: parseInt(e.detail.value)})
  }
})

29、使用 <audio> 音频组件

<audio    
  poster="http://xxx.png"
  name="凤凰花开的路口"
  author="林志炫"
  src="http://xxxxx.mp4"
  id="myAudio"
  controls
  loop>
</audio>
<button bindtap='startPlay'>播放</button>
Page({
    startPlay() {
    const audioCtx = wx.createInnerAudioContext()
    console.log('ctx', audioCtx)
    audioCtx.play()
  }
})

30、使用 <camera> 相机组件

<camera device-position="back" flash="off" binderror="error" style="width: 100%; height: 300px;"></camera>
<button bindtap='takeCamera'>校友拍照</button>
<image src='{{avatar}}'></image>

Page({
  data: {avatar:''},
  takeCamera() {
    const ctx = wx.createCameraContext()
    ctx.takePhoto({
      quality: 'high',
      success: (res) => {
        this.setData({
          avatar: res.tempImagePath
        })
      }
    })
  }
})

31、小程序路由跳转

<button bindtap='skipToTabPage'>跳转到Tab页</button>
<button bindtap='skipToNotTabPage'>跳转到非Tab页</button>
Page({
    // 跳转到Tab页,使用switchTab,不能传参
    skipToTabPage() {
        wx.switchTab({
            url: '/pages/listen/listen'
        })
    },
    // 跳转到非Tab页,使用navigateTo,可以传参
    // 在另一个页面中,使用 onLoad 生命周期来接收参数
    skipToNotTabPage() {
        wx.navigateTo({
            url: '/pages/user/user?id=123&name=abc'
        })
    }
})

32、自定义ActionSheet

<button bindtap='selectMethod'>校友代言</button>
Page({
    selectMethod() {
    wx.showActionSheet({
      itemList: ['发送海报', '邀请校友'],
      success (res) {
        console.log('用户选择的邀请方式是:', res.tapIndex)
      }
    })
  }
})

33、使用小程序的功能 API

<button bindtap='testWXApi'>测试API</button>
Page({
    testWXApi() {
        wx.setNavigationBarTitle({title:'1234'})
        wx.setBackgroundColor({ backgroundColor: '#ff0000' })
        wx.hideTabBar()
    }
})

34、从手机相册中选取照片

<button bindtap='selectAvatar'>选择校友照片</button>
Page({
    selectAvatar() {
        // 可以先使用wx.getSetting先判断当前用户是否有访问相机和相册的权限
        wx.chooseImage({
            count: 9,
            sizeType: ['original', 'compressed'],
            sourceType: ['album', 'camera'],
            success (res) {
                // tempFilePath可以作为img标签的src属性显示图片
                const tempFilePaths = res.tempFilePaths
                console.log('tempFilePaths', tempFilePaths)
            }
        })
    }
})

35、微信小程序校友会费支付(伪代码)

<button bindtap='pay'>立即会费支付</button>
Page({
    pay () {
        // 前提:先开通微信支付平台(收款平台),小程序要认证
        // 1、在小程序管理后台绑定已有的支付账号
        // 2、使用wx.request把订单信息发送给业务服务器,后端会返回支付信息
        // 3、使用wx.requestPayment()请求完成支付
    }
})

36、小程序中实现复制黏贴、打电话、扫码功能

<view bindlongtap='copy'>订单号:ALULMNI2022211323</view>
<button bindtap='call'>打电话</button>
<button bindtap='scan'>扫码</button>
Page({
    // 复制黏贴
  copy() {
    wx.setClipboardData({
      data: 'ALULMNI2022211323'
    })
  },
  call() {
    wx.makePhoneCall({
      phoneNumber: '0755-12345698'
    })
  },
  scan() {
    wx.scanCode({
      scanType: 'barCode',
      success(res){
        console.log('扫码结果', res)
      }
    })
  }
})

37、下拉刷新与触底加载

Page({
    // 触底加载
  onReachBottom() {
    console.log('到底了,我准备调接口')
  },
  // 下拉刷新
  onPullDownRefresh() {
    console.log('正在下拉刷新')
    setTimeout(()=>{
      // 当调接口完成时,手动停止掉下拉刷新
      wx.stopPullDownRefresh()
    }, 1000)
  },
})
# .json 局部配置
{
  "navigationStyle": "custom",
  "onReachBottomDistance": 100,
  "enablePullDownRefresh": true
}

38、Request 封装

const baseUrl = 'http://localhost:8888'
const version = '/api/v1'

module.exports = function(url,method,data) {
    return new Promise(function(resolve, reject){
        wx.request({
          url: baseUrl+version+url,
          data,
            method,
          header: {
            Authorization: wx.getStorageSync('token')
          },
          success (res) {
            // 成功后数据过滤
                resolve(res.data.data)
          },
            fail(err) {
                wx.showToast({title:'网络异常'})
                reject(err)
            }
        })
    })
}

39、微信小程序的校友登录流程

# app.js
const api = require('./utils/api')
App({
  // 整个应用程序的入口
  onLaunch() {
    // 登录
    wx.login({
      success: res => {
        console.log('登录', res.code)
        // 使用 wx.request 调接口,用code换取token
        api.fetchLogin({code: res.code}).then(res=>{
          // console.log('登录token', res)
          wx.setStorageSync('token', res.token)
        })
      }
    })
  }
})
# Node.js + Koa 代码示例接口
const axios = require('../utils/axios')
const jwt = require('../utils/jwt')
// 引入model
const userModel = require('../model/user')

class UserController {
    // 登录接口
    static async login(ctx) {
        // 接收入参
        console.log('post body', ctx.request.body)
        let { code } = ctx.request.body
        console.log('code', code)
        // 第1步,用code+appid+secret换取微信服务器的openid+session-key
        const res = await axios({
            url: '/sns/jscode2session',
            method: 'get',
            params: {
                appid: 'w2x2f8b3892386e5e2ccf',
                secret: '345bc1b923ae7423bbf28146e31ff372e',
                js_code: code,
                grant_type: 'authorization_code'
            }
        })
        // 第2步,openid不能重复入库     
        const list = await userModel.find({openid: res.openid})     
        if(list.length > 0) {
            const token = jwt.createToken(res)
            ctx.body = {
                err: 0,
                msg: 'success',
                data: { token }
            }
        }else{
            const ele = {
                openid: res.openid,
            }
            await userModel.insertMany([ele])
            const token = jwt.createToken(res)
            ctx.body = {
                err: 0,
                msg: 'success',
                data: { token }
            }
        }
    }
}
module.exports = UserController

__40、写在最后 __

小程序原生+ColorUI+云函数+云开发+Json数据库

 

不管选用那种框架,对微信小程序的基础知识,基础概念还是要花时间去学习的(看官方文档或者微信小程序全面实战,架构设计 && 躲坑攻略),因为很多问题,采用原生的去解决更好,组件也是。

回到顶部