<template>
  <div style="position: relative">
    <!-- 验证码图片容器 -->
    <div class="verify-img-out">
      <div class="verify-img-panel" :style="{
          'width': setSize.imgWidth,
          'height': setSize.imgHeight,
          'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
          'margin-bottom': vSpace + 'px'
        }">
        <!-- 刷新按钮 -->
        <div class="verify-refresh" style="z-index:3" @click="refresh" v-show="showRefresh">
          <i class="iconfont icon-refresh"></i>
        </div>
        <!-- 背景图片 -->
        <img :src="'data:image/png;base64,'+pointBackImgBase"
             ref="canvas"
             alt="" style="width:100%; height:100%; display:block"
             @click="bindingClick ? canvasClick($event) : undefined">
        <!-- 点击位置标记 -->
        <div v-for="(tempPoint, index) in tempPoints" :key="index" class="point-area"
             :style="{
               'background-color': '#1abd6c',
               color: '#fff',
               'z-index': 9999,
               width: '20px',
               height: '20px',
               'text-align': 'center',
               'line-height': '20px',
               'border-radius': '50%',
               position: 'absolute',
               top: parseInt(tempPoint.y - 10) + 'px',
               left: parseInt(tempPoint.x - 10) + 'px'
             }">
          {{ index + 1 }}
        </div>
      </div>
    </div>
    <!-- 验证条区域 -->
    <div class="verify-bar-area"
         :style="{'width': setSize.imgWidth, 'color': barAreaColor, 'border-color': barAreaBorderColor, 'line-height': barSize.height}">
      <span class="verify-msg">{{ text }}</span>
    </div>
  </div>
</template>

<script type="text/babel">
/**
 * VerifyPoints 组件
 * @description 实现点选验证码功能。
 */
import {resetSize, _code_chars, _code_color1, _code_color2} from './../utils/util';
import {aesEncrypt} from './../utils/ase';
import {reqGet, reqCheck} from './../api/index';
import {computed, onMounted, reactive, ref, watch, nextTick, toRefs, watchEffect, getCurrentInstance} from 'vue';

