canvas绘制多个矩形实现热区图功能

热区图功能:

  1. 在运用后台上传一张背景图,在背景图上框选指定区域,配置对应的跳转链接或领券信息
  2. 小程序端判断用户点击位置是否在矩形框选范围内,如果在指定范围内,根据后台配置的功能进行页面跳转或领券。
    canvas绘制多个矩形实现热区图功能_第1张图片
    运营后台canvas绘制矩形组件
<template>
    <div class="img-intercept-content" :style="hotStyle">
        <div class="imgContainer" :style="hotStyle" ref="imgContainer">
            <canvas
                ref="refInterceptCanvas"
                class="canvasClass"
                :width="divWidth"
                :height="divHeight"
                @mousedown="canvasMouseDown"
                @mouseup="canvasMouseUp"
                @mousemove="canvasMouseMove"
                @mouseleave="canvasMouseLeave"
            ></canvas>
            <img
                :id="'image'"
                :src="imageSrc"
                ref="refInterceptImage"
                class="imgClass"
                @load="uploadImgLoad"
                v-show="false"
            />
        </div>
    </div>
</template>
<script>
export default {
    name: 'imageIntercept',
    data() {
        return {
            divWidth: 0,
            divHeight: 0,
            // canvas的配置部分
            canvasObj: '',
            cxt: '',
            canvasImg: '',
            imgWidth: 0, // img框的宽度
            imgHeight: 0, // img框的高度
            targetMarkIndex: -1, // 目标标注index
            params: {
                currentX: 0,
                currentY: 0,
                flag: false, // 用来判断在canvas上是否有鼠标down的事件,
                editFlag: false,
                editIndex: -1
            },
            // 目标类别list
            imageCategoryList: [],
            targetMarkArray: [],
            allPosition: [],
            hotStyle: {
                height: (window.innerHeight - 130 > 300 ? window.innerHeight - 130 : 400) + 'px',
                overflow: 'auto'
            },
            deleteSubIndex: -1,
            editSubIndex: -1
        }
    },
    props: {
        imageSrc: {
            type: String,
            default: ''
        },
        positionList: {
            type: Array,
            default: ()=>{
                return []
            }
        },
        change: {
            type: Function,
            default: ()=>{

            }
        },
        editHotNumber: {
            type: Number,
            default: -1
        }
    },
    computed: {},
    watch: {

    },
    mounted() {
        this.initCanvas()
    },
    methods: {
        // 初始化canvas
        initCanvas() {
            try {
                this.canvasObj = this.$refs.refInterceptCanvas;
                this.canvasImg = this.$refs.refInterceptImage;
                this.cxt = this.canvasObj.getContext('2d');
                this.divWidth = this.$refs.imgContainer.offsetWidth;
                this.divHeight = this.$refs.imgContainer.offsetHeight;
            } catch (err) {
                console.log(err);
            }
            try {
                this.canvasOnDraw(this.imgWidth, this.imgHeight);
            } catch (err) {
                console.log(err);
            }
        },
        // 鼠标down事件
        canvasMouseDown(e) {
            if (this.editHotNumber === -1){
                return
            }
            this.params.flag = true;
            if (!e) {
                e = window.event;
                // 防止IE文字选中
                this.$refs.refInterceptCanvas.onselectstart = function () {
                    return false;
                };
            }
            // 这里先判断一下,看是否是在有效数据,并且初始化参数
            if ((this.params.flag === true) && (this.params.editFlag === false)) {
                this.params.currentX = 0;
                this.params.currentY = 0;
                this.params.currentX = e.layerX;
                this.params.currentY = e.layerY;
                const dict1 = {
                    x1: this.params.currentX, // 开始的x坐标
                    y1: this.params.currentY, // 开始的y坐标
                    x2: this.params.currentX, // 结束的x坐标
                    y2: this.params.currentY, // 结束的y坐标
                    flag: false, // 图片区域是否高亮,
                    targetMarkValue: '', // 目标类别值
                    wid: 0, // 矩形宽度
                    hei: 0, // 矩形高度
                    // final 是最终的框选的坐标值
                    left: this.params.currentX,
                    top: this.params.currentY,
                    width: 0, // 矩形宽度
                    height: 0, // 矩形高度
                    finalX1: this.params.currentX,
                    finalY1: this.params.currentY,
                    finalX2: this.params.currentX,
                    finalY2: this.params.currentY
                };
                this.targetMarkIndex = this.targetMarkIndex + 1;
                if (this.editHotNumber !== -1) {
                    dict1.hotNumber = this.editHotNumber
                }
                this.targetMarkArray.push(dict1);
            }
            // 执行渲染操作
            try {
                this.canvasOnDraw(this.imgWidth, this.imgHeight);
            } catch (err) {
                console.log(err);
            }
        },
        canvasMouseUp(e) {
            if (this.editHotNumber === -1){
                return
            }

            this.params.flag = false;
            try {
                // 数据去重
                let oldDataLength = this.targetMarkArray.length
                this.targetMarkArray = this.uniqueArrByObj(this.targetMarkArray, 'hotNumber')
                if (oldDataLength !== this.targetMarkArray.length) {
                    this.refreshCanvas()
                    return
                }

                let isOriginalPoint = false
                this.targetMarkArray = this.targetMarkArray.filter((mark) => {
                    // 如果x1>x2,调换x1和x2的值
                    if (mark.x1 >= mark.x2) {
                        let x1 = mark.x1
                        mark.x1 = mark.x2
                        mark.x2 = x1
                    }
                    // 如果y1>y2,调换y1和y2的值
                    if (mark.y1 >= mark.y2) {
                        let y1 = mark.y1
                        mark.y1 = mark.y2
                        mark.y2 = y1
                    }
                    if (mark.x2 - mark.x1 < 5 || mark.y2 - mark.y1 < 5) {
                        isOriginalPoint = true
                        return false
                    } else {
                        return true
                    }
                })
                // 判断是否原点,宽高均为0或宽高小于5, 重绘
                if (isOriginalPoint) {
                    this.refreshCanvas()
                    return
                }

                // 矩形重叠判断
                let changeData = {
                    tempNumber: this.deleteSubIndex !== -1 ? this.deleteSubIndex : (this.editSubIndex !== -1 ? this.editSubIndex : this.targetMarkIndex),
                    position: {}
                }
                this.targetMarkArray.filter(row=>{
                    if (this.editHotNumber === row.hotNumber) {
                        changeData.position = row
                    }
                })
                // console.log('当前坐标:' + JSON.stringify(changeData.position))

                let repeatList = this.judgePositionRepeat(this.targetMarkArray)
                if ((!repeatList || !repeatList.length) && Object.keys(changeData.position).length) {
                    this.$emit('change', changeData)
                } else if (repeatList && repeatList.length) {
                    let repeatIndexMap = {}
                    repeatList.filter(row => {
                        let repeatIndex = null
                        Array.isArray(row) && row.filter((item) => {
                            this.targetMarkArray.filter((mark, markIndex) => {
                                if (item.x1 === mark.x1 && item.x2 === mark.x2 && item.y1 === mark.y1 && item.y2 === mark.y2) {
                                    repeatIndex = repeatIndex == null ? markIndex : (repeatIndex > markIndex ? repeatIndex : markIndex)
                                }
                            })
                        })
                        if (repeatIndex != null) {
                            repeatIndexMap[repeatIndex] = repeatIndex
                        }
                    })
                    this.targetMarkArray = this.targetMarkArray.filter((row, index) => {
                        if (index === repeatIndexMap[index]) {
                            return false
                        } else {
                            return true
                        }
                    })
                    this.refreshCanvas()
                    this.$message.error('选择的区域不能重叠')
                }
            } catch (err) {
                console.log(err);
            }
        },
        canvasMouseMove(e) {
            if (this.editHotNumber === -1){
                return
            }
            if (e === null) {
                e = window.event;
            }
            if ((this.params.flag === true) && (this.params.editFlag === false)) {
                this.params.currentX = e.layerX;
                this.params.currentY = e.layerY;
                this.targetMarkArray[this.targetMarkIndex].x2 = this.params.currentX; // x1 值
                this.targetMarkArray[this.targetMarkIndex].y2 = this.params.currentY; // y1 值
                this.targetMarkArray[this.targetMarkIndex].wid = this.params.currentX - this.targetMarkArray[this.targetMarkIndex].x1; // 宽度值
                this.targetMarkArray[this.targetMarkIndex].hei = this.params.currentY - this.targetMarkArray[this.targetMarkIndex].y1; // 高度
            }
            // 执行渲染操作
            try {
                this.canvasOnDraw(this.imgWidth, this.imgHeight);
            } catch (err) {
                console.log(err);
            }
        },
        canvasMouseLeave(e){
            this.canvasMouseUp(e)
        },
        uploadImgLoad(e) {
            try {
                this.imgWidth = e.path[0].naturalWidth;
                this.imgHeight = e.path[0].naturalHeight;
                let timeout = setTimeout(()=>{
                    this.positionListChange()
                    this.canvasOnDraw(this.imgWidth, this.imgHeight)
                    clearTimeout(timeout)
                }, 200)
                this.canvasOnDraw(this.imgWidth, this.imgHeight);
            } catch (err) {
                console.log(err);
            }
        },
        positionListChange () {
            let targetMarkArray = []
            if (Array.isArray(this.positionList)) {
                this.positionList.filter(row => {
                    if (row.position && Object.keys(row.position).length) {
                        if (row.hotNumber || row.hotNumber === 0) {
                            row.position.hotNumber = row.hotNumber
                        }
                        targetMarkArray.push(row.position)
                    }
                })
                this.targetMarkArray = targetMarkArray
                this.targetMarkIndex = this.targetMarkArray.length - 1
            }
        },
        // 输入两个坐标值,判断哪个坐标值离左上角最近,其中特殊情况需要进行坐标查找工作
        findWhichIsFirstPoint(x1, y1, x2, y2) {
            // 首先判断x轴的距离谁更近
            if (x1 <= x2) {
                // 说明x1 比较小,接下来判断y谁更近
                if (y1 <= y2) {
                    // 说明第一个坐标离得更近,直接顺序return就好
                    return [x1, y1, x2, y2];
                } else {
                    // 这里遇见一个奇葩问题,需要进行顶角变换
                    return [x1, y2, x2, y1];
                }
            } else {
                // 这里是x1 大于 x2 的情况
                if (y2 <= y1) {
                    return [x2, y2, x1, y1];
                } else {
                    // y2 大于 y1 的情况, 这里需要做顶角变换工作
                    return [x2, y1, x1, y2];
                }
            }
        },
        // canvas绘图部分
        canvasOnDraw(imgW = this.imgWidth, imgH = this.imgHeight) {
            if (!imgW) {
                return
            }
            const imgWidth = imgW;
            const imgHeight = imgH;
            this.divWidth = imgW;
            this.divHeight = imgH;
            this.cxt.clearRect(0, 0, this.canvasObj.width, this.canvasObj.height);
            // 当前的图片和现有的canvas容器之前的一个关系,是否有必要,我们后续做讨论
            var resPointList = this.changeOldPointToNewPoint(
                imgWidth,
                imgHeight,
                this.divWidth,
                this.divHeight
            );
            this.cxt.drawImage(
                this.canvasImg,
                0,
                0,
                imgWidth,
                imgHeight,
                0,
                0,
                resPointList[0],
                resPointList[1]
            );
            for (const index in this.targetMarkArray) {
                let markItem = this.targetMarkArray[index]
                const x1 = markItem.x1;
                const y1 = markItem.y1;
                const x2 = markItem.x2;
                const y2 = markItem.y2;
                const wid = markItem.wid;
                const hei = markItem.hei;
                const FinalPointList = this.findWhichIsFirstPoint(
                    (x1 * this.imgWidth) / resPointList[0],
                    (y1 * this.imgHeight) / resPointList[1],
                    (x2 * this.imgWidth) / resPointList[0],
                    (y2 * this.imgHeight) / resPointList[1]
                );
                markItem.finalX1 = FinalPointList[0];
                markItem.finalY1 = FinalPointList[1];
                markItem.finalX2 = FinalPointList[2];
                markItem.finalY2 = FinalPointList[3];
                // 必须要有的字段
                markItem.left = markItem.finalX1;
                markItem.top = markItem.finalY1;
                markItem.width = markItem.finalX2 - markItem.finalX1;
                markItem.height = markItem.finalY2 - markItem.finalY1;
                // 调整四个顶角的函数,为了能让整体框选区域更好看
                const FinalPointListNow = this.findWhichIsFirstPoint(
                    x1,
                    y1,
                    x2,
                    y2
                );
                const tmpX1 = FinalPointListNow[0];
                const tmpY1 = FinalPointListNow[1];
                const tmpX2 = FinalPointListNow[2];
                const tmpY2 = FinalPointListNow[3];
                this.cxt.strokeStyle = '#ff6600';
                this.cxt.strokeRect(tmpX1, tmpY1, tmpX2 - tmpX1, tmpY2 - tmpY1);
                this.cxt.fillStyle = 'red';
                this.cxt.font = '30px Arial bold';
                // canvas的标题部分
                let number = (markItem.hotNumber || markItem.hotNumber === 0) ? markItem.hotNumber : parseInt(index) + 1
                this.cxt.fillText(number, parseInt((tmpX1 + tmpX2) / 2) - 4, parseInt((tmpY1 + tmpY2) / 2) + 10);
                // 矩形背景色
                this.cxt.fillStyle = 'rgba(255, 0, 0, 0.1)';
                this.cxt.fillRect(tmpX1, tmpY1, wid, hei);
                // 说明被点击了
                this.canvasDrowBorder(
                    '#FF6600',
                    tmpX1,
                    tmpY1,
                    tmpX2 - tmpX1,
                    tmpY2 - tmpY1
                );
                this.canvasDrowInnerColor(
                    'rgba(255, 0, 0, 0.2)',
                    tmpX1,
                    tmpY1,
                    tmpX2 - tmpX1,
                    tmpY2 - tmpY1
                );
            }
        },
        // canvas框选区域的内容颜色
        canvasDrowInnerColor(color, x, y, w, h) {
            this.cxt.fillStyle = color;
            this.cxt.fillRect(x, y, w, h);
        },
        // canvas框选区域的边框颜色
        canvasDrowBorder(color, x, y, w, h) {
            this.cxt.strokeStyle = color;
            this.cxt.strokeRect(x, y, w, h);
        },
        // 尺寸变换函数
        changeOldPointToNewPoint(imgw, imgH, canvasW, canvasH) {
            // 这里有个要求,先以宽度为准,然后再一步步调整高度
            var tmpW = canvasW;
            var tmpH = (tmpW * imgH) / imgw;
            // 如果转换之后的高度正好小于框的高度,则直接进行显示
            if (tmpH <= canvasH) {
                // 尺寸完美匹配
                return [tmpW, tmpH];
            } else {
                // 高度超出框了,需要重新调整高度部分
                tmpW = canvasW;
                tmpH = (tmpW * imgH) / imgw;
                var count = 1;
                var raise = 0.05;
                while (tmpH > canvasH || tmpW > canvasW) {
                    tmpW = tmpW * (1 - raise * count);
                    tmpH = (tmpW * imgH) / imgw;
                }
                return [tmpW, tmpH];
            }
        },
        // 判断多个矩线框是否重叠
        judgePositionRepeat(layers) {
            let repeatList = [] // 重复矩形列表
            let formatLayers = [];
            for (let i = 0, j = layers.length; i < j; i++) {
                formatLayers.push({
                    // x 和 y 就是矩形框 左上角的位置的点
                    x: (layers[i].x1 <= layers[i].x2 ? layers[i].x1 : layers[i].x2) + layers[i].width / 2,
                    y: (layers[i].y1 <= layers[i].y2 ? layers[i].y1 : layers[i].y2) + layers[i].height / 2,
                    x1: layers[i].x1,
                    x2: layers[i].x2,
                    y1: layers[i].y1,
                    y2: layers[i].y2,
                    width: layers[i].width,
                    height: layers[i].height
                });
            }

            for (let i = 0; i < formatLayers.length; i++) {
                for (let j = i + 1; j < formatLayers.length; j++) {
                    if (i >= 0 && i < formatLayers.length - 1) {
                        if (intersects(formatLayers[i], formatLayers[j])) {
                            // 走到这里 就说明 已经有交叉的线框了, 你可以做错误的提示了
                            repeatList.push([formatLayers[i], formatLayers[j]])
                        }
                    }
                }
            }

            //判断是否有交叉的矩形框
            function intersects(first, second) {
                if (Math.abs(first.x - second.x) < first.width / 2 + second.width / 2 && Math.abs(first.y - second.y) < first
                    .height / 2 + second.height / 2) {
                    return true;
                }
                return false;
            }

            return repeatList
        },
        deleteIntercept(index, hotNumber){
            this.deleteSubIndex = index
            this.editSubIndex = -1
            if (!this.imgWidth || index === -1) {
                return
            }
            try {
                let editIdx = -1
                Array.isArray(this.targetMarkArray) && this.targetMarkArray.filter((row, rowIndex)=>{
                    if (hotNumber != undefined && hotNumber === row.hotNumber) {
                        editIdx = rowIndex
                    }
                })
                index = editIdx !== -1 ? editIdx : index

                if (this.targetMarkArray[index]) {
                    this.targetMarkArray.splice(index, 1)
                }
                // todo 加这行代码防止vue视图不更新
                this.targetMarkArray = JSON.parse(JSON.stringify(this.targetMarkArray))
                this.refreshCanvas()
            } catch (err) {
                console.log(err);
            }
        },
        editIntercept(index, hotNumber){
            this.deleteSubIndex = -1
            this.editSubIndex = index
            if (!this.imgWidth || index === -1) {
                return
            }
            // 重绘矩形
            try {
                let editIdx = -1
                Array.isArray(this.targetMarkArray) && this.targetMarkArray.filter((row, rowIndex)=>{
                    if (hotNumber != undefined && hotNumber === row.hotNumber) {
                        editIdx = rowIndex
                    }
                })
                let curMark = editIdx !== -1 ? this.targetMarkArray[editIdx] : {}
                if (curMark && (curMark.width || curMark.height)) {
                    this.targetMarkArray.splice(editIdx, 1)
                    // todo 加这行代码防止vue视图不更新
                    this.targetMarkArray = JSON.parse(JSON.stringify(this.targetMarkArray))
                    this.refreshCanvas()
                }
            } catch (err) {
                console.log(err);
            }
        },
        // 更新canvas视图
        refreshCanvas () {
            // 执行渲染操作
            try {
                this.targetMarkIndex = this.targetMarkArray.length - 1
                this.canvasOnDraw(this.imgWidth, this.imgHeight);
            } catch (err) {
                console.log('canvas绘制异常', err);
            }
        },
        /** 对象数组去重
         * @param {Object} oldArr 需要去重的数组
         * @param {String} key 需要去重的对象属性
         */
        uniqueArrByObj (oldArr, key) {
            var newArr = [];
            var obj = {};
            for(var i =0; i<oldArr.length; i++){
                if(!obj[oldArr[i][key]]){
                    newArr.push(oldArr[i]);
                    obj[oldArr[i][key]] = true;
                }
            }
            return newArr;
        }
    }
}
</script>
<style scoped lang="scss">
.img-intercept-content {
    width: 750px;
    min-width: 750px;
    max-width: 750px;
    min-height: 480px;
}

