微信小程序
微信小程序开发
环境搭建
-
申请一个微信公众平台
-
保存自己的appid
wxc9ee81ed63a565eb
-
下载开发者工具
地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
-
创建项目
开发小程序
全局配置
{
"pages": [
"pages/index/index",
"pages/home/home"
],
"window": {
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "小白开发"
},
"tabBar": {
"selectedColor":"#FF8C00",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/image/index.png",
"selectedIconPath": "static/image/index1.png"
},
{
"pagePath": "pages/home/home",
"text": "家",
"iconPath": "static/image/yonghu.png",
"selectedIconPath": "static/image/yonghu1.png"
}
]
}
}
组件
text
编写文本信息,类似于span标签
view
容器,类似于div标签
image
图片
样式
像素
- px:由于每个手机的像素都不一样,定义px后不会自动拉伸,因此使用rpx
- rpx:规定整个页面的宽度为750rpx
flex布局
/* flex布局 */
display: flex;
/* 在水平方向排列 */
/* row,规定主轴方向水平*/
/* column,规定主轴方向垂直*/
flex-direction: row;
/* 在主轴方向如何展示 :flex-start(左边)、center(居中)、space-around(平均)、space-between(两边)*/
justify-content: space-around;
/* 在副轴方向如何展示 :flex-start(左边)、center(居中)、space-around(平均)、space-between(两边)*/
align-items: center;
跳转
对标签绑定事件
<view bindtap="clickMe" data-nid="123">点我跳转</view>
/*
点击跳转的绑定事件
*/
clickMe:function(e){
var nid = e.currentTarget.dataset.nid;
console.log(nid);
...
}
页面跳转
wx.navigateTo({
url: '/pages/redirect/redirect?id='+nid,
})
跳转到的页面如果想要接受参数,可以在onLoad方法中接收。
redirect.js
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log(options)
},
})
注意:只能跳转到非tabbar页面
通过标签跳转
<navigator url="/pages/redirect/redirect?id=666">跳转到新页面</navigator>
数据绑定
前端事件绑定
<view>数据:{{ message }}</view>
<button bindtap="changeData">点击修改数据</button>
后端js处理
Page({
/**
* 页面的初始数据
*/
data: {
message:"小程序开发"
},
/*
修改数据
*/
changeData:function(){
// 获取数据
console.log(this.data.message);
// 修改数据(错误,只是修改了后端)
this.data.message = "小程序"
// 修改数据
this.setData({message:"小程序"})
}
})
获取用户信息
方式一(微信官方不推荐方法)
前端绑定事件
<view>当前用户名:{{ name }}</view>
<view>用户的头像:<image src="{{ path }}" style="height:200rpx;width:200rpx"></image></view>
<view bindtap="getUserName">获取当前用户</view>
后端js处理
Page({
/**
* 页面的初始数据
*/
data: {
message:"小程序开发",
path:"/static/timg.jpg"
},
getUserName:function(){
var that = this;
// 调用微信接口,获取当前用户信息
wx.getUserInfo({
// 调用成功后触发
success:function(res){
console.log('success',res.userInfor.nickNames)
that.setData({
name:res.userInfo.nickName,
path:res.userInfo.avatarUrl
})
},
// 调用失败后触发
fail:function(res){
console.log('fail',res)
}
})
}
})
方式二(微信推荐用法)
前端绑定事件
<button open-type="getUserInfo" bindgetuserinfo="xxx">授权登录</button>
后端js处理
xxx:function(res){
var that = this
// 调用微信接口,获取当前用户信息
wx.getUserInfo({
// 调用成功后触发
success:function(res){
console.log('success',res)
that.setData({
name:res.userInfo.nickName,
path:res.userInfo.avatarUrl
})
},
// 调用失败后触发
fail:function(res){
console.log('fail',res)
}
})
}
注意:
-
获取用户信息,必须经过用户授权(button)
-
不授权,通过调用
wx.openSetting
// 打开配置,手动授权 wx.openSetting({})
获取用户的位置信息
前端绑定事件
<view bindtap="getLocalPath">位置:{{ localPath }}</view>
后端js处理
data: {
localPath:"请选择位置",
},
getLocalPath:function(){
var that = this
wx.chooseLocation({
success:function(res){
console.log(res.address)
that.setData({
localPath:res.address
})
}
})
}
for指令
前端展示
<view>
<view wx:for="{{dataList}}" wx:for-index="idx" wx:for-item="x"><text>商品{{idx}}:{{x}}</text></view>
</view>
<view >
<view wx:for="{{ userInfo }}">{{index}}: {{item}}</view>
</view>
后端js处理
Page({
data: {
dataList:["灌汤面","手抓饼","牛杂"],
userInfo:{
name:"hp",
age:18
}
},
})
上传图片
前端展示
<!--pages/publish/publish.wxml-->
<view bindtap="uploadImage">请上传图片</view>
<view class="container">
<image wx:for="{{imageList}}" src="{{item}}"></image>
</view>
后端js处理
data: {
imageList:["/static/timg.jpg","/static/timg.jpg"]
},
uploadImage:function(){
var that = this
wx.chooseImage({
count: 9, // 一次最多上传多少张
sizeType:["original","compressed"], // original:原图,compressed:压缩图
sourceType:["album","camera"], // album:本地图片, camera:现拍
success:function(res){
//that.setData({
// imageList:res.tempFilePaths
// 默认图片 + 选择图片
that.setData({
// 通过js的concat拼接两个列表
imageList:that.data.imageList.concat(res.tempFilePaths)
})
},
fail:function(res){
console.log(res)
}
})
}
注意:图片目前只是上传到内存。
双向绑定
前端事件绑定
通过 bindinput
对事件双向绑定
<view>您输入了:{{message}}</view>
<input type="text" value="{{message}}" bindinput="bindTxt"></input>
后端js处理
data: {
message:"xxx"
},
bindTxt:function(res){
console.log(res)
this.setData({message:res.detail.value})
},
用户登录(手机号登录)
小程序端
-
数据的双向绑定
-
wxml
<view>手机号:</view> <input value="{{phone}}" bindinput="bindPhone" placeholder="请输入手机号"></input> <view>验证码:<text>点击获取验证码</text></view> <input value="{{code}}" bindinput="bindCode" placeholder="请输入验证码"></input> <button bindtap="login">登录</button>
-
js
data: { phone:"", code:"" }, bindPhone:function(res){ this.setData({phone:res.detail.value}) }, bindCode:function(res){ this.setData({code:res.detail.value}) },
-
-
网络请求API
login:function(res){ //将手机和验证码发送到后端 // 网络请求 wx.request({ url: 'http://127.0.0.1:8000/api/login/', data: {phone:this.data.phone,code:this.data.code}, method: 'POST', success: (res) => { console.log(res) }, }) },
使用 wx.request 等有网络请求的API时,需要遵循: - 网络地址https - 后台设置要访问的域名 本地测试: - 不进行域名校验
API
-
创建虚拟环境
-
在虚拟环境中
- Django == 1.11.29
- drf
-
项目API
- login接口
- message验证码接口
用户登录
1. 小程序前端页面
用户页面
<!--pages/my/my.wxml-->
<view id="head">
<image src="{{userInfo.avatarUrl}}" wx:if="{{userInfo}}"></image>
<image src="/static/image/timg.jpg" wx:else="{{userInfo}}"></image>
<view class="login">
<view class="r" wx:if="{{userInfo}}">
<view bindtap="onClickLogout">{{userInfo.nickName}}</view>
</view>
<view class="r" wx:else="{{userInfo}}">
<navigator url="/pages/auth/auth">登录</navigator>
|
<navigator url="/pages/auth/auth">注册</navigator>
</view>
<view class="own">查看个人主页</view>
</view>
<view class="item">
<view class="w">
<view>0</view>
<view> 关注 </view>
</view>
<view class="w">
<view>0</view>
<view> 粉丝 </view>
</view>
<view class="w">
<view>0</view>
<view>点赞收藏</view>
</view>
<view class="w">
<view>0</view>
<view>好友动态</view>
</view>
</view>
</view>
<view class="pay">
<view class="menu">
<image src="/static/app.png"></image>
<text>待支付</text>
</view>
<view class="menu">
<image src="/static/app.png"></image>
<text>待支付</text>
</view>
<view class="menu">
<image src="/static/app.png"></image>
<text>待支付</text>
</view>
<view class="menu">
<image src="/static/app.png"></image>
<text>待支付</text>
</view>
</view>
<view id="total"></view>
<view>
<view class="core">
<text>我的钱包</text>
<text class="hub">¥20 > </text>
</view>
<view class="core">
<text>我的优惠券</text>
<text class="hub">暂无可用 > </text>
</view>
<view class="core">
<text>领劵中心</text>
<text class="hub">你的福利都在这 > </text>
</view>
</view>
登录页面
<view class="logo">
<image src="/static/image/timg.jpg"></image>
<text>交易社区</text>
</view>
<view class="form">
<view class="row-group">
<text>手机号</text>
<input placeholder="请填写手机号" bindinput="bindPhone" placeholder-class="txt" value="{{phone}}" maxlength="11"></input>
</view>
<view class="row-group">
<text>验证码</text>
<input placeholder="请填写验证码" value="{{code}}" bindinput="bindCode" placeholder-class="txt" maxlength="4"></input>
<view class="code" bindtap="onClickCheckCode">获取验证码</view>
</view>
<view>
<button class="submit" open-type="getUserInfo" bindgetuserinfo="onClickSubmit">登录 | 注册</button>
</view>
</view>
2. 登录页面js
获取验证码
Page({
/**
* 页面的初始数据
*/
data: {
phone:"",
code:"",
},
/**
* 数据双向绑定,手机号和验证码
*/
bindPhone:function(e){
this.setData({phone:e.detail.value});
},
bindCode:function(e){
this.setData({code:e.detail.value});
},
/**
* 发送验证码
*/
onClickCheckCode:function(e){
// 1.判断手机格式是否正确
if(this.data.phone.length == 0){
wx.showToast({
title: '请填写手机号',
icon:'none'
})
return ;
};
var reg = /^(1[3-9])\d{9}$/;
if (!reg.test(this.data.phone)){
wx.showToast({
title: '手机格式不正确',
icon:"none",
})
return ;
};
// 2.发送短信验证码,登录成功之后获取jwt和微信用户信息,保存到globalDate和本地存储
wx.request({
url: 'http://127.0.0.1:8000/api/message/',
data:{phone:this.data.phone},
method:"GET",
dataType:'json',
success:function(res){
if(res.data.status){
wx.showToast({
title: res.data.message,
icon: 'success',
});
}else{
// 短信发送失败
wx.showToast({
title: res.data.message,
icon:"none",
});
}
},
})
},
/**
* 获取用户信息推荐使用
*/
方式一 or 方式二
......
})
登录 or 注册
方式一(推荐使用)
/**
* 获取用户信息推荐使用
*/
onClickSubmit:function(e){
console.log(e)
e.detail.userInfo
wx.request({
url: 'http://127.0.0.1:8000/api/login/',
data: {phone:this.data.phone,code:this.data.code},
dataType: 'json',
method: "POST",
timeout: 0,
success: (res) => {
if (res.data.status){
// 初始化用户信息
app.initUserInfo(res.data.data,e.detail.userInfo);
// 跳转到上一级页面
wx.navigateBack({})
}else{
wx.showToast({
title: "登录失败",
icon: 'success',
});
}
},
})
},
方式二(不推荐使用)
/**
* 不推荐使用
* 获取用户信息
*/
onClickLogin:function(){
wx.request({
url: 'http://127.0.0.1:8000/api/login/',
data: {phone:this.data.phone,code:this.data.code},
dataType: 'json',
method: "POST",
timeout: 0,
success: (res) => {
if (res.data.status){
// 初始化用户信息
wx.getUserInfo({
success:function(local){
console.log(local)
app.initUserInfo(res.data.data,local.userInfo);
}
})
// 跳转到上一级页面
wx.navigateBack({})
}else{
wx.showToast({
title: "登录失败",
icon: 'success',
});
}
},
})
},
3.创建Django项目
小程序把用户的手机号发送到django,django通过腾讯的sms服务发送验证码,并将验证码存储到redis数据库中存储,设置验证码的超时时间。
Django项目采用rest_framework框架。
urls.py路由文件
from django.conf.urls import url
from api import views
urlpatterns = [
url(r'^login/', views.LoginView.as_view()),
url(r'^message/', views.MessageView.as_view()),
]
views.py文件
import uuid
import random
from rest_framework.views import APIView
from rest_framework.views import Response
from django_redis import get_redis_connection
from api import models
from utils.tencent import sms
from auction.settings import TENCENT_SMS_TEMPLATE
from api.serializer.account import LoginSerializer, MessageSerializer
class MessageView(APIView):
"""
发送短信验证
"""
def get(self, request, *args, **kwargs):
# 1.获取手机号
# 2.手机格式校验
# 方式一:
# if not re.match(r"^(1[3-9])\d{9}$",phone):
# return Response("手机号格式错误")
# 方式二
ser = MessageSerializer(data=request.query_params)
if not ser.is_valid():
return Response({'status': False, "message": "手机格式错误"})
phone = request.query_params.get("phone")
# 3.生成随机验证码
random_code = random.randint(1000, 9999)
print(random_code)
# 4.验证码发送到手机上(腾讯云)
"""
"""
login = TENCENT_SMS_TEMPLATE.get("login")
sms_code = sms.send_sms_single(phone, login, [random_code, ])
if sms_code['result'] != 0:
return Response({"status": False, "message": "验证码失败"})
# 5.验证码校验
# 搭建redis服务
conn = get_redis_connection()
conn.set(phone, random_code, ex=60)
return Response({"status": True, "message": "验证码发送成功"})
class LoginView(APIView):
"""
登录验证
1.校验手机号是否合法
2.校验验证码
3.去数据库中获取用户信息(获取/创建)
4.将信息返回给小程序
"""
def post(self, request, *args, **kwargs):
ser = LoginSerializer(data=request.data)
if not ser.is_valid():
return Response({"status": False, "message": '验证码错误'})
phone = ser.validated_data.get('phone')
# 方式一
# user = models.UserInfo.objects.filter(phone=phone).first()
# if not user:
# models.UserInfo.objects.create(phone=phone,token=str(uuid.uuid4()))
# else:
# user.token = str(uuid.uuid4())
# user.save()
# 方式二
user_object, flag = models.UserInfo.objects.get_or_create(phone=phone)
user_object.token = str(uuid.uuid4())
user_object.save()
return Response({"status": True, "data": {"token": user_object.token, 'phone': phone}})
创建serializer文件夹,在该文件夹下创建account.py文件,改文件内书写手机号和验证码的验证。
from rest_framework import serializers
from django_redis import get_redis_connection
from rest_framework.exceptions import ValidationError
from api.serializer.validators import phone_validator
import re
def phone_validator(value):
if not re.match(r"^(1[3-9])\d{9}$", value):
raise ValidationError("手机格式错误")
class MessageSerializer(serializers.Serializer):
phone = serializers.CharField(label='手机号', validators=[phone_validator, ])
class LoginSerializer(serializers.Serializer):
phone = serializers.CharField(label='手机号', validators=[phone_validator, ])
code = serializers.CharField(label='短信验证码')
def validate_code(self, value):
if len(value) != 4:
raise ValidationError('验证码格式不正确')
if not value.isdecimal():
raise ValidationError('验证码格式不正确')
phone = self.initial_data.get('phone')
conn = get_redis_connection()
code = conn.get(phone)
if not code:
raise ValidationError('验证码过期')
if value != code.decode('utf-8'):
raise ValidationError('验证码错误')
return value
models.py文件
from django.db import models
class UserInfo(models.Model):
phone = models.CharField(verbose_name='手机号', max_length=11, unique=True)
token = models.CharField(verbose_name='用户Token', max_length=64, blank=True)
4.小程序全局配置app.js文件
App({
onLaunch: function () {
var userInfo = wx.getStorageSync('userInfo');
if (userInfo){
this.globalData.userInfo = userInfo;
}
},
globalData: {
userInfo: null,
},
initUserInfo:function(res,localInfo){
var info = {
token:res.token,
phone:res.phone,
nickName:localInfo.nickName,
avatarUrl:localInfo.avatarUrl,
}
// 手机号放到全局位置
// 1. 去公共的app.js中调用globalData,在里面赋值。(在全局变量赋值)
this.globalData.userInfo = info;
// 2. 本地“cookie”中赋值
wx.setStorageSync('userInfo', info);
},
// 退出登录
delUserInfo:function(){
this.globalData.userInfo = null;
// 清除本地cookie
wx.removeStorageSync('userInfo')
}
})
页面传值
父页面向子页面传值
父页面
/pages/xx/xxx?id=1
子页面
onLoad: function (options) {
},
子页面向父页面传值
子页面
// 找到父级
var pages = getCurrentPages();
var prevPage = pages[pages.length -2];
// prevPage为父级对象
// 方法一:直接找到父级页面data的topicText属性进行赋值
// prevPage.setData({topicText:topicinfo.title})
// 方法二:在父级页面定义函数,通过函数进行赋值
prevPage.setTopicData(topicinfo)
父页面
//方法二
setTopicData:function(res){
this.setData({topicText:res.title,topicId:res.id});
},
注意:wxml给事件传值使用 data-xx
方法
腾讯云的对象存储
uploadFile:function(){
var that = this
// var cos = new COS({
// SecretId: 'AKIDpVw77wQndEaiZIsLXqGExRkodN1ZoRYT',
// SecretKey: '1wgGbbR2NN9h2VGWVGm14rMjkW2HGyPZ',
// });
// 获取临时秘钥
var cos = new COS({
getAuthorization: function (options, callback) {
wx.request({
url: 'http://127.0.0.1:8000/api/credential/',
data: {
// 可从 options 取需要的参数
},
success: function (result) {
var data = result.data;
var credentials = data && data.credentials;
if (!data || !credentials) return console.error('credentials invalid');
callback({
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
XCosSecurityToken: credentials.sessionToken,
// 建议返回服务器时间作为签名的开始时间,避免用户浏览器本地时间偏差过大导致签名错误
StartTime: data.startTime, // 时间戳,单位秒,如:1580000000
ExpiredTime: data.expiredTime, // 时间戳,单位秒,如:1580000900
});
}
});
}
});
console.log(this.data.imageList);
for(var index in this.data.imageList){
var filepath = this.data.imageList[index];
cos.postObject({
Bucket: 'wxchart-1302636832',
Region: 'ap-nanjing',
Key: index + 'yyy.png',
FilePath: filepath,
onProgress: function (info) {
console.log("进度条",JSON.stringify(info));
}
}, function (err, data) {
console.log("上传之后的返回值",data.Location);
that.data.onLineImageList.push(data.Location)
});
};
},
组件:进度条
<progress percent="{{percent}}"></progress>
<progress percent="{{percent}}" acticeColor="#DC143C"></progress>
局部修改data中的数据
<view>--------案例--------</view>
<view>点击按钮,图片1进度条更新为80%</view>
<view wx:for="{{imageList}}">
<view>{{item.title}}</view>
<progress percent="{{item.percent2}}"></progress>
</view>
<button bindtap="changePrecent">点击</button>
data: {
percent:20,
percent1:50,
imageList:[
{id:1,title:"图片1",percent2:20},
{id:2,title:"图片2",percent2:40},
{id:3,title:"图片3",percent2:60},
],
},
changePrecent:function(){
// 方式一:不推荐使用,需全部修改,性能差
// var dataList = this.data.imageList;
// dataList[0].percent2 = 80;
// this.setData({
// imageList:dataList
// })
// 方式二:推荐使用
var num = 2;
this.setData({
["imageList[0].percent2"]:80,
["imageList["+ num +"].percent2"]:90
})
},
闭包
使用闭包函数原因是因为wx.request
等程序是异步执行,console.log(data)程序运行只会执行最后一次结果。
var dataList = ["hp","mzx","xsj"]
for(var i in dataList){
(function(data){
wx.request({
url:"xxx",
success:function(res){
console.log(data);
}
})
})(dataList[i])
}
刷新页面
全局配置
"window": {
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "小白拍卖",
"enablePullDownRefresh": true // 下拉刷新
},
局部配置
{
"usingComponents": {},
"enablePullDownRefresh":true
}
停止下拉刷新加载
wx.stopPullDownRefresh();
自定义组件
-
在根目录下创建compones文件夹。
-
在compones文件夹下创建新组件名的文件夹。例如 header为主键名的文件夹。然后在header文件夹点击右键,新建component。
-
在创建好的组件下的wxml下写出要显示的内容,在wxss中编辑样式。
-
然后在需要调用该组件的文件下配置 .json文件。例如:在index.json中配置该组件。
{ "usingComponents": { "header":"/components/header/header" } }
-
然后在wxml下使用该组件
<header></header>
自定义组件属性传值
在自定义组件上传递自定义属性值:
<PubTitle myTitle="行业动态" myUrl="/pages/list/list"/>
到 PubTitle自定义组件的js中,接收该自定义属性
Component({
/**
* 组件的属性列表
*/
properties: {
// myTitle属性名,需指定type(String/Number/Array/Object),指定value默认值
myTitle:{
type:String,
value:""
},
myUrl:{
type:String,
value:""
}
},
})
在PubTitle的wxml中使用
<view class="pubTitle">
<view class="txt">{{myTitle}}</view>
<navigator class="more" open-type="switchTab" url="{{myUrl}}">更多 ></navigator>
</view>