仿微信项目-Vue

1.项目介绍

此项目运用运用 vue 全家桶技术(vue+vue-cli+vuex+vue-router+node...),前后台分离和组件化的方式开发,使用WeUl基础样式库进行前台页面的搭建,后台使用的是MongoDB数据库进行编写,使用阿里云短信服务进行验证码注册。实现了下拉刷新、消息发送、大图预览、朋友圈发表。。。。

2、技术实现

MVVM 框架:Vue.js 2.0
状态管理:Vuex
页面路由:Vue-router
弹窗插件:WeUl
聊天插件:vue-socket.io
环境配置:node.js + cnpm
图片插件:vue-photo-preview

3、项目内容

 

3.1 登陆/注册

                                                  

使用WeUl表单实现此样式

<div class="weui-form">
      <div class="weui-form__text-area">
        <h2 class="weui-form__title">手机号登录</h2>
      </div>
      <div class="weui-form__control-area">
        <div class="weui-cells__group weui-cells__group_form">
          <div class="weui-cells weui-cells_form">
              <div class="weui-cell weui-cell_active">
                  <div class="weui-cell__hd"><label class="weui-label" >手机号</label></div>
                  <div class="weui-cell__bd">
                      <input class="weui-input" type="number" v-model="mobile" pattern="[0-9]{11}" placeholder="请输入手机号"/>
                  </div>
              </div>
              <div class="weui-cell weui-cell_active weui-cell_vcode">
                  <div class="weui-cell__hd"><label class="weui-label">验证码</label></div>
                  <div class="weui-cell__bd">
                    <input autofocus class="weui-input" type="text" v-model="code" pattern="[0-9]*" id="js_input" placeholder="输入验证码" maxlength="6"/>
                  </div>
                  <div class="weui-cell__ft">
                    <button v-show="timecode===60" class="weui-btn weui-btn_default weui-vcode-btn" @click="getCode">获取验证码</button>
                    <div v-show="timecode!==60" class="time-code weui-vcode-btn"> {{timecode}}</div>
                  </div>
              </div>
          </div>
        </div>
      </div>
      <div class="weui-form__tips-area">
        <label id="weuiAgree" for="weuiAgreeCheckbox" class="weui-agree">
          <input id="weuiAgreeCheckbox" type="checkbox" v-model="agree" class="weui-agree__checkbox"/><span class="weui-agree__text">阅读并同意<a href="javascript:">《相关条款》</a>
          </span>
        </label>
      </div>
      <div class="weui-form__opr-area">
        <a :class="isSubmit?'weui-btn weui-btn_primary':'weui-btn weui-btn_primary  weui-btn_disabled'"  href="javascript:" id="showTooltips" @click="signup">注册/登录</a>
      </div>
    </div>

使用阿里云短信服务,输入手机号发送验证码,手机号进行验证,输入合法的手机号才可以发送验证码,验证码后台验证,要输入对的验证码才能登陆。验证码一分钟有效,一分钟之后要重新发送,重新获取。

async signup () {
      // 验证手机号
      if (!(/^1[3456789]\d{9}$/.test(this.mobile))) {
        weui.topTips('请输入合法的手机号', 2000)
        return false
      }
      // 验证码
      if (!this.code) {
        weui.topTips('请填入验证码', 2000)
        return false
      }
      // 发送请求,注册或者登录
      const res = await service.post('/users/signup', {
        phonenum: this.mobile,
        code: this.code
      })
      /* 注册成功后的处理
      1.将登录或者注册的用户信息保存起来,以供其他页面使用
      2.跳转到用户本来想要请求的页面
      */
      if (res.data.code === 0) {
        weui.toast('登录成功', 2000)
        this.$store.dispatch('setUser', res.data.data)
        this.$router.go(-1)
      }
    },
    // 验证码
    async getCode () {
      // 验证手机号
      if (!(/^1[3456789]\d{9}$/.test(this.mobile))) {
        weui.topTips('请输入合法的手机号', 2000)
        return false
      }
      // 向服务器接口发送请求
      const res = await service.get('/users/phonecode', {
        phonenum: this.mobile
      })
      console.log(res)
      // 开启定时器,60s倒计时
      this.countTimeCode()
    },
    // 倒计时
    countTimeCode () {
      // 为this对象(当前的vue实例)动态添加一个属性clearFlag
      this.clearFlag = setInterval(() => {
        if (this.timecode === 0) {
          this.timecode = 60
          // 清除定时器
          clearInterval(this.clearFlag)
          return
        }
        this.timecode--
      }, 1000)
    }
  },
  beforeDestroy () {
    clearInterval(this.clearFlag)
  }