.imgContainer {
    position: relative;
    width: 750px;
    height: 480px;
}

.canvasClass {
    position: absolute;
    width: 750px;
    height: auto;
    //border: 1px solid #e4e4e4;
}

.imgClass {
    width: 750px;
    height: auto;
}

::-webkit-scrollbar {
    /*滚动条整体样式*/
    width : 8px;  /*高宽分别对应横竖滚动条的尺寸*/
    height: 0;
    border-radius: 4px;
}
::-webkit-scrollbar-thumb {
    /*滚动条里面小方块*/
    box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.07);
    background: #c1c1c1;
    border-radius: 4px;
}
::-webkit-scrollbar-track {
    /*滚动条里面轨道*/
    box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.07);
    background: #f1f1f1;
}
</style>

运营后台热区图组件

<template>
    <div>
        <div class="hot-zone-item-content">
            <div class="hot-card-title">
                <h4>热区</h4>
                <el-upload
                    v-if="hotItem.icon"
                    class="change-hot-img"
                    :action="imgAction"
                    :accept="accept"
                    :show-file-list="false"
                    :on-success="(res, file)=>{uploadImgSuccess(res, file, 'hot_list_icon')}"
                    :on-error="(res, file)=>{uploadImgError(res, file, 'hot_list_icon')}"
                    :before-upload="(file)=>{return beforeImgUpload(file, 'hot_list_icon')}">
                    <el-button type="primary">更换热区图片</el-button>
                </el-upload>
                <el-button
                    class="del-hot-btn"
                    type="danger"
                    @click="delItem()"
                >删除
                </el-button>
            </div>
            <el-form
                :model="hotItem"
                :ref="`refHotItem${refIndex}`"
                label-width="120px"
                label-position="right"
                class="hot-form-main"
                :validate-on-rule-change="false"
            >
                <div class="hot-form-content">
                    <div class="float-left hot-left">
                        <imageIntercept
                            :ref="`refImageIntercept${refIndex}`"
                            v-if="hotItem.icon"
                            :imageSrc="hotItem.icon"
                            :positionList="hotItem.subItems"
                            :editHotNumber="editHotNumber"
                            @change="(data)=>{changeHotSubItems(data)}"
                        />
                        <el-form-item
                            label="图片"
                            prop="icon"
                            :rules="{
                            required: true,
                            message: '请上传图片',
                            trigger: 'blur'
                        }"
                            v-if="!hotItem.icon"
                        >
                            <el-upload
                                class="hot-upload-btn"
                                :action="imgAction"
                                :accept="accept"
                                :show-file-list="false"
                                :on-success="(res, file)=>{uploadImgSuccess(res, file, 'hot_list_icon')}"
                                :on-error="(res, file)=>{uploadImgError(res, file, 'hot_list_icon')}"
                                :before-upload="(file)=>{return beforeImgUpload(file, 'hot_list_icon')}">
                                <img v-if="hotItem.icon" :src="hotItem.icon" class="hot-avatar"/>
                                <span v-if="hotItem.icon" class="content-img-close el-icon-circle-close"
                                      @click="hotItem.icon=''"></span>
                                <i v-else class="el-icon-plus avatar-uploader-icon"/>
                            </el-upload>
                            <div class="el-upload__tip">只能上传.jpg,.jpeg,.png,.gif文件,且不超过300kb</div>
                        </el-form-item>
                    </div>
                    <div class="float-left hot-right" :style="hotStyle">
                        <div v-for="(sub, index) in hotItem.subItems" :key="index">
                            <div>
                                <el-form-item
                                    label="热区编号"
                                    class="is-required"
                                    style="margin-bottom:30px;"
                                >
                                    <el-input
                                        class="hot-no-input"
                                        :disabled="true"
                                        v-model="sub.hotNumber"
                                    ></el-input>
                                    <label class="batch-no-oprbtn">
                                        <el-button type="text" class="color-blue" @click="editSubItem(index)">{{sub.position && Object.keys(sub.position).length ? '重绘矩形' : '编辑'}}</el-button>
                                        <el-button type="text" @click="addSubItem(index)">添加</el-button>
                                        <el-button v-if="index !== 0" type="text" class="color-red" @click="delSubItem(index)">删除</el-button>
                                    </label>
                                </el-form-item>
                                <el-form-item
                                    :label="`券批次号${batchIndex + 1}`"
                                    :prop="'subItems.' + index + '.hdActivityId'"
                                    :rules="{
                                    required: false,
                                    message: sub.url ? '请填写活动券码':' ',
                                    trigger: 'blur'
                                }"
                                    style="margin-bottom:30px;"
                                    v-for="(batch, batchIndex) in sub.hdActivityIdList"
                                    :key="batchIndex"
                                >
                                    <el-input
                                        class="batch-no-input"
                                        v-model="batch.hdActivityId"
                                        :disabled="!(!sub.url)"
                                        @blur="verifyCouponFun(index, batchIndex)"
                                        :placeholder="`请填写券批次号${batchIndex + 1}`"
                                    ></el-input>
                                    <label class="batch-no-oprbtn">
                                        <el-button v-if="sub.hdActivityIdList && sub.hdActivityIdList.length === (batchIndex + 1)"  type="text" @click="addBatch(index, batchIndex)">添加</el-button>
                                        <el-button v-if="sub.hdActivityIdList && sub.hdActivityIdList.length !== (batchIndex + 1)" type="text" class="del-btn" @click="delBatch(index, batchIndex)">删除</el-button>
                                    </label>
                                </el-form-item>
                                <div v-if="sub.showMaxQtyAndQuantity">
                                    <el-form-item
                                        :label="'活动优惠券总数'"
                                        :prop="'subItems.' + index + '.maxQty'"
                                        :rules="{
                                        required: sub.showMaxQtyAndQuantity || sub.getQuantity,
                                        trigger: 'blur',
                                        validator: maxQtyValidator
                                    }"
                                        style="margin-bottom:30px;"
                                    >
                                        <el-input
                                            v-model="sub.maxQty"
                                            :disabled="!(!sub.url)"
                                            type="text"
                                            :min="0"
                                            placeholder="请填写活动优惠券总数"
                                        ></el-input>
                                    </el-form-item>
                                    <el-form-item
                                        :label="'每人可领券数量'"
                                        :prop="'subItems.' + index + '.getQuantity'"
                                        :rules="{
                                        required: sub.showMaxQtyAndQuantity || sub.maxQty,
                                        trigger: 'blur',
                                        validator: getQuantityValidator
                                    }"
                                        style="margin-bottom:30px;"
                                    >
                                        <el-input
                                            v-model="sub.getQuantity"
                                            :disabled="!(!sub.url)"
                                            type="text"
                                            :min="0"
                                            placeholder="请填写每人可领券数量"
                                        ></el-input>
                                    </el-form-item>
                                </div>
                            </div>
                            <el-form-item
                                style="margin-bottom:30px;"
                                :label="'小程序内链接'"
                                :prop="'subItems.' + index + '.url'"
                                :rules="{
                                required: false,
                                message: sub.showMaxQtyAndQuantity ? '请填写链接' : ' ',
                                trigger: 'blur'
                            }"
                            >
                                <el-input
                                    v-model="sub.url"
                                    :disabled="!(!sub.showMaxQtyAndQuantity)"
                                    placeholder="请填写小程序内链接"
                                ></el-input>
                            </el-form-item>
                            <el-form-item
                                :label="'订阅消息'"
                                :prop="'subItems.' + index + '.wxTemplateIds'"
                                :rules="{
                                required: sub.mustSubscribe == 1,
                                validator: (_, value, callback)=>{testTemplateIds(index, value, callback)},
                                trigger: 'blur'
                            }"
                                style="margin-bottom:30px;"
                            >
                                <el-input
                                    v-model="sub.wxTemplateIds"
                                    placeholder="请填写模板ID,最多三个用英文逗号隔开"
                                ></el-input>
                            </el-form-item>
                            <el-form-item
                                :label="'订阅'"
                                :prop="'subItems.' + index + '.mustSubscribe'"
                                style="margin-bottom:30px;"
                            >
                                <el-checkbox v-model="sub.mustSubscribe" :true-label="1" :false-label="0" @change="changeMustSubscribe(index)">是否强制订阅</el-checkbox>
                            </el-form-item>
                        </div>
                    </div>
                </div>
            </el-form>
        </div>
    </div>
