[学习笔记]微信小程序全套开发流程(ing)

https://www.bilibili.com/video/BV1mV4y1o7fu

1.整体概述

在这里插入图片描述

2.环境搭建

4.纯净版项目

在这里插入图片描述

5.快速入门

5.1组件(类似HTML标签)

wxml中的标签 html中的标签
text span
view div
image img
icon
navigator a

view组件

<view>
  <view class="c0">学生:</view>
  <view class="c1">微信:nkehougaosuni</view>
</view>

text组件

小程序组件文档

  • 在text组件中使用函数,并传递参数
# html
<text bindtap="clickMe" data-nid="999" data-name="nkehougaosuni">源代码学城</text>
  • 在js中定义函数
# js
clickMe(e){
    
    
    console.log("点我了")
    console.log(e.target.dataset);
},

e里面封装了请求传递过来的数据。
上面html传递的data-nide.target
在这里插入图片描述

image组件

<image src="/images/1.png" style="width: 200rpx; height: 150rpx;"></image>

px是像素,rpx是微信里设置的动态像素点,总共宽度是750rpx,有些手机宽,有些窄,使用rpx会自动适配手机进行缩放。

icon组件-图标

<icon type="info" size="93"></icon>

大小也支持rpx

navigator组件

<navigator url="/pages/mine/mine">跳转</navigator>
# html
<view bindtap="clickGo">跳转2</view>
# js
clickGo(e){
    
    

  //跳转,微信API
  wx.navigateTo({
    
    
    url: '/pages/mine/mine',
  })
},

注意:只能跳转到页面(非tabBar)
要跳转到tabBar
对于navigator标签,需要加上参数open-type=“switchTab”
对于调用AIP的形式,需要使用wx.switchTab({url: ’ '})

5.2 数据展示和绑定

  • 在js的data中写入数据
# js
  data: {
    
    
      city:"北京",
      nameList: ["张三", "李四", "王五"]
  },
  • html中使用数据
# html
<view>
  <text>{
   
   {city}}</text>
</view>

wx:for语法

# html
<view>
  <view wx:for="{
     
     {nameList}}" wx:key="index">{
   
   {index}}===={
   
   {item}}</view>
</view>
<view>
  <view wx:for="{
     
     {nameList}}" wx:for-index="idx"
  wx:for-item="ele"
  wx:key="idx">
  	{
   
   {idx}}-{
   
   {ele}}
  </view>
</view>

wx:for-index='idx’相当于起别名
使用wx:key参数可以提高性能:当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。

wx:if语法

基本使用

<view>
  <text wx:if="{
     
     {city=='北京1'}}"></text>
  <text wx:elif="{
     
     {city=='北京2'}}">开开</text>
  <text wx:else></text>
</view>

注意wx:if中的双引号里面要用单引号

block

要展现好几个标签,有下面两种方法:
方法1:

<view>
  <view wx:if="{
     
     {city}}=='北京'">
    <text>中国</text>
    <text>北京</text>
    <text>故宫</text>
  </view>
</view>

方法2:

<view>
  <block wx:if="{
     
     {city}}=='北京'">
    <text>中国</text>
    <text>北京</text>
    <text>故宫</text>
  </block>
</view>

方法1展示的三个text标签在第三级,而方法2展示的三个text标签在第二级。因为block只用于处理条件逻辑,而不会渲染,所以会比方法1少一层结构。

hidden

类似于vue中的show

# 在js的data中已经设置isHide: false
<view hidden="{
     
     {isHide}}">
  <icon type="success"></icon>
</view>

用上hidden,会将标签渲染到页面上,只不过会隐藏起来,而对于wx:if,如果条件不满足,则不会渲染到页面上。

10.数据绑定

单值绑定

之前的小程序版本,只支持单向绑定,不支持双向绑定。例如:

  • 通过setData实现双向绑定效果
# html
<view>
  <text>{
   
   {name}}</text>
  <button bindtap="changeName">修改</button>
</view>
# js
  changeName(e){
    
    
    this.setData({
    
    
      name: "张开"
    });
  },

这样操作,点击修改会把name变成张开。但这不是双向绑定。而是因为setData修改完数据后,页面发生了变化。

  • input单向绑定效果
<view>
  <text>{
   
   {name}}</text>
  <button bindtap="changeName">修改</button>
  <input type='text' value="{
     
     {name}}" placeholder="请输入"/>
</view>

