智慧社区

智慧社区

# 智慧社区-小程序
-欢迎页面
-首页
-轮播图
-公告
-信息采集,社区活动,人脸检测,语音识别,心率检测,积分商城
-信息采集页面
-采集人数
-采集详情页面
-采集统计页面
-人脸检测页面
-语音识别页面
-积分商城页面
-活动
-活动列表
-报名活动
-加载更多
-公告
-公告列表
-我的
-信息展示
-登录

欢迎页面

后端

models

class Welcome(models.Model):
img = models.ImageField(upload_to='welcome', default='slash.png')
order = models.IntegerField()
link = models.CharField(max_length=32)
create_time = models.DateTimeField(auto_now=True)
is_delete = models.BooleanField(default=False)

views

from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from .serializers import WelcomeSerializer
from .models import Welcome
class WelcomeView(GenericViewSet, ListModelMixin):
queryset = Welcome.objects.all().filter(is_delete=False).order_by("-order")
serializer_class = WelcomeSerializer

serializer

class WelcomeSerialzier(serializers.ModelSerializer):
class Meta:
model=Welcome
fields=['img','link']

前端

# 1 欢迎页面
-onLoad---》向后端发送请求
-拿回图片,盖住整个屏幕
-倒计时3s(跳过)--》进入到首页

wxml

<view class="container">
<text bindtap="doJump" class="jump">跳过{{seconds}}秒</text>
<image class="img" src="{{img}}" bind:tap="goPage"/>
</view>

wxss

page{
height: 100%;
}
.container {
height: 100%;
width: 100%;
}
.container .img{
height: 100%;
width: 100%;
}
.jump{
font-size: 30rpx;
position: absolute;
left: 600rpx;
top: 80rpx;
background-color: #dddddd;
padding: 10rpx 20rpx;
border-radius: 20rpx;
}

js

import settings from '../../static/js/settings.js'
Page({
data: {
seconds:3,
img:'/static/img/bg/splash3.png',
url:'/pages/log/log'
},
onLoad(options) {
// 发送请求,获取欢迎页
wx.request({
url: settings.welcome,
method:'GET',
success:(res)=>{
this.setData({
img:res.data[0].img,
url:res.data[0].link
})
}
})
let t=setInterval(()=>{
if(this.data.seconds <= 0){
//定时器清除
clearInterval(t)
//跳转到项目页面 + 配置tabBar
wx.switchTab({
url: '/pages/index/index',
})
}else{
this.setData({
seconds:this.data.seconds - 1
})
}
},1000)
},
goPage(){
wx.navigateTo({
url: '/pages/second/collection/collection',
})
},
doJump(){
wx.switchTab({
url: '/pages/index/index',
})
}
})

image-20240528185350249

首页

"pages": [
"pages/welcome/welcome",
"pages/index/index",
"pages/my/my",
"pages/activity/activity",
"pages/notice/notice",
],

前端

wxml

<view class="container">
<!-- 轮播图 -->
<view class="banner">
<swiper autoplay indicator-dots circular indicator-color='#FFFFF' interval='3000'>
<swiper-item>
<image src="/static/img/banner/banner1.png" mode="widthFix" />
</swiper-item>
<swiper-item>
<image src="/static/img/banner/banner2.png" mode="widthFix" />
</swiper-item>
<swiper-item>
<image src="/static/img/banner/banner3.png" mode="widthFix" />
</swiper-item>
</swiper>
</view>
<van-notice-bar left-icon="volume-o" text="在代码阅读过程中人们说脏话的频率是衡量代码质量的唯一标准。" />
<van-grid column-num="3">
<van-grid-item icon="/static/img/menu/ht.png" text="信息采集" bind:click="gotoCollection" />
<van-grid-item icon="/static/img/menu/wyf.png" text="社区活动" bind:click="gotoActivity" />
<van-grid-item icon="/static/img/menu/wygl.png" text="人脸检测" bind:click="gotoFace" />
<!-- 可以根据需要添加更多的快捷入口 -->
<van-grid-item icon="/static/img/menu/wylx.png" text="语音识别" bind:click="gotoVoice" />
<van-grid-item icon="https://b.yzcdn.cn/vant/icon-demo-1126.png" text="心率检测" bind:click="gotoHeart" />
<van-grid-item icon="/static/img/menu/ht.png" text="积分商城" bind:click="gotoGoods" />
</van-grid>
<view class="bottom">
<view>
<image src="/static/img/home/cute_1.jpg" mode="scaleToFill" />
</view>
<view>
<image src="/static/img/home/cute_2.jpg" mode="scaleToFill" />
</view>
<view>
<image src="/static/img/home/cute_3.jpg" mode="scaleToFill" />
</view>
<view>
<image src="/static/img/home/cute_4.jpg" mode="scaleToFill" />
</view>
</view>
</view>

wxss

.banner image{
width: 100%; /* 图片宽度拉伸至容器宽度 */
height: 100%; /* 图片高度拉伸至容器高度 */
object-fit: cover; /* 图片将覆盖整个容器区域,可能被裁剪 */
}
.bottom{
display: flex;
justify-content: space-evenly;
margin-top: 20rpx;
flex-wrap: wrap;
}
.bottom>view>image{
width: 345rpx;
height: 200rpx;
}

js

// index.js
Page({
gotoCollection(){
wx.navigateTo({
url: '/pages/second/collection/collection'
})
},
gotoFace(){
wx.navigateTo({
url: '/pages/second/face/face'
})
},
gotoVoice(){
wx.navigateTo({
url: '/pages/second/voice/voice'
})
},
gotoActivity(){
wx.switchTab({
url: '/pages/activity/activity',
})
},
gotoHeart(){
wx.navigateTo({
url: '/pages/second/heart/heart',
})
},
gotoGoods(){
wx.navigateTo({
url: '/pages/second/goods/goods',
})
},
})

image-20240528185421570

信息采集

<view class="container">
<view class="top">
<view class="tip">今日采集数量(人)</view>
<view class="count">{{dataDict.today_count}}</view>
</view>
<view class="function">
<view class="menu" style="border-right:1rpx solid #ddd;" bindtap="bindToForm">
<text class="iconfont icon-xinxicaiji"></text> 信息采集
</view>
<view class="menu" bindtap="bindToStatistics">
<text class="iconfont icon-shujutongji" ></text> 数据统计
</view>
</view>
<view class="table">
<view class="item">
<view class="title">社区信息列表({{dataDict.today_count}}人)</view>
</view>
<view class="item" wx:for="{{dataDict.result}}" 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 class="en"> | {{row.area.desc}}</label>
</view>
<view class="area">
<label class="fa fa-map-marker"></label> {{row.area.name}}
</view>
</view>
</view>
<view class="delete" bindtap="doDeleteRow" data-nid="{{row.id}}" data-index="{{index}}" >
<label class="iconfont icon-shanchu"></label>
</view>
</view>
</view>
</view>
</view>

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: 58rpx;
}
.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;
flex-direction: row;
flex-direction: column;
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;
}
.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-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;
}

json

{
"usingComponents": {},
"navigationBarBackgroundColor": "#01ccb6",
"navigationBarTitleText": "",
"enablePullDownRefresh": true,
"navigationBarTextStyle":"white",
"backgroundColor":"#01ccb6"
}

js

// pages/second/collection/collection.js
Page({
data: {
},
onLoad(options) {
},
bindToForm(){
wx.navigateTo({
url: '/pages/second/camera/camera',
})
}
})

image-20240528185732014

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="/static/img/camera/record_on.png"></image>
</view>
<view class="switch" bindtap="switchCamera">
<image src="/static/img/camera/rotate-camera-white.png"></image>
</view>
</view>
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;
height: 80rpx;
}
.switch image{
width: 80rpx;
height: 80rpx;
}
Page({
data: {
backFront:true
},
switchCamera(e) {
var old = this.data.backFront
this.setData({
backFront: !old
})
},
})

image-20240528185743783

首页轮播图和公告接口

前端

html

<view class="container">
<!-- 轮播图 -->
<view class="banner">
<swiper autoplay indicator-dots circular indicator-color='#FFFFF' interval='3000'>
<swiper-item wx:for="{{banner_list}}" wx:key="id">
<image src="{{item.img}}" mode="widthFix" />
</swiper-item>
</swiper>
</view>
<van-notice-bar left-icon="volume-o" text="{{notice}}" />
<van-grid column-num="3">
<van-grid-item icon="/static/img/menu/ht.png" text="信息采集" bind:click="gotoCollection" />
<van-grid-item icon="/static/img/menu/wyf.png" text="社区活动" bind:click="gotoActivity" />
<van-grid-item icon="/static/img/menu/wygl.png" text="人脸检测" bind:click="gotoFace" />
<!-- 可以根据需要添加更多的快捷入口 -->
<van-grid-item icon="/static/img/menu/wylx.png" text="语音识别" bind:click="gotoVoice" />
<van-grid-item icon="https://b.yzcdn.cn/vant/icon-demo-1126.png" text="心率检测" bind:click="gotoHeart" />
<van-grid-item icon="/static/img/menu/ht.png" text="积分商城" bind:click="gotoGoods" />
</van-grid>
<view class="bottom">
<view>
<image src="/static/img/home/cute_1.jpg" mode="scaleToFill" />
</view>
<view>
<image src="/static/img/home/cute_2.jpg" mode="scaleToFill" />
</view>
<view>
<image src="/static/img/home/cute_3.jpg" mode="scaleToFill" />
</view>
<view>
<image src="/static/img/home/cute_4.jpg" mode="scaleToFill" />
</view>
</view>
</view>

css

.banner image{
width: 100%; /* 图片宽度拉伸至容器宽度 */
height: 100%; /* 图片高度拉伸至容器高度 */
object-fit: cover; /* 图片将覆盖整个容器区域,可能被裁剪 */
}
.bottom{
display: flex;
justify-content: space-evenly;
margin-top: 20rpx;
flex-wrap: wrap;
}
.bottom>view>image{
width: 345rpx;
height: 200rpx;
}

js

import settings from '../../static/js/settings.js'
Page({
data: {
banner_list: {},
notice: ''
},
onLoad(options) {
wx.request({
url: settings.banner,
method:'GET',
success:(res)=>{
if(res.data.code==100){
this.setData({
banner_list:res.data.banner,
notice:res.data.notice.title + "快来玩呀~~~~嗯~~~"
})
}else{
wx.showToast({
title: '轮播图网络加载失败',
})
}
}
})
},
gotoCollection() {
wx.navigateTo({
url: '/pages/second/collection/collection'
})
},
gotoFace() {
wx.navigateTo({
url: '/pages/second/face/face'
})
},
gotoVoice() {
wx.navigateTo({
url: '/pages/second/voice/voice'
})
},
gotoActivity() {
wx.switchTab({
url: '/pages/activity/activity',
})
},
gotoHeart() {
wx.navigateTo({
url: '/pages/second/heart/heart',
})
},
gotoGoods() {
wx.navigateTo({
url: '/pages/second/goods/goods',
})
},
})