</template>

<script>
import imgUpload from "@/mixins/upload";
import RequestLoading from "@/utils/global-loading";
import {verifyBatchIds} from "@/api/couponActivitypage";
import imageIntercept from '@/components/image-intercept/image-intercept'

const loadingInstance = new RequestLoading();

function showLoading() {
    loadingInstance.showFullScreenLoading();
}

function hideLoading() {
    loadingInstance.hideFullScreenLoading();
}

let indexSubItems = {
    hotId: 1,
    hotNumber: 1,
    canvas: '',
    sort: 1,
    pit: 1,
    name: '',
    icon: '',
    url: '',
    hdActivityIdList: [{hdActivityId: ''}],
    hdActivityId: '',
    showMaxQtyAndQuantity: '',
    getQuantity: '',
    maxQty: '',
    wxTemplateIds: '',
    mustSubscribe: 0,
    position: {}
}
export default {
    name: 'hotZoneItem',
    mixins: [imgUpload],
    data() {
        return {
            hotItem: {},
            imgConfig: {
                // 没规定
                noRules: {
                    maxSize: 300
                },
                hot_list_icon: {
                    width: 750,
                    maxSize: 300
                }
            },
            accept: ".jpg,.jpeg,.png,.gif",
            imgAction: "/operation-service/upload/uploadPic",
            fileActicon: "/operation-service/operation/content/excel",
            loading: false,
            uploadLoading1: false,
            uploadLoading2: false,
            uploadLoading3: false,
            incidentNameLength: 50,
            colorNoLength: 30,
            moduleNameLength: 8,
            editHotNumber: -1,
            hotStyle: {
                height: (window.innerHeight - 130 > 300 ? window.innerHeight - 130 : 400) + 'px',
                overflow: 'auto'
            },
        };
    },
    props: {
        hotData: {
            type: Object,
            default: () => {
                return {}
            }
        },
        refIndex: {
            type: Number,
            default: 0
        },
    },
    components: {
        imageIntercept
    },
    watch: {
        'hotItem': {
            handler(val) {
                this.hotItem && Array.isArray(this.hotItem.subItems) && this.hotItem.subItems.filter(sub => {
                    let showMaxQtyAndQuantity = false
                    sub.hdActivityIdList && sub.hdActivityIdList.filter(batch => {
                        // 如果填写了券批次号,显示活动优惠券总数和每人可领券数量
                        if (batch.hdActivityId && String(batch.hdActivityId).trim()) {
                            showMaxQtyAndQuantity = true
                        }
                    })
                    sub.showMaxQtyAndQuantity = showMaxQtyAndQuantity
                })
            },
            deep: true
        },
        'hotData': {
            handler(val) {
                this.hotItem = JSON.parse(JSON.stringify(this.hotData))
            },
            deep: true
        }
    },
    mounted() {
        let dataList = JSON.parse(JSON.stringify(this.hotData))
        let subItems = dataList.subItems
        if(!subItems || (Array.isArray(subItems) && !subItems.length)) {
            subItems = [JSON.parse(JSON.stringify(indexSubItems))]
        } else if (subItems.length && (!subItems[0].hotNumber && subItems[0].hotNumber !== 0)){
            subItems[0].hotNumber = 1
        }
        this.hotItem = JSON.parse(JSON.stringify(dataList))
    },
    methods: {
        changeUrl(val) {
            this.hotItem.url
            this.$emit(this.hotItem, 'url', val)
        },
        delItem() {
            this.$emit('delItem')
        },
        changeHotSubItems({tempNumber, position}, index) {
            this.editHotNumber = -1
            let subItems = this.hotItem.subItems || []
            if (subItems[tempNumber]) {
                subItems[tempNumber].position = position
            } else {
                let subObj = JSON.parse(JSON.stringify(indexSubItems))
                subObj.tempNumber = tempNumber
                subObj.position = position
                subObj.sort = subItems.length + 1
                subItems.push(subObj)
            }
            this.$set(this.hotItem, 'subItems', subItems)
        },
        addSubItem(index) {
            let subItems = this.hotItem.subItems || []
            let subObj = JSON.parse(JSON.stringify(indexSubItems))
            subObj.sort = subItems.length + 1

            let copySubItems = JSON.parse(JSON.stringify(subItems))
            copySubItems.sort((a, b) => {
                return a.hotNumber - b.hotNumber;
            })
            subObj.hotNumber = copySubItems.length ? Number(copySubItems[copySubItems.length - 1].hotNumber) + 1 : subItems.length + 1
            subItems.push(subObj)
        },
        editSubItem(index) {
            let sub = this.hotItem.subItems[index]
            this.editHotNumber = sub.hotNumber
            if (sub.position && Object.keys(sub.position).length) {
                sub.position = {}
            }
            let refImageIntercept = this.$refs[`refImageIntercept${this.refIndex}`]
            refImageIntercept && refImageIntercept.editIntercept(index, sub.hotNumber)
        },
        delSubItem(index) {
            let sub = this.hotItem.subItems[index]
            this.editHotNumber = -1
            this.hotItem.subItems.splice(index, 1)
            let refImageIntercept = this.$refs[`refImageIntercept${this.refIndex}`]
            refImageIntercept && refImageIntercept.deleteIntercept(index, sub.hotNumber)
        },
        maxQtyValidator(_, val, cb) {
            if (!val) {
                return cb(new Error("请输入活动优惠券总数"))
            }
            let regPos = /^[1-9]+[0-9]*]*$/; // 正整数
            if (!regPos.test(val)) {
                return cb(new Error("只支持正整数"))
            }
            cb()
        },
        getQuantityValidator(_, val, cb) {
            if (!val) {
                return cb(new Error("请输入每人可领券数量"))
            }
            let regPos = /^[1-9]+[0-9]*]*$/; // 正整数
            if (!regPos.test(val)) {
                return cb(new Error("只支持正整数"))
            }
            cb()
        },
        changeMustSubscribe(index) {
            if (this.hotItem.subItems[index].mustSubscribe != 1) {
                this.$refs[`refHotItem${this.refIndex}`] && this.$refs[`refHotItem${this.refIndex}`].clearValidate()
            }
        },
        testTemplateIds(index, value, callback) {
            if (value && value.match(/[^\x00-\xff]/ig)) {
                return callback(new Error("模板ID只允许输入英文逗号隔开"));
            } else if (this.hotItem.subItems[index].mustSubscribe == 1 && !value) {
                return callback(new Error("请填写模板ID"));
            } else {
                callback();
            }
        },
        // 校验券批次号是否有用
        verifyCouponFun(index, batchIndex) {
            let subObj = this.hotItem.subItems[index]
            let batchObj = subObj.hdActivityIdList[batchIndex]
            let hdActivityId = batchObj.hdActivityId
            if (hdActivityId) {
                verifyBatchIds({batchIds: [hdActivityId]}).then(res => {
                    if (typeof res.data === 'object' && Object.keys(res.data).length) {
                        this.$message.error(res.data[hdActivityId] ? res.data[hdActivityId] : '券批次号有误')
                        subObj.hdActivityId = ''
                        batchObj.hdActivityId = ''
                        this.$forceUpdate()
                    }
                }).catch(() => {
                    // this.$message.error('券批次号有误')
                    subObj.hdActivityId = ''
                    batchObj.hdActivityId = ''
                    this.$forceUpdate()
                })
            }
        },
        // 添加券批次号
        addBatch(index, batchIndex) {
            this.hotItem.subItems[index].hdActivityIdList.push({
                hdActivityId: ''
            })
        },
        // 删除券批次号
        delBatch(index, batchIndex) {
            let hdActivityIdList = this.hotItem.subItems[index].hdActivityIdList
            if (hdActivityIdList && hdActivityIdList.length === 1) {
                return
            }
            hdActivityIdList.splice(batchIndex, 1)
        },
        // 图片上传前
        beforeImgUpload(file, type) {
            let imgAccept = ['image/jpeg', 'image/png', 'image/gif']
            if (!file.type || (file.type && imgAccept.indexOf(file.type) === -1)) {
                this.$message.warning('请上传.jpg,.jpeg,.png,.gif等格式的图片')
                return false
            }
            let value = this.beforeUploadCheck(file, this.imgConfig[type] || this.imgConfig.noRules);
            if (type && type === 'hot_list_icon') {
                this.$set(this.hotItem, 'uploadLoading', value)
            }
            return value
        },
        // 上传图片成功
        uploadImgSuccess(res, file, type, rowIndex) {
            if (type && type === 'hot_list_icon') {
                this.$set(this.hotItem, 'uploadLoading', false)
            }
            if (res.code === 200 && res.data && res.data.pictUrl) {
                let url = res.data.pictUrl;
                if (type && type === 'hot_list_icon') {
                    if (this.hotItem.icon) {
                        this.initHotData()
                    }
                    this.hotItem.icon = url
                }
            } else {
                this.$message.error(res.msg);
            }
        },
        // 上传失败
        uploadImgError(res, file, type, rowIndex) {
            if (type && type === 'list_icon') {
                this.$set(this.hotItem, 'uploadLoading', false)
            }
        },

        initHotData(){
            Array.isArray(this.hotItem.subItems) && this.hotItem.subItems.filter(row => {
                row.position = {}
                row.canvas = ''
            })
            let refImageIntercept = this.$refs[`refImageIntercept${this.refIndex}`] || {}
            refImageIntercept.targetMarkArray = []
            refImageIntercept.targetMarkIndex = -1
            refImageIntercept.deleteSubIndex = -1
            refImageIntercept.editSubIndex = -1
            refImageIntercept.editHotNumber = -1
        }
    }
};
</script>