3.2 朋友圈列表

 

                                                       

朋友圈列表是分组件开发,几个组件组合成了这个页面。分别有列表组件、头部组件、背景图页面、下拉刷新组件、上拉刷新组件、评论组件和点赞组件。

3.2.1头部组件

<div class="header-bar" :class="headerClass">
    <p class="title">朋友圈</p>
    <div class="photo-icon" @click="goPublish"></div>
</div>

滚到到一点高度会显示,点击相机会跳转到发表朋友圈页面,进行判断如果没有登录则返回登录页面若登录则跳转到发布朋友圈页面。

goPublish () {
      /**
         * 判断用户是否登录
         * - 如果没有登录,跳转到登录页面
         * - 如果登录,则跳转到publish页面
         */
      if (!this.$store.state.currentUser._id) {
        this.$router.push('login')
        return
      }
      this.$router.push('publish')
    }

3.2.2背景图页面

背景图展示和更换,若此用户没有更换过页面会展示系统默认背景图片和头像(从文件夹里自动分配),若此用户有自己的背景图和头像(数据库会有对应的数据可以拿出来进行展示),会展示自己的背景图和头像

computed: {
    // 设置用户的背景图像
    topBgImg () {
      const url = this.$store.state.currentUser.bgurl || require('../../assets/images/topbg.jpg')
      // console.log(url)
      return {
        backgroundImage: `url(${url})`
      }
    },
    // 设置用户头像
    usreAvatar () {
      const url = this.$store.state.currentUser.avatar || require('../../assets/images/avatar.jpg')
      return url
    },
    // 设置用户昵称
    nickname () {
      return this.$store.state.currentUser.nickname
    }
  }

                                                  

点击背景图片可以选择更换背景图片,用户从相册中选了了图片后,将图片上传,服务器返回上传成功的信息。根据服务器返回的图片url,更新用户背景图像:1)更新数据库中用户的背景图像;2)更换页面上用户的背景图像(使用了weui里面的upload组件上传图片和actionsheet组件弹出弹窗)

changeBg () {
      const sefl = this
      // 弹出菜单
      weui.actionSheet([
        {
          label: '从相册选择',
          onClick: function () {
            // 展示一个用于选择图片的对话框
            sefl.$refs.uploaderBg.click()
          }
        }], [
        {
          label: '取消',
          onClick: function () {
            console.log('取消')
          }
        }
      ])
    },
    // 更换用户的背景 图像
    async submit (obj) {
      // 更换数据库中用户的背景图像
      const res = await service.post('users/update', {
        userId: this.$store.state.currentUser._id,
        bgurl: obj.data.url
      })
      // 更新本地用户背景图像
      if (res.data.code === 0) {
        this.$store.dispatch('setUser', {
          ...this.$store.state.currentUser,
          bgurl: obj.data.url
        })
      }
    }

3.2.3列表组件

展示用户头像、帖子内容、帖子的发表时间和点赞和评论功能,实现了大图预览,可以查看朋友发表的图片的高清大图。(使用了vue-photo-preview插件进行图片大图预览的功能)