image-20240529150327551

后端

views

from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from .serializers import WelcomeSerializer, BannerSerializer, NoticeSerializer
from .models import Welcome, Notice, Banner
from rest_framework.response import Response
# 广告
class WelcomeView(GenericViewSet, ListModelMixin):
queryset = Welcome.objects.all().filter(is_delete=False).order_by("-order")
serializer_class = WelcomeSerializer
# 轮播图
class BannerView(GenericViewSet, ListModelMixin):
queryset = Banner.objects.all().filter(is_delete=False).order_by("order")[:2]
serializer_class = BannerSerializer
def list(self, request, *args, **kwargs):
res = super().list(request, *args, **kwargs)
notice = Notice.objects.all().order_by("create_time").first()
serializer = NoticeSerializer(instance=notice)
return Response(
{"code": 100, "msg": "成功", "banner": res.data, "notice": serializer.data}
)

models

from django.db import models
# 广告表
class Welcome(models.Model):
img = models.ImageField(upload_to="welcome", default="slash.png")
order = models.IntegerField()
link = models.CharField(max_length=32)
create_time = models.DateTimeField(auto_now=True)
is_delete = models.BooleanField(default=False)
# 轮播图
class Banner(models.Model):
img = models.ImageField(
upload_to="banner", default="banner1.png", verbose_name="图片"
)
order = models.IntegerField(verbose_name="顺序")
create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
is_delete = models.BooleanField(default=False, verbose_name="是否删除")
class Meta:
verbose_name_plural = "轮播图"
# 公告
class Notice(models.Model):
title = models.CharField(max_length=64, verbose_name="公共标题")
content = models.TextField(verbose_name="内容")
img = models.ImageField(
upload_to="notice", default="notice.png", verbose_name="公告图片"
)
create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
class Meta:
verbose_name_plural = "公告表"

admin

from django.contrib import admin
# Register your models here.
from .models import Welcome, Banner, Notice
admin.site.register(Welcome)
admin.site.register(Banner)
admin.site.register(Notice)

serializer

from rest_framework import serializers
from .models import Welcome, Banner, Notice
class WelcomeSerializer(serializers.ModelSerializer):
class Meta:
model = Welcome
fields = ["img", "link"]
# 轮播图表序列化类
class BannerSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
fields = "__all__"
# 社区通知序列化类
class NoticeSerializer(serializers.ModelSerializer):
class Meta:
model = Notice
fields = ["id", "title"]

url

from django.contrib import admin
from django.urls import path, include
from .views import WelcomeView, BannerView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register("welcome", WelcomeView, "welcome")
router.register("banner", BannerView, "banner")
urlpatterns = [path("", include(router.urls))]

信息采集拍照上传

# 1 小程序端拍照---》上传到咱们后台--》后台保存一份(media)--》把图片---》传到百度ai人脸识别库中存着
# 2 学习
-百度ai人脸库上传
-百度ai人脸库删除
-百度ai人脸库根据人脸匹配

人脸识别(上传-删除-查询)

详情见 https://ai.baidu.com/ai-doc/FACE/ek37c1qiz

from aip import AipFace
import base64
from pypinyin import lazy_pinyin, Style
class BaiDuAI:
def __init__(self,APP_ID='',API_KEY='',SECRET_KEY=''):
""" 你的 APPID AK SK """
self.APP_ID = APP_ID
self.API_KEY = API_KEY
self.SECRET_KEY = SECRET_KEY
self.client = AipFace(self.APP_ID, self.API_KEY, self.SECRET_KEY)
def name_to_pinyin(self,text):
style = Style.TONE3
name_list=lazy_pinyin(text, style=style)
return ''.join(name_list)
def add_user(self):
image = base64.b64encode(open('./彭于晏.jpg','rb').read()).decode('utf-8')
imageType = "BASE64"
groupId = "100"
userId=self.name_to_pinyin('彭于晏')
""" 调用人脸注册 """
res=self.client.addUser(image, imageType, groupId, userId)
print(res)
""" 如果有可选参数 """
# options = {}
# options["user_info"] = "彭于晏"
# options["quality_control"] = "NORMAL"
# options["liveness_control"] = "LOW"
# options["action_type"] = "REPLACE"
#
# """ 带参数调用人脸注册 """
# self.client.addUser(image, imageType, groupId, userId, options)
'''
{'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 937906163, 'timestamp': 1716948937, 'cached': 0, 'result': {'face_token': '7598884f9d9a349218941e8e6f52c884', 'location': {'left': 493.4, 'top': 329.74, 'width': 348, 'height': 321, 'rotation': -8}}}
'''
def search(self):
image = base64.b64encode(open('./pyy.jpeg', 'rb').read()).decode('utf-8')
imageType = "BASE64"
groupIdList = "100,2"
""" 调用人脸搜索 """
res=self.client.search(image, imageType, groupIdList);
""" 如果有可选参数 """
# options = {}
# options["match_threshold"] = 70
# options["quality_control"] = "NORMAL"
# options["liveness_control"] = "LOW"
# # options["user_id"] = "233451"
# options["max_user_num"] = 3
#
# """ 带参数调用人脸搜索 """
# res=self.client.search(image, imageType, groupIdList, options)
print(res)
'''
{'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 1190062038, 'timestamp': 1716949190, 'cached': 0, 'result': {'face_token': 'bdbc6214eb18ccee1bf72d1f72f0c979', 'user_list': [{'group_id': '100', 'user_id': 'peng2yu2yan4', 'user_info': '', 'score': 14.822490692139}]}}
'''
def delete(self):
userId = ""
groupId = ""
faceToken = ""
""" 调用人脸删除 """
res=self.client.faceDelete(userId, groupId, faceToken);
print(res)
'''
{'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 1355696697, 'timestamp': 1716949355, 'cached': 0, 'result': None}
'''
if __name__ == '__main__':
ai=BaiDuAI()
# ai.add_user()
# ai.search()
ai.delete()

后端接口

# 1 采集上传接口---》存本地--》存百度ai
# 2 查询今天采集数据--》1 当前用户(没实现) 2 当天采集的数据 3 总条数Collection.objects.all().filter(create_time__gte=datetime.now().date())
# 3 删除采集接口-->id号删除

views

class CollectionView(GenericViewSet, ListModelMixin, DestroyModelMixin, CreateModelMixin):
# 查出当天的--》没过滤当前用户
queryset = Collection.objects.all().filter(create_time__gte=datetime.now().date())
serializer_class = CollectionSerializer
def get_serializer_class(self):
if self.action == 'create':
return CollectionSaveSerializer
else:
return CollectionSerializer
def list(self, request, *args, **kwargs):
# 过滤当前用户采集的--》多种类型用户,数据权限不一样
res = super().list(request, *args, **kwargs)
today_count = len(self.get_queryset())
return Response({'code': 100, 'msg': '成功', 'result': res.data, 'today_count': today_count})
## 删除人脸,没有删除 具体的图片--》定时任务--》每天晚上备份用户头像
def destroy(self, request, *args, **kwargs):
from libs.baidu_ai import BaiDuFace
instance = self.get_object()
# 百度ai中删除
baidu = BaiDuFace()
res = baidu.delete(instance.name_pinyin, instance.face_token)
print(res)
self.perform_destroy(instance)
return Response()
# def perform_destroy(self, instance):
# from libs.baidu_ai import BaiDuFace
# baidu = BaiDuFace()
# res = baidu.delete(instance.name_pinyin, instance.face_token)
# print(res)
# super().perform_destroy(instance)

urls

router.register('collection', CollectionView, 'collection') # 3个接口
## 3个接口######
http://192.168.1.96:8000/api/v1/collection/ # get 获取数据
http://192.168.1.96:8000/api/v1/collection/ # post 提交采集
http://192.168.1.96:8000/api/v1/collection/1/ # delete 删除

serializer

# 上传人脸序列化类
class CollectionSaveSerializer(serializers.ModelSerializer):
class Meta:
model = Collection
fields = ['name', 'avatar', 'area']
def create(self, validated_data):
# 在百度ai注册
from libs.baidu_ai import BaiDuFace
baidu=BaiDuFace()
avatar_file_object = validated_data.get('avatar')
print(avatar_file_object)
name = validated_data.get('name')
name_pinyin=baidu.name_to_pinyin(name)
res=baidu.add_user(avatar_file_object,name,name_pinyin)
validated_data['name_pinyin'] = name_pinyin
validated_data['face_token'] = res.get('result').get('face_token')
instance=super().create(validated_data)
return instance
# 采集序列化类
class CollectionSerializer(serializers.ModelSerializer): # 查询所有序列化类
class Meta:
model = Collection
fields = ['id', 'name', 'avatar', 'area']
depth = 1 # area 外键关联详情拿到

image-20240529154533105

采集列表页面 collection

wxml

<view class="container">
<view class="top">
<view class="tip">今日采集数量(人)</view>
<view class="count">{{dataDict.today_count}}</view>
</view>
<view class="function">
<view class="menu" style="border-right:1rpx solid #ddd;" bindtap="bindToForm">
<text class="iconfont icon-xinxicaiji"></text> 信息采集
</view>
<view class="menu" bindtap="bindToStatistics">
<text class="iconfont icon-shujutongji" ></text> 数据统计
</view>
</view>
<view class="table">
<view class="item">
<view class="title">社区信息列表({{dataDict.today_count}}人)</view>
</view>
<view class="item" wx:for="{{dataDict.result}}" 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 class="en"> | {{row.area.desc}}</label>
</view>
<view class="area">
<label class="fa fa-map-marker"></label> {{row.area.name}}
</view>
</view>
</view>
<view class="delete" bindtap="doDeleteRow" data-nid="{{row.id}}" data-index="{{index}}" >
<label class="iconfont icon-shanchu"></label>
</view>
</view>
</view>
</view>
</view>

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: 58rpx;
}
.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;
flex-direction: row;
flex-direction: column;
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;
}
.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-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;
}

js

import api from '../../../static/js/api.js'
Page({
data: {
dataDict: {
result: [
],
today_count: 0,
}
},
onLoad(options) {
//发送网络请求...
this.refresh();
},
onShow(){
this.refresh();
},
refresh() {
//1.发送网络请求
//2.数据绑定
wx.showLoading({
mask: true
})
wx.request({
url: api.collection,
method: "GET",
success: (res) => {
this.setData({
dataDict: res.data
})
},
complete() {
wx.hideLoading()
}
})},
bindToForm(){
wx.navigateTo({
url: '/pages/second/form/form',
})
},
doDeleteRow(e){
wx.showModal({
title: '确认是否删除?',
confirmColor: "#ff461f",
success: (res) => {
if (!res.confirm) {
return
}
var nid = e.currentTarget.dataset.nid
wx.showLoading({
title: '删除中',
mask:true
})
wx.request({
url: api.collection + nid + '/',
method:'DELETE',
success:(res) =>{
this.refresh()
},
complete() {
wx.hideLoading()
}
})
}
})
},
})