input标签的value可以设置绑定的变量,这里输入框绑定了name变量
在vue中,如果修改输入框,因为是双向绑定的,所以读取到name的text标签也会跟着变化
但是微信中默认不是双向绑定的,所以修改输入框的内容,并不会修改name
要双向绑定,就需要在input里面加上bindinput参数指定的函数doChange,并在doChange中加入setData函数

  • input+bindinput实现双向绑定
# html
<view>
  <text>{
   
   {name}}</text>
  <button bindtap="changeName">修改</button>
  <input type='text' value="{
     
     {name}}" bindinput="doChange" placeholder="请输入"/>
</view>
# js
  doChange(e){
    
    
    // console.log('doChange', e.detail.value);
    this.setData({
    
    
      name: e.detail.value
    })
  },
  • input组件中,利用model:value实现双向绑定(新版本)
# html
<view>
  <text>{
   
   {name}}</text>
  <button bindtap="changeName">修改</button>
  <input type='text' value="{
     
     {name}}" bindinput="doChange" placeholder="请输入"/>
  <input type='text' model:value="{
     
     {name}}" bindinput="doChange2"/>
</view>
# js
  doChange2(e){
    
    

  },

可实现类似vue中的双向绑定的效果,但是存在bug,警告意思是必须再定义一个bindinput的事件(函数doChange2可以啥都不写)。不加会报错
在这里插入图片描述

列表绑定

  • 基于setData完成列表的双向绑定
# js
  changeName(e){
    
    
    // this.data.nameList.push("alex")

    var info = this.data.nameList;
    info.push("alex");
    console.log(info);
    this.setData({
    
    
      name: "张开",
      nameList:info
    });
  },

11.API

帮助文档-API

  clickFunc(e){
    
    
    wx.showToast({
    
    
      title: '成功', // 提示内容
      icon: 'success', // 图标
      duration: 2000 // 持续两秒 
    })
  },

发送请求

# js
wx.request({
    
    
  url: 'example.php', //仅为示例,并非真实的接口地址
  data: {
    
    
    x: '',
    y: ''
  },
  header: {
    
    
    'content-type': 'application/json' // 默认值
  },
  success (res) {
    
    
    console.log(res.data)
  }
})

12.样式和icon

文档-icon
文档中的icon内容比较少,所以可以引入第三方icon,fontawesome

更多icon-fontawesome

[微信小程序]在微信小程序中使用fontawesome6

  • fontawesome下载(CSS+TTF)
  • TTF转换->base64
  • 引入项目+CSS

不同版本的fontawesome支持的图标是不同的,视频里用的是v4版本。

13.tabBar

app.json中设置tabBar
全局配置-tabBar

14.案例-菜单-展示

布局-menu.wxml

<view class="container">
  <navigator class="menu">
    <label class="fa fa-superpowers" style="color:#32CD32"></label>
    <view>
      信息采集
    </view>
  </navigator>

  <navigator class="menu">
    <label class="fa fa-meh-o" style="color:#FFA500"></label>
    <view>
      人脸识别
    </view>
  </navigator>

  <navigator class="menu">
    <label class="fa fa-bell-o" style="color:#87CEFA"></label>
    <view>
      社区活动
    </view>
  </navigator>

  <navigator class="menu">
    <label class="fa fa-microphone" style="color:#7D9EC0"></label>
    <view>
      语音识别
    </view>
  </navigator>

  <navigator class="menu">
    <label class="fa fa-heartbeat" style="color:#EE0000"></label>
    <view>
      心率检测
    </view>
  </navigator>

  <navigator class="menu">
    <label class="fa fa-credit-card" style="color:#778899"></label>
    <view>
      积分兑换商城
    </view>
  </navigator>

</view>

布局的样式-menu.wxss

/* pages/menu/menu.wxss */

page{
    
    
  height: 100%;
  background-color: #F5F5F5;
}

.container{
    
    
  border-top: 1px solid #ddd;

  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  flex-wrap: wrap;
}

.container .menu{
    
    
  width: 374rpx;
  height: 354rpx;
  border-bottom: 1px solid #ddd;

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: white;
}

.container .menu label{
    
    
  padding: 20rpx 0;
}

.container .menu:nth-child(odd){
    
    
  border-right: 1px solid #ddd;
}

弹性盒子参考:https://www.runoob.com/css3/css3-flexbox.html

15.案例-采集-列表

布局-info_collect.wxml