export default {
  name: 'VerifyPoints',
  props: {
    // 显示模式：弹出式（pop）或固定（fixed）
    mode: {
      type: String,
      default: 'fixed'
    },
    captchaType: {
      type: String
    },
    // 图片和验证条之间的间隔
    vSpace: {
      type: Number,
      default: 5
    },
    // 图片尺寸
    imgSize: {
      type: Object,
      default() {
        return {
          width: '310px',
          height: '155px'
        };
      }
    },
    // 验证条尺寸
    barSize: {
      type: Object,
      default() {
        return {
          width: '310px',
          height: '40px'
        };
      }
    }
  },
  setup(props, context) {
    const {mode, captchaType, vSpace, imgSize, barSize} = toRefs(props);
    const {proxy} = getCurrentInstance();

    // 后端返回的AES加密秘钥
    const secretKey = ref('');
    // 默认需要点击的字数
    const checkNum = ref(3);
    // 存储选中的坐标信息
    const fontPos = reactive([]);
    // 用户点击的坐标数组
    const checkPosArr = reactive([]);
    // 记录用户点击次数
    const num = ref(1);
    // 后端获取到的背景图片（base64编码）
    const pointBackImgBase = ref('');
    // 后端返回的点击字体顺序
    const poinTextList = reactive([]);
    // 后端返回的token值
    const backToken = ref('');

    // 存储调整后的尺寸
    const setSize = reactive({
      imgHeight: 0,
      imgWidth: 0,
      barHeight: 0,
      barWidth: 0
    });

    // 存储临时点位信息
    const tempPoints = reactive([]);
    // 验证条文本信息
    const text = ref('');
    // 验证条的颜色
    const barAreaColor = ref(undefined);
    // 验证条边框颜色
    const barAreaBorderColor = ref(undefined);
    // 是否显示刷新按钮
    const showRefresh = ref(true);
    // 是否允许点击事件
    const bindingClick = ref(true);

    /**
     * 初始化组件
     */
    const init = () => {
      // 清空之前的数据
      fontPos.splice(0, fontPos.length);
      checkPosArr.splice(0, checkPosArr.length);
      num.value = 1;
      getPictrue();
      nextTick(() => {
        const size = resetSize(proxy);
        setSize.imgHeight = size.imgHeight;
        setSize.imgWidth = size.imgWidth;
        setSize.barHeight = size.barHeight;
        setSize.barWidth = size.barWidth;
        proxy.$parent.$emit('ready', proxy);
      });
    };

    onMounted(() => {
      // 禁止拖拽和选择文本
      init();
      proxy.$el.onselectstart = function () {
        return false;
      };
    });

    const canvas = ref(null);

    /**
     * 处理画布点击事件
     * @param {Event} e - 点击事件对象
     */
    const canvasClick = (e) => {
      checkPosArr.push(getMousePos(canvas, e));
      if (num.value === checkNum.value) {
        num.value = createPoint(getMousePos(canvas, e));
        // 按比例转换坐标值
        const arr = pointTransfrom(checkPosArr, setSize);
        checkPosArr.length = 0;
        checkPosArr.push(...arr);

        setTimeout(() => {
          const captchaVerification = secretKey.value
            ? aesEncrypt(backToken.value + '---' + JSON.stringify(checkPosArr), secretKey.value)
            : backToken.value + '---' + JSON.stringify(checkPosArr);

          const data = {
            captchaType: captchaType.value,
            pointJson: secretKey.value
              ? aesEncrypt(JSON.stringify(checkPosArr), secretKey.value)
              : JSON.stringify(checkPosArr),
            token: backToken.value
          };

          reqCheck(data).then(res => {
            if (res.repCode === '0000') {
              barAreaColor.value = '#4cae4c';
              barAreaBorderColor.value = '#5cb85c';
              text.value = '验证成功';
              bindingClick.value = false;

              if (mode.value === 'pop') {
                setTimeout(() => {
                  proxy.$parent.clickShow = false;
                  refresh();
                }, 1500);
              }

              proxy.$parent.$emit('success', {captchaVerification});
            } else {
              proxy.$parent.$emit('error', proxy);
              barAreaColor.value = '#d9534f';
              barAreaBorderColor.value = '#d9534f';
              text.value = '验证失败';
              setTimeout(refresh, 700);
            }
          });
        }, 400);
      } else {
        num.value = createPoint(getMousePos(canvas, e));
      }
    };

    /**
     * 获取鼠标点击坐标
     * @param {HTMLElement} obj - DOM元素
     * @param {Event} e - 事件对象
     * @returns {Object} 坐标对象
     */
    const getMousePos = (obj, e) => ({
      x: e.offsetX,
      y: e.offsetY
    });

    /**
     * 创建坐标点并添加到临时点列表中
     * @param {Object} pos - 坐标对象
     * @returns {Number} 更新后的点击计数值
     */
    const createPoint = (pos) => {
      tempPoints.push(Object.assign({}, pos));
      return num.value + 1;
    };

    /**
     * 刷新验证码
     */
    const refresh = () => {
      tempPoints.splice(0, tempPoints.length);
      barAreaColor.value = '#000';
      barAreaBorderColor.value = '#ddd';
      bindingClick.value = true;
      fontPos.splice(0, fontPos.length);
      checkPosArr.splice(0, checkPosArr.length);
      num.value = 1;
      getPictrue();
      text.value = '验证失败';
      showRefresh.value = true;
    };

    /**
     * 请求背景图片和验证图片
     */
    const getPictrue = () => {
      const data = {captchaType: captchaType.value};
      reqGet(data).then(res => {
        if (res.repCode === '0000') {
          pointBackImgBase.value = res.repData.originalImageBase64;
          backToken.value = res.repData.token;
          secretKey.value = res.repData.secretKey;
          poinTextList.value = res.repData.wordList;
          text.value = `请依次点击【${poinTextList.value.join(',')}】`;
        } else {
          text.value = res.repMsg;
        }
      });
    };

    /**
     * 将用户点击的坐标转换为原始图片的比例坐标
     * @param {Array} pointArr - 用户点击的坐标数组
     * @param {Object} imgSize - 图片尺寸信息
     * @returns {Array} 转换后的坐标数组
     */
    const pointTransfrom = (pointArr, imgSize) => pointArr.map(p => ({
      x: Math.round(310 * p.x / parseInt(imgSize.imgWidth)),
      y: Math.round(155 * p.y / parseInt(imgSize.imgHeight))
    }));

    return {
      secretKey,
      checkNum,
      fontPos,
      checkPosArr,
      num,
      pointBackImgBase,
      poinTextList,
      backToken,
      setSize,
      tempPoints,
      text,
      barAreaColor,
      barAreaBorderColor,
      showRefresh,
      bindingClick,
      init,
      canvas,
      canvasClick,
      getMousePos,
      createPoint,
      refresh,
      getPictrue,
      pointTransfrom
    };
  }
};
</script>