信息采集form页

html

<view class="avatar">
<image src='{{avatar}}' bindtap="bindToCamera"></image>
</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>

wxss

.avatar{
display: flex;
flex-direction: column;
align-items: center;
}
.avatar image {
margin-top: 140rpx;
width: 300rpx;
height: 300rpx;
border-radius: 30rpx;
border: 1px solid #ddd;
}
.form{
padding: 40rpx;
}
.form .row-group{
padding: 10rpx 0;
border-bottom: 1rpx solid #ddd;
position: relative;
margin-top: 30rpx;
}
.form .row-group text{
font-size: 28rpx;
padding:20rpx 0;
}
.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;
}

js

.avatar{
display: flex;
flex-direction: column;
align-items: center;
}
.avatar image {
margin-top: 140rpx;
width: 300rpx;
height: 300rpx;
border-radius: 30rpx;
border: 1px solid #ddd;
}
.form{
padding: 40rpx;
}
.form .row-group{
padding: 10rpx 0;
border-bottom: 1rpx solid #ddd;
position: relative;
margin-top: 30rpx;
}
.form .row-group text{
font-size: 28rpx;
padding:20rpx 0;
}
.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;
}

拍照页面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="/static/img/camera/record_on.png"></image>
</view>
<view class="switch" bindtap="switchCamera">
<image src="/static/img/camera/rotate-camera-white.png"></image>
</view>
</view>

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;
height: 80rpx;
}
.switch image{
width: 80rpx;
height: 80rpx;
}

js

// pages/second/camera/camera.js
Page({
/**
* 页面的初始数据
*/
data: {
backFront:true
},
takePhoto(){
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
// 获取照片
//console.log(res);
// 对上一个页面中的值,进行修改
var pages = getCurrentPages();
var prevPage = pages[pages.length - 2];
// 把上个页面的图片位置设为刚刚拍的照
prevPage.setData({
avatar: res.tempImagePath
})
//跳转回上一个页面 [v1,v2,v2]
wx.navigateBack({});
}
})
},
switchCamera(e) {
var old = this.data.backFront
this.setData({
backFront: !old
})
},
})

查询所有网格接口

不完善-当前用户网格

#### 路由####
router.register('area', AreaView, 'area')
## 视图类####
class AreaView(GenericViewSet, ListModelMixin):
queryset = Area.objects.all()
serializer_class = AreaSerializer
# 配置一个过滤类--》在过滤类中对 queryset进行过滤---》过滤出当前用户的网格
## 序列化类####
class AreaSerializer(serializers.ModelSerializer):
class Meta:
model = Area
fields = ['id', 'name', 'desc']
#### 网格数据录入使用admin####

信息采集统计

前端

wxml

<view class="container">
<view class="menu" wx:for="{{dataList}}" wx:key="index">
<view> <label class="iconfont icon-SCHEDULE" ></label> {{item.date}}</view>
<label>{{item.count}}个</label>
</view>
</view>

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

js

import settings from '../../../static/js/settings.js'
Page({
/**
* 页面的初始数据
*/
data: {
dataList:[{'date':'2024年4月20日','count':22},{'date':'2024年4月21日','count':12},{'date':'2024年4月22日','count':232}]
},
getRecord:function(){
wx.showLoading({mask:true})
wx.request({
url: settings.statistics,
method:"GET",
success :(res) =>{
this.setData({
dataList:res.data
})
},
complete:()=>{
wx.hideLoading()
}
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getRecord();
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
this.getRecord();
},
})

json

{
"usingComponents": {},
"navigationBarTitleText": "采集统计",
"enablePullDownRefresh": true
}

后端

views

class StatisticsView(GenericViewSet, ListModelMixin):
queryset = Collection.objects.annotate(date=Trunc('create_time', 'day')).values('date').annotate(count=Count('id')).values('date', 'count')
serializer_class = StatisticsListSerializer

serializer

# 采集统计序列化类
class StatisticsListSerializer(serializers.Serializer):
date = serializers.DateTimeField(format="%Y年%m月%d日")
count = serializers.IntegerField()

models

class Collection(models.Model):
name = models.CharField(max_length=32, verbose_name='采集人员姓名')
name_pinyin=models.CharField(max_length=32, verbose_name='姓名拼音',null=True)
avatar = models.ImageField(upload_to='collection/%Y/%m/%d/', default='default.png', verbose_name='头像')
create_time = models.DateTimeField(verbose_name='采集时间',default=datetime.now())
face_token=models.CharField(max_length=128, verbose_name='百度ai的Token',null=True)
area = models.ForeignKey(to='Area', null=True, verbose_name='网格区域', on_delete=models.CASCADE)
class Meta:
verbose_name_plural = '采集表'
def __str__(self):
return self.name

image-20240529154807975

人脸检测功能

前端

<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/camera/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.code == 100}}" class="username">检测成功:{{row.user_id}}</view>
<view wx:else class="username">检测失败:{{row.msg}}</view>
<view>
<view class="txt-group">
<label class="zh">{{row.error_msg}}</label>
</view>
</view>
</view>
<view class="delete">
<block wx:if="{{row.code == 100}}">
<label class="iconfont icon-ziyuanxhdpi" style="color:green"></label>
</block>
<block wx:else>
<label class="iconfont icon-ziyuanxhdpi" 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: 10rpx;
right: 20rpx;
height: 80rpx;
width: 80rpx;
}
.switch image{
height: 100%;
width: 100%;
}
.submit{
margin-top: 40rpx;
color: #fff;
border: 2rpx solid #00c8b6;
background-color: #00c8b6;
font-size: 32rpx;
font-weight: normal;
}
.table{
margin-top: 40rpx;
border-top: 1rpx solid #e7e7e7;
}
.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: 10rpx 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

import settings from '../../../static/js/settings.js'
Page({
data: {
backFront:true,
record:[]
},
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({
url: settings.face,
filePath: res.tempImagePath,
name: 'avatar',
success:(response)=>{
let resdata = JSON.parse(response.data)
console.log(resdata)
if(resdata.code==100 || resdata.code==102){
console.log(resdata)
resdata.avatar = res.tempImagePath
var oldRecord = this.data.record
oldRecord.unshift(resdata)
console.log(oldRecord)
this.setData({
record:oldRecord
})
}else{
wx.showToast({
title: '请正常拍照'
})
}
},
complete:function(){
wx.hideLoading()
}
})
}
})
},
})

json

{
"usingComponents": {},
"navigationBarTitleText": "人脸检测"
}

后端

# 人脸检测接口
class FaceView(GenericViewSet):
def create(self, request, *args, **kwargs):
avatar_object = request.data.get('avatar')
if not avatar_object:
return Response({"msg": "未提交图像", "code": 101})
from libs.baidu_ai import BaiDuAI
ai = BaiDuAI()
result = ai.search(avatar_object)
if result.get('error_code') == 0: # 查询到
# {'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 2159604393, 'timestamp': 1713864959, 'cached': 0, 'result': {'face_token': '095994eca64424cee347b59e0a7edc0e', 'user_list': [{'group_id': '100', 'user_id': 'li3si1xian4', 'user_info': '', 'score': 98.035797119141}]}}
user = result.get('result').get('user_list')[0]
user_info = user.get('user_info')
user_id = user.get('user_id')
score = user.get('score')
return Response({"code": 100, 'msg': '匹配成功', 'user_info': user_info, 'user_id': user_id, 'score': score,'avatar':''})
else:
return Response({"code": 102, 'msg': '匹配失败,该人员可能不是我社区人员,注意防范'})

baidu_api

...

image-20240529155244440

语音识别

前端

wxml

<textarea class="text" placeholder="等待语音识别自动录入..." placeholder-class="hoder" model:value="{{content}}" maxlength="{{-1}}"></textarea>
<button class="btn" hover-class="press" bind:longpress="recordStart" bind:touchcancel="recordCancel" bind:touchend="recordStop"> <label class="fa fa-microphone"></label> 按住说话</button>

wxss

page{
background-color: #f5f5f5;
}
.text{
height: 400rpx;
background-color: white;
width: 100%;
padding: 20rpx;
}
.btn{
margin-top: 30rpx;
/* color: #fff; */
border: 2rpx solid #ddd;
background-color: white;
font-size: 32rpx;
font-weight: normal;
}
.press label{
color: #179B16;
}
.press{
background-color: #ddd;
}
.hoder{
font-size: 28rpx;
}
#####json####
{
"usingComponents": {},
"navigationBarTitleText": "语音识别"
}

js

const recorderManager = wx.getRecorderManager()
import settings from '../../../static/js/settings.js'
Page({
/**
* 页面的初始数据
*/
data: {
content:"",
record:false
},
recordStart:function(){
this.setData({record:true})
const options = {
// duration: 6000,//指定录音的时长,单位 ms
sampleRate: 16000,//采样率
numberOfChannels: 1,//录音通道数
encodeBitRate: 48000,//编码码率
format: 'wav'//音频格式,有效值
}
//开始录音
recorderManager.start(options)
},
recordCancel:function(){
console.log("停止");
this.setData({record:false})
wx.hideLoading()
},
recordStop:function(){
if(!this.data.record){return}
recorderManager.stop();
recorderManager.onStop((res) => {
// this.tempFilePath = res.tempFilePath
wx.showLoading()
wx.uploadFile({
filePath: res.tempFilePath,
name: 'voice',
url: settings.voice,
success:(response)=>{
console.log(response)
// {'code': 100, 'msg':'成功','result': ['欢迎欢迎']}
let voiceResponse = JSON.parse(response.data)
if(voiceResponse.code == 100){
console.log(voiceResponse)
this.setData({
content:this.data.content + voiceResponse.result[0]
})
}else{
wx.showToast({
title: '识别失败,请重新操作!',
icon: "none"
})
}
},
complete:()=>{
wx.hideLoading()
}
},
)
})
this.setData({record:false})
},
})

后端

baidu_ai

# pip install baidu-aip
from aip import AipSpeech
import base64
# 语音识别
class BaiDuVoice:
def __init__(
self,
APP_ID="",
API_KEY="",
SECRET_KEY="",
):
"""你的 APPID AK SK"""
self.APP_ID = APP_ID
self.API_KEY = API_KEY
self.SECRET_KEY = SECRET_KEY
self.client = AipSpeech(self.APP_ID, self.API_KEY, self.SECRET_KEY)
def speed(self, voice_object):
res = self.client.asr(
voice_object.read(),
"pcm",
16000,
{
"dev_pid": 1537,
},
)
return res