<view class="container">
  <view class="top">
    <view class="tip">今日采集数量(人)</view>
    <view class="count">100</view>
  </view>

  <view class="function">
    <view class='menu' style='border-right: 1rpx solid #ddd;'>
      信息采集
    </view>
    <view class="menu">
      数据统计
    </view>
  </view>

  <view class="table">
    <view class="item">
      <view class="title">
        社区信息列表(100人)
      </view>
    </view>

    <view class="item" wx:for="{
     
     {dataDict.data}}" wx:for-item="row" wx:key="index">
      <view class="record">
        <view class="avatar">
          <image src="{
     
     {row.avatar}}"></image>
        </view>
        <view class="desc">
          <view class="username">
            {
   
   {row.name}}
          </view>
          <view>
            <view class='txt-group'>
              <label class="zh">网格区域</label>
              <label calss="en"> | AREA</label>
            </view>
            <view class="area">
              <label class="fa fa-map-marker">{
   
   {row.area}}</label>
            </view>
          </view>
        </view>
        <view class="delete"           data-nid="{
     
     {row.id}}" data-index="{
     
     {index}}">
          <label class="fa fa-trash"></label>
        </view>
      </view>
    </view>
  </view>
</view>

样式-info_collect.wxss

.top{
    
    
  background-color: #01ccb6;
  height: 200rpx;

  display: flex;
  flex-direction: column;
  align-items: center;
  color: white;
}

.top .tip {
    
    
  font-size: 22rpx;
  font-weight: 100;
}

.top .count{
    
    
  padding: 10rpx;
  font-size: 68rpx;
}

.function{
    
    
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  background-color: #02bfae;
}

.function .menu{
    
    
  font-size: 28rpx;
  margin: 25rpx 0;
  color: white;
  width: 50%;
  text-align: center;
  align-items: center;
}

.table .item{
    
    
  border-bottom: 1rpx solid #e7e7e7;
}

.table .item .title{
    
    
  margin: 20rpx 30rpx;
  padding-left: 10rpx;
  border-left: 5rpx solid #02bfae;
  font-size: 26rpx;
}

.record{
    
    
  margin: 30rpx 40rpx;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
}

.record .avatar{
    
    
  width: 200rpx;
  height: 200rpx;
  margin-right: 40rpx;
}

.record .avatar image{
    
    
  width: 100%;
  height: 100%;
  border-radius: 30rpx;
}