<style lang="scss" scoped>
.batch-no-input {
    width: calc(100% - 70px);
}

.batch-no-oprbtn {
    button{
        margin-left: 15px;
    }
    .del-btn {
        color: red;
    }
}

.float-left {
    float: left;
}

.hot-zone-item-content {
    background: #ffffff;

    .hot-card-title {
        height: 50px;
        line-height: 50px;
        border-bottom: 1px solid rgb(220, 223, 230);
        background: #ffffff;
        z-index: 1;
        h4 {
            padding: 0;
            margin: 0;
            display: inline-block;
            line-height: 40px;
            font-size: 18px;
            float: left;
        }

        .change-hot-img {
            display: inline-block;
            position: absolute;
            top: 10px;
            right: 110px;
        }

        .del-hot-btn {
            display: inline-block;
            position: absolute;
            top: 15px;
            right: 20px;
        }
    }

    .hot-form-content {
        margin: 20px 0 0 0;
        width: 100%;
        min-width: 1185px;
        background: #ffffff;
        overflow: auto;
        .hot-right {
            /deep/ .el-form-item__content{
                width: calc(100vw - 1310px);
                min-width: 250px;
            }
        }
    }
    .hot-left {

    }
    .hot-right {
        padding: 0 20px;
    }


    .hot-upload-btn {
        font-size: 28px;
        font-weight: 500;
        color: #ccc;
        width: 250px;
        height: 250px;
        border: 1px dashed #999;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        margin-right: 20px;
        position: relative;

        .avatar-uploader-icon {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .content-img-close {
            position: absolute;
            top: -1px;
            right: -1px;
            font-size: 24px;
            cursor: pointer;
            color: #666666;
        }
    }

    .hot-avatar {
        width: 130px;
        height: auto;
        max-height: 130px;
    }

    .hot-no-input {
        width: 60px;
        text-align: center;
    }

    .color-blue {
        color: #2d8cf0;
    }

    .color-red {
        color: red;
    }
}

::-webkit-scrollbar {
    /*滚动条整体样式*/
    width : 8px;  /*高宽分别对应横竖滚动条的尺寸*/
    height: 8px;
    border-radius: 4px;
}
::-webkit-scrollbar-thumb {
    /*滚动条里面小方块*/
    box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.07);
    background: #c1c1c1;
    border-radius: 4px;
}
::-webkit-scrollbar-track {
    /*滚动条里面轨道*/
    box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.07);
    background: #f1f1f1;
}
</style>