views

from libs.baidu_ai import BaiDuVoice
class VoiceView(GenericViewSet):
def create(self, request, *args, **kwargs):
voice_object = request.data.get('voice')
# with open('./a.wav','wb') as f:
# f.write(voice_object.read())
ai = BaiDuVoice()
result = ai.speed(voice_object)
# {'corpus_no': '6847771638436561158', 'result': ['你是不是打过来?'], 'sn': '15921476781594371078', 'err_msg': 'success.', 'err_no': 0}
if result.get('err_no') == 0:
return Response({'code': 100, 'msg': '识别成功', 'result': result.get('result')})
else:
return Response({'code': 101, 'msg': '识别失败'})

image-20240530210822410

活动页面

前端

wxml

<view class="container">
<!-- 使用wx:for循环遍历活动报名列表 -->
<view wx:for="{{activityList}}" wx:key="index" class="activity-item">
<!-- 活动内容 -->
<view class="activity-content">
<view class="activity-title">{{item.title}}</view>
<view class="activity-enrollment">报名人数:{{item.count}} | 总人数:{{item.total_count}}</view>
<view class="activity-time">获得积分:{{item.score}}</view>
<view class="activity-time">{{item.date}}</view>
<view class="activity-description">{{item.text}}</view>
</view>
<!-- 报名按钮 -->
<button class="signup-btn" bindtap="handleSignup">报名</button>
</view>
</view>

wxss