.desc{
    
    
  width: 290rpx;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.desc .username{
    
    
  margin-top: 25rpx;
  font-size: 38rpx;
}

.txt-group{
    
    
  font-size: 27rpx;
  margin: 10rpx 0;
}
.txt-group .zh{
    
    
  color: #8c8c8c;
}
.txt-group .en{
    
    
  color: #cccccc
}

.area{
    
    
  color: #00c8b6;
  font-weight: bold;
}

.delete{
    
    
  width: 100rpx;
  color: red;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

数据-info_collect.js

data: {
    
    
    dataDict:{
    
    
      data:[
        {
    
    
          "id": 45,
          "name": "谢新雪",
          "area": "#19",
          "avatar": "/images/人物图片1.png"
        }
      ]
    }
  },

16.案例-采集-数据列表

  refresh(){
    
    
    //刷新或获取请求
    //1.发送网络请求
    //2.数据绑定
    wx.showLoading({
    
    mask:true})
    wx.request({
    
    
      url: ,
      method: "GET",
      success: (res) => {
    
    
        this.setData({
    
    
          dataDict: res.data
        })
      }
    })
  },

17.案例-采集-删除

<view class="delete" bindtap="doDeleteRow" data-nid="{
     
     {row.id}}" data-index="{
     
     {index}}">
  doDeleteRow(e) {
    
    
    wx.showModal({
    
    
      title: "确认是否删除?",
      confirmColor: "#ff461f",
      success: (res) => {
    
    
        if (!res.confirm) {
    
    
          return
        }
        console.log(e)
        var nid = e.currentTarget.dataset.nid
        var index = e.currentTarget.dataset.index
        var dataList = this.data.dataDict.data
        dataList.splice(index, 1)

        wx.showLoading({
    
    
          title: "删除中",
          mask: true
        })

        wx.request({
    
    
          url: api.bank + nid + '/',
          method: 'DELETE',
          success: (res) => {
    
    
            let total_count = this.data.dataDict.total_count - 1
            if (total_count < 0) {
    
    
              total_count = 0
            }

            let today_count = this.data.dataDict.today_count - 1
            if (today_count < 0) {
    
    
              today_count = 0
            }

            this.setData({
    
    
              'dataDict.data': dataList,
              'dataDict.total_count': total_count,
              'dataDict.today_count': today_count
            })
          },
          complete() {
    
    
            wx.hideLoading()
          }
        })
      }
    })
  },

18.案例-采集-后端API

简易版的API

1.新建项目
2.创建名为api的应用
3.在settings注册api,rest_framework

  • 4.创建表结构
    models.py
class UserInfo(models.Model):
    '''用户信息'''
    uid = models.CharField(verbose_name="ID", max_length=64)

    area_choices = (
        (1, '#19'),
        (2, '#20'),
        (3, '#21'),
        (4, '#22'),
    )
    area = models.IntegerField(verbose_name='网络', choices=area_choices)

    name = models.CharField(verbose_name='姓名', max_length=32)
    avatar = models.FileField(verbose_name='头像', max_length=128, upload_to='bank/%Y/%m/%d/')
    create_date = models.DateField(verbose_name='日期', auto_now_add=True)

    face_token = models.CharField(verbose_name='FaceToken', max_length=32)
    score = models.IntegerField(verbose_name='积分', default=0)

通过make migrationsmigrate进行迁移

FileField可以将文件直接上传到文件文件目录

  • 5.创建路由
    • 5.1 项目总的url
from django.contrib import admin
from django.urls import path, re_path, include
from django.views.static import serve
from django.conf import settings

urlpatterns = [
    re_path("admin/", admin.site.urls),

    re_path(r'^api/', include('api.urls')),
    re_path(r'^media/(?P<path>.*)', serve, {
    
    'document_root': settings.MEDIA_ROOT}),
]

    • 5.2 settings.py中配置媒体
import os
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
    • 5.3 api下的urls.py:接收分发过来的路由请求
from django.urls import path, re_path
from api.views import bank

urlpatterns = [
    re_path(r'^bank$/', bank.BankView.as_view()),
    re_path(r'^bank/(?P<pk>\d+)/$', bank.BankView.as_view()),
]

这里有个知识点是有名分组

    1. 在应用下新建一个serializers文件夹

根据请求的不同进入不同的序列化器
序列化器统一放在serializers文件夹中

import uuid
import datetime
from rest_framework.serializers import ModelSerializer, Serializer, ListSerializer
from rest_framework import serializers
from rest_framework.utils.serializer_helpers import (
    BindingDict, BoundField, JSONBoundField, NestedBoundField, ReturnDict,
    ReturnList
)

from api import models


class BankListSerializer(ListSerializer):

    @property
    def data(self):
        ret = super(ListSerializer, self).data
        total_count = models.UserInfo.objects.all().count()
        today_count = models.UserInfo.objects.filter(create_date=datetime.datetime.today()).count()

        info = {
    
    
            'total_count': total_count,
            'today_count': today_count,
            'data': ret,
        }
        return ReturnDict(info, serializer=self)


class BankListModelSerializer(ModelSerializer):
    area = serializers.CharField(source="get_area_display") # 序列化的字段是执行get_area_display后得到,即area_choices对应内容

    class Meta:
        list_serializer_class = BankListSerializer # 里面的每个元素用什么序列化
        model = models.UserInfo
        fields = ['id', 'name', 'area', 'avatar']


class BankCreateModelSerializer(ModelSerializer):
    area_text = serializers.CharField(source='get_area_display', read_only=True)

    class Meta:
        model = models.UserInfo
        exclude = ['face_token', 'uid', ]

    def validate(self, data):
        uid = str(uuid.uuid4()).replace('-', '_')  # 基于随机数来生成UUID. 使用的是伪随机数有一定的重复概率
        name = data.get('name')
        avatar_file_object = data.get('avatar')
        from utils import ai
        data['face_token'] = ai.register_image(uid, name, avatar_file_object) # 上传到百度云的人脸库,返回face_token
        data['uid'] = uid
        return data

python @property的介绍与使用

    1. 删除views.py文件,添加views.py文件夹,并创建bank.py文件

根据请求的不同走不同的seriealizers

from rest_framework.generics import ListAPIView, CreateAPIView, DestroyAPIView
from api.serializers.bank import BankListSerializer, BankListModelSerializer, BankCreateModelSerializer
from api import models


class BankView(ListAPIView, CreateAPIView, DestroyAPIView):
    queryset = models.UserInfo.objects.all().order_by('-id')

    def get_serializer_class(self):
        """根据请求的不同进入不同的序列化器"""
        if self.request.method == "POST":
            return BankCreateModelSerializer
        return BankListModelSerializer

    def delete(self, request, *args, **kwargs):
        user_object = self.get_object()
        from utils import ai
        ai.delete(user_object.uid, user_object.face_token)
        response = super().delete(request, *args, **kwargs)
        return response

super()并不是调用父类的同名函数
super()实际上是super(type, obj):会去obj.mro中的type的下一个类开始找。
MRO,使用C3算法,遇到汇聚点就回到上一个分叉点
参考资料:MROhttps://www.bilibili.com/video/BV1mV4y1o7fu

  • 8.在项目目录下创建utils目录,并创建ai.py文件

  • 百度云:https://cloud.baidu.com/
    免费领取资源-创建应用-…
    根据api文档上传图片

import base64
import urllib
import requests
import json

API_KEY = "xxx"
SECRET_KEY = "xxx"


def get_access_token():
    """
    使用 AK,SK 生成鉴权签名(Access Token)
    :return: access_token,或是None(如果错误)
    """
    url = "https://aip.baidubce.com/oauth/2.0/token"
    params = {
    
    "grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY}
    return str(requests.post(url, params=params).json().get("access_token"))


def register_image(user_id, user_info, file_object, group_id='test'):
    # 1.获取access token,并拼接得到请求地址
    url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add?access_token=" + get_access_token()

    # 2.图片进行base64编码
    data = base64.b64encode(file_object.read()).decode("utf8") # 图片文件的二进制字节流转化为base64字节流,再转化为字符串流

    # 3.上传图片
    payload = json.dumps({
    
    
        "group_id": group_id,
        "image": data,
        "image_type": "BASE64",
        "user_id": user_id,
        "user_info": user_info
    })
    headers = {
    
    
        'Content-Type': 'application/json'
    }

    response = requests.request(
        "POST",
        url=url,
        headers=headers,
        data=payload
    )
    return response.json()['result']['face_token'] # response.json()转为dict


def delete(user_id, face_token, group_id='alex'):
    # 1.获取access token,并拼接得到请求地址
    url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/face/delete?access_token=" + get_access_token()

    # 2.发送删除请求
    payload = json.dumps({
    
    
        'user_id': user_id,
        'group_id': group_id,
        'face_token': face_token,
    })
    headers = {
    
    
        'Content-Type': 'application/json'
    }
    requests.request("POST", url, headers=headers, data=payload)

base64参考资料:为什么要使用base64编码,有哪些情景需求? - flydean的回答 - 知乎
返回的结果需要通过.json()转化为字典

  • 9.修改settings文件
ALLOWED_HOSTS = ["*"]
  • 10.修改项目的configurations
    主机改为0.0.0.0,为了所有ip都能访问到
    端口改成8001端口
    在这里插入图片描述

  • 11.在微信小程序开发工具中,项目中创建api.js

const rootUrl = 'http://192.168.1.226:8001/api';


module.exports = {
    
    
  bank: rootUrl + '/bank/',
  bankStatistics: rootUrl + '/bank/statistics/',
  bankFace: rootUrl + '/bank/face/',
  bankActivity: rootUrl + '/bank/activity',
  bankApply: rootUrl + '/bank/apply/',
  bankVoice: rootUrl + '/bank/voice/',
  bankHrv: rootUrl + '/bank/hrv/',
  bankExchange: rootUrl + '/bank/exchange/',
  bankScore: rootUrl + '/bank/score/',
  bankGoods: rootUrl + '/bank/goods/',
  bankexchangeRecord: rootUrl + '/bank/exchange/record/',
}
  • 12.在info_collect.js中导入api.js
const api = require('../../config/api.js') // 导入

url: api.bank,

19.案例-表单-展示

  • 信息采集绑定事件
<view class='menu' style='border-right: 1rpx solid #ddd;' bindtap="bindToForm">
  信息采集
  • 在js中定义事件bindToForm
  bindToForm(e){
    
    
    wx.navigateTo({
    
    
      url: '/pages/form/form',
    })
  },
  • 在pages下创建form文件夹,再创建form页面,接下来开发form页面
    要实现的效果:
    在这里插入图片描述
    代码
<!--pages/form/form.wxml-->
<view class='avatar'>
  <image src="{
     
     {avatar}}" bindtap='bindToCamera'/>
</view>

<view class="form">
  <view class="row-group">
    <input placeholder="请填写姓名" placeholder-class="txt" model:value="{
     
     {name}}" bindinput="bindNameChange" />
  </view>
  <view class="picker-group">
    <picker bindchange='bindPickerChange' value='{
     
     {index}}' range='{
     
     {objectArray}}' range-key="name">
      <view wx:if='{
     
     {index > -1}}' class="picker-txt picker">当前网络: {
   
   {objectArray[index].name}}</view>
      <view wx:else class="picker-txt">请选择网络</view>
    </picker>
  </view>
  <view>
    <button class="submit" bindtap="postUser"> 提 交 </button>
  </view>