小程序端热区图组件

import Taro, {getCurrentInstance} from '@tarojs/taro'
import React, {Component} from 'react'
import {View, Image} from '@tarojs/components'
import {clearStorageBy401, loginLoading, needRegisterByUrl, newLogin, safeNavigateTo} from '@/utils/shop_utils/util'
import {godPolicyBuriedPoint, getCouponTypeZh} from '@/utils/shop_utils/godpolicy'
import LoginBtn from "@/components/store_components/loginBtn/index"
import {baseUrl} from "@/utils/conf.js";
import classnames from "classnames";
import request from "@/utils/request";
import {getJsonHead} from "@/utils/header";
import {tip} from "@/utils/util";
import {statReport} from "@/utils/report";
import SubscribePopup from '@/components/store_components/SubscribePopup/index'
import checkAddGroup from '@/utils/shop_utils/checkAddGroup'
import {globalNearestStore} from '@/utils/global-data/store-address/index';
import {sleep} from "@/utils/common/time/sleep";
import './hotZone.scss'

export default class HotZone extends Component {
  static defaultProps = {
    userInfo: Taro.getStorageSync('memberInfo') || {},
    couponInfo: {},
    imgIndex: '0',
    hotSource: '首页',
    activityId: '',
    receiveCouponSuccess: () => {
    },
    updateUserInfo: () => {
    }
  }