<div class="avatar-wrap">
        <img :src="data.user.avatar"  class="avatar" @click="goPersonPage(data.user._id)">
    </div>
    <!-- 帖子的详细信息 -->
    <div class="content-info">
      <!-- 用户昵称 -->
      <p class="nickname">{{data.user.nickname}}</p>
      <!-- 帖子内容 -->
      <div class="post-content">{{data.content}}</div>
      <!-- 帖子如果有多张图片,按照列表展示,图片的尺寸都一样 -->
      <div class="img-content" v-if="data.picList.length>1">
        <img v-for="imgInfo in data.picList"
        :key="imgInfo.id"
        :src="imgInfo.url"
        :preview="data._id"
          class="img-wrap">
      </div>
      <!-- 帖子如果只有1张图片,图片可以放大展示,要求:宽度和高度不能超过200px -->
      <div class="img-content-one" v-if="data.picList.length===1">
        <img
        :src="data.picList[0].url"
          class="img-wrap-one"
          :preview="data._id"
          :style="initImgSize(data.picList[0])">
      </div>
      <!-- 帖子的发表时间 -->
      <div class="time">{{formatTime(data.create)}}</div>
      <!-- 点赞和评论功能 -->
      <div class="opera-box" @click.stop="showPanel($event)">
        <div class="box-panel-wrap">
          <div class="box-panel animate__animated animate__bounceInRight" v-show="showOpera">
            <!-- 点赞和取消 -->
            <div class="like-box" @click="operaLike">
              <!-- 显示一个喜欢的图标 -->
              <div class="like-icon"></div>
              <div class="like-text" v-if="!data.isLike">赞</div>
              <div class="like-text" v-if="data.isLike">取消</div>
            </div>
            <!-- 发表评论 -->
            <div class="comment-box" @click="addComment($event)">
              <div class="comment-icon"></div>
              <div class="comment-text">评论</div>
            </div>
          </div>
        </div>
      </div>
      <!-- 赞和评论的展示列表 -->
      <div class="comment-list"
      v-show="data.likes.length>0 || data.comments.length>0">
          <!-- 赞的展示区域 -->
          <div class="like-content" v-show="data.likes.length>0">
              <!-- 赞的图标 -->
              <div class="like-heart-icon"></div>
              <!-- 赞的作者 -->
              <span  class="like-nickname"
              v-for="(item,index) in data.likes"
              :key="index"
              >
                {{item.nickname ||item.user.nickname}}
              </span>
          </div>
          <!-- 评论展示区域 -->
          <div class="comment-item  scale-1px-top"
          v-for="(item,index) in data.comments"
          :key="index">
              <!-- 评论作者 -->
              <div class="comment-nickname one-line">
                {{item.user.nickname}}:
              </div>
              <!-- 评论内容 -->
              <div>{{item.content}}</div>
          </div>
      </div>
    </div>

时间展示

formatTime (time) {
      return formatTime(new Date(time).getTime() / 1000)
    }

3.2.4点赞组件

                                                         

点赞喜欢的帖子,通知vuex的store更新朋友圈的列表的数据

// 点赞和取消点赞
    operaLike () {
      if (this.data.isLike) {
        // 取消点赞
        this.removeLike()
      } else {
        // 点赞
        this.addLike()
      }
    },
    // 取消点赞
    async removeLike () {
      const res = await service.post('likecomment/removelike', {
        postId: this.data._id
      })
      if (res.data.code === 0) {
        this.$store.dispatch('removeLike', {
          pid: this.data._id,
          user: this.$store.state.currentUser
        })
      }
    },
    // 点赞
    async addLike () {
      const res = await service.post('likecomment/addlike', {
        postId: this.data._id
      })
      if (res.data.code === 0) {
        this.$store.dispatch('addLike', {
          pid: this.data._id,
          user: this.$store.state.currentUser
        })
      }
    }

3.2.5评论组件

                                                         

点击评论获取用户点击的位置,展示文本框,输入内容成功评论。对安卓系统和iOS系统进行了判断,根据系统的不同展示位置也不同。使用了vuex公交车,进行数据传值,更新展示到页面。

addComment (e) {
      // 获取当前点击的坐标
      this.data.pageY = e.pageY
      this.data.clientY = e.clientY
      this.$bus.$emit('showInput', this.data)
    }
        <div ref="inputWrap" class="input-wrap ios"
         :style="{zIndex:showInput?'999':'-1',opacity:showInput?'1':'0'}"
         v-if="iosInput">
          <inputbar ref="inputBar" :option="option" @publish="publish"></inputbar>
        </div>
        <!-- 安卓 -->
        <div ref="inputWrap" class="input-wrap android"
        :style="{zIndex:showInput?'999':'-1',bottom:showInput?'0':'-60'}"
        v-if="androidInput">
          <inputbar ref="inputBar" :option="option" @publish="publish"></inputbar>
        </div>
