[Notas del estudio] Un conjunto completo de procesos de desarrollo para los applets de WeChat (ing)

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

1. Visión general

inserte la descripción de la imagen aquí

2. Construcción del entorno

levemente

4. Proyecto de versión pura

inserte la descripción de la imagen aquí

5. Inicio rápido

5.1 Componentes (similares a las etiquetas HTML)

etiquetas en wxml etiquetas en html
texto durar
vista división
imagen imagen
icono
navegador a

ver componente

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

componente de texto

Documentación del componente del subprograma

  • Use la función en el componente de texto y pase parámetros
# html
<text bindtap="clickMe" data-nid="999" data-name="nkehougaosuni">源代码学城</text>
  • definir función en js
# js
clickMe(e){
    
    
    console.log("点我了")
    console.log(e.target.dataset);
},

e encapsula los datos pasados ​​por la solicitud.
El html anterior pasado data-nidene.target
inserte la descripción de la imagen aquí

componente de imagen

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

px es un píxel y rpx es un conjunto de píxeles dinámicos en WeChat. El ancho total es de 750 rpx. Algunos teléfonos son anchos y otros estrechos. El uso de rpx se adaptará automáticamente al teléfono para escalar.

icono componente - icono

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

El tamaño también es compatible con rpx

componente del navegador

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

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

Nota: solo puede saltar a la página (no a la barra de pestañas).
Para saltar a la barra de pestañas
, para la etiqueta del navegador, debe agregar el parámetro open-type="switchTab"
Para la forma de llamar a AIP, debe usar wx .switchTab({url: ' '} )

5.2 Visualización y vinculación de datos

  • Escribir datos en datos js
# js
  data: {
    
    
      city:"北京",
      nameList: ["张三", "李四", "王五"]
  },
  • Usar datos en html
# html
<view>
  <text>{
   
   {city}}</text>
</view>

wx:para sintaxis

# 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' es equivalente a la creación de alias y
el uso del parámetro wx:key puede mejorar el rendimiento: cuando los cambios de datos activan la capa de renderizado para volver a renderizar, los componentes con la clave se corregirán y el marco garantizará que se reordenan, mientras que en lugar de recrearlos, para garantizar que el componente mantenga su propio estado y para mejorar la eficiencia de la representación de la lista.

wx:si sintaxis

uso básico

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

Tenga en cuenta que las comillas simples deben usarse dentro de las comillas dobles en wx:if

bloquear

Para mostrar varias etiquetas, existen dos métodos:
Método 1:

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

Método 2:

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

Las tres etiquetas de texto que muestra el método 1 están en el tercer nivel, mientras que las tres etiquetas de texto que muestra el método 2 están en el segundo nivel. Debido a que el bloque solo se usa para procesar la lógica condicional y no para renderizar, tendrá una estructura menos que el método 1.

oculto

Similar a mostrar en vue

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

El uso de hidden mostrará la etiqueta en la página, pero estará oculta. Para wx:if, si no se cumple la condición, no se mostrará en la página.

10. Enlace de datos

enlace de valor único

La versión anterior del mini programa solo admitía el enlace unidireccional, no el enlace bidireccional. Por ejemplo:

  • Realice un efecto vinculante bidireccional a través de setData
# html
<view>
  <text>{
   
   {name}}</text>
  <button bindtap="changeName">修改</button>
</view>
# js
  changeName(e){
    
    
    this.setData({
    
    
      name: "张开"
    });
  },

De esta forma, al hacer clic en Modificar cambiará el nombre para abrir. Pero esto no es un vínculo bidireccional. Es porque la página ha cambiado después de que setData haya modificado los datos.

  • efecto de enlace unidireccional de entrada
<view>
  <text>{
   
   {name}}</text>
  <button bindtap="changeName">修改</button>
  <input type='text' value="{
     
     {name}}" placeholder="请输入"/>
</view>