  state = {
    imageRect: {},
    getImageRectError: false,
    rpxRate: 2,
    isAcquire: false,
    isReject: false,
    showSubscribe: false,
    subscribeData: {},
    newHotItem: {},
    hotClickData: {}
  }

  componentDidMount() {
    let sysinfo = Taro.getSystemInfoSync()
    sysinfo = sysinfo || {}
    sysinfo.screenWidth = sysinfo.screenWidth || 375
    let rpxRate = 750 / sysinfo.screenWidth
    this.setState({
      rpxRate: rpxRate
    })
  }

  componentWillReceiveProps() {
    // 如果图片宽高获取失败,重新获取
    if (this.state.getImageRectError) {
      this.setImageRect(this.props.imgIndex)
    }
  }

  onLogined() {
    this.props.updateUserInfo('hotZone', this.state.hotClickData)
  }

  setImageRect(imgIndex, e){
    this[`hotImg${imgIndex}Timeout`] = setTimeout(() => {
      let query = Taro.createSelectorQuery();
      let that = this;
      let classname = '.hot-img-' + imgIndex
      query.select(classname).boundingClientRect(function (rect) {
        // console.log('热区图rect:', rect)
        if (!rect) {
          that.setState({
            getImageRectError: true
          })
          return
        }
        that.setState({
          imageRect: rect,
          getImageRectError: false
        })
      }).exec();
      clearTimeout(this[`hotImg${imgIndex}Timeout`])
    }, 150)
  }

  imageLoad = (imgIndex, e) => {
    this.setImageRect(imgIndex, e)
  }

  acquireCouponIndex(hdActivityId, id) {
    if (this.state.isAcquire) {
      this.closeTabLoading()
      return
    }
    this.setState.isAcquire = true
    let {userInfo} = this.props
    let memberInfo = userInfo && (userInfo.phone || userInfo.mobile) ? userInfo : (Taro.getStorageSync('memberInfo') || {})
    let data = {
      id: id,
      hdActivityId: hdActivityId,
      // storeId: globalNearestStore.storeId,
      // memberPhone: memberInfo.phone || memberInfo.mobile || '',
      // openId: memberInfo.openid || memberInfo.openId || ''
    }
    request(
      {
        url: baseUrl + `/member-service/coupon/activity/getHomePageCouponLocation`,
        method: 'POST',
        header: {
          ...getJsonHead(),
          "through": "true"
        },
        data: data
      },
      {outDate: false, showLoad: true}
    )
      .then((res) => {
        res = Array.isArray(res) ? res : []
        res.forEach(item => {
          item = item || {}
          godPolicyBuriedPoint('getCoupon', {
            coupon_id: item.batchId,
            coupon_name: item.couponName,
            coupon_amount: item.faceValue,
            coupon_type: getCouponTypeZh(item.couponType)
          })
        })
        this.setState.isAcquire = false
        this.props.receiveCouponSuccess()
        Taro.showModal({
          title: '',
          content: '你已经领取成功,现在就去购物吧',
          confirmText: '立即使用',
          success: (modalRes) => {
            if (modalRes.confirm) {
              Taro.navigateTo({
                url: '/pages/couponList/couponList'
              })
            } else if (modalRes.cancel) {
              console.log('用户点击取消')
            }
            this.closeTabLoading()
          }
        })
      })
      .catch((err) => {
        this.setState.isAcquire = false
        this.closeTabLoading()
        if (err.code == 401) {
          clearStorageBy401()
        } else {
          tip(err.message || '领券失败')
        }
      })
  }

  async acquireCouponGetCoupons(hdActivityId, id) {
    if (this.state.isAcquire) {
      this.closeTabLoading()
      return
    }
    let {userInfo, activityId} = this.props
    const isAddGroup = await checkAddGroup(getCurrentInstance().router.params.id)
    if(!isAddGroup){
      this.tabLoading = false;
      return Taro.showToast({
        title: '您还没有进群哦,请进群后再来领取优惠券吧',
        icon: 'none'
      })
    }
    this.setState.isAcquire = true
    let memberInfo = userInfo && (userInfo.phone || userInfo.mobile) ? userInfo : (Taro.getStorageSync('memberInfo') || {})
    let locationAddress = Taro.getStorageSync('locationAddress') || {}
    let data = {
      id: activityId || getCurrentInstance().router.params.id,
      hdActivityId: hdActivityId,
      detailId: id,
      storeId: globalNearestStore.storeId,
      memberPhone: memberInfo.phone || memberInfo.mobile || '',
      openId: memberInfo.openid || memberInfo.openId || '',
      longitude: locationAddress.longitude || '',
      latitude: locationAddress.latitude || ''
    }
    request(
      {
        url: baseUrl + `/member-service/coupon/activity/getCouponLocation`,
        method: 'POST',
        header: {
          ...getJsonHead(),
          "through": "true"
        },
        data: data
      },
      {outDate: false, showLoad: true}
    )
      .then((res) => {
        res = Array.isArray(res) ? res : []
        res.forEach(item => {
          item = item || {}
          godPolicyBuriedPoint('getCoupon', {
            coupon_id: item.batchId,
            coupon_name: item.couponName,
            coupon_amount: item.faceValue,
            coupon_type: getCouponTypeZh(item.couponType)
          })
        })
        this.setState.isAcquire = false
        Taro.showModal({
          title: '',
          content: '你已经领取成功,现在就去购物吧',
          confirmText: '立即使用',
          success: (modalRes) => {
            if (modalRes.confirm) {
              Taro.switchTab({
                url: '/shop/index/index'
              })
            } else if (modalRes.cancel) {
              console.log('用户点击取消')
            }
            this.closeTabLoading()
          }
        })
      })
      .catch((err) => {
        this.setState.isAcquire = false
        this.closeTabLoading()
        if (err.code == 401) {
          clearStorageBy401()
        } else {
          tip(err.message || '领券失败')
        }
      })
  }