</view>

微信开发文档-picker

  • 样式,form.wxss
/* pages/form/form.wxss */
.avatar{
    
    
  display: flex;
  flex-direction: column;
  align-items: center;
}

.avatar image{
    
    
  margin-top:140rpx;
  width: 300rpx;
  height: 300rpx;
  border-radius: 30rpx;
  border: 1rpx solid #ddd;
}

.form{
    
    
  padding: 40rpx;
}

.form .row-group{
    
    
  padding: 10rpx 0;
  border-bottom: 1rpx solid #ddd;
  position: relative;
  margin-top: 80rpx
}


.form .row-group input{
    
    
  padding: 10rpx 0;
}

.form .row-group .txt{
    
    
  color: #ccc;
  font-size: 28rpx;
}

.form .picker-group{
    
    
  border-bottom: 1rpx solid #ddd;
}

.form .picker-group .picker-txt{
    
    
  color: #ccc;
  font-size: 28rpx;
  padding: 40rpx 0 20rpx 0;
}

.form .picker-group .picker{
    
    
  color: black;
}

.form .submit{
    
    
  margin-top: 80rpx;
  color: #fff;
  border: 2rpx solid #00c8b6;
  background-color: #00c8b6;
  font-size: 32rpx;
  font-weight: bold;
}
  • 编写form.js
  data: {
    
    
    avatar: "/images/camera.png",
    objectArray: [{
    
    
      id: 1,
      name: '#19'
    },
    {
    
    
      id: 2,
      name: '#20'
    },
    {
    
    
      id: 3,
      name: '#21'
    },
    {
    
    
      id: 4,
      name: '#22'
    },
  ],
  index: -1,
  name: ""
  },
  bindToCamera(e){
    
    
    wx.navigateTo({
    
    
      url: '/pages/camera/camera',
    })
  },
  bindPickerChange(e){
    
    
    // console.log(e);
    this.setData({
    
    
      index: e.detail.value
    })
  },
  bindNameChange(e){
    
    },