.container {
padding: 20rpx;
}
.activity-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20rpx;
border-bottom: 1px solid #ebebeb;
padding-bottom: 20rpx;
}
.activity-content {
flex: 1;
}
.activity-title {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.activity-time {
font-size: 24rpx;
color: #666666;
margin-bottom: 10rpx;
}
.activity-enrollment {
font-size: 24rpx;
color: #999999;
margin-bottom: 10rpx;
}
.activity-description {
font-size: 24rpx;
color: #333333;
margin-top: 10rpx;
white-space: pre-wrap; /* 自动换行 */
}
.signup-btn {
background-color: #50c8ff;
color: #ffffff;
border: none;
border-radius: 4rpx;
padding: 10rpx 20rpx;
font-size: 24rpx;
}

js

var app = getApp();
import settings from '../../static/js/settings.js'
Page({
data: {
activityList: [
]
},
onLoad: function () {
// 页面加载时执行的逻辑
this.refresh()
},
refresh(){
wx.showLoading({
mask: true
})
wx.request({
url: settings.activity,
method: "GET",
success: (res) => {
this.setData({
activityList: res.data
})
},
complete() {
wx.hideLoading()
}
})
},
handleSignup: function (event) {
// 处理报名按钮点击事件
var index = event.currentTarget.dataset.index; // 获取当前点击的活动索引
console.log('点击了报名按钮,索引为:', index);
}
})

后端

views

class ActivityView(GenericViewSet,ListModelMixin):
queryset =Activity.objects.all().order_by('date')
serializer_class = ActivitySerializer

serializer

class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
fields = ['id', 'title','text','date','count','score','total_count']
extra_kwargs={
'date':{'format':"%Y-%m-%d"}
}

models

class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
avatar = models.FileField(verbose_name="头像", max_length=128, upload_to='avatar')
create_date = models.DateField(verbose_name="日期", auto_now_add=True)
score = models.IntegerField(verbose_name="积分", default=0)
class Meta:
verbose_name_plural = '用户表'
def __str__(self):
return self.name
# 活动表
class Activity(models.Model):
title = models.CharField(verbose_name="活动标题", max_length=128)
text = models.TextField(verbose_name="活动描述", null=True, blank=True)
date = models.DateField(verbose_name="举办活动日期")
count = models.IntegerField(verbose_name='报名人数', default=0)
total_count = models.IntegerField(verbose_name='总人数', default=0)
score = models.IntegerField(verbose_name="积分", default=0)
join_record = models.ManyToManyField(verbose_name="参与者",
through="JoinRecord",
through_fields=("activity", "user"),
to="UserInfo")
class Meta:
verbose_name_plural = '活动表'
def __str__(self):
return self.title
# 活动报名记录
class JoinRecord(models.Model):
user = models.ForeignKey(verbose_name='用户', to="UserInfo", on_delete=models.CASCADE)
activity = models.ForeignKey(verbose_name="活动", to="Activity", on_delete=models.CASCADE, related_name='ac')
exchange = models.BooleanField(verbose_name="是否已兑换", default=False)
class Meta:
verbose_name_plural = '活动报名记录'

image-20240530210808124

公告页面

前端

wxml

<view class="container">
<!-- 使用wx:for循环遍历社区公告列表 -->
<view wx:for="{{noticeList}}" wx:key="index" class="notice-item">
<!-- 左侧图片 -->
<image class="notice-image" src="{{item.img}}" mode="aspectFill"></image>
<!-- 右侧内容 -->
<view class="notice-content">
<view class="notice-title">{{item.title}}</view>
<view class="notice-time">{{item.create_time}}</view>
<view class="notice-details">{{item.content}}</view>
</view>
</view>
</view>

wxss

.container {
padding: 20rpx;
}
.notice-item {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx; /* 添加间距 */
border-bottom: 1px solid #f0f0f0; /* 添加底部边框 */
padding-bottom: 20rpx; /* 增加底部内边距 */
}
.notice-image {
width: 150rpx;
height: 120rpx;
border-radius: 6rpx;
margin-right: 20rpx;
}
.notice-content {
flex: 1;
}
.notice-title {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.notice-time {
font-size: 24rpx;
color: #666666;
margin-bottom: 10rpx;
}
.notice-details {
font-size: 24rpx;
color: #333333;
}

js

import settings from '../../static/js/settings.js'
Page({
data: {
noticeList: [
{
title: '公告标题1',
create_time: '2024-04-25',
content: '公告内容描述1,公告内容描述1,公告内容描述1。', // 可以根据实际情况添加更多内容
igm: '/images/notice/notice1.jpg' // 图片路径,根据实际情况修改
},
{
title: '公告标题2',
create_time: '2024-04-26',
content: '公告内容描述2,公告内容描述2,公告内容描述2。', // 可以根据实际情况添加更多内容
igm: '/images/notice/notice2.jpg' // 图片路径,根据实际情况修改
},
// 可以添加更多社区公告数据
]
},
onLoad: function () {
// 页面加载时执行的逻辑
this.refresh()
},
refresh(){
wx.showLoading({
mask: true
})
wx.request({
url: settings.notice,
method: "GET",
success: (res) => {
this.setData({
noticeList: res.data
})
},
complete() {
wx.hideLoading()
}
})
}
})

后端

views

class NoticeView(GenericViewSet,ListModelMixin):
queryset =Notice.objects.all().order_by('create_time')
serializer_class = NoticeSerializer

serializer

class NoticeSerializer(serializers.ModelSerializer):
class Meta:
model = Notice
fields = ['id', 'title','img','create_time','content']
extra_kwargs={
'create_time':{'format':"%Y-%m-%d"}
}

image-20240530210758857

登陆功能

前端

个人中心页面

<block wx:if="{{userInfo==null}}">
<view class="container1">
<view class="main">
<view class="icon-view">
<!-- 应用图标 -->
<image src="/static/img/icon/icon.png" class="app-icon"></image>
<text class="title">智慧社区</text>
</view>
</view>
<van-cell-group>
<van-cell>
<button type="warn" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">手机号快捷登录</button>
</van-cell>
</van-cell-group>
<!-- 其他手机号登录 -->
<van-cell-group>
<van-cell>
<button type="primary" plain bindtap="handleOtherLogin">其他手机号登录</button>
</van-cell>
</van-cell-group>
<!-- 用户协议同意 -->
<view class="agreement-container">
<checkbox class="checkbox" value="{{agreed}}" bindchange="handleAgreeChange"></checkbox>
<text class="agreement-text">我已阅读并同意</text>
<navigator url="" class="agreement-link">《用户协议》</navigator>
</view>
</view>
</block>
<block wx:else>
<view class="container">
<view class="top-view">
<view class="user">
<view class="row">
<image class="avatar" src="{{userInfo.avatar}}"></image>
<view class="name">
<view bindtap="logout">{{userInfo.name}}</view>
</view>
</view>
</view>
<view class="numbers">
<view class="row">
<text>{{userInfo.score}}</text>
<text>积分</text>
</view>
<view class="row">
<text>55</text>
<text>其他</text>
</view>
<view class="row">
<text>77</text>
<text>其他</text>
</view>
<view class="row">
<text>56</text>
<text>其他</text>
</view>
</view>
</view>
<van-list>
<van-cell title="积分兑换记录" is-link />
<van-cell title="我参加的活动" is-link />
<van-cell title="分享应用" is-link />
<van-cell title="联系客服" is-link />
<van-cell title="退出登录" is-link bind:tap="handleLogout"/>
</van-list>
</view>
</block>

wxss

page{
height: 100%;
}
.login-area{
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.login-area .btn{
width: 200rpx;
height: 200rpx;
border-radius: 500%;
background-color: #5cb85c;
color: white;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.user-area{
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.user-area image{
width: 200rpx;
height: 200rpx;
border-radius: 500%;
}
.user-area .name{
font-size: 30rpx;
padding: 30rpx 0;
}
.user-area .logout{
color: #a94442;
}
.top-view{
background-color: #01ccb6;
color: white;
padding: 40rpx;
}
.top-view .user{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.top-view .user .row{
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.top-view .user .avatar{
width: 100rpx;
height: 100rpx;
border-radius: 50%;
}
.top-view .user .name{
display: flex;
flex-direction: row;
justify-content: flex-start;
padding-left: 20rpx;
}
.top-view .user .name navigator{
padding: 0 5rpx;
}
.top-view .site{
background-color: rgba(0, 0, 0, 0.16);
padding: 20rpx;
border-top-left-radius: 32rpx;
border-bottom-left-radius: 32rpx;
}
.top-view .numbers{
display: flex;
flex-direction: row;
justify-content: space-between;
font-size: 28rpx;
padding: 40rpx;
padding-bottom: 0rpx;
}
.top-view .numbers .row{
display: flex;
flex-direction: column;
align-items: center;
}
/* login.wxss */
.container1 {
padding: 20rpx;
}
.main{
display: flex;
justify-content: center;
align-items: center;
}
.icon-view{
display: flex;
flex-direction: column;
margin-bottom: 50rpx;
}
.app-icon {
width: 100rpx;
height: 100rpx;
margin: 40rpx auto 20rpx; /* 上边距为40rpx,下边距为20rpx,左右居中 */
}
.quick-login-header {
display: flex;
align-items: center;
}
.icon {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
.title {
font-size: 28rpx;
font-weight: bold;
color: #333333;
}
.divider {
height: 20rpx;
}
.login-option {
font-size: 28rpx;
color: #333333;
}
.login-option .van-cell__icon {
color: #07c160;
}
.agreement-container {
display: flex;
align-items: center;
margin-top: 20rpx;
}
.checkbox {
margin-right: 10rpx;
}
.agreement-text {
font-size: 24rpx;
color: #666666;
}
.agreement-link {
font-size: 24rpx;
color: #07c160;
}

js

var app = getApp(); // 拿到的是 app.js中data的数据
import settings from '../../static/js/settings.js'
Page({
data: {
userInfo: null,
},
getPhoneNumber(event) {
console.log(event)
// 通过获取手机号返回的code--传递给后端--后端调用:POST https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESS_TOKEN -->获取手机号--》后端签发token给前端
wx.request({
url: settings.quick_login,
method: 'POST',
data: {
code: event.detail.code
},
success: (res) => {
console.log(res)
//在此返回登录信息,用户登录
var data = res.data;
console.log(data)
if (data.code == 100) {
console.log('---', data)
var token = data.token
var name = data.name
var score = data.score
var avatar = data.avatar
app.initUserInfo(name, score, avatar, token)
var info = app.globalData.userInfo
console.log('globalData.userInfo', info)
if (info) {
this.setData({
userInfo: info
})
}
} else {
wx.showToast({
title: '登录失败',
})
}
}
})
},
handleOtherLogin(e) {
wx.navigateTo({
url: '/pages/second/otherlogin/otherlogin'
})
},
onShow() {
var info = app.globalData.userInfo
console.log('globalData.userInfo', info)
if (info) {
this.setData({
userInfo: info
})
}
},
handleLogout() {
app.logoutUserInfo()
this.setData({
userInfo: null
})
}
})

app.js

// app.js
App({
globalData: {
userInfo: null
},
initUserInfo: function (name, score, avatar, token) {
var info = {
name: name,
score: score,
avatar: avatar,
token: token
};
this.globalData.userInfo = info
wx.setStorageSync('userInfo', info);
},
logoutUserInfo: function () {
wx.removeStorageSync('userInfo');
this.globalData.userInfo = null;
},
onLaunch() {
var info = wx.getStorageSync('userInfo')
console.log(info)
this.globalData.userInfo = info
}
})

登录页面

wxml

<view class="container">
<view class="main">
<view class="icon-view">
<!-- 应用图标 -->
<image src="/static/img/icon/icon.png" class="app-icon"></image>
<text class="title">智慧社区</text>
</view>
</view>
<van-field value="{{ phone }}" bind:input="onPhoneInput" label="手机号" type="tel" placeholder="请输入手机号" clearable="{{ true }}" />
<van-field value="{{code}}" bind:input="onCodeInput" center clearable label="验证码" placeholder="请输入验证码" use-button-slot>
<van-button slot="button" size="small" type="primary" bind:tap="sendCode" disabled='{{sendCodeDisabled}}'>
{{buttonText}}
</van-button>
</van-field>
<van-button type="info" block="{{ true }}" bind:tap="login">登录</van-button>
</view>

wxss

.container {
padding: 20rpx;
}
.main{
display: flex;
justify-content: center;
align-items: center;
}
.icon-view{
display: flex;
flex-direction: column;
margin-bottom: 50rpx;
}
.title {
font-size: 28rpx;
font-weight: bold;
color: #333333;
}
.app-icon {
width: 100rpx;
height: 100rpx;
margin: 40rpx auto 20rpx; /* 上边距为40rpx,下边距为20rpx,左右居中 */
}

js

import settings from '../../../static/js/settings.js'
var app = getApp()
Page({
data: {
phone: '',
code: '',
agreed: false,
sendCodeDisabled: false,
buttonText: '发送验证码',
loading: false,
timer: null,
countDown: 60
},
// 监听手机号输入
onPhoneInput(event) {
this.setData({
phone: event.detail
});
},
// 监听验证码输入
onCodeInput(event) {
this.setData({
code: event.detail
});
},
// 发送验证码
sendCode() {
// 在这里编写发送验证码的逻辑,此处仅做示例
console.log('发送验证码',this.data.phone,this.data.code);
if(this.data.phone){
wx.request({
url: settings.send_sms+'?mobile='+this.data.phone,
method:'GET',
success:(res)=>{
wx.showToast({
title: res.data.msg,
})
}
})
this.setData({
sendCodeDisabled: true,
timer: setInterval(this.countDown, 1000)
});
}else{
wx.showToast({
title: '请输入手机号',
})
}
},
// 登录
login() {
// 在这里编写登录逻辑,此处仅做示例
console.log('登录');
if(this.data.phone&&this.data.code){
wx.request({
url: settings.login,
method:'POST',
data:{mobile:this.data.phone,code:this.data.code},
success:(res)=>{
var data = res.data;
console.log(data)
if (data.code == 100) {
console.log('---', data)
var token = data.token
var name = data.name
var score = data.score
var avatar = data.avatar
app.initUserInfo(name, score, avatar, token)
var info = app.globalData.userInfo
console.log('globalData.userInfo', info)
wx.navigateBack()
} else {
wx.showToast({
title: '登录失败',
})
}
}
})
this.setData({
sendCodeDisabled: true,
timer: setInterval(this.countDown, 1000)
});
}else{
wx.showToast({
title: '请输入手机号和验证码',
})
}
},
// 倒计时
countDown() {
let countDown = this.data.countDown;
if (countDown === 0) {
clearInterval(this.data.timer);
this.setData({
buttonText: '发送验证码',
sendCodeDisabled: false,
countDown: 60
});
return;
}
this.setData({
buttonText: countDown + 's',
countDown: countDown - 1
});
},
onUnload() {
clearInterval(this.data.timer);
}
});

后端

views

from rest_framework.decorators import action
from django.core.cache import cache
from faker import Faker
from libs.send_tx_sms import send_sms_by_phone, get_code
from rest_framework_simplejwt.tokens import RefreshToken
class LoginView(GenericViewSet):
@action(methods=['GET'], detail=False)
def send_sms(self, request, *args, **kwargs):
# 1 取出前端传入手机号
mobile = request.query_params.get('mobile')
# 2 获取随机验证码
code = get_code()
# 3 验证码放到缓存
cache.set(f'sms_{mobile}', code)
# 4 发送短信
res = send_sms_by_phone(mobile, code)
if res:
return Response({'code': 100, 'msg': '短信发送成功'})
else:
return Response({'code': 101, 'msg': '短信发送失败,请稍后再试'})
@action(methods=['POST'], detail=False)
def login(self, request, *args, **kwargs):
# 1 取出手机号和验证码
mobile = request.data.get('mobile')
code = request.data.get('code')
# 2 校验验证码是否正确
old_code = cache.get(f'sms_{mobile}')
if old_code == code:
# 3 数据库查询用户,如果存在直接签发token登录成功
user = UserInfo.objects.filter(mobile=mobile).first()
if not user:
# 4 如果用户不存在,创建用户,再签发token
fake = Faker('zh_CN')
username = fake.name()
user = UserInfo.objects.create(mobile=mobile, name=username)
refresh = RefreshToken.for_user(user)
return Response(
{'code': 100, 'msg': '登录成功', 'token': str(refresh.access_token), 'name': user.name,
'score': user.score, 'avatar': 'http://127.0.0.1:8000/media/' + str(user.avatar)})
else:
return Response({'code': 101, 'msg': '验证码错误'})
@action(methods=['POST'], detail=False)
def quick_login(self, request, *args, **kwargs):
# 1 取出前端传入的code
code = request.data.get('code')
# 2 通过code,调用微信开发平台接口,换取手机号
# 3 拿到手机号再自己库中查,能查到,签发token
# 4 查不到注册再签发token
# 假数据---》都签发成第一个用户
user = UserInfo.objects.filter(pk=1).first()
refresh = RefreshToken.for_user(user)
return Response(
{'code': 100, 'msg': '登录成功', 'token': str(refresh.access_token), 'name': user.name, 'score': user.score,
'avatar': 'http://127.0.0.1:8000/media/' + str(user.avatar)})

image-20240530215426185

image-20240530215436628

image-20240530215503582

活动报名功能

前端

activity.js

var app = getApp();
import settings from '../../static/js/settings.js'
Page({
data: {
activityList: [
]
},
onLoad: function () {
// 页面加载时执行的逻辑
this.refresh()
},
refresh(){
wx.showLoading({
mask: true
})
wx.request({
url: settings.activity,
method: "GET",
success: (res) => {
this.setData({
activityList: res.data
})
},
complete() {
wx.hideLoading()
}
})
},
handleSignup: function (event) {
var index = event.currentTarget.dataset.mark; // 获取当前点击的活动索引
// 1 校验用户是否登录
var info = app.globalData.userInfo
console.log(info)
if (info) {
//2 处理报名按钮点击事件
console.log('点击了报名按钮,索引为:', index);
wx.request({
url: settings.join,
method:'POST',
data:{'id':index},
header:{token:info.token},
success:(res)=>{
console.log(res.data)
wx.showToast({
title: res.data.msg,
})
}
})
} else {
wx.showToast({
title: '请先登录',
})
}
}
})

后端

views

class ActivityJoinView(GenericViewSet):
authentication_classes = [MyJSONWebTokenAuthentication]
@action(methods=['POST'], detail=False)
def join(self, request, *args, **kwargs):
# 1 取出要参加的活动id
activity_id = request.data.get('id')
# 2 取出当前登录用户
user = request.user
# 2 查到当前活动
activity = Activity.objects.filter(pk=activity_id).first()
# 3 判断时间,判断人数
# 4 判断是否报名过
join_record=JoinRecord.objects.filter(activity_id=activity_id,user=user).first()
if join_record:
return Response({'code': 101, 'msg': "已经报名过,不用重复报名"})
else:
# 5 包名人数+1,报名报存入
activity.count = activity.count + 1
activity.save()
JoinRecord.objects.create(activity=activity,user=user)
# 6 返回报名成功
return Response({'code': 100, 'msg': "报名成功"})

Authentication1.py

from .models import UserInfo
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.authentication import JWTAuthentication
class MyJSONWebTokenAuthentication(JWTAuthentication):
def authenticate(self, request):
jwt_value = request.META.get("HTTP_TOKEN")
if not jwt_value:
raise AuthenticationFailed('token 字段是必须的')
validated_token = self.get_validated_token(jwt_value)
print(validated_token['user_id'])
user = UserInfo.objects.filter(pk=validated_token['user_id']).first()
return user, jwt_value

image-20240530215408361

商城兑换页面

前端

wxml

<van-dropdown-menu active-color="#1989fa">
<van-dropdown-item value="{{ value1 }}" options="{{ option1 }}" />
<van-dropdown-item value="{{ value2 }}" options="{{ option2 }}" />
</van-dropdown-menu>
<van-grid column-num="3" border="{{ true }}">
<van-grid-item use-slot wx:for="{{ 8 }}" wx:for-item="index" border>
<image style="width: 100%; height: 90px;" src="https://img.yzcdn.cn/vant/apple-{{ index + 1 }}.jpg" />
<view class="desc">
<view class="title">{{item.title}}</view>
<view class="exchange">
<view>{{item.price}}积分</view>
<van-button color="linear-gradient(to right, #4bb0ff, #6149f6)" bindtap="doExchange" data-gid="{{item.id}}" size="mini">兑换</van-button>
</view>
</view>
</van-grid-item>
</van-grid>

js

import settings from '../../../static/js/settings.js'
var app = getApp()
Page({
data: {
option1: [
{ text: '全部商品', value: 0 },
{ text: '最新上架', value: 1 },
{ text: '活动商品', value: 2 },
],
option2: [
{ text: '默认排序', value: 'a' },
{ text: '好评排序', value: 'b' },
{ text: '销量排序', value: 'c' },
],
value1: 0,
value2: 'a',
},
})

image-20240530215354510

小程序上线https

# 开发者工具上传
# 小程序后台--》提交审核--》审核通过,全网可用
-备案
# 配置好合法域名
# 1 小程序后台,配置合法地址
https://mp.weixin.qq.com/wxamp/devprofile/get_profile?token=948853190&lang=zh_CN
# 2 在小程序源码中,访问后台地址,改成
const rootUrl = 'https://www.liuqingzheng.top/smart'
# 3 小程序上线
-在开发者工具选上传,填入版本号
# 4 小程序后台
-把刚刚上传的小程序设为体验版
-只有允许体验的人才能用(测试人员)
# 5 提交审核--》一个周审核
-需要备案

后端上线

#1 后端要部署在https地址上
# 2 http和https区别
https=http+ssl/tls 证书
# 3 部署在https上,需要申请证书
-合法机构申请
-如果证书过期了,不合法--》浏览器会提示安全
# 4 证书有收费,也有免费
-阿里云
-七牛云
-https://zhuanlan.zhihu.com/p/561907474

nginx

events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
client_max_body_size 20M;
server {
listen 443 ssl;
ssl_certificate /usr/local/nginx/cert/liuqingzheng.top.pem;
ssl_certificate_key /usr/local/nginx/cert/liuqingzheng.top.key;
server_name liuqingzheng.top;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8080;
uwsgi_param UWSGI_SCRIPT smart_backend.wsgi;
uwsgi_param UWSGI_CHDIR /root/smart_backend/;
}
location /static {
alias /home/static;
}
}
}
# https://www.liuqingzheng.top/api/v1/banner/

安装python3.9

#1 linux 服务器它有些系统服务,使用python2写的,linux操作系统要运行,需要有python2的解释器环境
-自带
# 2 阿里云云服务装了linux--》自带python3.6
# 3 centos7.9
-python python2.7.5 -——》 pip
-python3 python3.6.8 --->pip3
# 4 在这个基础上装一个项目需要的3.9
-到时候,机器上有3个python解释器环境,每个解释器环境又一一个pip--》千万别乱了
-pip 安装--》模块装在哪个解释器中了
# 5 如果只想装个python解释器 yum install python---》这样装指定不了版本--》咱们不用这种方式
# 6 咱们选择源码编译安装--》下载python解释器源码--》自己编译
#1 源码安装python,依赖一些第三方zlib* libffi-devel,如果不装,编译会报错
yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel zlib* libffi-devel -y
# 2 前往用户根目录
cd ~
#3 下载 3.9.10 源码 服务器终端
# https://registry.npmmirror.com/binary.html?path=python/
wget https://registry.npmmirror.com/-/binary/python/3.9.10/Python-3.9.10.tgz
# wget https://www.python.org/ftp/python/3.9.16/Python-3.9.16.tgz
#4 解压安装包
tar -xf Python-3.9.10.tgz
#5 进入目标文件
cd Python-3.9.10
#6 配置安装路径:/usr/local/python3
# 把python3.9.10 编译安装到/usr/local/python38路径下
./configure --prefix=/usr/local/python39
#6 编译并安装,如果报错,说明缺依赖
yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel zlib* libffi-devel -y
# make只是编译----》可执行文件,没有安装
# 类似于在win上下载了安装包,但是没安装
# make install 安装---》类似于在win上下了安装包,一路下一步安装了,指定安装位置---》/usr/local/python39
make && make install
#7 建立软连接:/usr/local/python38路径不在环境变量,终端命令 python3,pip3
ln -s /usr/local/python39/bin/python3 /usr/bin/python3.9
ln -s /usr/local/python39/bin/pip3 /usr/bin/pip3.9
# 机器上有多个python和pip命令,对应关系如下
python 2.x pip
python3 3.6 pip3
python3.9 3.9 pip3.9
#8 删除安装包与文件:
rm -rf Python-3.9.10
rm -rf Python-3.9.10.tar.xz

安装nginx

# 软件:反向代理服务器 (搜一下:什么是正向代理,什么是反向代理) 反向带代理服务器
- 做请求转发 (前端来了个请求---》打在了80端口上---》转到本地8888端口,或者其他机器的某个端口)
- 静态资源代理 前端项目直接放在服务器上某个位置----》请求来了,使用nginx拿到访问的内容,直接返回
- 负载均衡 假设来了1000个请求--》打在nginx上,nginx性能很高,能顶住---》只转发到某个django项目,可能顶不住---》集群化的不是3台django---》均匀的打在3台机器上
#1 前往用户根目录
cd ~
#2 下载nginx 1.24.0
wget http://nginx.org/download/nginx-1.24.0.tar.gz
#3 解压安装包
tar -xf nginx-1.24.0.tar.gz
#4 进入目标文件
cd nginx-1.24.0
#5 配置安装路径:/usr/local/nginx
./configure --prefix=/usr/local/nginx --with-http_ssl_module
#6 编译并安装
make && make install
#7 建立软连接:终端命令 nginx
ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx
#8 删除安装包与文件:
cd ~
rm -rf nginx-1.13.7
rm -rf nginx-1.13.7.tar.xz
#9 测试Nginx环境,服务器运行nginx,本地访问服务器ip
nginx # 启动nginx服务,监听80端口----》公网ip 80 端口就能看到页面了
服务器绑定的域名 或 ip:80
# 静态文件放的路径
/usr/local/nginx/html
# 查看进程
ps aux | grep nginx
# 关闭和启动
关闭:nginx -s stop
启动:nginx

安装mysql5.7

# mysql 5.7
#1 前往用户根目录
# pwd 查看我在哪个目录下
# cd 切换到某个路径下
cd ~ # 回到家路径
#2 下载mysql57
wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
# ls 查看当前目录下的文件和文件夹
#3 安装mysql57
yum -y install mysql57-community-release-el7-10.noarch.rpm
yum install mysql-community-server --nogpgcheck -y
#4 启动mysql57并查看启动状态
systemctl start mysqld # 启动mysql服务
systemctl status mysqld # 查看mysql状态
#5 查看默认密码并登录(第一次安装,root密码是随机的)
# 去/var/log/mysqld.log 中过滤出包含 password 的多行
grep "password" /var/log/mysqld.log # Gxn*u8rqM=ol
mysql -uroot -p
#6 修改root密码
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Lqz12345?';
grant all privileges on *.* to 'root'@'%' identified by 'Lqz12345?';
如果还连不上,就是mysql 3306的安全组没开---》防火墙端口没开
# 7 远程连接:win---》navicate--》
连接成功
如果链接不成功,需要在阿里云开启安全组
# 8 安装mysqlclient
yum install python3-devel -y
yum install mysql-devel --nogpgcheck -y
pip3.9 install mysqlclient
# 9
pip3.9 install urllib3==1.26.15
pip3.9 install chardet
#10
mkdir static
STATIC_ROOT = '/root/smart_backend/static/'

编写uwsgi配置文件

# 项目运行,不是使用 python manage.py runserver 运行,这样运行性能很低,没有并发,开发测试阶段用
# 上线项目,需要使用一个性能很高的web服务,c语言写的,uwsgi,并发量高,线上环境用他
# 在项目中写uwsgi的配置文件--》用uwsgi软件,根据这个配置文件运行django项目
#1 smart.xml
<uwsgi>
<socket>127.0.0.1:8080</socket>
<chdir>/home/smart_backend/</chdir>
<module>smart_backend.wsgi</module>
<processes>4</processes>
<daemonize>uwsgi.log</daemonize>
</uwsgi>

上传后端项目到云服务器

# 1 配置文件修改
DEBUG = False
ALLOWED_HOSTS = ['*']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'smart',
'HOST':'127.0.0.1',
'PORT':3306,
'USER':'root',
'PASSWORD':'Lqz12345?'
}
}
BACKEND_URL='https://www.liuqingzheng.top'
# BACKEND_URL='https://106.14.134.13'
STATIC_URL='/static/'
# 2 导出项目依赖,项目根路径下有个 requirements.txt--->放了当前项目所有依赖
pip3 install pipreqs
pipreqs ./ --encoding=utf-8
# requirements.txt
baidu_aip==4.16.13
Django==3.2.22
djangorestframework==3.14.0
djangorestframework_simplejwt==5.3.1
Faker==25.0.1
pypinyin==0.51.0
tencentcloud_sdk_python==3.0.1115
urllib3==1.26.15
django-simpleui
pillow
chardet
# 3 项目压缩 zip
# 4 服务器上来到 home 目录
cd /home/
yum install lrzsz unzip -y
rz 上传
unzip smart_backend.zip
# 5 在服务端,安装项目依赖
cd smart_backend
pip3.9 install -r requirements.txt
# 6 服务端链接mysql ,需要装连mysql模块
# pip3.9 install mysqlclient # linux上装会报错
yum install python3-devel -y
yum install mysql-devel --nogpgcheck -y
pip3.9 install mysqlclient
# 7 装uwsig,运行django项目
pip3.9 install uwsgi
ln -s /usr/local/python39/bin/uwsgi /usr/bin/uwsgi
# 8 启动uwsgi
uwsgi -x smart.xml # 等同于原来的 python manage.py runserver 127.0.0.1:8080
# 9 查看
ps aux |grep uwsgi
# 10 停止
pkill -9 uwsgi

nginx配置--后端接口--(http)

# 1 修改nginx 配置文件
cd /usr/local/nginx/conf
mv nginx.conf nginx.conf.bak
vi nginx.conf # 敲 a 才能粘贴
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
server {
listen 80;
server_name 127.0.0.1;
charset utf-8;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8080;
uwsgi_param UWSGI_SCRIPT smart_backend.wsgi;
uwsgi_param UWSGI_CHDIR /root/smart_backend/;
}
}
}
# 按esc
# 敲 :
# 敲 wq
# 敲 回车
# 2 重启nginx即可
nginx -s reload # 之前运行着
# 之前没运行
nginx # 运行起来了

创建数据库,迁移表,导入数据

# 1 在navicat中创建 smart 库
# 2 迁移表
python3.9 manage.py makemigrations
python3.9 manage.py migrate
# 3 根据报错,安装缺的模块
pip3.9 install django-simpleui
# 4 报错:python3.9 manage.py makemigrations
pip3.9 install urllib3==1.26.15
# 5 根据报错 :chardet
pip3.9 install chardet
# 6 缺pillow
pip3.9 install pillow
# 7 去后台admin,录入数据
# 8 直接导入一个sql文件----之前项目测试的数据
# 9 关闭uwsgi,重启
pkill -9 uwsgi
uwsgi -x smart.xml
# 10 对于小程序来讲,接口能用了,但是admin访问,加载不了静态资源
-uwsgi不能代理静态资源,需要使用nginx代理

配置admin访问管理后台

# 0 创建文件号
mkdir /home/smart_backend/static
# 1 在项目配置文件中,加入
STATIC_ROOT = '/home/smart_backend/static/'
# 2 执行采集命令--》把静态文件采集到static文件夹下
python3.9 manage.py collectstatic
# 3 配置nginx代理
cd /usr/local/nginx/conf
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
server {
listen 80;
server_name 127.0.0.1;
charset utf-8;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8080;
uwsgi_param UWSGI_SCRIPT smart_backend.wsgi;
uwsgi_param UWSGI_CHDIR /root/smart_backend/;
}
location /static {
alias /home/smart_backend/static;
}
}
}
#4 重启nginx
nginx -s reload
# 5 访问admin即可
http://106.14.134.13/admin

