微信小程序开发第七课

一 人脸识别

1.1 使用步骤

# 1 注册百度人脸识别接口
https://cloud.baidu.com/product/face.html
# 2 免费领取额度:https://console.bce.baidu.com/ai/#/ai/face/overview/index

# 3 创建应用:https://console.bce.baidu.com/ai/#/ai/face/app/list

# 4 查看人脸库:https://console.bce.baidu.com/ai/#/ai/face/facelib/groupList~appId=4652887

在这里插入图片描述

在这里插入图片描述

1.2 上传-删除-匹配人脸

# 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='62643893',API_KEY='hfAwQUE1fwyCjXG01ZCHbaSG',SECRET_KEY='qgxJneAt0ovmGA2rLrdq99GEFs1apjte'):
        """ 你的 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):
        data = base64.b64encode(open('./gtl.png','rb').read()).decode('utf-8')
        image = data
        imageType = "BASE64"
        groupId = "100"
        userId=self.name_to_pinyin('古天乐')
        """ 调用人脸注册 """
        self.client.addUser(image, imageType, groupId, userId);

        """ 如果有可选参数 """
        options = {}
        options["user_info"] = "彭于晏"
        options["quality_control"] = "NORMAL"
        options["liveness_control"] = "LOW"
        options["action_type"] = "REPLACE"

        """ 带参数调用人脸注册 """
        self.client.addUser(image, imageType, groupId, userId, options)
    def search(self):
        data = base64.b64encode(open('./pyy2.png', 'rb').read()).decode('utf-8')
        image = data
        imageType = "BASE64"
        groupIdList = "100,2"
        """ 调用人脸搜索 """
        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)
    def delete(self):
        userId = "user1"
        groupId = "group1"
        faceToken = "face_token_23123"
        """ 调用人脸删除 """
        self.client.faceDelete(userId, groupId, faceToken);
if __name__ == '__main__':
    ai=BaiDuAI()
    # ai.add_user()
    ai.search()

1.3 项目中集成

#################### serializer.py########################
class CollectionSaveSerializer(serializers.ModelSerializer):
    class Meta:
        model = Collection
        fields = ['name', 'avatar', 'area']
    def create(self, validated_data):
        # 在百度ai注册
        from libs.baidu_ai import BaiDuAI
        baidu=BaiDuAI()
        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
    
###### views.py#######
def destroy(self, request, *args, **kwargs):
    from libs.baidu_ai import BaiDuAI
    instance = self.get_object()
    # 百度ai中删除
    baidu=BaiDuAI()
    res=baidu.delete(instance.name_pinyin,face_token=instance.face_token)
    print(res)
    self.perform_destroy(instance)
    return Response()

二 语音识别

2.1 收费方案

在这里插入图片描述

# 使用百度,科大讯飞等第三方平台
# https://ai.baidu.com/ai-doc/SPEECH/0lbxfnc9b
def speed(file_object):
    from aip import AipSpeech
    APP_ID = ''
    API_KEY = ''
    SECRET_KEY = ''
    client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
    data = file_object.read()
    # 识别本地文件
    return client.asr(data, 'pcm', 16000, {'dev_pid': 1537})

2.2 免费方案

# pip3.11 install --force- SpeechRecognition
# pip3.11 install -i https://pypi.tuna.tsinghua.edu.cn/simple pocketsphinx
#https://pypi.org/project/pocketsphinx/#files

# 1 目前speech_recognition只有英文(en-US)的脱机识别库,如果需要脱机识别中文(zh-CN),需要手动加入对应的识别库,可参考如下操作

# 2 逐层找到文件夹 pocketsphinx-data (参考如下路径):
\site-packages\speech_recognition\pocketsphinx-data

# 3 随后将 【zh-CN文件】 下载解压到pocketsphinx-data 中,如下图所示
 #4 检查文件是否缺失以及命名、存放的位置是否一致

# 5 语音文件准备 speech_recognition 对脱机识别的文件有要求,目前支持的格式有
1.WAV
2.AIFF/AIFF-C
3.FLAC

# 6 代码
import speech_recognition as sr

audio_file = './test1.wav'

r = sr.Recognizer()

with sr.AudioFile(audio_file) as source:
    audio = r.record(source)

# 识别音频文件
result = r.recognize_sphinx(audio, language="zh-CN")
print(result)

三 采集统计

小程序端statistics

#############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###############
var app = getApp();
var api = require("../../config/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: api.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.py############
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.py################
###采集统计序列化类
class StatisticsListSerializer(serializers.Serializer):
    date = serializers.DateTimeField(format="%Y年%m月%d日")
    count = serializers.IntegerField()
    
####models.py###########
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

四 人脸检测

4.1 小程序端

#####wxml########
<!--pages/face/face.wxml-->
<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>

#######wxss#####
/* 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
var api = require("../../config/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: api.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": "人脸检测"
}


4.2 后端

###############views.py###############
###人脸检测接口
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_ai.py#########
# 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='62643893', API_KEY='hfAwQUE1fwyCjXG01ZCHbaSG',
                 SECRET_KEY='qgxJneAt0ovmGA2rLrdq99GEFs1apjte'):
        """ 你的 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, path, name, userId,groupId=100):
        # image = base64.b64encode(open(path).read()).decode('utf-8')
        image = base64.b64encode(path.read()).decode('utf-8')
        imageType = "BASE64"
        """ 调用人脸注册 """
        res=self.client.addUser(image, imageType, groupId, userId);

        # """ 如果有可选参数 """
        # options = {}
        # options["user_info"] = name
        # options["quality_control"] = "NORMAL"
        # options["liveness_control"] = "LOW"
        # options["action_type"] = "REPLACE"
        #
        # """ 带参数调用人脸注册 """
        # res = self.client.addUser(image, imageType, groupId, userId, options)
        print(res)
        return res

    def search(self,img_obj):
        image = base64.b64encode(img_obj.read()).decode('utf-8')
        image_type = "BASE64"
        group_id_list = "100,101"
        """ 调用人脸搜索 """
        res=self.client.search(image, image_type, group_id_list);
        # """ 如果有可选参数 """
        # 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)
        return res

    def delete(self,user_id,face_token,group_id=100):
        """ 调用人脸删除 """
        res=self.client.faceDelete(user_id, group_id, face_token)
        return res