20.案例-表单-拍照

  • 在pages文件夹下创建camera/camera页面
  • camera.wxml文件
    小程序文档-组件-媒体组件-camera
<!--pages/camera/camera.wxml-->
<camera class="camera" device-position="{
    
    {backFront ? 'back' : 'front'}}" flash="off" frame-size="medium"></camera>

<view class="function">
  <view class="switch"> </view>
  <view class="record" bindtap="takePhoto">
    <image src="/images/record_on.png"></image>
  </view>
  <view class="switch" bindtap="switchCamera">
    <image src="/images/rotate-camera-white.png"></image>
  </view>
</view>
  • css
/* pages/camera/camera.wxss */
page{
    
    
  height: 100%
}

.camera{
    
    
  height: 80%;
  width: 100%;
}

.function{
    
    
  height: 20%;
  background-color: black;

  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;
}

.record image{
    
    
  width: 160rpx;
  height: 160rpx;
}

.switch{
    
    
  color: white;
  width: 80rpx;
}

.switch image{
    
    
  width: 80rpx;
  height: 80rpx;
}
  • js
  data: {
    
    
    backFront: true
  },
  switchCamera(e){
    
    
    var old = this.data.backFront
    this.setData({
    
    
      backFront: !old
    })
  },
  takePhoto(){
    
    
    const ctx = wx.createCameraContext()
    ctx.takePhoto({
    
    
      quality: 'high',
      success: (res) => {
    
    
        var pages = getCurrentPages();
        var prevPage = pages[pages.length - 2] //上一个页面
        prevPage.setData({
    
    
          avatar: res.tempImagePath
        })
        wx.navigateBack()
      }
    })
  },

修改上一个页面的值:
getCurrentPages:获取直到当前页面的页面组成的列表
pages[pages.length - 2]: 获取当前页面上一个页面
wx.navigateBack(): 返回上一页

21.案例-表单-提交表单

