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-nid
在e.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>
- 利用微信API和函数,也可以实现跳转功能
微信API文档
# 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
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
- 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 migrations
和migrate
进行迁移
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()),
]
这里有个知识点是有名分组
-
- 在应用下新建一个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
-
- 删除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>
- 样式,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;
}
- 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(文件)+识别