微信小程序开发第八课

news2024/9/25 14:26:14

一 公告

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

1.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"}
        }

二 活动列表

2.1 小程序端

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


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 = 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 = '活动报名记录'

三 登录功能

login

3.1 小程序端 my页面

############ wxml##############

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

##################js####################
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.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/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
    })
  }
})

#################wxss##################


page{
  height: 100%;
}

.login-area{
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.login-area .btn{
  width: 200rpx;
  height: 200rpx;
  border-radius: 500%;
  background-color: #5cb85c;
  color: white;
  
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}




.user-area{
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.user-area image{
  width: 200rpx;
  height: 200rpx;
  border-radius: 500%;
}
.user-area .name{
  font-size: 30rpx;
  padding: 30rpx 0;
}

.user-area .logout{
  color: #a94442;
}



.top-view{
  background-color: #01ccb6;

  color: white;
  padding: 40rpx;
}

.top-view .user{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}
.top-view .user .row{
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
}
.top-view .user .avatar{
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
}

.top-view .user .name{
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  padding-left: 20rpx;
}
.top-view .user .name navigator{
  padding: 0 5rpx;
}

.top-view .site{
  background-color: rgba(0, 0, 0, 0.16);
  padding: 20rpx;
  border-top-left-radius: 32rpx;
  border-bottom-left-radius: 32rpx;
}

.top-view .numbers{
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  font-size: 28rpx;
  padding: 40rpx;
  padding-bottom: 0rpx;
}

.top-view .numbers .row{
   display: flex;
  flex-direction: column;
  align-items: center;
}




/* login.wxss */
.container1 {
  padding: 20rpx;
}
.main{
  display: flex;
  justify-content: center;
  align-items: center;
}
.icon-view{
  display: flex;
  flex-direction: column;
  margin-bottom: 50rpx;
}

.app-icon {
  width: 100rpx;
  height: 100rpx;
  margin: 40rpx auto 20rpx; /* 上边距为40rpx,下边距为20rpx,左右居中 */
}

.quick-login-header {
  display: flex;
  align-items: center;
}

.icon {
  width: 40rpx;
  height: 40rpx;
  margin-right: 20rpx;
}

.title {
  font-size: 28rpx;
  font-weight: bold;
  color: #333333;
}

.divider {
  height: 20rpx;
}

.login-option {
  font-size: 28rpx;
  color: #333333;
}

.login-option .van-cell__icon {
  color: #07c160;
}

.agreement-container {
  display: flex;
  align-items: center;
  margin-top: 20rpx;
}

.checkbox {
  margin-right: 10rpx;
}

.agreement-text {
  font-size: 24rpx;
  color: #666666;
}

.agreement-link {
  font-size: 24rpx;
  color: #07c160;
}

3.2 小程序端-app.js

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


####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://127.0.0.1:8000/app01';
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

######wxml#####
<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>
######wxss####
.container {
  padding: 20rpx;

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



#####js#####
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.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);
  }
});

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 Faker


class LoginView(GenericViewSet):
    @action(methods=['GET'], detail=False)
    def send_sms(self, request, *args, **kwargs):
        # 1 取出前端传入手机号
        mobile = request.query_params.get('mobile')
        # 2 获取随机验证码
        code = get_code()
        # 3 验证码放到缓存
        cache.set(f'sms_{mobile}', code)
        # 4 发送短信
        res = send_sms_by_phone(mobile, code)
        if res:
            return Response({'code': 100, 'msg': '短信发送成功'})
        else:
            return Response({'code': 101, 'msg': '短信发送失败,请稍后再试'})

    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        # 1 取出手机号和验证码
        mobile = request.data.get('mobile')
        code = request.data.get('code')
        # 2 校验验证码是否正确
        old_code = cache.get(f'sms_{mobile}')
        if old_code == code:
            # 3 数据库查询用户,如果存在直接签发token登录成功

            user = UserInfo.objects.filter(mobile=mobile).first()
            if not user:
                # 4 如果用户不存在,创建用户,再签发token
                fake = Faker('zh_CN')
                username = fake.name()
                user = UserInfo.objects.create(mobile=mobile, name=username)
            refresh = RefreshToken.for_user(user)
            return Response(
                {'code': 100, 'msg': '登录成功', 'token': str(refresh.access_token), 'name': user.name,
                 'score': user.score,'avatar':'http://127.0.0.1:8000/media/'+str(user.avatar)})
        else:
            return  Response({'code':101,'msg':'验证码错误'})

    @action(methods=['POST'], detail=False)
    def quick_login(self, request, *args, **kwargs):
        # 1 取出前端传入的code
        code = request.data.get('code')
        # 2 通过code,调用微信开发平台接口,换取手机号
        # 3 拿到手机号再自己库中查,能查到,签发token
        # 4 查不到注册再签发token
        # 假数据---》都签发成第一个用户
        user=UserInfo.objects.filter(pk=1).first()
        refresh = RefreshToken.for_user(user)
        return Response({'code': 100, 'msg': '登录成功', 'token': str(refresh.access_token), 'name': user.name,'score': user.score,'avatar':'http://127.0.0.1:8000/media/'+str(user.avatar)})