在form.js中编写form.wxml中的postUser事件

  postUser(e){
    
    
    wx.showLoading({
    
    
      title: '提交中',
      mask: true
    })

    wx.uploadFile({
    
    
      filePath: this.data.avatar,
      name: 'avatar',
      url: api.bank,
      formData: {
    
    
        'name': this.data.name,
        'area': this.data.objectArray[this.data.index].id
      },
      success(res){
    
    
        // console.log(res)
        // 上一个页面新增数据
        var dataDict = JSON.parse(res.data)
        var row = {
    
    
          id: dataDict.id,
          area: dataDict.area_text,
          name: dataDict.name,
          avatar: dataDict.avatar,
        }
        var pages = getCurrentPages();
        var prevPage = pages[pages.length - 2];
        prevPage.addRow(row);
        wx.navigateBack()

        // 上传成功后,自动跳转回上一页
        wx.navigateBack();
      },
      complete(){
    
    
        wx.hideLoading()
      }
    })
  },
  • 在info_collects.js中写入
  addRow(row){
    
    
    var dataList = this.data.dataDict.data;
    dataList.unshift(row)
    this.setData({
    
    
      "dataDict.data": dataList,
      "dataDict.total_count": this.data.dataDict.total_count + 1,
      "dataDict.today_count": this.data.dataDict.today_count + 1
    })
  },

22.案例-采集-统计

  • 页面效果
    在这里插入图片描述

22.1 前端

  • 在html的数据统计部分绑定bindToStatistics函数
    <view class="menu" bindtap='bindToStatistics'>
      数据统计
    </view>
  • 在js中编写bindToStatistics函数
  bindToStatistics(){
    
    
    wx.navigateTo({
    
    
      url: '/pages/statistics/statistics',
    })
  },
  • 新建文件夹,页面statistics
  • html
<view class="container">
  <view class="menu" wx:for="{
     
     {dataList}}" wx:key="index">
    <view> <label class="fa fa-calendar"></label> 
    {
   
   {item.create_date}}
    </view>
    <label>{
   
   {item.count}}个</label>
  </view>
</view>
  • css样式
/* pages/statistics/statistics.wxss */
.container{
    
    
  border-top: 1px solid #ddd;
}

.container .menu{
    
    
  font-size: small;
  padding: 10px 40rpx;
  border-bottom: 1px dotted #ddd;
  text-align: center;

  display: flex;
  flex-direction: row;
  justify-content: space-between;
  background-color: white;
}
  • 编写statistics.js
  getRecord(){
    
    
    wx.showLoading({
    
    
      mask: true,
    })
    wx.request({
    
    
      url: api.bankStatistics,
      method: "GET",
      success: (res)=>{
    
    
        this.setData({
    
    
          dataList: res.data
        })
      },
      complete:()=>{
    
    
        wx.hideLoading()
      }
    })
  },
  • 配置中开启下拉刷新
{
    
    
  "usingComponents": {
    
    },
  "navigationBarTitleText": "数据统计",
  "enablePullDownRefresh": true
}

22.2 后端

# api\urls.py
re_path(r'^bank/statistics/$', bank.StatisticsView.as_view()),

# views\bank.py
class StatisticsView(ListAPIView):
    queryset = models.UserInfo.objects.values('create_date').annotate(count=Count('create_date'))\
        .order_by('-create_date')
    # 按create_date分组,组内count计数,基于create_date desc
    serializer_class = StatisticsListSerializer

# serializers\bank.py
class StatisticsListSerializer(serializers.Serializer):
    create_date = serializers.DateField(format='%Y-%m-%d')
    count = serializers.IntegerField()

23.案例-采集-分页问题

24.案例-人脸匹配

要实现的功能:人员信息录入、然后录入信息比对(AI)

  • 实现的效果:

在这里插入图片描述

24.1 前端

  • html中写入url
  <navigator class="menu" url="/pages/face/face">
    <label class="fa fa-meh-o" style="color:#FFA500"></label>
    <view>
      人脸识别
    </view>
  </navigator>
  • 创建face文件夹,face页面
  • 开发face页面的html
<view class="header">
  <camera class="camera" device-position="{
     
     {backFront? 'back': 'front'}}" flash="off" frame-size="medium"></camera>

  <view class="switch" bindtap="switchCamera">
    <image src="/images/rotate-camera-white.png"></image>
  </view>
  <button class="submit" bindtap="takePhoto"> 拍照检测 </button>
</view>


<view class="table">
  <view class="item">
    <view class="title">检测记录</view>
  </view>

  <view class="item" wx:for="{
     
     {record}}" wx:for-item='row' wx:key='index'>
    <view class="record">
      <view class="avatar">
        <image src="{
     
     {row.avatar}}"></image>
      </view>
      <view class="desc">
        <view wx:if="{
     
     {row.error_code == 0}}" class="username">检测成功: {
   
   {row.result.face_list[0].user_list[0].user_info}}</view>
        <view wx:else class="username">检测失败</view>
        <view>
          <view class="txt-group">
            <label class="zh">{
   
   {row.error_msg}}</label>
          </view>
        </view>
      </view>
      <view class="delete">
        <block wx:if="{
     
     {row.error_code == 0}}">
          <label class="fa fa-check-circle" style="color:green"></label>
        </block>
        <block wx:else>
          <label class='fa fa-times-circle' style="color: red"></label>
        </block>
      </view>
    </view>
  </view>