if __name__ == '__main__':
    ai = BaiDuAI()
    # ai.add_user()
    ai.search()

五 语音识别

5.1 小程序前端

#####js#########
// https://developers.weixin.qq.com/miniprogram/dev/api/media/recorder/RecorderManager.start.html
const recorderManager = wx.getRecorderManager()
var api = require("../../config/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: api.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})
  },
 
})

#####wxml#########
<!--pages/voice/voice.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#####
/* pages/voice/voice.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": "语音识别"
}

5.2 后端

######views.py########
### 语音识别
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': '识别失败'})
        
        
#######baidu_ai.py
# https://ai.baidu.com/ai-doc/FACE/ek37c1qiz

from aip import AipFace
import base64
from pypinyin import lazy_pinyin, Style
from aip import AipSpeech

class BaiDuAI:
    def __init__(self, APP_ID='62643893', API_KEY='hfAwQUE1fwyCjXG01ZCHbaSG',
                 SECRET_KEY='qgxJneAt0ovmGA2rLrdq99GEFs1apjte'):
        """ 你的 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, path, name, userId,groupId=100):
        # image = base64.b64encode(open(path).read()).decode('utf-8')
        image = base64.b64encode(path.read()).decode('utf-8')
        imageType = "BASE64"
        """ 调用人脸注册 """
        res=self.client.addUser(image, imageType, groupId, userId);

        # """ 如果有可选参数 """
        # options = {}
        # options["user_info"] = name
        # options["quality_control"] = "NORMAL"
        # options["liveness_control"] = "LOW"
        # options["action_type"] = "REPLACE"
        #
        # """ 带参数调用人脸注册 """
        # res = self.client.addUser(image, imageType, groupId, userId, options)
        print(res)
        return res

    def search(self,img_obj):
        image = base64.b64encode(img_obj.read()).decode('utf-8')
        image_type = "BASE64"
        group_id_list = "100,101"
        """ 调用人脸搜索 """
        res=self.client.search(image, image_type, group_id_list);
        # """ 如果有可选参数 """
        # 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)
        return res

    def delete(self,user_id,face_token,group_id=100):
        """ 调用人脸删除 """
        res=self.client.faceDelete(user_id, group_id, face_token)
        return res

class BaiDuVoice:
    def __init__(self, APP_ID='63701411', API_KEY='b0tYjKxPmcuoSm4SAen19p2c',SECRET_KEY='m8ramzeLtMLHEa9XRPXQJNbcAWGiIuZ8'):
        """ 你的 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

if __name__ == '__main__':
    ai = BaiDuVoice()
    # file=open('../test1.wav','rb')
    file=open('../a.wav','rb')
    res=ai.speed(file)
    print(res)

六 公告

6.1 微信小程序端

#js###

const api = require("../../config/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: api.notice,
      method: "GET",
      success: (res) => {
        this.setData({
          noticeList: res.data
        })
      },
      complete() {
        wx.hideLoading()
      }
    })

  }
})



#####wxml##
<!-- community_notice.wxml -->
<view class="container">
  <!-- 使用wx:for循环遍历社区公告列表 -->
  <view wx:for="{{noticeList}}" wx:key="index" class="notice-item">
    <!-- 左侧图片 -->
    <image class="notice-image" src="{{item.igm}}" 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###
/* community_notice.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;
}

6.2 后端接口

####views.py######
from .models import Notice
from .serializer import NoticeSerializer
class NoticeView(GenericViewSet,ListModelMixin):
    queryset =Notice.objects.all().order_by('create_time')
    serializer_class = NoticeSerializer


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

七 活动列表

小程序端

##### js####
var app = getApp();
var api = require("../../config/settings.js")
Page({

  data: {
    activityList: [
      
    ]
  },
  onLoad: function () {
    // 页面加载时执行的逻辑
    this.refresh()
  },
  refresh(){
    wx.showLoading({
      mask: true
    })
    wx.request({
      url: api.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);
  
  }
})
###wxml#####
<!-- activity_signup.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###
/* activity_signup.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;
}


后端接口

####视图类
from .models import Activity
from .serializer import ActivitySerializer
class ActivityView(GenericViewSet,ListModelMixin):
    queryset =Activity.objects.all().order_by('date')
    serializer_class = ActivitySerializer
### 序列化类
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 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 = '活动报名记录'
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无敌开心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值