当前位置: 首页 > article >正文

微信小程序开发第八课

一 公告

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.kler.cn/a/323666.html

相关文章:

  • 智能化护士排班系统的设计与实现(文末附源码)
  • (附项目源码)nodejs开发语言,212 个性化音乐推荐系统的设计与实现,计算机毕设程序开发+文案(LW+PPT)
  • C语言之MakeFile
  • springboot 文件高效上传
  • R语言贝叶斯分析:INLA 、MCMC混合模型、生存分析肿瘤临床试验、间歇泉喷发时间数据应用|附数据代码...
  • LinuxCentos中安装apache网站服务详细教程
  • 16.第二阶段x86游戏实战2-发包函数和怎么去找改写过的发包函数
  • 进程与线程之间的关系与区别
  • C++20中头文件compare的使用
  • 数据仓库简介(一)
  • 鸿蒙OS开发之动画相关示例分享, 关于弹出倒计时动画的实战案例源码分享
  • netty之Future和Promise
  • leetcode946. 验证栈序列
  • ARM GNU工具链
  • 赵长鹏今日获释,下一步会做什么?币安透露2024年加密货币牛市的投资策略!
  • 【数据结构-栈】力扣71. 简化路径
  • react-native如何一个项目生成两个安装包
  • 什么是Kafka?
  • 利用 Llama-3.1-Nemotron-51B 推进精度-效率前沿的发展
  • PlayerPerfs-不同平台的存储位置
  • 十一假期地区人流量出行大数据分析:技术驱动下的深度洞察
  • [Excel VBA办公]如何使用VBA批量删除空行
  • 基于微信小程序的网上商城+ssm(lw+演示+源码+运行)
  • 基于Hive和Hadoop的病例分析系统
  • Web会话跟踪+代码分析
  • 在C#中实现WebSocket的单聊和分频道聊天