// 发表评论
async publish (data) {
      const res = await service.post('likecomment/addcomment', {
        postId: data.data._id,
        content: data.value
      })
      if (res.data.code === 0) {
        this.showInput = false
        this.$store.dispatch('addComment', {
          pid: res.data.data.post, // 帖子的id
          content: data.value,
          user: this.$store.state.currentUser
        })
      }
    }

3.2.6 上拉刷新组件

                                                          

每页默认展示五条数据,点击屏幕向上滑动时触发向上刷新加载更多内容。

  async getCircleList () {
      this.readyToload = false // 表示数据还没有加载完毕
      const res = await service.get('post/getcirclepost', {
        pageStart: this.pageStart
      })
      if (res.data.code === 0) {
        // 通知下拉刷新组件,数据已经加载完毕
        this.$bus.$emit('dataLoadReady')
        this.$store.dispatch('setCircleDatalist', res.data.data)
        this.readyToload = true
        this.$store.commit('setFlag', false)
      }
      console.log(this.circleList)
    },
  loadData () {
      // console.log('加载更多数据')
      this.pageStart++
      this.getCircleList()
    }

3.2.7下拉刷新

                                                 

下拉刷新,请求最新的数据,展示到页面。小圆球转动,数据请求完毕小圆球停止转动归位,实现加载页面的功能。

 methods: {
    touchstart (e) {
      // 记录触摸的起始纵坐标
      this.pullRefresh.dragStart = e.targetTouches[0].clientY
    },
    touchmove (e) {
      console.log('2222')
      const target = e.targetTouches[0]
      // 记录(手指现在位置-初始位置)/屏幕高度 计算得来的数值
      this.pullRefresh.percentage = (this.pullRefresh.dragStart - target.clientY) / window.screen.height
      // 获取scrollTop的值,只有值为0时,才会开始下拉刷新逻辑
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      if (scrollTop === 0) {
        // 必须是向下拖动
        if (this.pullRefresh.percentage < 0 && e.cancelable) {
          // 满足上面两个条件,才是真正进入到下拉逻辑
          this.pullRefresh.isPull = true
          // 禁用浏览器的默认行为
          e.preventDefault()
          // 计算圆球的纵向移动距离
          const translateY = -this.pullRefresh.percentage * this.pullRefresh.moveCount
          if (Math.abs(this.pullRefresh.percentage) <= this.pullRefresh.dragEnd) {
            // 计算圆球的旋转角度
            const rotate = translateY / 100 * 360
            // 设置圆球的纵向位置
            this.$refs.circleIcon.style.transform = `translate(0,${translateY}px)  rotate(${rotate}deg)`
          }
        } else {
          // 向上拖动,就不会进入下拉刷新逻辑
          this.pullRefresh.dragStart = null
        }
      } else {
        // 如果没有在页面顶部执行拖动事件,则不执行下拉刷新逻辑
        this.pullRefresh.dragStart = null
      }
    },
    // 手指松开屏幕后,圆球归位,加载最新数据
    touchend (e) {
      if (!this.pullRefresh.isPull) {
        return
      }
      console.log(1111)
      if (Math.abs(this.pullRefresh.percentage) > this.pullRefresh.dragEnd) {
        // 为小圆球引用动画
        this.$refs.circleIconInner.classList.add('circle-rotate')
        // 通知使用此组件的组件加载最新数据
        this.$emit('onRefresh')
      } else {
        // 如果用户松开手指时,下拉的距离没有达到临界值,就自动收回
        this.$refs.circleIcon.style.transition = 'all 500ms'
        this.$refs.circleIcon.style.transform = 'translate(0,0)  rotate(0deg)'
      }
      // 重置dragstart
      this.pullRefresh.dragStart = null
      this.pullRefresh.percentage = null
    }
  }

3.3发表朋友圈

                                             

点击列表页的相机图标,跳转到发布朋友圈页面。动态内容限制在200字以内,上传图片限制在五张,上传的图片可以预览,删除。

