微信小程序绘制连线题(自参)

原理

计算出连线两个点的坐标,点击任意一个点再点击另外一个点时,就使用canvas利用两个点的坐标进行绘制直线。

import util from '../../../../utils/util'

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    questionSubject: {
      type: Object,
    },
  },

  observers: {
    questionSubject({content: { connectingLineList, optionList }}) {
      this.setData({
        leftData: connectingLineList,
        rightData: optionList
      });
      wx.nextTick(() => {
        this.getCanvas()
      })
    },
    context(val) {
      val && this.displayData()
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    leftActive: -1,
    rightActive: -1,
    leftPoint: new Map(),
    rightPoint: new Map(),
    leftData: [],
    rightData: [],
    myAnswer: new Map(),
    context: null,
    canvasHeight: 0,
    canvasWidth: 0,
    width: 0,
    height: 0,
    top: 0,
  },

  /**
   * 组件的方法列表
   */
  methods: {
    // 获取本地数据,有则回显
    async displayData() {
      let data = wx.getStorageSync('connectionAnswer')
      if (data) {
        const arr = data.filter(item => item[0] == this.data.questionSubject.questionTopicId)
        if (arr.length === 0) return
        const tempMap = new Map()
        this.setData({
          myAnswer: util.objToMap(arr[0][1], tempMap)
        })
        const list = this.data.questionSubject.answer.connectingLineList
        const newTempMap = this.addColorToAnswerData(tempMap, list)
        wx.nextTick(() => {
          this.rePaint(newTempMap)
        })
      } 
    },
 // 为提交的答案数据回显时添加正确错误的颜色差异
 addColorToAnswerData(map, list) {
   const newMap = new Map()
   for (const [key, val] of map) {
     val.forEach(id => {
       // green 正确 red 错误  includes为true代表有正确答案
       const color = list.find(item => item.prefix == key).answerPrefix.includes(id) ? 'green' : 'red'
       const oldVal = newMap.get(key) // 获取旧值重组数据项
       oldVal ? newMap.set(key, [...oldVal, { id, color }]) : newMap.set(key, [{ id, color }])
     })
   }
   return newMap
 },
 // 提交答案
 handleBeSure() {
   const id = this.data.questionSubject.questionTopicId
   let data = wx.getStorageSync('connectionAnswer')
   if (!data) {
     data = []
   } else {
     // 把之前存储对应id的数据删除
     const index = data.findIndex(item => item[0] == id)
     index !== -1 && data.splice(index, 1)
   }
   let myAnswer = {}
   myAnswer = util.mapToObj(this.data.myAnswer, myAnswer) // 转换数据
   data.push([id, myAnswer]) // 添加
   wx.setStorageSync('connectionAnswer', data) // 存储
   this.triggerEvent('onSubmit')
 },
// 点击连线的两个元素
itemClick(e) {
      let {
        isleft,
        prefix,
      } = e.target.dataset;

      if (isleft) {
        this.data.leftActive = prefix
      } else {
        this.data.rightActive = prefix
      }

      // 当前点击的左右元素
      let leftActive = this.data.leftActive;
      let rightActive = this.data.rightActive;
      // 点击左右两边的按钮
      if ((leftActive !== -1) && (rightActive !== -1)) {
        const source = this.data.leftPoint.get(leftActive);
        const target = this.data.rightPoint.get(rightActive);
        const myAnswer = this.data.myAnswer;
        // 左边连接点是否已存储进myAnswer
        const existenceLeftActive = myAnswer.get(leftActive)
        if (existenceLeftActive !== undefined) {
          // length为0则表示左右两个点还没进行过连线,判断为false。
          if (existenceLeftActive.filter(item => item == rightActive).length) {
            myAnswer.set(leftActive, existenceLeftActive.filter(item => item != rightActive));
            // 重绘删除重复连接线
            this.rePaint(this.data.myAnswer)

            this.setData({
              leftActive: -1,
              rightActive: -1,
              myAnswer
            })
            return;
          }
          existenceLeftActive.push(rightActive);
          myAnswer.set(leftActive, existenceLeftActive)
        } else {
          // 存储连线的左右两个点
          myAnswer.set(leftActive, [rightActive])
        }

        const sy = source.y - this.data.top + (source.height / 2);
        const ty = target.y - this.data.top + (target.height / 2);
        const dpr = wx.getWindowInfo().pixelRatio;

        this.lineTo({
          x: 0,
          y: sy * dpr
        }, {
          x: this.data.width * dpr,
          y: ty * dpr
        })

        // 重置数据
        this.setData({
          leftActive: -1,
          rightActive: -1,
          myAnswer
        });
      }
    },
    // 重绘
    rePaint: function (myAnswer) {
      const dpr = wx.getWindowInfo().pixelRatio;
      const context = this.data.context;
      
      context.clearRect(0, 0, this.data.width * dpr, this.data.height * dpr); // 清空线条
      for (let [key, val] of myAnswer) {
        val.forEach(point => {
          const source = this.data.leftPoint.get(key);
          const target = this.data.rightPoint.get(point.id ?? point);
          const sy = source.y - this.data.top + (source.height / 2);
          const ty = target.y - this.data.top + (target.height / 2);
          this.lineTo({
            x: 0,
            y: sy * dpr
          }, {
            x: this.data.width * dpr,
            y: ty * dpr
          }, point.color ?? 'blue')
        });
      }
    },
    // 绘画
    lineTo(source, target, color = 'blue') {
      const context = this.data.context;
      if (context) {
        context.beginPath(); // beginPath() 方法开始一条路径,或重置当前的路径。
        context.moveTo(source.x, source.y); //起始位置
        context.lineTo(target.x, target.y); //终止位置
        context.lineWidth = '3' //线段宽
        context.strokeStyle = color //线段颜色
        context.stroke();
        context.closePath();
      }
    },
    // 准备连线位置数据
    getCanvas() {
      this.setData({
        canvasHeight: 0,
        myAnswer: new Map()
      })
      // canvas宽高
      const query = this.createSelectorQuery();
      query.select('#matching-box').boundingClientRect();
      query.exec((res) => {
        const dpr = wx.getWindowInfo().pixelRatio
        
        this.setData({
          canvasWidth: res[0].width * dpr,
          canvasHeight: res[0].height * dpr - 200
        });
        
        const width = res[0].width * dpr;
        // 获取左侧按钮位置
        const leftPoint = new Map();
        this.data.leftData.forEach(item => {
          const query = this.createSelectorQuery();
          query.select('#left' + item.prefix).boundingClientRect();
          query.exec((res) => {
            leftPoint.set(item.prefix, {
              x: 0,
              y: res[0].top,
              height: res[0].height
            })
          });
        })

        // 获取右侧按钮位置
        const rightPoint = new Map();
        this.data.rightData.forEach(item => {
          const query = this.createSelectorQuery();
          query.select('#right' + item.prefix).boundingClientRect();
          query.exec((res) => {
            rightPoint.set(item.prefix, {
              x: width,
              y: res[0].top,
              height: res[0].height
            })
          });
        })
        this.setData({
          rightPoint,
          leftPoint
        });

        this.createSelectorQuery().select('#canvas')
          .fields({
            node: true,
            size: true
          })
          .exec((res) => {
            const {
              node,
              width,
              height
            } = res[0];
            if (!node) return
            /* 获取 canvas 实例 */
            const dpr = wx.getSystemInfoSync().pixelRatio;
            node.width = res[0].width * dpr;
            node.height = res[0].height * dpr;
            const context = node.getContext('2d');
            // canvas宽高
            this.setData({
              context,
              width,
              height
            })
          });
        this.createSelectorQuery().select('#matching-box').boundingClientRect().exec((res) => {
          const {
            top
          } = res[0];
          // 距离顶部高度
          this.setData({
            top
          })
        })
      });
    },

父组件

// 对连线题答案数据翻译成id对应的内容进行页面显示 
    formatConnectionQuestionAnswer(subject, list) {
      list.connectingLineList.map(item => {
        const { content } = subject.content.connectingLineList.find(left => left.prefix === item.prefix)
        item.formatPrefix = content
        item.formatAnswerPrefix = []
        item.answerPrefix.forEach(answerRight => {
          const { content } = subject.content.optionList.find(right => right.prefix === answerRight)
          item.formatAnswerPrefix.push(content)
        })
      })
    },
// 连线题提交答案
    connectionQuestionSumbit() {
      const { questionMainId, questionTopicId, questionVodUserRecordId } = this.data.questionSubject
      this.setData({
        questionMainId,
        questionTopicId,
        questionVodUserRecordId
    });
      const connectionQuestionRef = this.selectComponent('#connection-question')
      let submitAnswer = {
        connectingLineList: [],
        resourceList: null
      }
      const answer = connectionQuestionRef.data.myAnswer
      for(const [key, value] of answer) {
        submitAnswer.connectingLineList.push({
          prefix: key,
          answerPrefix: value,
        })
      }
      this.questionVodUserSubmit(submitAnswer)
    },

utils

/**
 * Map数据转换Object,小程序本地不支持存储Map数据
 */
function mapToObj(map, obj = {}) {
  for (const [key, value] of map) {
    obj[key] = value
  }
  return obj
}
/**
 * Object数据转换Map
 */
function objToMap(obj, map) {
  for (const key in obj) {
    map.set(key, obj[key])
  }
  return map
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Amodoro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值