一 公告
1.1 微信小程序端
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( ) } } ) }
} )
< !- - 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>
/ * community_notice. wxss * /
. container { padding: 20rpx;
} . notice- item { display: flex; align- items: flex- start; margin- bottom: 20rpx; / * 添加间距 * / border- bottom: 1px solid 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: margin- bottom: 10rpx;
} . notice- details { font- size: 24rpx; color:
}
1.2 后端接口
from . models import Notice
from . serializer import NoticeSerializer
class NoticeView ( GenericViewSet, ListModelMixin) : queryset = Notice. objects. all ( ) . order_by( 'create_time' ) serializer_class = NoticeSerializer
class NoticeSerializer ( serializers. ModelSerializer) : class Meta : model = Noticefields = [ 'id' , 'title' , 'igm' , 'create_time' , 'content' ] extra_kwargs= { 'create_time' : { 'format' : "%Y-%m-%d" } }
二 活动列表
2.1 小程序端
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) ; }
} )
< !- - 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>
/ * 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 padding- bottom: 20rpx;
} . activity- content { flex: 1 ;
} . activity- title { font- size: 28rpx; font- weight: bold; margin- bottom: 10rpx;
} . activity- time { font- size: 24rpx; color: margin- bottom: 10rpx;
} . activity- enrollment { font- size: 24rpx; color: margin- bottom: 10rpx;
} . activity- description { font- size: 24rpx; color: margin- top: 10rpx; white- space: pre- wrap; / * 自动换行 * /
} . signup- btn { background- color: color: border: none; border- radius: 4rpx; padding: 10rpx 20rpx; font- size: 24rpx;
}
2.2 后端接口
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 = Activityfields = [ '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 = '活动报名记录'
三 登录功能
3.1 小程序端 my页面
< block wx: if = "{{userInfo==null}}" >
< !- - login. wxml - - >
< view class = "container1" > < view class = "main" > < view class = "icon-view" > < !- - 应用图标 - - > < image src= "/images/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>
var app = getApp( ) ;
var api = require( "../../config/settings.js" )
Page( { data: { userInfo: null, } , getPhoneNumber( event) { console. log( event) // 通过获取手机号返回的code- - 传递给后端- - 后端调用:POST https: // api. weixin. qq. com/ wxa/ business/ getuserphonenumber?access_token= ACCESS_TOKEN - - > 获取手机号- - 》后端签发token给前端wx. request( { url: api. 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. tokenvar name = data. namevar score = data. scorevar avatar = data. avatarapp. initUserInfo( name, score, avatar, token) var info = app. globalData. userInfoconsole. log( 'globalData.userInfo' , info) if ( info) { this. setData( { userInfo: info} ) } } else { wx. showToast( { title: '登录失败' , } ) } } } ) } , handleOtherLogin( e) { wx. navigateTo( { url: '/pages/otherlogin/otherlogin' } ) } , onShow( ) { var info = app. globalData. userInfoconsole. log( 'globalData.userInfo' , info) if ( info) { this. setData( { userInfo: info} ) } } , handleLogout( ) { app. logoutUserInfo( ) this. setData( { userInfo: null} ) }
} ) 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: 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:
} . top- view{ background- color: color: white; padding: 40rpx;
} . top- view . user{ display: flex; flex- direction: row; justify- content: space- between; align- items: center;
}
. top- view . user . row{ display: flex; flex- direction: row; justify- content: flex- start; align- items: center;
}
. top- view . user . avatar{ width: 100rpx; height: 100rpx; border- radius: 50 % ;
} . top- view . user . name{ display: flex; flex- direction: row; justify- content: flex- start; padding- left: 20rpx;
}
. top- view . user . name navigator{ padding: 0 5rpx;
} . top- view . site{ background- color: rgba( 0 , 0 , 0 , 0.16 ) ; padding: 20rpx; border- top- left- radius: 32rpx; border- bottom- left- radius: 32rpx;
} . top- view . numbers{ display: flex; flex- direction: row; justify- content: space- between; font- size: 28rpx; padding: 40rpx; padding- bottom: 0rpx;
} . top- view . numbers . row{ display: flex; flex- direction: column; align- items: center;
} / * login. wxss * /
. container1 { padding: 20rpx;
}
. main{ display: flex; justify- content: center; align- items: center;
}
. icon- view{ display: flex; flex- direction: column; margin- bottom: 50rpx;
} . app- icon { width: 100rpx; height: 100rpx; margin: 40rpx auto 20rpx; / * 上边距为40rpx,下边距为20rpx,左右居中 * /
} . quick- login- header { display: flex; align- items: center;
} . icon { width: 40rpx; height: 40rpx; margin- right: 20rpx;
} . title { font- size: 28rpx; font- weight: bold; color:
} . divider { height: 20rpx;
} . login- option { font- size: 28rpx; color:
} . login- option . van- cell__icon { color:
} . agreement- container { display: flex; align- items: center; margin- top: 20rpx;
} . checkbox { margin- right: 10rpx;
} . agreement- text { font- size: 24rpx; color:
} . agreement- link { font- size: 24rpx; color:
}
3.2 小程序端-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 = infowx. 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}
} ) ####app. json###"usingComponents" : { "van-field" : "@vant/weapp/field/index" , "van-button" : "@vant/weapp/button/index" , "van-cell-group" : "@vant/weapp/cell-group/index" , "van-nav-bar" : "@vant/weapp/nav-bar/index" , "van-grid" : "@vant/weapp/grid/index" , "van-grid-item" : "@vant/weapp/grid-item/index" , "van-cell" : "@vant/weapp/cell/index" , "van-notice-bar" : "@vant/weapp/notice-bar/index" , "van-image" : "@vant/weapp/image/index" , "van-divider" : "@vant/weapp/divider/index" , "van-tab" : "@vant/weapp/tab/index" , "van-tabs" : "@vant/weapp/tabs/index" , "van-dropdown-menu" : "@vant/weapp/dropdown-menu/index" , "van-dropdown-item" : "@vant/weapp/dropdown-item/index" } ,
3.3 小程序端-settings.js
const rootUrl = 'http://192.168.71.100:8000/app01' ; module. exports = { welcome : rootUrl+ '/welcome/' , banner : rootUrl+ '/banner/' , collection : rootUrl+ '/collection/' , statistics : rootUrl+ '/statistics/' , face : rootUrl+ '/face/' , voice : rootUrl+ '/voice/' , notice : rootUrl+ '/notice/' , activity : rootUrl+ '/activity/' , quick_login : rootUrl+ '/user/quick_login/' , send_sms : rootUrl+ '/user/send_sms/' , login : rootUrl+ '/user/login/' , }
3.4 小程序端-login
< view class = "container" > < view class = "main" > < view class = "icon-view" > < !- - 应用图标 - - > < image src= "/images/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>
. 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:
}
. app- icon { width: 100rpx; height: 100rpx; margin: 40rpx auto 20rpx; / * 上边距为40rpx,下边距为20rpx,左右居中 * /
}
const api = require( "../../config/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: api. 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: api. 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. tokenvar name = data. namevar score = data. scorevar avatar = data. avatarapp. initUserInfo( name, score, avatar, token) var info = app. globalData. userInfoconsole. 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) ; }
} ) ;
3.3 后端接口
router. register( 'user' , LoginView, 'user' )
from libs. send_tx_sms import get_code, send_sms_by_phone
from django. core. cache import cache
from rest_framework. decorators import action
from . models import UserInfo
from rest_framework_simplejwt. tokens import RefreshToken
from faker import Fakerclass 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) } )
四 活动报名
4.1 小程序端
handleSignup: function ( event) { // 1 校验用户是否登录var info = app. globalData. userInfoif ( info) { // 2 处理报名按钮点击事件var index = event. mark. id ; // 获取当前点击的活动索引console. log( '点击了报名按钮,索引为:' , index) ; wx. request( { url: api. join, method: 'POST' , data: { 'id' : index} , header: { token: info. token} , success: ( res) = > { wx. showToast( { title: res. data. msg, } ) } } ) } else { wx. showToast( { title: '请先登录' , } ) } }
4.2 后端接口
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. useractivity = 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' : "报名成功" } )
from . models import UserInfo
from rest_framework. authentication import BaseAuthentication
from rest_framework. exceptions import AuthenticationFailed
from rest_framework_simplejwt. authentication import JWTAuthenticationclass 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
五 积分商城
5.1 小程序端
< 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>
const api = require( "../../config/settings" )
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' , } , } )
{ "usingComponents" : { } , "navigationBarTitleText" : "积分商城"
}