  showSubscribe = () => {
    this.closeTabLoading()
    this.setState({
      showSubscribe: true
    })
  }

  // 最外层热区组件点击事件,防止未注册时热区点击事件不触发获取不到点击位置
  async hotZoneClick(item, imgIndex, e) {
    let newItem = {}
    if (!this.dealHasTempIds(item, imgIndex, e)) { // 没有消息模板的情况下直接获取图片信息,防止计算的点击位置不精准
      let rect = await this.getRectPromise(imgIndex)
      if (rect && rect !== 'fail') {
        newItem = this.getClickPosition(item, imgIndex, e, rect)
      } else {
        newItem = this.getClickPosition(item, imgIndex, e)
      }
    } else { // 有消息模板的情况下,不能直接获取图片信息,小程序createSelectorQuery方法有延迟,会导致之后调用订阅相关接口报错(微信订阅相关接口需要tab点击事件触发后立马调用)。
      newItem = this.getClickPosition(item, imgIndex, e)
    }
    this.setState({
      hotClickData: {item, imgIndex, e, newItem}
    })
  }

  async hotImgClick(item, imgIndex, e) {
    if (!this.dealHasTempIds(item, imgIndex, e)) { // 没有消息模板的情况下直接获取图片信息,防止计算的点击位置不精准
      let rect = await this.getRectPromise(imgIndex)
      if (rect && rect !== 'fail') {
        this.addSubscribe(item, imgIndex, e, rect)
      } else {
        this.addSubscribe(item, imgIndex, e)
      }
    } else { // 有消息模板的情况下,不能直接获取图片信息,小程序createSelectorQuery方法有延迟,会导致之后调用订阅相关接口报错(微信订阅相关接口需要tab点击事件触发后立马调用)。
      this.addSubscribe(item, imgIndex, e)
    }
  }

  // 判断是否有消息订阅模板
  dealHasTempIds(item, imgIndex, e) {
    let {rpxRate} = this.state
    let tabPosition = e && e.touches && e.touches.length ? JSON.parse(JSON.stringify(e.touches[0])) : {}
    tabPosition.x = tabPosition.clientX * rpxRate
    let hasTempIds = false // 判断是否有订阅模板
    let hasCanvas = false
    if (Array.isArray(item.subItems) && item.subItems.length) {
      item.subItems.filter(row => {
        let canvas = {}
        try {
          canvas = typeof row.canvas === 'string' && row.canvas ? JSON.parse(row.canvas) : {}
        } catch (err) {
        }
        // 如果x1>x2,调换x1和x2的值
        if (canvas.x1 >= canvas.x2) {
          let x1 = canvas.x1
          canvas.x1 = canvas.x2
          canvas.x2 = x1
        }
        if (tabPosition.x >= canvas.x1 && tabPosition.x <= canvas.x2) {
          hasCanvas = true
          hasTempIds = !!row.wxTemplateIds ? true : hasTempIds
        }
      })
    }
    return hasTempIds = !hasCanvas && item.wxTemplateIds ? true : hasTempIds
  }

  // 计算点击位置
  getClickPosition(item, imgIndex, e, rect){
    let newItem = {}
    let imageRect = rect || this.state.imageRect
    let tabPosition = {}
    if (e) {
      let {rpxRate} = this.state
      console.log('图片信息:', imageRect)
      // if (rect) {
      tabPosition = e.touches && e.touches.length ? JSON.parse(JSON.stringify(e.touches[0])) : {}
      console.log('原先点击位置:', e)
      tabPosition.y = tabPosition.clientY - imageRect.top
      tabPosition.x = tabPosition.clientX * rpxRate
      tabPosition.y = Math.abs(tabPosition.y * rpxRate)
      // } else {
      //   tabPosition = e.detail ? JSON.parse(JSON.stringify(e.detail)) : {}
      //   console.log('原先点击位置:', e)
      //   tabPosition.y = tabPosition.y - imageRect.top
      //   tabPosition.x = tabPosition.x * rpxRate
      //   tabPosition.y = Math.abs(tabPosition.y * rpxRate)
      // }
      console.log('计算后的点击位置:', tabPosition)
      let hasCanvas = false
      // 根据点击位置获取跳转路径,如果全部的canvas为空,拿默认路径
      if (Array.isArray(item.subItems) && item.subItems.length) {
        let newSubItems = JSON.parse(JSON.stringify(item.subItems))
        item.subItems.filter(row => {
          let canvas = {}
          try {
            canvas = typeof row.canvas === 'string' && row.canvas ? JSON.parse(row.canvas) : {}
            if (Object.keys(canvas).length) {
              hasCanvas = true
            }
          } catch (err) {
          }
          // 如果x1>x2,调换x1和x2的值
          if (canvas.x1 >= canvas.x2) {
            let x1 = canvas.x1
            canvas.x1 = canvas.x2
            canvas.x2 = x1
          }
          // 如果y1>y2,调换y1和y2的值
          if (canvas.y1 >= canvas.y2) {
            let y1 = canvas.y1
            canvas.y1 = canvas.y2
            canvas.y2 = y1
          }
          if (tabPosition.x >= canvas.x1 && tabPosition.x <= canvas.x2
            && tabPosition.y >= canvas.y1 && tabPosition.y <= canvas.y2) {
            newItem = JSON.parse(JSON.stringify(row))
            newItem.subItems = newSubItems
          }
        })
        console.log('点击位置Item:', newItem)
      }

      // 如果newItem为空对象且canvas也是空对象(图片未框选矩形),默认取第一个对象的值
      if (!Object.keys(newItem).length && !hasCanvas) {
        newItem = JSON.parse(JSON.stringify(item))
      }
    } else {
      newItem = this.state.newHotItem
    }
    return newItem
  }