四 活动报名

4.1 小程序端

#### js####
  handleSignup: function (event) {
    // 1 校验用户是否登录
    var info = app.globalData.userInfo
    if (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):
        # 1 取出要参加的活动id
        activity_id = request.data.get('id')
        # 2 取出当前登录用户
        user = request.user
        # 2 查到当前活动
        activity = Activity.objects.filter(pk=activity_id).first()
        # 3 判断时间,判断人数
        # 4 判断是否报名过
        join_record=JoinRecord.objects.filter(activity_id=activity_id,user=user).first()
        if join_record:
            return Response({'code': 101, 'msg': "已经报名过,不用重复报名"})
        else:
            # 5 包名人数+1,报名报存入
            activity.count = activity.count + 1
            activity.save()
            JoinRecord.objects.create(activity=activity,user=user)
            # 6 返回报名成功
            return Response({'code': 100, 'msg': "报名成功"})
        
        
#####认证类#####
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

五 积分商城

5.1 小程序端

#####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###
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',
  },

})

####json###
{
  "usingComponents": {},
  "navigationBarTitleText": "积分商城"
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2163801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

几个将ppt文件压缩变小的方法!

几个将ppt文件压缩变小的方法&#xff01;在构建集文字、图像、视频及数据表于一体的综合PPT演示文稿时&#xff0c;一个常见挑战是随着内容的不断丰富&#xff0c;文件体积也随之膨胀&#xff0c;这往往源于直接嵌入未经优化的多媒体资源及设计上的冗余元素&#xff0c;如繁复…

数字人实战第五天——Dinet 训练自己的数字人

一、简介 DINet 是一个形变修复网络&#xff0c;专门用于解决高分辨率人脸视觉配音中的难题。它的设计目的是为了提升视觉配音的保真度和细节丰富性&#xff0c;特别是在少样本学习的情境下&#xff0c;即在训练数据较少的情况下依然能够实现较好的配音效果。 DINet的技术实现…

html TAB切换按钮变色、自动生成table

<!DOCTYPE html> <head> <meta charset"UTF-8"> <title>Dynamic Tabs with Table Data</title> <style> /* 简单的样式 */ .tab-content { display: none; border: 1px solid #ccc; padding: 1px; marg…

【专题总结】【一文解决】多继承下的构造函数执行顺序

多继承下的构造函数执行顺序 派生类构造函数执行顺序如下 ①调用基类构造函数→调用顺序按它们被继承时【从左至右】被说明的次序 ②调用子对象的构造函数→调用顺序按它们在【类中说明次序】 ③调用派生类的构造函数 【典型题1】13浙工大卷二读程序4题 【分析】下面①classC:p…

Mybatis Mapper 代理开发

文章目录 1, 实体类2、将Mapper接口写好3、UserMapper.xml 配置文件4、mybatis-config.xml 配置文件5、编写代码 Mybatis Mapper 代理开发 总的来说&#xff0c;有这几点&#xff1a; 定义域SQL映射文件同名的Mapper接口&#xff0c;并且将Mapper接口和SQL映射文件位置在同一目…

OpenCV图像分割(2)分水岭算法图像分割函数watershed()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用分水岭算法执行基于标记的图像分割。 该函数实现了分水岭算法的一种变体&#xff0c;即无参数基于标记的分割算法&#xff0c;在文献 [186]…

GPG(GNU Privacy Guard)简介

GPG&#xff08;GNU Privacy Guard&#xff09;简介 加密基础 对称加密 对称加密是一种加密方式&#xff0c;其中加密和解密使用相同的密钥。常见的对称加密算法包括AES和DES。这种方法的优点是速度快&#xff0c;但缺点是密钥分发和管理比较困难&#xff0c;一旦密钥泄露&a…

网络安全-jsp绕过

一、思路(这里给出jsp的WebShell样本) 1.1 加载字节码getshell <% page import"com.sun.org.apache.bcel.internal.util.ClassLoader" %> <html> <body> <h2>BCEL字节码的JSP Webshell</h2> <%String bcelCode "$$BCEL$$$l…

零基础转行学网络安全怎么样?能找到什么样的工作?

网络安全对于现代社会来说变得越来越重要&#xff0c;但是很多人对于网络安全的知识却知之甚少。那么&#xff0c;零基础小白可以学网络安全吗&#xff1f;答案是肯定的。 零基础转行学习网络安全是完全可行的&#xff0c;但需要明确的是&#xff0c;网络安全是一个既广泛又深入…

常用的10款加密软件排行榜(2024年办公文件加密干货收藏)

随着数据泄露事件的增加和隐私安全意识的提升&#xff0c;文件加密已经成为企业和个人保护敏感信息的必备手段。无论是防止外部攻击&#xff0c;还是内部数据泄露&#xff0c;文件加密工具都是重要的防护措施。以下是2024年最常用的10款办公文件加密软件&#xff0c;帮助你在工…

全新热门电商API接口,实现闲鱼商品详细搜索功能

近年来&#xff0c;电商行业蓬勃发展&#xff0c;API&#xff08;Application Programming Interface&#xff09;接口已经成为电商平台的重要组成部分。API接口不仅可以实现平台间的数据交互&#xff0c;还可以为开发者提供丰富的功能&#xff0c;满足用户多样化的需求。在这个…

吴师兄:非科班程序员,创作出Github标星75.3K的宝藏项目,一周爆火……

这是《开发者说》的第18期&#xff0c;今天我们采访的是在Github上传LeetCode动画题解&#xff0c;获得75.3K标星宝藏项目的程序员吴师兄。 吴师兄从985大学毕业&#xff0c;从通信工程外包零基础转码程序员&#xff0c;逐渐进入一些中厂和大厂&#xff0c;工资也从三四千起步…

靶向捕获探针设计软件包

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&…

什么是容性负载箱?

容性负载箱是用于模拟电网中感性负载的装置&#xff0c;通常由电容器、电抗器等元件组成。在电力系统中&#xff0c;负载箱被广泛应用于测试和评估各种电气设备的性能&#xff0c;如变压器、发电机、开关设备等。容性负载箱的主要作用是模拟实际负载对电网的影响&#xff0c;以…

用甘特图做项目计划,你就是时间管理大师!

当我们想要清晰地展现一个活动流程或项目进展时&#xff0c;如何提升对时间和项目的管理能力呢&#xff1f;本文将为你介绍一种新颖的甘特图&#xff0c;特别适合用来进行长期工作安排。借助甘特图&#xff0c;我们可以将中长期目标切分为更易掌控的短期计划&#xff0c;让计划…

无人机之可承受风速的影响因素

无人机可承受风速的影响因素是多方面的&#xff0c;这些因素共同决定了无人机在特定风速条件下的飞行稳定性和安全性。以下是一些主要的影响因素&#xff1a; 一、无人机设计与结构 无人机的大小、形状和重量都会直接影响其抗风能力。大型无人机由于具有更大的表面积和质量&am…

Linux:进程间通信之命名管道

Linux&#xff1a;进程间通信-CSDN博客 我们说匿名管道只能用于父子进程这样的关系通信&#xff0c;那么陌生进程怎么通信&#xff1f; 我们之前说父子进程能通信的最关键的地方就在于子进程复制了一份父进程的files_struct&#xff0c;从而通过文件的inode映射同一份文件来通…

c++反汇编逆向还原指令jge jg jle jl jne je

c反汇编逆向还原jge jg jle jl jne je所还原的c代码 c反汇编逆向还原&#xff0c;if语句、赋值、算数|逻辑运算符(> 、>、、<、<、if中的&&、!、||) 在汇编指令中 jge 前面>后面 jg 前面>后面 jle 前面<后面 jl 前面<后面 jne 前面不等…

从零开始学习Python

目录 从零开始学习Python 引言 环境搭建 安装Python解释器 选择IDE 基础语法 注释 变量和数据类型 变量命名规则 数据类型 运算符 算术运算符 比较运算符 逻辑运算符 输入和输出 控制流 条件语句 循环语句 for循环 while循环 循环控制语句 函数和模块 定…

酒店智能插座有什么用途

作为提升住宿体验的关键一环&#xff0c;酒店智能插座正以其独特的功能和便捷性&#xff0c;悄然改变着旅客的居住体验。本文将深入探讨酒店智能插座的多种用途。 一、能源管理与节能减排 酒店智能插座首要的功能之一在于其强大的能源管理能力。通过内置的传感器和智能算法&…