最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 小程序开发一个朋友圈热门的互动答题应用

    正文概述 掘金(JesseLuo)   2021-03-25   924

    这是之前受到朋友圈的一些 亲密度测试 的启发,开发的一个互动答题应用。

    功能

    1. 创建自定义题目,设置正确选项
    2. 生成分享图邀请好友来答题
    3. 好友得到成绩单,并可以生成图片分享
    4. 通知中心可以查看好友答题记录

    技术栈

    • 前端:小程序
    • 后端:云开发
    • 框架:mpx

    展示

    预览地址

    小程序开发一个朋友圈热门的互动答题应用

    截图

    小程序开发一个朋友圈热门的互动答题应用

    代码

    git仓库:github.com/luosijie/mi…

    代码结构

    小程序开发一个朋友圈热门的互动答题应用

    示例

    新建题目

    <template>
      <view>
        <swiper class="cards" bindchange="swiperChange" current="{{ current }}">
          <!-- 全局配置 -->
          <swiper-item>
            <view class="card config">
              <textarea type="text" maxlength="12" auto-height="true" wx:model="{{ formData.title }}"  placeholder="请输入题卡名称"/>
            </view>
          </swiper-item>
          <swiper-item wx:for="{{ formData.cards }}" wx:key="id" wx:for-item="card">
            <view class="card">
              <!-- 标题 -->
              <input type="text" maxlength="12" class="title" value="{{ card.title }}" placeholder="请输入标题" bindblur="titleChange"/>
              <!-- 选项 -->
              <view
                class="option"
                wx:for="{{ card.options }}"
                wx:key="id"
                wx:for-item="option"
                wx:for-index="optionIndex"
              >
                <!-- 移除选项 -->
                <i class="remove iconfont icon-remove" bindtap="removeOption(optionIndex)"></i>
                <!-- 选项标题 -->
                <input type="text" maxlength="12" value="{{ option.value }}" placeholder="请输入选项标题" bindblur="optionTitleChange(optionIndex, $event)"/>
                <!-- 正确选项 -->
                <view class="correct" bindtap="setCorrect(index, optionIndex)">
                  <i class="check iconfont icon-check" wx:if="{{ card.correct === optionIndex }}"></i>
                </view>
              </view>
              <!-- 新增 -->
              <view class="add-option" wx:if="{{ card.options.length < 4 }}" bindtap="addOption">新增选项</view>
            </view>
          </swiper-item>
          <swiper-item>
            <view class="card" bindtap="addCard">
              <view class="new-card">
                <i class="iconfont icon-new-card"></i>
              </view>
            </view>
          </swiper-item>
        </swiper>
        <view class="controls">
          <view class="ps">
            <block wx:if="{{ current < formData.cards.length + 1 && current > 0 }}">
              <view class="tip">
                点击选项右边标记正确答案
              </view>
              <i class="delete-card iconfont icon-clear" bindtap="deleteCard" wx:if="{{ current > 1 }}"></i>
            </block>
          </view>
          <view class="swip">
            <view class="pre iconfont icon-pre" bindtap="pre"></view>
            <view class="current">
              <block wx:if="{{ current === 0 }}">
                题卡配置
              </block>
              <block wx:elif="{{ current < formData.cards.length + 1 }}">
                第{{ current }}/{{ formData.cards.length }}题
              </block>
              <block wx:else>
                新增题目
              </block>
            </view>
            <view class="next iconfont icon-next" bindtap="next"></view>
          </view>
          <view class="generate" bindtap="generate">生成</view>
        </view>
      </view>
    </template>
    
    <script>
      import { createPage } from '@mpxjs/core'
      createPage({
        data: {
          current: 0,
          formData: {
            title: '',
            cards: []
          }
        },
        onLoad () {
          this.formData.cards = []
          const card = this.generateCard()
          this.formData.cards.push(card)
        },
        methods: {
          // 生成选项
          generateOption (id) {
            return {
              id,
              value: ''
            }
          },
          // 生成一个起始题目
          generateCard () {
            const id = new Date().getTime()
            const card = {
              id,
              title: '',
              options: [
                this.generateOption(id + 1),
                this.generateOption(id + 2),
                this.generateOption(id + 3),
                this.generateOption(id + 4)
              ],
              correct: 0
            }
            return card
          },
          addCard () {
            const card = this.generateCard()
            this.formData.cards.push(card)
          },
          swiperChange (e) {
            this.current = e.detail.current
          },
          // 切换上一题
          pre () {
            if (this.current > 0) {
              this.current--
            }
          },
          // 切换下一题目
          next () {
            if (this.current < this.formData.cards.length + 1) {
              this.current++
              console.log('cards:', this.formData)
            }
          },
          // 移除选项
          removeOption (index) {
            const options = this.formData.cards[this.current - 1].options
            if (options.length < 3) {
              wx.showToast({
                title: '至少保留2个选项',
                icon: 'none'
              })
              return
            }
            options.splice(index, 1)
          },
          // 新增选项
          addOption () {
            const options = this.formData.cards[this.current - 1].options
            const option = this.generateOption(new Date().getTime())
            options.push(option)
          },
          // 删除卡片
          deleteCard () {
            console.log('delete')
            if (this.formData.cards.length < 2) {
              wx.showToast({
                title: '不能再删了',
                icon: 'none'
              })
              return
            }
            wx.showModal({
              title: '提示',
              content: '确定删除该题目吗',
              confirmColor: '#40A9FF',
              success: () => {
                this.formData.cards.splice(this.current - 1, 1)
                console.log('删除卡片', this.formData.cards)
              }
            })
          },
          titleChange (e) {
            this.formData.cards[this.current - 1].title = e.detail.value
            console.log('e', this.formData.cards)
          },
          optionTitleChange (index, e) {
            const options = this.formData.cards[this.current - 1].options
            options[index].value = e.detail.value
          },
          // 校验题卡表单
          validateForm () {
            if (!this.formData.title) {
              wx.showToast({
                title: '题卡名称未填写',
                icon: 'none'
              })
              return false
            }
            for (let i = 0; i < this.formData.cards.length; i++) {
              const card = this.formData.cards[i]
              if (!card.title) {
                wx.showToast({
                  title: `第${i + 1}道题 标题未填写`,
                  icon: 'none'
                })
                return false
              }
              // 校验选项标题填写情况
              for (let j = 0; j < card.options.length; j++) {
                const option = card.options[j]
                if (!option.value) {
                  wx.showToast({
                    title: `第${i + 1}道题 选项未完善`,
                    icon: 'none'
                  })
                  return false
                }
              }
            }
            return true
          },
          // 设置正确选项
          setCorrect (index, optionIndex) {
            const card = this.formData.cards[index]
            card.correct = optionIndex
            this.$set(this.formData.cards, index, card)
          },
          async generate () {
            const valid = this.validateForm()
            if (!valid) return
            wx.showLoading({
              title: '处理中...',
              mask: true
            })
            const res = await wx.cloud.callFunction({
              name: 'questionAdd',
              data: this.formData
            })
            if (res.result.success) {
              wx.showToast({
                title: '创建成功',
                icon: 'success'
              })
              // 跳转到投票详情页
              setTimeout(() => {
                wx.redirectTo({
                  url: `detail-entry?_id=${res.result._id}`
                })
              }, 1500)
            }
          }
        }
      })
    </script>
    
    
    // 省略样式
    
    

    成绩单

    <template>
      <view class="main" wx:if="{{ detail }}">
        <view class="card">
          <view class="title">“ {{ detail.question.title }} ”</view>
          <view class="zql">正确率</view>
          <view class="score">{{ detail.score }}</view>
          <view class="from">
            <image src="{{ detail.creator.avatarUrl }}"></image> {{ detail.creator.nickName }} 的成绩单
          </view>
          <view class="result" wx:if="{{ showResult }}">
            <view
              class="item"
              wx:for="{{ detail.result }}"
              wx:key="index"
              wx:style="{{ { background: item.right ? '#4faf70' : '#d94948' } }}"
            >
              {{ item.letter }}
            </view>
          </view>
          <view class="me-too" wx:else bindtap="toCreate">
            我也来出一题
          </view>
          <view class="info">
            <view class="date">
              {{ detail.createTime }}
            </view>
            <view class="date">
              出题人: {{ detail.questionCreator.nickName }}
            </view>
            <view class="num" wx:if="{{ detail.question.answers }}">
              {{ detail.question.answers.length }}次参与
            </view>
          </view>
        </view>
        <view class="action">
          <view class="share" bindtap="generateShareImage">生成分享图</view>
          <view class="detail" bindtap="toCards" wx:if="{{ user.OPENID === detail.creator.OPENID }}">再试一次</view>
          <view class="detail" bindtap="toDetail" wx:else>我试一下</view>
        </view>
        <!-- 用来生成分享图 -->
        <canvas
          type="2d"
          id="canvas_share"
          class="canvas-share"
          style="width: {{canvasShare.width}}px; height: {{canvasShare.height}}px"
        />
        <pop visible="{{ imageShare.visible }}" bindclose="closeImageShare">
          <image src="{{ imageShare.image }}" mode="aspectFit" class="image-share"></image>
        </pop>
      </view>
    </template>
    
    <script>
      import { createPage } from '@mpxjs/core'
      import no2letter from '../utils/no2letter'
      import loadImage from '../utils/loadImage'
      createPage({
        data: {
          user: null,
          detail: null,
          canvasShare: {
            width: 0,
            height: 0
          },
          imageShare: {
            visible: false,
            image: ''
          },
          showResult: false
        },
        onLoad (params) {
          const id = params._id || params.scene
          this.user = wx.getStorageSync('user')
          this.getDetail(id)
        },
        onShareAppMessage () {
          const title = `我在${this.detail.questionCreator.nickName}的题目中得分${this.detail.score},你也来试试?`
          return {
            title
          }
        },
        methods: {
          closeImageShare () {
            this.imageShare.visible = false
          },
          // 生成分享图
          async generateShareImage () {
            if (this.imageShare.image) {
              this.imageShare.visible = true
              wx.saveImageToPhotosAlbum({
                filePath: this.imageShare.image,
                success () {
                  wx.showToast({
                    title: '图片已经保存到相册',
                    icon: 'none'
                  })
                },
                fail () {
                  wx.showToast({
                    title: '请先在设置里打开相册权限',
                    icon: 'none'
                  })
                }
              })
              return
            }
            wx.showLoading({
              title: '处理中...'
            })
            const res = await wx.cloud.callFunction({
              name: 'wxacode',
              data: {
                page: 'pages/result',
                scene: this.detail._id
              }
            })
            let pageCode
            if (res.result.errCode === 0) {
              pageCode = `data:image/png;base64,${wx.arrayBufferToBase64(res.result.buffer)}`
            } else {
              return
            }
            const query = this.createSelectorQuery()
            query
              .select('#canvas_share')
              .fields({ node: true, size: true })
              .exec(async res => {
                console.log('ressss', res)
                // 获取 canvas 实例
                const canvas = res[0].node
                // 获取 canvas 绘图上下文
                const ctx = canvas.getContext('2d')
                const width = 700
                const height = 900
                this.canvasShare.width = 700
                this.canvasShare.height = 900
                canvas.width = width
                canvas.height = height
                // 绘制背景
                ctx.fillStyle = 'white'
                ctx.fillRect(0, 0, width, height)
                // 绘制head区域
                ctx.textBaseline = 'top'
                ctx.font = '32px sans-serif'
                ctx.fillStyle = '#000000'
                ctx.fillText('小题卡', 20, 20)
                ctx.fillStyle = '#999999'
                ctx.fillText('成绩单', 585, 20)
                // 绘制title
                const title = `“${this.detail.question.title}”`
                ctx.font = 'normal bold 50px sans-serif'
                ctx.fillStyle = '#000000'
                ctx.fillText(title, (width - title.length * 50) / 2 + 25, 150)
                // 绘制sub-title
                const subtitle = '我在      的题目中正确率为'
                ctx.font = '24px sans-serif'
                ctx.fillStyle = '#999'
                ctx.fillText(subtitle, (width - subtitle.length * 24) / 2 + 48, 250)
                // 绘制出题者头像
                const photoCreator = await loadImage.call(this, this.detail.questionCreator.avatarUrl, 'canvas_share')
                ctx.drawImage(photoCreator, 263, 250, 24, 24)
                // 绘制score
                ctx.font = 'normal bold 200px sans-serif'
                ctx.fillStyle = '#70B7FC'
                ctx.fillText(this.detail.score, (width - this.detail.score.length * 100) / 2 - 80, 350)
                // 绘制welcome
                const welcome = '你也来试试吧'
                ctx.font = '24px sans-serif'
                ctx.fillStyle = '#999'
                ctx.fillText(welcome, (width - welcome.length * 24) / 2, 620)
                // 绘制创建人头像
                const photoAnswer = await loadImage.call(this, this.detail.creator.avatarUrl, 'canvas_share')
                ctx.drawImage(photoAnswer, 40, 780, 24, 24)
                // 绘制foot-title
                const footTitle = '邀请你一起来答题'
                ctx.font = '24px sans-serif'
                ctx.fillStyle = '#999'
                ctx.fillText(footTitle, 70, 780)
                // 绘制foot-title
                const footSubTitle = '长按图片识别进入小程序'
                ctx.font = '24px sans-serif'
                ctx.fillStyle = '#999'
                ctx.fillText(footSubTitle, 40, 820)
                // 绘制小程序码
                const photoPage = await loadImage.call(this, pageCode, 'canvas_share')
                ctx.drawImage(photoPage, 510, 730, 150, 150)
                // 绘制边框和分割线
                ctx.strokeStyle = '#eee'
                ctx.lineWidth = 8
                ctx.strokeRect(0, 0, width, height)
                ctx.lineWidth = 3
                ctx.beginPath()
                ctx.moveTo(0, 700)
                ctx.lineTo(700, 700)
                ctx.stroke()
                ctx.save()
                wx.hideLoading()
                // 生成图片预览
                wx.canvasToTempFilePath({
                  x: 0,
                  y: 0,
                  width,
                  height,
                  canvas,
                  complete: resTemp => {
                    console.log('resTemp', canvas, resTemp)
                    if (resTemp.errMsg === 'canvasToTempFilePath:ok') {
                      this.imageShare.image = resTemp.tempFilePath
                      this.imageShare.visible = true
                      wx.saveImageToPhotosAlbum({
                        filePath: resTemp.tempFilePath,
                        success () {
                          wx.showToast({
                            title: '图片已经保存到相册',
                            icon: 'none'
                          })
                        },
                        fail () {
                          wx.showToast({
                            title: '请先在设置里打开相册权限',
                            icon: 'none'
                          })
                        }
                      })
                    }
                  }
                })
              })
          },
          async getDetail (_id) {
            wx.showLoading({
              title: '加载中...'
            })
            const res = await wx.cloud.callFunction({
              name: 'answerDetail',
              data: {
                _id
              }
            })
            this.detail = res.result.data
            this.detail.result = this.detail.result.map((e, index) => {
              return {
                letter: no2letter(this.detail.answer[index]),
                right: e
              }
            })
            const OPENID = this.user.OPENID
            this.showResult = OPENID === this.detail.creator.OPENID || OPENID === this.detail.questionCreator.OPENID
            wx.hideLoading()
          },
          toCards () {
            wx.navigateTo({
              url: `detail-cards?_id=${this.detail.question._id}`
            })
          },
          toCreate () {
            wx.navigateTo({
              url: 'new'
            })
          },
          toDetail () {
            wx.navigateTo({
              url: `detail-entry?_id=${this.detail.question._id}`
            })
          }
        }
      })
    </script>
    
    // 样式省略
    

    谢谢阅读


    起源地下载网 » 小程序开发一个朋友圈热门的互动答题应用

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元