证书

配置https访问

# 1 购买证书
-https://zhuanlan.zhihu.com/p/561907474
# 2 申请了证书,会有俩文件
key
pem
# 3 传到服务器上
cd /usr/local/nginx
mkdir cert
rz # 把证书放在这个位置
unzip 解压
# 保证在/usr/local/nginx/cert 有俩文件
# 4 修改nginx的配置文件
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
client_max_body_size 20M;
server {
listen 443 ssl;
ssl_certificate /usr/local/nginx/cert/liuqingzheng.top.pem;
ssl_certificate_key /usr/local/nginx/cert/liuqingzheng.top.key;
server_name liuqingzheng.top;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8080;
uwsgi_param UWSGI_SCRIPT smart_backend.wsgi;
uwsgi_param UWSGI_CHDIR /root/smart_backend/;
}
location /static {
alias /home/smart_backend/static;
}
}
}
# 重启nginx
nginx -s reload

app.json

{
"pages": [
"pages/welcome/welcome",
"pages/index/index",
"pages/my/my",
"pages/activity/activity",
"pages/notice/notice",
"pages/second/collection/collection",
"pages/second/face/face",
"pages/second/voice/voice",
"pages/second/heart/heart",
"pages/second/goods/goods",
"pages/second/camera/camera",
"pages/second/form/form",
"pages/second/statistics/statistics",
"pages/second/otherlogin/otherlogin",
"pages/second/store/store"
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "test",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
},
"tabBar": {
"selectedColor": "#1c1c1b",
"position": "bottom",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "/static/img/icon/home.png",
"selectedIconPath": "/static/img/icon/home-o.png"
},
{
"pagePath": "pages/activity/activity",
"text": "活动",
"iconPath": "/static/img/icon/aid.png",
"selectedIconPath": "/static/img/icon/aid-o.png"
},
{
"pagePath": "pages/notice/notice",
"text": "公告",
"iconPath": "/static/img/icon/circle.png",
"selectedIconPath": "/static/img/icon/circle-o.png"
},
{
"pagePath": "pages/my/my",
"text": "我的",
"iconPath": "/static/img/icon/my.png",
"selectedIconPath": "/static/img/icon/my-o.png"
}
]
},
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents",
"usingComponents": {
"van-button": "@vant/weapp/button/index",
"van-grid": "@vant/weapp/grid/index",
"van-grid-item": "@vant/weapp/grid-item/index",
"van-notice-bar": "@vant/weapp/notice-bar/index",
"van-field": "@vant/weapp/field/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index"
}
}