El valor de la etiqueta de entrada puede establecer la variable vinculada. Aquí, el cuadro de entrada está vinculado a la variable de nombre.
En Vue, si modifica el cuadro de entrada, debido a que está vinculado en dos direcciones, la etiqueta de texto que lee el nombre también cambia en consecuencia.
Pero en WeChat El valor predeterminado no es el enlace bidireccional, por lo que modificar el contenido del cuadro de entrada no modificará el nombre. Para el enlace
bidireccional, debe agregar la función doChange especificada por el parámetro bindinput al entrada y agregue la función setData al doChange

  • input+bininput realiza un enlace bidireccional
# 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
    })
  },
  • En el componente de entrada, use model:value para realizar un enlace bidireccional (nueva versión)
# 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){
    
    

  },

Puede realizar un efecto similar al enlace bidireccional en vue, pero hay un error y la advertencia significa que se debe definir un evento de entrada de enlace (la función doChange2 no puede escribir nada). Si no lo agregas, reportarás un error.
inserte la descripción de la imagen aquí

enlace de lista

  • Enlace bidireccional basado en la lista de finalización 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

Ayuda Documentación-API

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

Enviar petición

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

12. Estilo e icono

Ícono de documentación
El contenido del ícono en el documento es relativamente pequeño, por lo que puede introducir íconos de terceros, fontawesome

Más icon-fontawesome

[Miniprograma de WeChat] Use fontawesome6 en el programa de WeChat Mini

  • descarga de fontawesome (CSS+TTF)
  • Conversión TTF -> base64
  • importar proyecto+css

Las diferentes versiones de fontawesome admiten diferentes íconos, y la versión v4 se usa en el video.

13. Barra de pestañas

Establezca
la configuración global de tabBar en app.json -tabBar

14. Caso-menú-pantalla

diseño-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>

diseño-estilo-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;
}

Referencia de caja flexible: https://www.runoob.com/css3/css3-flexbox.html

15. Lista de recopilación de casos

diseño-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>

estilo-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;
}

datos-info_collect.js

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

16. Lista de datos de recopilación de casos

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

17. Caso-colección-eliminación

<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 backend de recopilación de casos

API sencilla

1. Cree un nuevo proyecto
2. Cree una aplicación llamada api
3. Registre api, rest_framework en la configuración

  • 4. Crear estructura de tabla
    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)

Migrar a través make migrationsde ymigrate

FileField puede cargar archivos directamente al directorio de archivos

  • 5. Crea una ruta
    • 5.1 La url total del proyecto
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 Configurar medios en settings.py
import os
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
    • 5.3 urls.py bajo api: recibe las solicitudes de enrutamiento distribuidas
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()),
]

Hay un punto de conocimiento aquí es el famoso grupo.

    1. Cree una nueva carpeta de serializadores en la aplicación

Ingrese diferentes serializadores de acuerdo con diferentes solicitudes.
Los serializadores se colocan uniformemente en la carpeta de serializadores.

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

Introducción y uso de python @property

    1. Elimine el archivo views.py, agregue la carpeta views.py y cree el archivo bank.py

Ir diferentes serializadores de acuerdo a diferentes solicitudes

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() no está llamando a la función del mismo nombre de la clase principal
super() es en realidad super(tipo, obj): irá a la siguiente clase de tipo en obj.mro para comenzar a buscar.
MRO, utilizando el algoritmo C3, vuelve al punto de bifurcación anterior cuando encuentra un punto de convergencia
Referencia: MRO https://www.bilibili.com/video/BV1mV4y1o7fu

  • 8. Cree el directorio utils en el directorio del proyecto y cree el archivo ai.py

  • Baidu Cloud: https://cloud.baidu.com/Reciba
    recursos gratis - cree aplicaciones -...
    cargue imágenes de acuerdo con los documentos 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)

Materiales de referencia Base64: ¿Por qué usar la codificación base64? ¿Cuáles son los requisitos situacionales? - respuesta de flydean - Sabiendo que
el resultado devuelto debe convertirse en un diccionario a través de .json ()

  • 9. Modificar el archivo de configuración
