智慧社区
| |
| -欢迎页面 |
| -首页 |
| -轮播图 |
| -公告 |
| -信息采集,社区活动,人脸检测,语音识别,心率检测,积分商城 |
| -信息采集页面 |
| -采集人数 |
| -采集详情页面 |
| -采集统计页面 |
| -人脸检测页面 |
| -语音识别页面 |
| -积分商城页面 |
| -活动 |
| -活动列表 |
| -报名活动 |
| -加载更多 |
| -公告 |
| -公告列表 |
| -我的 |
| -信息展示 |
| -登录 |
欢迎页面
后端
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'] |
前端
| |
| -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) |
| |
| 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', |
| }) |
| } |
| }) |

首页
| "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
| |
| 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', |
| }) |
| }, |
| }) |

信息采集
| <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
| |
| Page({ |
| data: { |
| }, |
| onLoad(options) { |
| }, |
| bindToForm(){ |
| wx.navigateTo({ |
| url: '/pages/second/camera/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="/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 |
| }) |
| }, |
| }) |

首页轮播图和公告接口
前端
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', |
| }) |
| }, |
| }) |

后端
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 |
| |
| |
| 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))] |
信息采集拍照上传
| |
| |
| |
| -百度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) |
| |
| """ 如果有可选参数 """ |
| |
| |
| |
| |
| |
| |
| |
| |
| ''' |
| {'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); |
| """ 如果有可选参数 """ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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.delete() |
后端接口
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() |
| |
| baidu = BaiDuFace() |
| res = baidu.delete(instance.name_pinyin, instance.face_token) |
| print(res) |
| self.perform_destroy(instance) |
| return Response() |
| |
| |
| |
| |
| |
| |
| |
urls
| router.register('collection', CollectionView, 'collection') |
| |
| |
| |
| http://192.168.1.96:8000/api/v1/collection/ |
| http://192.168.1.96:8000/api/v1/collection/ |
| http://192.168.1.96:8000/api/v1/collection/1/ |
serializer
| |
| class CollectionSaveSerializer(serializers.ModelSerializer): |
| class Meta: |
| model = Collection |
| fields = ['name', 'avatar', 'area'] |
| def create(self, validated_data): |
| |
| 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 |

采集列表页面 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() { |
| |
| |
| 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() |
| } |
| }) |
| |
| } |
| }) |
| }, |
| }) |
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
| |
| Page({ |
| |
| |
| |
| |
| data: { |
| backFront:true |
| |
| }, |
| |
| 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({}); |
| } |
| }) |
| }, |
| |
| |
| 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 |
| |
| |
| class AreaSerializer(serializers.ModelSerializer): |
| class Meta: |
| model = Area |
| fields = ['id', 'name', 'desc'] |
| |
信息采集统计
前端
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 |

人脸检测功能
前端
| <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
| |
| .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: |
| |
| 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
...

语音识别
前端
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; |
| |
| 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 = { |
| |
| 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) => { |
| |
| wx.showLoading() |
| wx.uploadFile({ |
| filePath: res.tempFilePath, |
| name: 'voice', |
| url: settings.voice, |
| success:(response)=>{ |
| console.log(response) |
| |
| 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
| |
| 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') |
| |
| |
| ai = BaiDuVoice() |
| result = ai.speed(voice_object) |
| |
| if result.get('err_no') == 0: |
| return Response({'code': 100, 'msg': '识别成功', 'result': result.get('result')}) |
| else: |
| return Response({'code': 101, 'msg': '识别失败'}) |

活动页面
前端
wxml
| <view class="container"> |
| |
| <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 = '活动报名记录' |

公告页面
前端
wxml
| <view class="container"> |
| |
| <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"} |
| } |

登陆功能
前端
个人中心页面
| <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; |
| } |
| |
| |
| |
| .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; |
| } |
| |
| .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(); |
| import settings from '../../static/js/settings.js' |
| Page({ |
| data: { |
| userInfo: null, |
| }, |
| getPhoneNumber(event) { |
| console.log(event) |
| |
| 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({ |
| 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; |
| } |
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): |
| |
| mobile = request.query_params.get('mobile') |
| |
| code = get_code() |
| |
| cache.set(f'sms_{mobile}', code) |
| |
| 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): |
| |
| mobile = request.data.get('mobile') |
| code = request.data.get('code') |
| |
| old_code = cache.get(f'sms_{mobile}') |
| if old_code == code: |
| |
| |
| user = UserInfo.objects.filter(mobile=mobile).first() |
| if not user: |
| |
| 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): |
| |
| code = request.data.get('code') |
| |
| |
| |
| |
| 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)}) |



活动报名功能
前端
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; |
| |
| var info = app.globalData.userInfo |
| console.log(info) |
| if (info) { |
| |
| 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): |
| |
| activity_id = request.data.get('id') |
| |
| user = request.user |
| |
| activity = Activity.objects.filter(pk=activity_id).first() |
| |
| |
| join_record=JoinRecord.objects.filter(activity_id=activity_id,user=user).first() |
| if join_record: |
| return Response({'code': 101, 'msg': "已经报名过,不用重复报名"}) |
| else: |
| |
| activity.count = activity.count + 1 |
| activity.save() |
| JoinRecord.objects.create(activity=activity,user=user) |
| |
| 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 |

商城兑换页面
前端
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', |
| }, |
| }) |

小程序上线https
| |
| |
| -备案 |
| |
| |
| |
| https://mp.weixin.qq.com/wxamp/devprofile/get_profile?token=948853190&lang=zh_CN |
| |
| const rootUrl = 'https://www.liuqingzheng.top/smart' |
| |
| |
| -在开发者工具选上传,填入版本号 |
| |
| |
| -把刚刚上传的小程序设为体验版 |
| -只有允许体验的人才能用(测试人员) |
| |
| |
| |
| -需要备案 |
后端上线
| |
| |
| https=http+ssl/tls 证书 |
| |
| |
| -合法机构申请 |
| -如果证书过期了,不合法--》浏览器会提示安全 |
| |
| -阿里云 |
| -七牛云 |
| -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; |
| } |
| } |
| } |
| |
| |
安装python3.9
| |
| -自带 |
| |
| |
| |
| |
| -python python2.7.5 -——》 pip |
| -python3 python3.6.8 --->pip3 |
| |
| -到时候,机器上有3个python解释器环境,每个解释器环境又一一个pip--》千万别乱了 |
| -pip 安装--》模块装在哪个解释器中了 |
| |
| |
| |
| |
| |
| |
| |
| yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel zlib* libffi-devel -y |
| |
| |
| cd ~ |
| |
| |
| |
| wget https://registry.npmmirror.com/-/binary/python/3.9.10/Python-3.9.10.tgz |
| |
| |
| |
| tar -xf Python-3.9.10.tgz |
| |
| |
| cd Python-3.9.10 |
| |
| |
| |
| ./configure --prefix=/usr/local/python39 |
| |
| |
| yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel psmisc libffi-devel zlib* libffi-devel -y |
| |
| |
| |
| make && make install |
| |
| |
| ln -s /usr/local/python39/bin/python3 /usr/bin/python3.9 |
| ln -s /usr/local/python39/bin/pip3 /usr/bin/pip3.9 |
| |
| |
| python 2.x pip |
| python3 3.6 pip3 |
| python3.9 3.9 pip3.9 |
| |
| |
| 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台机器上 |
| |
| |
| |
| |
| |
| cd ~ |
| |
| |
| wget http://nginx.org/download/nginx-1.24.0.tar.gz |
| |
| |
| tar -xf nginx-1.24.0.tar.gz |
| |
| |
| cd nginx-1.24.0 |
| |
| |
| ./configure --prefix=/usr/local/nginx --with-http_ssl_module |
| |
| |
| make && make install |
| |
| |
| ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx |
| |
| |
| cd ~ |
| rm -rf nginx-1.13.7 |
| rm -rf nginx-1.13.7.tar.xz |
| |
| |
| nginx |
| 服务器绑定的域名 或 ip:80 |
| |
| |
| /usr/local/nginx/html |
| |
| |
| ps aux | grep nginx |
| |
| |
| |
| 关闭:nginx -s stop |
| 启动:nginx |
安装mysql5.7
| |
| |
| |
| |
| |
| cd ~ |
| |
| |
| wget http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm |
| |
| |
| |
| yum -y install mysql57-community-release-el7-10.noarch.rpm |
| yum install mysql-community-server --nogpgcheck -y |
| |
| |
| systemctl start mysqld |
| systemctl status mysqld |
| |
| |
| |
| grep "password" /var/log/mysqld.log |
| mysql -uroot -p |
| |
| |
| |
| ALTER USER 'root'@'localhost' IDENTIFIED BY 'Lqz12345?'; |
| grant all privileges on *.* to 'root'@'%' identified by 'Lqz12345?'; |
| 如果还连不上,就是mysql 3306的安全组没开---》防火墙端口没开 |
| |
| |
| |
| 连接成功 |
| 如果链接不成功,需要在阿里云开启安全组 |
| |
| |
| yum install python3-devel -y |
| yum install mysql-devel --nogpgcheck -y |
| pip3.9 install mysqlclient |
| |
| |
| pip3.9 install urllib3==1.26.15 |
| pip3.9 install chardet |
| |
| |
| mkdir static |
| STATIC_ROOT = '/root/smart_backend/static/' |
编写uwsgi配置文件
| |
| |
| |
| |
| |
| |
| |
| |
| <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> |
上传后端项目到云服务器
| |
| 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' |
| |
| STATIC_URL='/static/' |
| |
| |
| |
| pip3 install pipreqs |
| pipreqs ./ --encoding=utf-8 |
| |
| 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 |
| |
| |
| |
| |
| |
| cd /home/ |
| yum install lrzsz unzip -y |
| rz 上传 |
| |
| unzip smart_backend.zip |
| |
| |
| |
| cd smart_backend |
| pip3.9 install -r requirements.txt |
| |
| |
| |
| yum install python3-devel -y |
| yum install mysql-devel --nogpgcheck -y |
| pip3.9 install mysqlclient |
| |
| |
| |
| pip3.9 install uwsgi |
| ln -s /usr/local/python39/bin/uwsgi /usr/bin/uwsgi |
| |
| |
| uwsgi -x smart.xml |
| |
| |
| ps aux |grep uwsgi |
| |
| |
| pkill -9 uwsgi |
nginx配置--后端接口--(http)
| |
| cd /usr/local/nginx/conf |
| mv nginx.conf nginx.conf.bak |
| vi 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/; |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| nginx -s reload |
| |
| nginx |
创建数据库,迁移表,导入数据
| |
| |
| |
| python3.9 manage.py makemigrations |
| python3.9 manage.py migrate |
| |
| pip3.9 install django-simpleui |
| |
| pip3.9 install urllib3==1.26.15 |
| |
| |
| pip3.9 install chardet |
| |
| |
| pip3.9 install pillow |
| |
| |
| |
| |
| |
| |
| pkill -9 uwsgi |
| uwsgi -x smart.xml |
| |
| |
| -uwsgi不能代理静态资源,需要使用nginx代理 |
配置admin访问管理后台
| |
| mkdir /home/smart_backend/static |
| |
| STATIC_ROOT = '/home/smart_backend/static/' |
| |
| python3.9 manage.py collectstatic |
| |
| |
| 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; |
| } |
| } |
| } |
| |
| |
| nginx -s reload |
| |
| |
| http://106.14.134.13/admin |
证书
配置https访问
| |
| -https://zhuanlan.zhihu.com/p/561907474 |
| |
| key |
| pem |
| |
| |
| cd /usr/local/nginx |
| mkdir cert |
| rz |
| unzip 解压 |
| |
| |
| |
| 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 -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/', |
| } |

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 |
| |
| |
| 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="采集人员姓名") |
| |
| 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 = 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): |
| |
| 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 |
| |
| |
| |
| 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() |
| |
| 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 |
| |
| |
| |
| |
| 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: |
| |
| |
| 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") |
| |
| |
| ai = BaiDuVoice() |
| result = ai.speed(voice_object) |
| |
| 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): |
| |
| mobile = request.query_params.get("mobile") |
| |
| code = get_code() |
| |
| cache.set(f"sms_{mobile}", code) |
| |
| 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): |
| |
| mobile = request.data.get("mobile") |
| code = request.data.get("code") |
| |
| old_code = cache.get(f"sms_{mobile}") |
| if old_code == code: |
| |
| |
| user = UserInfo.objects.filter(mobile=mobile).first() |
| if not user: |
| |
| 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): |
| |
| code = request.data.get("code") |
| |
| |
| |
| |
| 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): |
| |
| activity_id = request.data.get("id") |
| |
| user = request.user |
| |
| activity = Activity.objects.filter(pk=activity_id).first() |
| |
| |
| join_record = JoinRecord.objects.filter( |
| activity_id=activity_id, user=user |
| ).first() |
| if join_record: |
| return Response({"code": 101, "msg": "已经报名过,不用重复报名"}) |
| else: |
| |
| activity.count = activity.count + 1 |
| activity.save() |
| JoinRecord.objects.create(activity=activity, user=user) |
| |
| return Response({"code": 100, "msg": "报名成功"}) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通