oninput () {
      if (this.content.length > 200) {
        this.content = this.content.substr(0, 200)
      }
      this.letterCount = this.content.length
    },
    // 查看大图
    preImg (e) {
      const self = this
      const style = e.target.getAttribute('style')
      const url = style.split('"')[1]
      var gallery = weui.gallery(url, {
        onDelete: function () {
          self.deleteImg(e.target, gallery)
        }
      })
    },
    // 删除图片
    deleteImg (target, gallery) {
      const self = this
      weui.confirm('确定删除该图片?', () => {
        // 从数组picList中删除图片
        const id = target.dataset.id
        const index = self.picList.findIndex(item => {
          return item.id === id
        })
        self.picList.splice(index, 1)
        // 删除对应的dom元素
        target.remove()
        self.uploadCount--
      })
      gallery.hide(function () {
        console.log('`gallery` has been hidden')
      })
    }
const self = this
    weui.uploader('#uploader', {
      url: service.baseURL + '/post/uploadimg',
      auto: true,
      type: 'file', // 将图片文件上传,而不是base64再上传
      fileVal: 'image',
      compress: {
        width: 1600,
        height: 1600,
        quality: 0.8
      },
      onBeforeQueued: function (files) {
        // `this` 是轮询到的文件, `files` 是所有文件

        if (['image/jpg', 'image/jpeg', 'image/png', 'image/gif'].indexOf(this.type) < 0) {
          weui.alert('请上传符合条件的图片')
          return false // 阻止文件添加
        }
        if (this.size > 10 * 1024 * 1024) {
          weui.alert('请上传不超过10M的图片')
          return false
        }
        if (files.length > self.totalUploadCount) { // 防止一下子选择过多文件
          weui.alert('最多只能上传' + self.totalUploadCount + '张图片,请重新选择')
          return false
        }
        if (self.uploadCount + 1 > self.totalUploadCount) {
          weui.alert('最多只能上传' + self.totalUploadCount + '张图片')
          return false
        }

        ++self.uploadCount

        // return true; // 阻止默认行为,不插入预览图的框架
      },
      onBeforeSend: function (data, headers) {
        const token = document.cookie.split('=')[1]
        headers['wec-access-token'] = token

        // return false; // 阻止文件上传
      },
      onProgress: function (procent) {
        // console.log(this, procent)
        // return true; // 阻止默认行为,不使用默认的进度显示
      },
      onSuccess: function (ret) {
        ret.data.id = this.id
        self.picList.push(ret.data)
        // console.log(this, ret)
        // return true; // 阻止默认行为,不使用默认的成功态
      },
      onError: function (err) {
        console.log(this, err)
        // return true; // 阻止默认行为,不使用默认的失败态
      }
    })

3.4个人信息

                                                

此页面展示了个人详细信息,可以修改个人资料。

<div class="panel">
                <!-- 头像 -->
                <a class="weui-cell  weui-cell_access" href="javascript:" @click="uploadavatar">
                    <div class="weui-cell__bd">
                        <p>头像</p>
                    </div>
                    <img :src="myUser.avatar" class="avatar" alt="">
                    <div class="weui-cell__ft"></div>
                    <div id="uploaderavatar" class="weui-uploader__input-box" style="display:none">
                        <input ref="uploaderavatar" class="weui-uploader__input" type="file" accept="image/*" capture="camera" multiple="" />
                    </div>
                </a>
                <!-- 昵称 -->
                <a class="weui-cell  weui-cell_access" href="javascript:" @click="changname">
                    <div class="weui-cell__bd">
                        <p class="name">昵称</p>
                    </div>
                    <div class="weui-cell__ft">
                        {{myUser.nickname}}
                    </div>
                </a>
                <!-- 个性签名 -->
                <a class="weui-cell  weui-cell_access" href="javascript:" @click="changdesc">
                    <div class="weui-cell__bd">
                        <p class="name">个性签名</p>
                    </div>
                    <div class="weui-cell__ft">
                        {{myUser.desc}}
                    </div>
                </a>
                <!-- 性别 -->
                <a class="weui-cell  weui-cell_access" href="javascript:" @click="changgender">
                    <div class="weui-cell__bd">
                        <p class="name">性别</p>
                    </div>
                    <div class="weui-cell__ft">
                        {{myUser.gender==='1'?'男':'女'}}
                    </div>
                </a>
                <!-- 电话号码 -->
                <a class="weui-cell  weui-cell_access" href="javascript:">
                    <div class="weui-cell__bd">
                        <p class="name">电话号码</p>
                    </div>
                    <div>
                        {{myUser.phoneNum}}
                    </div>
                </a>
            </div>

                      