app.wxss

@import "/static/css/iconfont.wxss"

settings.js

const BASE_URL = 'http://192.168.1.38:8000/api/v1/'
export default {
welcome:BASE_URL + 'welcome/',
banner:BASE_URL + 'banner/',
collection:BASE_URL + 'collection/',
area:BASE_URL + 'area/',
statistics:BASE_URL + 'statistics/',
face:BASE_URL + 'face/',
voice:BASE_URL + 'voice/',
activity:BASE_URL + 'activity/',
notice:BASE_URL + 'notice/',
quick_login:BASE_URL+'user/quick_login/',
send_sms:BASE_URL+'user/send_sms/',
login:BASE_URL + 'user/login/',
join:BASE_URL + 'join/join/',
}

image-20240529155533364

app.js

// app.js
App({
globalData: {
userInfo: null
},
initUserInfo: function (name, score, avatar, token) {
var info = {
name: name,
score: score,
avatar: avatar,
token: token
};
this.globalData.userInfo = info
wx.setStorageSync('userInfo', info);
},
logoutUserInfo: function () {
wx.removeStorageSync('userInfo');
this.globalData.userInfo = null;
},
onLaunch() {
var info = wx.getStorageSync('userInfo')
console.log(info)
this.globalData.userInfo = info
}
})

urls.py

from django.contrib import admin
from django.urls import path, include
from .views import (
WelcomeView,
BannerView,
CollectionView,
AreaView,
StatisticsView,
FaceView,
VoiceView,
ActivityView,
NoticeView,
LoginView,
ActivityJoinView,
)
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register("welcome", WelcomeView, "welcome")
router.register("banner", BannerView, "banner")
router.register("collection", CollectionView, "collection")
router.register("area", AreaView, "area")
router.register("statistics", StatisticsView, "statistics")
router.register("face", FaceView, "face")
router.register("voice", VoiceView, "voice")
router.register("activity", ActivityView, "activity")
router.register("notice", NoticeView, "notice")
router.register("user", LoginView, "user")
router.register("join", ActivityJoinView, "join")
urlpatterns = [path("", include(router.urls))]

admin.py

from django.contrib import admin
# Register your models here.
from .models import *
admin.site.register(Welcome)
admin.site.register(Banner)
admin.site.register(Notice)
admin.site.register(UserInfo)
admin.site.register(Area)
admin.site.register(Collection)
admin.site.register(Activity)

Authentication1.py

from .models import UserInfo
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt.authentication import JWTAuthentication
class MyJSONWebTokenAuthentication(JWTAuthentication):
def authenticate(self, request):
jwt_value = request.META.get("HTTP_TOKEN")
if not jwt_value:
raise AuthenticationFailed("token 字段是必须的")
validated_token = self.get_validated_token(jwt_value)
print(validated_token["user_id"])
user = UserInfo.objects.filter(pk=validated_token["user_id"]).first()
return user, jwt_value

models.py

from django.db import models
# 广告表
class Welcome(models.Model):
img = models.ImageField(upload_to="welcome", default="slash.png")
order = models.IntegerField()
link = models.CharField(max_length=32)
create_time = models.DateTimeField(auto_now=True)
is_delete = models.BooleanField(default=False)
# 轮播图
class Banner(models.Model):
img = models.ImageField(
upload_to="banner", default="banner1.png", verbose_name="图片"
)
order = models.IntegerField(verbose_name="顺序")
create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
is_delete = models.BooleanField(default=False, verbose_name="是否删除")
class Meta:
verbose_name_plural = "轮播图"
# 公告
class Notice(models.Model):
title = models.CharField(max_length=64, verbose_name="公共标题")
content = models.TextField(verbose_name="内容")
img = models.ImageField(
upload_to="notice", default="notice.png", verbose_name="公告图片"
)
create_time = models.DateTimeField(auto_now=True, verbose_name="创建时间")
class Meta:
verbose_name_plural = "公告表"
# 采集表
class Collection(models.Model):
name = models.CharField(max_length=32, verbose_name="采集人员姓名")
# 做为人脸识别的id号
name_pinyin = models.CharField(max_length=32, verbose_name="姓名拼音", null=True)
avatar = models.ImageField(
upload_to="collection/%Y/%m/%d/", default="default.png", verbose_name="头像"
)
create_time = models.DateTimeField(auto_now=True, verbose_name="采集时间")
# face_token---->人脸识别的token唯一码
face_token = models.CharField(max_length=64, verbose_name="百度Token", null=True)
# 区域是外键关联
area = models.ForeignKey(
to="Area", null=True, verbose_name="网格区域", on_delete=models.CASCADE
)
class Meta:
verbose_name_plural = "采集表"
def __str__(self):
return self.name
# 区域表
class Area(models.Model):
name = models.CharField(max_length=32, verbose_name="网格区域名")
desc = models.CharField(max_length=32, verbose_name="网格简称")
# 跟用户一对多---》一个网格员,可以采集多个网格
user = models.ForeignKey(
to="UserInfo",
on_delete=models.CASCADE,
null=True,
verbose_name="负责用户",
blank=True,
)
class Meta:
verbose_name_plural = "区域表"
def __str__(self):
return self.name
# 用户表
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
mobile = models.CharField(max_length=11, verbose_name="手机号", null=True)
avatar = models.FileField(verbose_name="头像", max_length=128, upload_to="avatar")
create_date = models.DateField(verbose_name="日期", auto_now_add=True)
score = models.IntegerField(verbose_name="积分", default=0)
class Meta:
verbose_name_plural = "用户表"
def __str__(self):
return self.name
# 活动表
class Activity(models.Model):
title = models.CharField(verbose_name="活动标题", max_length=128)
text = models.TextField(verbose_name="活动描述", null=True, blank=True)
date = models.DateField(verbose_name="举办活动日期")
count = models.IntegerField(verbose_name="报名人数", default=0)
total_count = models.IntegerField(verbose_name="总人数", default=0)
score = models.IntegerField(verbose_name="积分", default=0)
join_record = models.ManyToManyField(
verbose_name="参与者",
through="JoinRecord",
through_fields=("activity", "user"),
to="UserInfo",
)
class Meta:
verbose_name_plural = "活动表"
def __str__(self):
return self.title
# 活动报名记录
class JoinRecord(models.Model):
user = models.ForeignKey(
verbose_name="用户", to="UserInfo", on_delete=models.CASCADE
)
activity = models.ForeignKey(
verbose_name="活动", to="Activity", on_delete=models.CASCADE, related_name="ac"
)
exchange = models.BooleanField(verbose_name="是否已兑换", default=False)
class Meta:
verbose_name_plural = "活动报名记录"

