1、初识微信小程序
1)小程序为什么存在?
2)为企业或校友提供便利的用户连接工具;它可以在一定程度上可以替代掉部分手机APP的作用(用完即走)。
3)产品设计标准:小而美、开发周期较短。
4) 云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代,同时这一能力,同开发者已经使用的云服务相互兼容,并不互斥。使用腾讯云开发技术,免费资源配额,无需域名和服务器即可搭建
5)小程序在微信里打开,无须下载app,也无须再访问传统的PC站点,随时随地互动
2、微信小程序开发前准备
- 翻阅微信小程序官方文档:
https://developers.weixin.qq.com/miniprogram/dev/framework/
- 下载、安装“微信者开发工具” :
3)注册一个校友小程序账号(校友会管理后台MP)
3、小程序管理后台的基本操作
(一)版本管理
小程序认证:填写基本信息、注意选择行业类目、备案付费300元。
小程序有三个版本:开发版、审核版、线上版(默认代码体积不能超过2M)。
小程序项目中用到的静态资源,可以放到CDN或者腾讯云上,以减小代码体积。
(二)成员管理
管理员(1人),是注册账号的微信用户。
项目成员(15人),可以登录小程序管理后台,开发者必须是项目成员。
体验成员(15人),只有体验的权限,没有开发的权限。
(三)开发管理
AppID,相当是小程序的身份证号码,创建项目、调试项目、小程序之间的跳转都要用到,还有比如支付等也要用到。
AppSecret,小程序密钥,一般要给后端,在登录、支付等功能中都要用到。
Request 地址,就是api 的 baseURL,本地开发时可以关闭https验证,上线时一定要小程序管理后台中添加上这个地址,并且要求https协议的。
4、微信开发者工具的基本使用
1)如何创建新项目?
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数据库
不管选用那种框架,对微信小程序的基础知识,基础概念还是要花时间去学习的(看官方文档或者微信小程序全面实战,架构设计 && 躲坑攻略),因为很多问题,采用原生的去解决更好,组件也是。