微信小程序

微信小程序开发

环境搭建

  1. 申请一个微信公众平台

    地址:https://mp.weixin.qq.com/

    微信公众号平台

  2. 保存自己的appid

    申请appid

    wxc9ee81ed63a565eb
    
  3. 下载开发者工具

    地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html

    下载开发者工具

  4. 创建项目

    创建小程序

    创建项目

 

开发小程序

全局配置

{
  "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();

 

自定义组件

  1. 在根目录下创建compones文件夹。

  2. 在compones文件夹下创建新组件名的文件夹。例如 header为主键名的文件夹。然后在header文件夹点击右键,新建component。

  3. 在创建好的组件下的wxml下写出要显示的内容,在wxss中编辑样式。

  4. 然后在需要调用该组件的文件下配置 .json文件。例如:在index.json中配置该组件。

    {
      "usingComponents": {
        "header":"/components/header/header"
      }
    }
    
  5. 然后在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>

 

posted @ 2021-03-09 16:05  Hp_mzx  阅读(307)  评论(0)    收藏  举报