| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- <template>
- <div>
- <div @click="show">
- <slot>
- <div v-if="value.heatMapData.length" class="cursor-pointer">
- 已选<span class="text-primary p-[4px]">{{ value.heatMapData.length }}</span
- >个热区
- </div>
- <div v-else class="cursor-pointer">添加热区</div>
- </slot>
- </div>
- <el-dialog v-model="showDialog" title="热区设置" width="810px" :destroy-on-close="true" :close-on-click-modal="false">
- <div class="flex">
- <div
- class="hot-area-img-wrap content-box relative bg-gray-100 border border-dashed border-gray-500 bg-no-repeat"
- :style="{ backgroundImage: 'url(' + img(value.imageUrl) + ')', width: contentBoxWidth + 'px', height: contentBoxHeight + 'px' }"
- >
- <div
- v-for="(item, index) in dragBoxArr"
- :id="'box_' + index"
- :key="index"
- class="area-box border border-solid border-[#ccc] w-[100px] h-[100px] absolute top-0 left-0 select-none p-[5px]"
- :style="{ left: item.left + item.unit, top: item.top + item.unit, width: item.width + item.unit, height: item.height + item.unit }"
- @mousedown="mouseDown($event, index)"
- >
- <span>{{ Number(index) + 1 }}</span>
- <!-- <template v-if="item.link">
- <span class="p-[4px]">|</span>
- <span>{{ item.link }}</span>
- </template> -->
- <span class="box1" @mousedown.stop="resizeMouseDown($event, index)"></span>
- <span class="box2" @mousedown.stop="resizeMouseDown($event, index)"></span>
- <span class="box3" @mousedown.stop="resizeMouseDown($event, index)"></span>
- <span class="box4" @mousedown.stop="resizeMouseDown($event, index)"></span>
- </div>
- </div>
- <el-form label-width="80px" class="pl-[20px]">
- <h3 class="mb-[10px] text-lg text-black">热区管理</h3>
- <el-button type="primary" plain size="small" class="mb-[10px]" @click="addArea">添加热区</el-button>
- <div class="overflow-y-auto h-[300px]">
- <template v-for="(item, index) in dragBoxArr" :key="index">
- <div class="mb-[16px]" v-if="item">
- <el-form-item :label="'热区' + (Number(index) + 1)">
- <div class="flex items-center">
- <div style="width: 230px">
- <WebLinkInput v-model="item.link" placeholder="请输入或选择链接" />
- </div>
- <icon
- class="del cursor-pointer mx-[10px]"
- name="element CircleCloseFilled"
- color="#bbb"
- size="20px"
- @click="dragBoxArr.splice(index, 1)"
- />
- </div>
- </el-form-item>
- </div>
- </template>
- </div>
- </el-form>
- </div>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="showDialog = false">返回</el-button>
- <el-button type="primary" @click="save">确定</el-button>
- </span>
- </template>
- </el-dialog>
- </div>
- </template>
- <script lang="ts" setup>
- import { ref, reactive, computed } from 'vue';
- import { ElMessage } from 'element-plus';
- import { img, deepClone } from '@/utils/common';
- const prop = defineProps({
- modelValue: {
- type: Object as () => Record<string, any>,
- default: () => {}
- }
- });
- const emit = defineEmits(['update:modelValue']);
- const value: any = computed({
- get() {
- return prop.modelValue;
- },
- set(value) {
- emit('update:modelValue', value);
- }
- });
- /**
- * 公式:
- * 宽度:400
- * 比例:原图高/原图宽,示例:466/698=0.66
- * 高度:宽度*比例,示例:400*0.66=264
- */
- const showDialog = ref(false);
- const contentBoxWidth = ref(400);
- const contentBoxHeight = ref(400);
- const dragBoxArr: any = reactive([]);
- const imgRatio = ref(1); // 图片比例
- // 热区尺寸
- const areaRadio = ref(0.25); // 占位图比例
- const areaWidth = ref(100);
- const areaHeight = ref(100);
- const areaNum = ref(4); // 每行显示的数量
- // 添加热区
- const addArea = () => {
- let left = (dragBoxArr.length % areaNum.value) * areaWidth.value;
- let top = Math.floor(dragBoxArr.length / areaNum.value) * areaHeight.value;
- const edgeHeight = top + areaHeight.value / 2;
- if (top >= contentBoxHeight.value || edgeHeight >= contentBoxHeight.value) {
- top = 0;
- left = 0;
- }
- dragBoxArr.push({
- left,
- top,
- width: areaWidth.value,
- height: areaHeight.value,
- unit: 'px',
- link: ''
- });
- };
- // 移动事件
- const mouseDown = (e: any, index: any) => {
- const box: any = document.getElementById('box_' + index);
- const disX = e.clientX - box.offsetLeft;
- const disY = e.clientY - box.offsetTop;
- // 鼠标移动时
- document.onmousemove = function (e) {
- box.style.left = e.clientX - disX + 'px';
- box.style.top = e.clientY - disY + 'px';
- // 边界判断
- if (e.clientX - disX < 0) {
- box.style.left = 0;
- }
- if (e.clientX - disX > contentBoxWidth.value - box.offsetWidth) {
- box.style.left = contentBoxWidth.value - box.offsetWidth + 'px';
- }
- if (e.clientY - disY < 0) {
- box.style.top = 0;
- }
- if (e.clientY - disY > contentBoxHeight.value - box.offsetHeight) {
- box.style.top = contentBoxHeight.value - box.offsetHeight + 'px';
- }
- dragBoxArr[index].left = box.offsetLeft;
- dragBoxArr[index].top = box.offsetTop;
- dragBoxArr[index].width = box.offsetWidth;
- dragBoxArr[index].height = box.offsetHeight;
- dragBoxArr[index].unit = 'px';
- };
- // 鼠标抬起时
- document.onmouseup = function (e) {
- document.onmousemove = null;
- };
- };
- // 拖拽大小事件
- const resizeMouseDown = (e: any, index: any) => {
- const oEv = e;
- oEv.stopPropagation();
- const box: any = document.getElementById('box_' + index);
- const className = e.target.className;
- // 获取移动前盒子的宽高,
- const oldWidth = box.offsetWidth;
- const oldHeight = box.offsetHeight;
- // 获取鼠标距离屏幕的left和top值
- const oldX = oEv.clientX;
- const oldY = oEv.clientY;
- // 元素相对于最近的父级定位
- const oldLeft = box.offsetLeft;
- const oldTop = box.offsetTop;
- // 设置最小的宽度
- const minWidth = 50;
- const minHeight = 50;
- document.onmousemove = function (e) {
- const oEv = e;
- // console.log('move', "width:" + oldWidth,
- // ',oldLeft: ' + oldLeft, ',oldTop: ' + oldTop,
- // ',oldX:clientX-- ' + oldX + ':' + oEv.clientX,
- // ',oldY:clientY-- ' + oldY + ':' + oEv.clientY,
- // )
- // 左上角
- if (className == 'box1') {
- let width = oldWidth - (oEv.clientX - oldX);
- const maxWidth = contentBoxWidth.value;
- let height = oldHeight - (oEv.clientY - oldY);
- const maxHeight = contentBoxHeight.value - oldTop;
- let left = oldLeft + (oEv.clientX - oldX);
- let top = oldTop + (oEv.clientY - oldY);
- if (width < minWidth) {
- width = minWidth;
- }
- if (width > maxWidth) {
- width = maxWidth;
- }
- if (height < minHeight) {
- height = minHeight;
- }
- if (height > maxHeight) {
- height = maxHeight;
- }
- if (oldLeft == 0 && oldTop == 0) {
- // 坐标:left = 0,top = 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 最小宽度,top = 最小高度
- left = minWidth;
- top = minHeight;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 最小宽度,top = 不予处理
- left = minWidth;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 最小高度
- top = minHeight;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
- }
- } else if (oldLeft == 0 && oldTop > 0) {
- // 坐标:left = 0,top > 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 最小宽度,top = 元素上偏移位置
- left = minWidth;
- top = box.offsetTop;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 最小宽度,top = 元素上偏移位置
- left = minWidth;
- top = box.offsetTop;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
- }
- } else if (oldLeft > 0 && oldTop == 0) {
- // 坐标:left > 0,top = 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 元素左偏移位置,top = 元素上偏移位置
- left = box.offsetLeft;
- top = box.offsetTop;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置,top = 0
- left = box.offsetLeft;
- top = 0;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
- }
- } else if (oldLeft > 0 && oldTop > 0) {
- // 坐标:left > 0,top > 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 元素左偏移位置,top = 元素上偏移位置
- left = box.offsetLeft;
- top = box.offsetTop;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置,top = 元素上偏移位置
- left = box.offsetLeft;
- top = box.offsetTop;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,left = 不予处理,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,left = 不予处理,top = 不予处理
- }
- }
- // 左上宽
- if (left < 0) {
- left = 0;
- width = oldWidth - (oEv.clientX - oldX) + (oldLeft + (oEv.clientX - oldX));
- }
- // 左上 高
- if (top < 0) {
- top = 0;
- height = oldTop + (oEv.clientY - oldY) + (oldHeight - (oEv.clientY - oldY));
- }
- box.style.width = width + 'px';
- box.style.height = height + 'px';
- box.style.left = left + 'px';
- box.style.top = top + 'px';
- } else if (className == 'box2') {
- // 右上角
- let width = oldWidth + (oEv.clientX - oldX);
- const maxWidth = contentBoxWidth.value - oldLeft;
- let height = oldHeight - (oEv.clientY - oldY);
- const maxHeight = contentBoxHeight.value - oldTop;
- let top = oldTop + (oEv.clientY - oldY);
- if (width < minWidth) {
- width = minWidth;
- }
- if (width > maxWidth) {
- width = maxWidth;
- }
- if (height < minHeight) {
- height = minHeight;
- }
- if (height > maxHeight) {
- height = maxHeight;
- }
- if (oldLeft == 0 && oldTop == 0) {
- // 坐标:left = 0,top = 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,top = 最小高度
- top = minHeight;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,不予处理
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,top = 最小高度
- top = minHeight;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- } else if (oldLeft == 0 && oldTop > 0) {
- // 坐标:left = 0,top > 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- } else if (oldLeft > 0 && oldTop == 0) {
- // 坐标:left = 0,top = 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,top = 0
- top = 0;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- } else if (oldLeft > 0 && oldTop > 0) {
- // 坐标:left > 0,top > 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,top = 元素上偏移位置
- top = box.offsetTop;
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- }
- // 右上高
- if (top < 0) {
- top = 0;
- height = oldTop + (oEv.clientY - oldY) + (oldHeight - (oEv.clientY - oldY));
- }
- box.style.width = width + 'px';
- box.style.height = height + 'px';
- box.style.top = top + 'px';
- } else if (className == 'box3') {
- // 左下角
- let width = oldWidth - (oEv.clientX - oldX);
- const maxWidth = contentBoxWidth.value;
- let height = oldHeight + (oEv.clientY - oldY);
- const maxHeight = contentBoxHeight.value - oldTop;
- let left = oldLeft + (oEv.clientX - oldX);
- if (width < minWidth) {
- width = minWidth;
- }
- if (width > maxWidth) {
- width = maxWidth;
- }
- if (height < minHeight) {
- height = minHeight;
- }
- if (height > maxHeight) {
- height = maxHeight;
- }
- if (oldLeft == 0 && oldTop == 0) {
- // 坐标:left = 0,top = 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 最小宽度
- left = minWidth;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 最小宽度
- left = minWidth;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,不予处理
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- } else if (oldLeft == 0 && oldTop > 0) {
- // 坐标:left = 0,top > 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 最小宽度
- left = minWidth;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 最小宽度
- left = minWidth;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,不予处理
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- } else if (oldLeft > 0 && oldTop == 0) {
- // 坐标:left > 0,top = 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 元素左偏移位置
- left = box.offsetLeft;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置
- left = box.offsetLeft;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,不予处理
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- } else if (oldLeft > 0 && oldTop > 0) {
- // 坐标:left > 0,top > 0
- if (width == minWidth && height == minHeight) {
- // 宽高 = 最小值,left = 元素左偏移位置
- left = box.offsetLeft;
- } else if (width == minWidth && height > minHeight) {
- // 宽 = 最小值,高 > 最小值,left = 元素左偏移位置
- left = box.offsetLeft;
- } else if (width > minWidth && height == minHeight) {
- // 宽 > 最小值,高 = 最小值,不予处理
- } else if (width > minWidth && height > minHeight) {
- // 宽 > 最小值,高 > 最小值,不予处理
- }
- }
- if (left < 0) {
- left = 0;
- width = oldWidth - (oEv.clientX - oldX) + (oldLeft + (oEv.clientX - oldX));
- }
- box.style.width = width + 'px';
- box.style.height = height + 'px';
- box.style.left = left + 'px';
- } else if (className == 'box4') {
- // 右下角
- let width = oldWidth + (oEv.clientX - oldX);
- const maxWidth = contentBoxWidth.value - oldLeft;
- let height = oldHeight + (oEv.clientY - oldY);
- const maxHeight = contentBoxHeight.value - oldTop;
- if (width < minWidth) {
- width = minWidth;
- }
- if (width > maxWidth) {
- width = maxWidth;
- }
- if (height < minHeight) {
- height = minHeight;
- }
- if (height > maxHeight) {
- height = maxHeight;
- }
- box.style.width = width + 'px';
- box.style.height = height + 'px';
- }
- dragBoxArr[index].left = box.offsetLeft;
- dragBoxArr[index].top = box.offsetTop;
- dragBoxArr[index].width = box.offsetWidth;
- dragBoxArr[index].height = box.offsetHeight;
- dragBoxArr[index].unit = 'px';
- };
- // 鼠标抬起时
- document.onmouseup = function () {
- document.onmousemove = null;
- document.onmouseup = null;
- };
- };
- const show = () => {
- // 每次打开时赋值
- if (!value.value.imageUrl) {
- ElMessage({
- type: 'warning',
- message: '请上传图片'
- });
- return;
- }
- // 计算图片比例
- imgRatio.value = value.value.imgHeight / value.value.imgWidth;
- // 根据图片比例,调整图片高度
- contentBoxHeight.value = Math.floor(contentBoxWidth.value * imgRatio.value);
- areaWidth.value = Math.floor(areaRadio.value * contentBoxHeight.value);
- areaHeight.value = areaWidth.value;
- areaNum.value = Math.floor(contentBoxWidth.value / areaWidth.value);
- if (Object.keys(value.value.heatMapData).length) {
- dragBoxArr.splice(0, dragBoxArr.length, ...value.value.heatMapData);
- } else {
- dragBoxArr.splice(0, dragBoxArr.length);
- addArea();
- }
- showDialog.value = true;
- };
- const save = () => {
- let isOk = true;
- for (let i = 0; i < dragBoxArr.length; i++) {
- if (!dragBoxArr[i].link) {
- ElMessage({
- type: 'warning',
- message: '请选择热区' + (i + 1) + '的链接地址'
- });
- isOk = false;
- break;
- }
- }
- if (!isOk) return;
- dragBoxArr.forEach((item: any, index: number) => {
- const box: any = document.getElementById('box_' + index);
- item.width = Number(((box.offsetWidth / contentBoxWidth.value) * 100).toFixed(2));
- item.height = Number(((box.offsetHeight / contentBoxHeight.value) * 100).toFixed(2));
- item.left = Number(((box.offsetLeft / contentBoxWidth.value) * 100).toFixed(2));
- item.top = Number(((box.offsetTop / contentBoxHeight.value) * 100).toFixed(2));
- item.unit = '%';
- });
- value.value.heatMapData = deepClone(dragBoxArr);
- showDialog.value = false;
- };
- defineExpose({
- showDialog
- });
- </script>
- <style lang="scss" scoped>
- .area-box {
- background-color: rgba(255, 255, 255, 0.7);
- }
- .hot-area-img-wrap {
- // width: 1200px;
- background-size: 100%;
- }
- .box1,
- .box2,
- .box3,
- .box4 {
- width: 10px;
- height: 10px;
- background-color: #fff;
- position: absolute;
- border-radius: 50%;
- border: 1px solid #333;
- }
- .box1 {
- top: -5px;
- left: -5px;
- cursor: nw-resize;
- }
- .box2 {
- top: -5px;
- right: -5px;
- cursor: ne-resize;
- }
- .box3 {
- left: -5px;
- bottom: -5px;
- cursor: sw-resize;
- }
- .box4 {
- bottom: -5px;
- right: -5px;
- cursor: se-resize;
- }
- </style>
|