点击头像弹出图片选择,选择图片进行头像的修改。

                                                   

点击昵称进入修改昵称界面,输入名字进行修改,修改的名字不得为空。

                                                    

点击个性签名进入修改界面,输入内容进行修改。

                                                    

点击性别弹出修改性别的选择框,修改性别。

 

3.4.1 消息列表

                                                     

点击私信进入消息列表页面,展示与好友们的聊天的最后一条消息,可以通过关键字进行消息搜索,点击与该好友的聊天内容,进入到与好友的聊天页面。

<div class="weui-search-bar" id="searchBar" :class="searchBarClass">
            <form class="weui-search-bar__form">
                <div class="weui-search-bar__box">
                    <i class="weui-icon-search"></i>
                    <input v-model="keyword" type="search" class="weui-search-bar__input" id="searchInput" placeholder="搜索" @input="searchChat($event)" required/>
                    <a href="javascript:"  @click="clearSearch" class="weui-icon-clear" id="searchClear"></a>
                </div>
                <label class="weui-search-bar__label" id="searchText">
                    <i class="weui-icon-search"></i>
                    <span>搜索</span>
                </label>
            </form>
            <a href="javascript:" class="weui-search-bar__cancel-btn" id="searchCancel">取消</a>
        </div>
async fetchData () {
      this.loading = true
      const res = await service.get('message/getchatlist', {
        keyword: this.keyword
      })
      if (res.data.code === 0) {
        this.loading = false
        this.dataList = res.data.data
      }
    },
    searchChat () {
      this.fetchData()
    }

3.5好友名片

                                                        

在朋友圈页面点击好友头像进入到好友名片页面,在跳转的同时将该好友的id传过去,然后根据id查询好友信息,并遍历展示到页面。

3.6 聊天页面

                                                 

点击好友名片中的发消息可以与好友进行聊天进入到聊天页面。获取与好友的历史聊天记录展示到页面。默认情况下只显示文本框,点击加号显示更多内容,可以发送图片。运用vue-socket.io和socket.io-client插件,实现了聊天的实时通讯。

created () {
    if (this.$store.state.currentUser && this.$store.state.currentUser._id) {
      // 登录socket
      this.$socket.emit('login', this.$store.state.currentUser)
    }
    // 获取历史聊天数据
    this.fetchData()
  },
sockets: {
    // 接收消息
    recieveMsg: function (obj) {
      if (obj.fromUser._id === this.toUserId) {
        this.addMessage({
          content: obj.content,
          fromUser: obj.fromUser,
          main: false
        })
      }
    },
    /**
    * 服务器掉之后,客户端会重新连接,连接成功后会触发下面的事件
    * 我们就在这个事件中,重新登录
    */
    reconnect (obj) {
      if (this.$store.state.currentUser && this.$store.state.currentUser._id) {
        // 登录socket
        this.$socket.emit('login', this.$store.state.currentUser)
      }
    }
  }
// 获取聊天记录
    async fetchData () {
      const res = await service.get('message/getchathistory', {
        toUser: this.toUserId
      })
      if (res.data.code === 0) {
        this.dataList = (res.data.data)
      }
    },
    // 发表文字内容
    async publish (data) {
      // 将当前的消息储存到数据库
      const res = await service.post('message/addmsg', {
        content: { type: 'str', value: data.value },
        toUser: this.toUserId
      })
      // 将当前的消息储存到dataList中
      this.addMessage({
        content: { type: 'str', value: data.value },
        fromUser: this.$store.state.currentUser,
        mine: true
      })
      if (res.data.code !== 0) {
        weui.topTips('消息发送失败')
      }
    }
// 上传图片成功之后,将图片作为一条消息
    async uploaded (data) {
      // 将当前的消息储存到数据库
      const res = await service.post('message/addmsg', {
        content: { type: 'pic', value: data.data },
        toUser: this.toUserId
      })
      // 将当前的消息储存到dataList中
      this.addMessage({
        content: { type: 'pic', value: data.data },
        fromUser: this.$store.state.currentUser,
        mine: true
      })
      if (res.data.code !== 0) {
        weui.topTips('图片发送失败')
      }
    }

 

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值