serializer.py

from rest_framework import serializers
from .models import *
class WelcomeSerializer(serializers.ModelSerializer):
class Meta:
model = Welcome
fields = ["img", "link"]
# 轮播图表序列化类
class BannerSerializer(serializers.ModelSerializer):
class Meta:
model = Banner
fields = "__all__"
# 社区通知序列化类
class NoticeSerializer(serializers.ModelSerializer):
class Meta:
model = Notice
fields = ["id", "title"]
# 上传人脸序列化类
class CollectionSaveSerializer(serializers.ModelSerializer):
class Meta:
model = Collection
fields = ["name", "avatar", "area"]
def create(self, validated_data):
# 在百度ai注册
from libs.baidu_ai import BaiDuFace
baidu = BaiDuFace()
avatar_file_object = validated_data.get("avatar")
print(avatar_file_object)
name = validated_data.get("name")
name_pinyin = baidu.name_to_pinyin(name)
res = baidu.add_user(avatar_file_object, name, name_pinyin)
validated_data["name_pinyin"] = name_pinyin
validated_data["face_token"] = res.get("result").get("face_token")
instance = super().create(validated_data)
return instance
# 采集序列化类
class CollectionSerializer(serializers.ModelSerializer): # 查询所有序列化类
class Meta:
model = Collection
fields = ["id", "name", "avatar", "area"]
depth = 1 # area 外键关联详情拿到
# 网格序列化类
class AreaSerializer(serializers.ModelSerializer):
class Meta:
model = Area
fields = ["id", "name", "desc"]
### 采集统计
class StatisticsListSerializer(serializers.Serializer):
date = serializers.DateTimeField(format="%Y年%m月%d日")
count = serializers.IntegerField()
class ActivitySerializer(serializers.ModelSerializer):
class Meta:
model = Activity
fields = ["id", "title", "text", "date", "count", "score", "total_count"]
extra_kwargs = {"date": {"format": "%Y-%m-%d"}}
class NoticeSerializer(serializers.ModelSerializer):
class Meta:
model = Notice
fields = ["id", "title", "img", "create_time", "content"]
extra_kwargs = {"create_time": {"format": "%Y-%m-%d"}}

views.py

from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin, DestroyModelMixin, CreateModelMixin
from .serializers import (
WelcomeSerializer,
BannerSerializer,
NoticeSerializer,
CollectionSerializer,
CollectionSaveSerializer,
ActivitySerializer,
)
from .models import Welcome, Notice, Banner, Collection, Activity, UserInfo, JoinRecord
from rest_framework.response import Response
from datetime import datetime
from libs.baidu_ai import BaiDuVoice
from rest_framework.decorators import action
from django.core.cache import cache
from faker import Faker
from libs.send_tx_sms import send_sms_by_phone, get_code
from rest_framework_simplejwt.tokens import RefreshToken
from core.Authentication1 import MyJSONWebTokenAuthentication
# 广告
class WelcomeView(GenericViewSet, ListModelMixin):
queryset = Welcome.objects.all().filter(is_delete=False).order_by("-order")
serializer_class = WelcomeSerializer
# 轮播图
class BannerView(GenericViewSet, ListModelMixin):
queryset = Banner.objects.all().filter(is_delete=False).order_by("order")[:2]
serializer_class = BannerSerializer
def list(self, request, *args, **kwargs):
res = super().list(request, *args, **kwargs)
notice = Notice.objects.all().order_by("create_time").first()
serializer = NoticeSerializer(instance=notice)
return Response(
{"code": 100, "msg": "成功", "banner": res.data, "notice": serializer.data}
)
class CollectionView(
GenericViewSet, ListModelMixin, DestroyModelMixin, CreateModelMixin
):
# 查出当天的--》没过滤当前用户
queryset = Collection.objects.all().filter(create_time__gte=datetime.now().date())
serializer_class = CollectionSerializer
def get_serializer_class(self):
if self.action == "create":
return CollectionSaveSerializer
else:
return CollectionSerializer
def list(self, request, *args, **kwargs):
# 过滤当前用户采集的--》多种类型用户,数据权限不一样
res = super().list(request, *args, **kwargs)
today_count = len(self.get_queryset())
return Response(
{"code": 100, "msg": "成功", "result": res.data, "today_count": today_count}
)
## 删除人脸,没有删除 具体的图片--》定时任务--》每天晚上备份用户头像
def destroy(self, request, *args, **kwargs):
from libs.baidu_ai import BaiDuFace
instance = self.get_object()
# 百度ai中删除
baidu = BaiDuFace()
res = baidu.delete(instance.name_pinyin, instance.face_token)
print(res)
self.perform_destroy(instance)
return Response()
from .models import Area
from .serializers import AreaSerializer
# 获取所有网格--》应该获取当前用户所管理的网格
class AreaView(GenericViewSet, ListModelMixin):
queryset = Area.objects.all()
serializer_class = AreaSerializer
# 配置一个过滤类--》在过滤类中对 queryset进行过滤---》过滤出当前用户的网格
#### 采集统计
from django.db.models import Count
from django.db.models.functions import Trunc
from .models import Collection
from .serializers import StatisticsListSerializer
class StatisticsView(GenericViewSet, ListModelMixin):
# 统计时按天分组---》数个数
queryset = (
Collection.objects.annotate(date=Trunc("create_time", "day"))
.values("date")
.annotate(count=Count("id"))
.values("date", "count")
)
serializer_class = StatisticsListSerializer
### 人脸检测视图类
class FaceView(GenericViewSet):
def create(self, request, *args, **kwargs):
avatar_object = request.data.get("avatar")
if not avatar_object:
return Response({"msg": "未提交图像", "code": 101})
from libs.baidu_ai import BaiDuFace
ai = BaiDuFace()
result = ai.search(avatar_object)
print(result)
if result.get("error_code") == 0: # 查询到
# {'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 2159604393, 'timestamp': 1713864959, 'cached': 0, 'result': {'face_token': '095994eca64424cee347b59e0a7edc0e', 'user_list': [{'group_id': '100', 'user_id': 'li3si1xian4', 'user_info': '', 'score': 98.035797119141}]}}
user = result.get("result").get("user_list")[0]
user_info = user.get("user_info")
user_id = user.get("user_id")
score = user.get("score")
return Response(
{
"code": 100,
"msg": "匹配成功",
"user_info": user_info,
"user_id": user_id,
"score": score,
"avatar": "",
}
)
else:
return Response(
{"code": 102, "msg": "匹配失败,该人员可能不是我社区人员,注意防范"}
)
# 语音识别
class VoiceView(GenericViewSet):
def create(self, request, *args, **kwargs):
voice_object = request.data.get("voice")
# with open('./a.wav','wb') as f:
# f.write(voice_object.read())
ai = BaiDuVoice()
result = ai.speed(voice_object)
# {'corpus_no': '6847771638436561158', 'result': ['你是不是打过来?'], 'sn': '15921476781594371078', 'err_msg': 'success.', 'err_no': 0}
if result.get("err_no") == 0:
return Response(
{"code": 100, "msg": "识别成功", "result": result.get("result")}
)
else:
return Response({"code": 101, "msg": "识别失败"})
class ActivityView(GenericViewSet, ListModelMixin):
queryset = Activity.objects.all().order_by("date")
serializer_class = ActivitySerializer
class NoticeView(GenericViewSet, ListModelMixin):
queryset = Notice.objects.all().order_by("create_time")
serializer_class = NoticeSerializer
class LoginView(GenericViewSet):
@action(methods=["GET"], detail=False)
def send_sms(self, request, *args, **kwargs):
# 1 取出前端传入手机号
mobile = request.query_params.get("mobile")
# 2 获取随机验证码
code = get_code()
# 3 验证码放到缓存
cache.set(f"sms_{mobile}", code)
# 4 发送短信
res = send_sms_by_phone(mobile, code)
if res:
return Response({"code": 100, "msg": "短信发送成功"})
else:
return Response({"code": 101, "msg": "短信发送失败,请稍后再试"})
@action(methods=["POST"], detail=False)
def login(self, request, *args, **kwargs):
# 1 取出手机号和验证码
mobile = request.data.get("mobile")
code = request.data.get("code")
# 2 校验验证码是否正确
old_code = cache.get(f"sms_{mobile}")
if old_code == code:
# 3 数据库查询用户,如果存在直接签发token登录成功
user = UserInfo.objects.filter(mobile=mobile).first()
if not user:
# 4 如果用户不存在,创建用户,再签发token
fake = Faker("zh_CN")
username = fake.name()
user = UserInfo.objects.create(mobile=mobile, name=username)
refresh = RefreshToken.for_user(user)
return Response(
{
"code": 100,
"msg": "登录成功",
"token": str(refresh.access_token),
"name": user.name,
"score": user.score,
"avatar": "http://127.0.0.1:8000/media/" + str(user.avatar),
}
)
else:
return Response({"code": 101, "msg": "验证码错误"})
@action(methods=["POST"], detail=False)
def quick_login(self, request, *args, **kwargs):
# 1 取出前端传入的code
code = request.data.get("code")
# 2 通过code,调用微信开发平台接口,换取手机号
# 3 拿到手机号再自己库中查,能查到,签发token
# 4 查不到注册再签发token
# 假数据---》都签发成第一个用户
user = UserInfo.objects.filter(pk=1).first()
refresh = RefreshToken.for_user(user)
return Response(
{
"code": 100,
"msg": "登录成功",
"token": str(refresh.access_token),
"name": user.name,
"score": user.score,
"avatar": "http://127.0.0.1:8000/media/" + str(user.avatar),
}
)
class ActivityJoinView(GenericViewSet):
authentication_classes = [MyJSONWebTokenAuthentication]
@action(methods=["POST"], detail=False)
def join(self, request, *args, **kwargs):
# 1 取出要参加的活动id
activity_id = request.data.get("id")
# 2 取出当前登录用户
user = request.user
# 2 查到当前活动
activity = Activity.objects.filter(pk=activity_id).first()
# 3 判断时间,判断人数
# 4 判断是否报名过
join_record = JoinRecord.objects.filter(
activity_id=activity_id, user=user
).first()
if join_record:
return Response({"code": 101, "msg": "已经报名过,不用重复报名"})
else:
# 5 包名人数+1,报名报存入
activity.count = activity.count + 1
activity.save()
JoinRecord.objects.create(activity=activity, user=user)
# 6 返回报名成功
return Response({"code": 100, "msg": "报名成功"})
posted @   ssrheart  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示