  async addSubscribe(item, imgIndex, e, rect) {
    if (this.tabLoading) {
      return false
    }
    this.tabLoading = true
    let newItem = this.getClickPosition(item, imgIndex, e, rect)
    this.setState({
      subscribeData: newItem,
      showSubscribe: false,
      newHotItem: newItem
    })
    /**
     * wxTemplateIds:消息id
     * mustSubscribe:是否强制
     */
    let {wxTemplateIds, mustSubscribe, id} = newItem
    if (wxTemplateIds) {
      const tempIds = wxTemplateIds.split(',')
      if (this.props.registered == 0) {
        return false
      }
      const hasItemSettings = await this.checkOpenSetting(
        tempIds,
        mustSubscribe
      )
      Taro.requestSubscribeMessage({
        tmplIds: tempIds
      })
        .then(async (res) => {
          console.log('【requestSubscribeMessage】:', res)
          const filterTmplId = tempIds.filter((v) => res[v] === 'accept')
          if (res.errMsg && res.errMsg === 'requestSubscribeMessage:ok' && filterTmplId && filterTmplId.length) {
            // 非强制||强制订阅
            const result = await this.subscribeSend(tempIds, id)
            const settings = await this.checkOpenSetting(tempIds)
            console.log(settings, 'settings')
            if (
              result === 'success' &&
              (settings === 'fail' ||
                hasItemSettings === 'fail')
            ) {
              // tip('订阅成功')
              // await sleep(2500)
            }
            this.handleForward(newItem)
          } else {
            // 拒绝条件
            if (mustSubscribe === 0) {
              this.handleForward(newItem)
            } else {
              this.toOpenSetting(tempIds)
            }
          }
        })
        .catch((e) => {
          console.log('【requestSubscribeMessage】fail:', e)
          if (mustSubscribe === 1) {
            if (e && e.errCode && e.errCode == 20001) {
              tip('订阅失败,模板ID无效')
              this.closeTabLoading()
              return
            }
            tip('订阅消息内容获取异常,请检查是否已打开接收开关')
            this.showSubscribe()
          } else {
            this.handleForward(newItem)
          }
        })
    } else {
      console.log('不需要订阅')
      this.handleForward(newItem)
    }
  }

  getRectPromise(imgIndex) {
    return new Promise((resolve) => {
      let query = Taro.createSelectorQuery();
      let classname = '.hot-img-' + imgIndex
      query.select(classname).boundingClientRect((rect) => {
        if (!rect) {
          this.closeTabLoading()
          return resolve('fail')
        } else {
          return resolve(rect)
        }
      }).exec();
    })
  }

  checkOpenSetting(tempIds, mustSubscribe) {
    return new Promise((resolve) => {
      Taro.getSetting({
        withSubscriptions: true,
        success: (settings) => {
          const subSet = settings.subscriptionsSetting
          console.log('checkopen settings', settings)
          if (subSet) {
            const itemSettings = subSet.itemSettings || {}
            let hasSettings = true
            if (mustSubscribe === 1) {
              hasSettings = tempIds.every((v) => itemSettings[v] === 'accept')
            } else {
              hasSettings = tempIds.some((v) => itemSettings[v] === 'accept')
            }

            console.log('itemSettings', itemSettings, hasSettings)
            return resolve(hasSettings ? 'success' : 'fail')
          } else {
            return resolve('fail')
          }
        }
      })
    })
  }

  toOpenSetting(tempIds) {
    // 获取小程序设置
    const tipsText = '必须全部勾选订阅才能继续操作'
    this.setState({
      isReject: false
    })
    Taro.getSetting({
      withSubscriptions: true,
      success: (res1) => {
        const subSet = res1.subscriptionsSetting
        console.log('【getSetting】:', res1)
        if (subSet) {
          const itemSettings = subSet.itemSettings || {}
          const rejectTmplId = tempIds.filter(
            (v) => itemSettings[v] === undefined
          )
          // 没有勾选不再询问,点取消或者不勾全
          if (rejectTmplId.length > 0) {
            this.showSubscribe()
            this.setState({
              isReject: true
            })
            return false
          }
          if (!subSet.itemSettings || !subSet.mainSwitch) {
            this.showSubscribe()
            tip(tipsText)
          } else {
            const hasSettings = tempIds.every(
              (v) => itemSettings[v] === 'accept'
            )
            if (!hasSettings) {
              this.showSubscribe()
              tip(tipsText)
            } else {
              // 防止:当模版标题相同requestSubscribeMessage返回filter时
              this.subscribeSend(tempIds)
            }
          }
        }
        this.closeTabLoading()
      },
      fail: () => {
        this.closeTabLoading()
      }
    })
  }

  handleForward(item) {
    // 神策埋点-资源位点击
    if((item.url || item.hdActivityId) && this.props.hotSource == '首页' ){
      godPolicyBuriedPoint('rcesourceClick',{
        hot_zone_number : item.hotNumber,
        Jump_the_link : item.url,
        coupon_lot_number : item.hdActivityId,
        mkt_name : this.props.incidentName,
        mkt_type : '热区'
      })
    }
    // 跳转还是领券判断
    let {hdActivityId, id} = item
    if (hdActivityId) {
      if (this.props.hotSource === '自定义页面') {
        this.acquireCouponGetCoupons(hdActivityId, id)
      } else {
        this.acquireCouponIndex(hdActivityId, id)
      }
    } else {
      this.onJump(item.url, item)
    }
  }

  subscribeSend(ids, activityDetailId) {
    return new Promise((resolve) => {
      const {userInfo} = this.props
      const data = {
        mobile: userInfo.mobile,
        templateIds: ids
      }
      console.log('【/member-service/subscribe/add】data:', data)
      request({
        url: `${baseUrl}/member-service/subscribe/add`,
        method: 'POST',
        header: getJsonHead(),
        data
      }).then((res) => {
        const subData = {
          activityDetailId: activityDetailId,
          phone: userInfo.mobile,
          templateIds: ids
        }
        this.activitySubscribe(subData)
        this.closeTabLoading()
        return resolve('success')
      }).catch(()=>{
        this.closeTabLoading()
      })
    })
  }

  activitySubscribe(data) {
    request({
      url: `${baseUrl}/member-service/wx/subscribe/locations?codes=8`,
      method: 'POST',
      header: getJsonHead(),
      data
    }).then((res) => {

    })
  }

  onJump = (url, item) => {  //地址跳转
    let registered = Taro.getStorageSync('registered')
    if (registered == undefined || registered === '') {
      if (loginLoading) {
        Taro.showToast({
          title: '登录中,请稍等...',
          icon: 'none',
          duration: 1500
        })
        this.closeTabLoading()
      } else {
        newLogin().then(res => {
          this.onJump(url)
        }).catch(() => {
          this.closeTabLoading()
        })
      }
      return
    }
    // 根据跳转的页面路径判断是否需要跳转注册页
    if (needRegisterByUrl(url)) {
      this.closeTabLoading()
      return
    }
    if (getCurrentInstance().router.path.indexOf('shop/index/index') !== -1 && item) {
      statReport({url: 'wowcolour_10008.txt', immediate: true}, {
        report_name: '首页-自定义模块',
        active_id: item && item.id ? item.id : '',
      })
    }
    if (url && url.indexOf('pages/goodsDetail/goodsDetail') !== -1 && item) { // 神策埋点-存储首页资源位
      Taro.setStorageSync('indexModuleData', {
        moduleName: '热区',
        hot_zone_number: item.hotNumber,
        Jump_the_link: item.url
      })
    }
    this.closeTabLoading()
    safeNavigateTo(url)
  };

  closeTabLoading(time) {
    sleep(typeof time === "number" ? time : 500).then(() => {
      this.tabLoading = false
    })
  }

  render() {
    const {registered, couponInfo, imgIndex} = this.props
    return (
      <View className='hot-img-view' onClick={this.hotZoneClick.bind(this, couponInfo, imgIndex)}>
        <LoginBtn
          customName='auth-btn'
          registered={registered}
          onLogined={this.onLogined.bind(this)}
        >
          <Image
            className={classnames('hot-coupon-img', 'hot-img-' + imgIndex)}
            mode='widthFix'
            onClick={this.hotImgClick.bind(this, couponInfo, imgIndex)}
            onLoad={this.imageLoad.bind(this, imgIndex)}
            src={couponInfo.icon || couponInfo.url[0]}
          />
        </LoginBtn>
        <SubscribePopup
          show={this.state.showSubscribe}
          clickPic={this.addSubscribe.bind(this, this.state.subscribeData)}
          reject={this.state.isReject}
        />
      </View>
    )
  }

}

你可能感兴趣的:(小程序,js,javascript,前端,javascript)