https://www.bilibili.com/video/BV1mV4y1o7fu
1. Visión general
2. Construcción del entorno
levemente
4. Proyecto de versión pura
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 pasadodata-nid
ene.target
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>
- Usando la API y las funciones de WeChat, también puede realizar la función de salto
Documentación de la API de WeChat
# 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.
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
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 migrations
de 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.
-
- 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
-
- 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
-
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á:
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>
- 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
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:
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