</view>
  • css
/* pages/face/face.wxss */
.header{
    
    
  position: relative;
}

.camera{
    
    
  height: 600rpx;
  width: 100%;
}

.switch{
    
    
  position: absolute;
  top: 50rpx;
  right: 50rpx;
}

.switch image{
    
    
  width: 80rpx;
  height: 80rpx;
}

.header .submit{
    
    
  margin-top: 20rpx;
  color: #fff;
  border: 2rpx solid #00c8b6;
  background-color: #00c8b6;
  font-size: 32rpx;
  font-weight: 400;
}



.table .item{
    
    
  margin-top: 30rpx;
  border-top: 4rpx solid #e7e7e7;
  border-bottom: 4rpx solid #e7e7e7;
}

.table .item .title{
    
    
  margin: 20rpx 30rpx;
  padding-left: 10rpx;
  border-left: 5rpx solid #02bfae;
  font-size: 26rpx;
  font-weight: bold;
}

.record{
    
    
  margin: 30rpx 40rpx;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

.record .avatar{
    
    
  width: 100rpx;
  height: 100rpx;
}

.record .avatar image{
    
    
  width: 100%;
  height: 100%;
  border-radius: 30rpx;
}

.record .desc{
    
    
  margin: 0 40rpx;
}

.desc{
    
    
  width: 290rpx;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
}

.desc .username{
    
    
  font-size: 25rpx;
}

.txt-group{
    
    
  font-size: 20rpx;
  margin: 5rpx 0;
}

.txt-group .zh{
    
    
  color: #8c8c8c;
}

.txt-group .en{
    
    
  color: #cccccc;
}

.area{
    
    
  color: #00c8b6;
  font-weight: bold;
}

.delete{
    
    
  width: 100rpx;
  text-align: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

CSS position 相对定位和绝对定位

  • js
  switchCamera(e){
    
    
    var old = this.data.backFront
    this.setData({
    
    
      backFront: !old
    })
  },
  takePhoto(e){
    
    
    wx.showLoading({
    
    
      title: '检测中',
      mask: true
    })

    const ctx = wx.createCameraContext()
    ctx.takePhoto({
    
    
      quality: 'high',
      success: (res) =>{
    
    
        wx.uploadFile({
    
    
          filePath: res.tempImagePath,
          name: 'avatar',
          url: api.bankFace,
          success: (response)=>{
    
    
            let data = JSON.parse(response.data)
            console.log(data)
            if(data.status){
    
    
              data.content.avatar = res.tempImagePath
              var oldRecord = this.data.record
              // console.log(data)
              oldRecord.unshift(data.content)
              this.setData({
    
    
                record: oldRecord
              })
            }else{
    
    
              wx.showToast({
    
    
                title: '请正常拍照',
                icon: none
              })
            }
          },
          complete: function(){
    
    
            wx.hideLoading()
          }
        })
      }
    })
  },

24.2 后端

# api/urls.py
re_path(r'^/bank/face/$', bank.FaceView.as_view()),
    
# view.bank.py
class FaceView(ListAPIView):
    """
    人脸检测,用户提交图片,后台根据图片进行人脸搜索
    """

    def post(self, request, *args, **kwargs):
        avatar_object = request.data.get('avatar')
        if not avatar_object:
            return Response({
    
    'msg': '未提交图像', "status": False})
        from utils import ai
        result = ai.search(avatar_object)

        return Response({
    
    "content": result, "status": True})

# ai.py
def search(file_object):
    url = "https://aip.baidubce.com/rest/2.0/face/v3/multi-search"

    data = base64.b64encode(file_object.read()).decode("utf8")
    res = requests.post(
        url=url,
        headers={
    
    
            'Content-Type': 'application/json'
        },
        params={
    
    
            'access_token': get_access_token()
        },
        data={
    
    
            'group_id_list': 'test',
            'image': data,
            "image_type": "BASE64",
            "match_threshold": 80,
            'liveness_control': 'HIGH', # 活体检测控制:高拒绝率
        }
    )

    return res.json()

25.案例-语音识别

功能:页面、录音、发送后端API(文件)+识别

猜你喜欢

转载自blog.csdn.net/zhangyifeng_1995/article/details/130502731