ALLOWED_HOSTS = ["*"]
  • 10. Modifique el host de configuraciones
    del proyecto a 0.0.0.0 y
    cambie el puerto al puerto 8001 para que se pueda acceder a todas las ips
    inserte la descripción de la imagen aquí

  • 11. En la herramienta de desarrollo del subprograma WeChat, cree api.js en el proyecto

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. Importar api.js en info_collect.js
const api = require('../../config/api.js') // 导入

url: api.bank,

19. Caso-Forma-Visualización

  • Evento vinculante de recopilación de información
<view class='menu' style='border-right: 1rpx solid #ddd;' bindtap="bindToForm">
  信息采集
  • Definir evento bindToForm en js
  bindToForm(e){
    
    
    wx.navigateTo({
    
    
      url: '/pages/form/form',
    })
  },
  • Cree una carpeta de formulario debajo de las páginas, luego cree una página de formulario y luego desarrolle la página de formulario.
    El efecto que se logrará:
    inserte la descripción de la imagen aquí
    código
<!--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>

Selector de documentación de desarrollo de WeChat

  • estilos, formulario.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;
}
  • Escribir formulario.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. Fotografía en forma de caso

  • Cree una cámara/página de cámara en la carpeta de páginas
  • cámara.wxml archivo
    subprograma documento-componente-medios componente-cámara
<!--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()
      }
    })
  },

Modificar el valor de la página anterior:
getCurrentPages: Obtiene la lista de páginas hasta la página actual
pages[pages.length - 2]: Obtiene la página anterior de la página actual
wx.navigateBack(): Regresa a la página anterior

21. Caso - formulario - enviar formulario

Escriba el evento postUser en form.wxml en form.js

  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()
      }
    })
  },
  • Escriba en 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. Estadísticas de recolección de casos

  • efecto de página
    inserte la descripción de la imagen aquí

22.1 Interfaz

  • Enlace la función bindToStatistics en la parte de estadísticas de datos de html
    <view class="menu" bindtap='bindToStatistics'>
      数据统计
    </view>
  • Escriba la función bindToStatistics en js
  bindToStatistics(){
    
    
    wx.navigateTo({
    
    
      url: '/pages/statistics/statistics',
    })
  },
  • Nueva carpeta, estadísticas de la página
  • 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>
  • estilo 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;
}
  • Escribir estadísticas.js
  getRecord(){
    
    
    wx.showLoading({
    
    
      mask: true,
    })
    wx.request({
    
    
      url: api.bankStatistics,
      method: "GET",
      success: (res)=>{
    
    
        this.setData({
    
    
          dataList: res.data
        })
      },
      complete:()=>{
    
    
        wx.hideLoading()
      }
    })
  },
  • Habilitar la actualización desplegable en la configuración
{
    
    
  "usingComponents": {
    
    },
  "navigationBarTitleText": "数据统计",
  "enablePullDownRefresh": true
}

22.2 Back-end

# 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. Problema de paginación de colección de casos

levemente

24. Caso - emparejamiento de caras

La función a realizar: entrada de información de personal y luego comparación de información de entrada (AI)

  • Efecto logrado:

inserte la descripción de la imagen aquí

24.1 Interfaz

  • escribir url en html
  <navigator class="menu" url="/pages/face/face">
    <label class="fa fa-meh-o" style="color:#FFA500"></label>
    <view>
      人脸识别
    </view>
  </navigator>
  • Crear carpeta de caras, página de caras
  • Desarrollar el html de la página de la cara
<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;
}

Posición CSS posicionamiento relativo y posicionamiento absoluto

  • 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 Back-end

# 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. Caso - Reconocimiento de voz

Función: página, grabación, envío de API de back-end (archivo) + reconocimiento

Supongo que te gusta

Origin blog.csdn.net/zhangyifeng_1995/article/details/130502731
Recomendado
Clasificación