vr-uniapp/src/components/common/hot/index.vue

448 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- 上传组件 -->
<template>
<el-dialog v-model="dialog_visible" fullscreen :close-on-click-modal="false" @close="close_event">
<template #header>
<div class="title re">
<div class="tc size-16 fw">编辑热区</div>
</div>
</template>
<el-scrollbar class="content-scrollbar">
<div class="pa-40 flex-row gap-40">
<div class="left-content flex-1 pa-20">
<el-scrollbar class="img-scrollbar">
<div class="img-container">
<div class="re">
<div ref="imgBoxRef" class="oh" @mousedown.prevent="start_drag" @mousemove.prevent="move_drag" @mouseup.prevent="end_drag">
<div ref="imgRef">
<el-image :src="hot_list.img" class="w img block" @selectstart.prevent @contextmenu.prevent @dragstart.prevent></el-image>
</div>
<div ref="areaRef" class="area" :style="init_drag_style"></div>
<div v-for="(item, index) in hot_list.data" :key="index" class="area-box" :style="rect_style(item.drag_start, item.drag_end)" @mousedown.stop="start_drag_area_box(index, $event)" @dblclick="dbl_drag_event(item, index)">
<div class="del-btn" @click.stop="del_area_event(index)"><icon name="close"></icon></div>
<div class="drag-btn drag-tl" :data-index="index" @mousedown.stop="start_drag_btn_tl(index, $event)"></div>
<div class="drag-btn drag-tc" :data-index="index" @mousedown.stop="start_drag_btn_tc(index, $event)"></div>
<div class="drag-btn drag-lc" :data-index="index" @mousedown.stop="start_drag_btn_lc(index, $event)"></div>
<div class="drag-btn drag-bl" :data-index="index" @mousedown.stop="start_drag_btn_bl(index, $event)"></div>
<div class="drag-btn drag-bc" :data-index="index" @mousedown.stop="start_drag_btn_bc(index, $event)"></div>
<!-- 已完成 -->
<div class="drag-btn drag-br" :data-index="index" @mousedown.stop="start_drag_btn_br(index, $event)"></div>
<div class="drag-btn drag-rc" :data-index="index" @mousedown.stop="start_drag_btn_rc(index, $event)"></div>
<div class="text">
<div class="name">{{ item.name }}</div>
<div class="status" :class="!is_obj_empty(item.link) ? 'cr-primary' : 'cr-error'">{{ !is_obj_empty(item.link) ? (item.link?.name ?? '未设置') : '未设置' }}</div>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
<div class="right-content flex-1 pa-20">
<div class="size-16 fw mb-10">图片热区</div>
<div class="flex-col gap-20 item">
<div v-for="(item, index) in hot_list.data" :key="index" class="flex-row align-c gap-10">
<el-input v-model="item.name" class="name" placeholder="名称"></el-input>
<url-value v-model="item.link"></url-value>
<icon name="del" size="20" @click="del_event(index)"></icon>
</div>
<el-button type="primary" class="add_hot" @click="add_event">添加选区</el-button>
<div class="size-12 cr-9">框选热区范围,双击设置热区信息</div>
</div>
</div>
</div>
</el-scrollbar>
<template #footer>
<span class="dialog-footer">
<el-button class="plr-28 ptb-10" type="primary" @click="confirm_event">完成</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="hot_dialog_visible" width="560" append-to-body draggable :close-on-click-modal="false" @close="hot_close_event">
<template #header>
<div class="title re">
<div class="tc size-16 fw">设置热区</div>
</div>
</template>
<div class="content">
<el-form ref="formRef" :model="form" label-width="85px" class="pa-20 mt-16">
<el-form-item label="名称">
<el-input v-model="form.name" placeholder="请输入名称"></el-input>
</el-form-item>
<el-form-item label="热区跳转链接">
<url-value v-model="form.link"></url-value>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button class="plr-28 ptb-10" @click="hot_close_event">取消</el-button>
<el-button class="plr-28 ptb-10" type="primary" @click="hot_confirm_event">确定</el-button>
</span>
</template>
</el-dialog>
<el-button class="w" @click="open_hot_event"><icon name="add"></icon></el-button>
</template>
<script lang="ts" setup>
import { cloneDeep } from 'lodash';
import { is_obj_empty } from '@/utils';
const app = getCurrentInstance();
/**
* @description: 热区
* @param modelValue{Object} 默认值
* @param dialog_visible {Boolean} 弹窗显示
* @return {*} update:modelValue
*/
const props = defineProps({});
const modelValue = defineModel({ type: Object as PropType<hotData>, default: {} });
const dialog_visible = defineModel('visibleDialog', { type: Boolean, default: false });
const hot_list = ref<hotData>({
img: '',
img_height: 1,
img_width: 1,
data: [],
});
const hot_list_index = ref(0);
//#region 左侧画布-----------------------------------------------start
const imgBoxRef = ref<HTMLElement | null>(null);
const imgRef = ref<HTMLElement | null>(null);
const rect_start = ref<rectCoords>({ x: 0, y: 0, width: 0, height: 0 });
const rect_end = ref<rectCoords>({ x: 0, y: 0, width: 0, height: 0 });
const areaRef = ref<HTMLElement | null>(null);
const init_drag_style = ref('');
// 拖拽生成盒子的开关
const drag_bool = ref(false);
// 拖拽盒子的开关
const drag_box_bool = ref(false);
// 拖拽放大缩小盒子的开关
const drag_box_scale_bool = ref(false);
const start_drag = (event: MouseEvent) => {
drag_bool.value = true;
if (!imgBoxRef.value) return;
rect_start.value.x = rect_start.value.x !== 0 ? rect_start.value.x : event.clientX - imgBoxRef.value.getBoundingClientRect().left;
rect_start.value.y = rect_start.value.y !== 0 ? rect_start.value.y : event.clientY - imgBoxRef.value.getBoundingClientRect().top;
rect_start.value.width = 0;
rect_start.value.height = 0;
};
const move_drag = (event: MouseEvent) => {
if (drag_bool.value) {
if (!imgBoxRef.value) return;
rect_end.value.x = event.clientX - imgBoxRef.value.getBoundingClientRect().left;
rect_end.value.y = event.clientY - imgBoxRef.value.getBoundingClientRect().top;
rect_end.value.width = rect_end.value.x - rect_start.value.x > 0 ? rect_end.value.x - rect_start.value.x : 0;
rect_end.value.height = rect_end.value.y - rect_start.value.y > 0 ? rect_end.value.y - rect_start.value.y : 0;
init_drag_style.value = `left: ${rect_start.value.x}px;top: ${rect_start.value.y}px;width: ${Math.max(rect_end.value.width, 1)}px;height: ${Math.max(rect_end.value.height, 1)}px;display: flex;`;
}
};
const end_drag = (event: MouseEvent) => {
drag_bool.value = false;
if (areaRef.value) areaRef.value.style.display = 'none';
if (!imgBoxRef.value) return;
init_drag_style.value = ``;
if (rect_end.value.width > 16 && rect_end.value.height > 16) {
hot_list.value.data.push({
name: '热区' + (hot_list.value.data.length + 1),
link: {},
drag_start: cloneDeep(rect_start.value),
drag_end: cloneDeep(rect_end.value),
});
}
rect_start.value = { x: 0, y: 0, width: 0, height: 0 };
rect_end.value = { x: 0, y: 0, width: 0, height: 0 };
};
const area_box_point = ref({ x: 0, y: 0 });
// area-box
const dbl_drag_event = (item: hotListData, index: number) => {
hot_dialog_visible.value = true;
form.value.link = item.link;
form.value.name = item.name;
hot_list_index.value = index;
};
const start_drag_area_box = (index: number, event: MouseEvent) => {
hot_list_index.value = index;
event.stopPropagation();
drag_box_bool.value = true;
let clone_drag_start = cloneDeep(hot_list.value.data[hot_list_index.value].drag_start);
let clone_drag_end = cloneDeep(hot_list.value.data[hot_list_index.value].drag_end);
// 记录原始位置
area_box_point.value = {
x: clone_drag_start.x - event.clientX,
y: clone_drag_start.y - event.clientY,
};
// 当子元素拖拽方法触发后父元素方法不触发
document.onmousemove = (areaBoxEvent) => {
// areaBoxEvent.stopPropagation();
if (drag_box_bool.value) {
if (!imgBoxRef.value) return;
const new_coordinate = {
x: areaBoxEvent.clientX + area_box_point.value.x,
y: areaBoxEvent.clientY + area_box_point.value.y,
};
// 左上边界判断
if (new_coordinate.x < 0) {
new_coordinate.x = 0;
}
if (new_coordinate.y < 0) {
new_coordinate.y = 0;
}
// 右下边界判断
if (new_coordinate.x + Math.max(clone_drag_end.width, 1) > imgBoxRef.value.getBoundingClientRect().width) {
new_coordinate.x = imgBoxRef.value.getBoundingClientRect().width - Math.max(clone_drag_end.width, 1);
}
if (new_coordinate.y + Math.max(clone_drag_end.height, 1) > imgBoxRef.value.getBoundingClientRect().height) {
new_coordinate.y = imgBoxRef.value.getBoundingClientRect().height - Math.max(clone_drag_end.height, 1);
}
hot_list.value.data[hot_list_index.value].drag_start.x = new_coordinate.x;
hot_list.value.data[hot_list_index.value].drag_start.y = new_coordinate.y;
hot_list.value.data[hot_list_index.value].drag_end.x = new_coordinate.x + Math.max(clone_drag_end.width, 1);
hot_list.value.data[hot_list_index.value].drag_end.y = new_coordinate.y + Math.max(clone_drag_end.height, 1);
}
};
document.onmouseup = (areaBoxEvent) => {
// areaBoxEvent.stopPropagation();
drag_box_bool.value = false;
};
};
// drag-btn
const start_drag_btn_br = (index: number, event: MouseEvent) => {
start_drag_btn(index, event, 'br');
};
const start_drag_btn_bl = (index: number, event: MouseEvent) => {
start_drag_btn(index, event, 'bl');
};
const start_drag_btn_bc = (index: number, event: MouseEvent) => {
start_drag_btn(index, event, 'bc');
};
const start_drag_btn_tl = (index: number, event: MouseEvent) => {
start_drag_btn(index, event, 'tl');
};
const start_drag_btn_tc = (index: number, event: MouseEvent) => {
start_drag_btn(index, event, 'tc');
};
const start_drag_btn_lc = (index: number, event: MouseEvent) => {
start_drag_btn(index, event, 'lc');
};
const start_drag_btn_rc = (index: number, event: MouseEvent) => {
start_drag_btn(index, event, 'rc');
};
// 画布拖拽公用方法
const start_drag_btn = (index: number, event: MouseEvent, type: string) => {
hot_list_index.value = index;
event.stopPropagation();
drag_box_scale_bool.value = true;
let clone_drag_start = hot_list.value.data[hot_list_index.value].drag_start;
let clone_drag_end = hot_list.value.data[hot_list_index.value].drag_end;
document.onmousemove = (dragBtnEvent) => {
// dragBtnEvent.stopPropagation();
//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
if (drag_box_scale_bool.value) {
if (!imgBoxRef.value) return;
switch (type) {
case 'br':
// 下右
clone_drag_end.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, imgBoxRef.value.getBoundingClientRect().width);
clone_drag_end.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, imgBoxRef.value.getBoundingClientRect().height);
hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, clone_drag_end);
break;
case 'bl':
// 下左
clone_drag_start.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, clone_drag_end.x);
clone_drag_end.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, imgBoxRef.value.getBoundingClientRect().height);
hot_list.value.data[hot_list_index.value].drag_start.x = clone_drag_start.x;
hot_list.value.data[hot_list_index.value].drag_end.y = clone_drag_end.y;
hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { y: clone_drag_end.y });
break;
case 'bc':
// 下中
clone_drag_end.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, imgBoxRef.value.getBoundingClientRect().height);
hot_list.value.data[hot_list_index.value].drag_end.y = clone_drag_end.y;
hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { y: clone_drag_end.y });
break;
case 'tl':
// 上左
clone_drag_start.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, clone_drag_end.x);
clone_drag_start.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, clone_drag_end.y);
hot_list.value.data[hot_list_index.value].drag_start.x = clone_drag_start.x;
hot_list.value.data[hot_list_index.value].drag_start.y = clone_drag_start.y;
hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, {});
break;
case 'tc':
// 上中
clone_drag_start.y = handleBoundary(dragBtnEvent.clientY - imgBoxRef.value.getBoundingClientRect().top, 0, clone_drag_end.y);
hot_list.value.data[hot_list_index.value].drag_start.y = clone_drag_start.y;
hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { y: clone_drag_end.y });
break;
case 'lc':
// 左中
clone_drag_start.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, clone_drag_end.x);
hot_list.value.data[hot_list_index.value].drag_start.x = clone_drag_start.x;
hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, {});
break;
case 'rc':
// 右中
clone_drag_end.x = handleBoundary(dragBtnEvent.clientX - imgBoxRef.value.getBoundingClientRect().left, 0, imgBoxRef.value.getBoundingClientRect().width);
hot_list.value.data[hot_list_index.value].drag_end.x = clone_drag_end.x;
hot_list.value.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { x: clone_drag_end.x });
break;
}
}
};
document.onmouseup = (dragBtnEvent2) => {
// dragBtnEvent2.stopPropagation();
drag_box_scale_bool.value = false;
};
};
// 辅助函数用于更新drag_end
const updateDragEnd = (dragStart: { x: number; y: number }, dragEnd: { x: number; y: number }, newDragEnd: { x?: number; y?: number }) => {
const newX = newDragEnd.x !== undefined ? newDragEnd.x : dragEnd.x;
const newY = newDragEnd.y !== undefined ? newDragEnd.y : dragEnd.y;
return {
x: newX,
y: newY,
width: newX - dragStart.x > 0 ? newX - dragStart.x : 0,
height: newY - dragStart.y > 0 ? newY - dragStart.y : 0,
};
};
// 辅助函数用于更新drag_start
const updateDragStart = (dragStart: { x: number; y: number }, newDragStart: { x?: number; y?: number }) => {
const newX = newDragStart.x !== undefined ? newDragStart.x : dragStart.x;
const newY = newDragStart.y !== undefined ? newDragStart.y : dragStart.y;
return { x: newX, y: newY };
};
// 辅助函数用于处理边界
const handleBoundary = (value: number, min: number, max: number) => Math.max(min, Math.min(value, max));
const del_area_event = (index: number) => {
hot_list.value.data.splice(index, 1);
};
const rect_style = computed(() => {
return (start: rectCoords, end: rectCoords) => {
return `left: ${start.x}px;top: ${start.y}px;width: ${Math.max(end.width, 1)}px;height: ${Math.max(end.height, 1)}px;display: flex;`;
};
});
//#endregion 左侧画布-----------------------------------------------end
//#region 右侧热区编辑-----------------------------------------------start
const del_event = (index: number) => {
hot_list.value.data.splice(index, 1);
};
const add_event = () => {
hot_list.value.data.push({
name: '热区' + (hot_list.value.data.length + 1),
link: {},
drag_start: {
x: 0,
y: 0,
width: 0,
height: 0,
},
drag_end: {
x: 100,
y: 100,
width: 100,
height: 100,
},
});
};
//#endregion 右侧热区编辑-----------------------------------------------end
//#region 设置热区弹窗-----------------------------------------------start
const hot_dialog_visible = ref(false);
interface formData {
link: pageLinkList;
name: string;
}
const form = ref<formData>({
link: {
name: '',
},
name: '',
});
const hot_close_event = () => {
hot_dialog_visible.value = false;
};
const hot_confirm_event = () => {
hot_list.value.data[hot_list_index.value].name = form.value.name;
if (hot_list.value.data[hot_list_index.value].link) {
hot_list.value.data[hot_list_index.value].link = form.value.link;
}
hot_close_event();
};
//#endregion 设置热区弹窗-----------------------------------------------end
//#region 热区开启关闭确认取消回调 -----------------------------------------------start
// 打开热区弹窗
const open_hot_event = () => {
if (modelValue.value.img.length > 0) {
dialog_visible.value = true;
hot_list.value.img = modelValue.value.img;
setTimeout(() => {
// 创建临时变量储存传过来的数据
let temp_data = cloneDeep(modelValue.value);
// 获取最新的图片高度和宽度
temp_data.img_height = imgBoxRef.value?.clientHeight || 0;
temp_data.img_width = imgBoxRef.value?.clientWidth || 0;
// 根据原始数据的宽高和更新后的宽高的比例,计算出事实的坐标比例
const scale = temp_data.img_width / modelValue.value.img_width;
console.log(scale);
temp_data.data.forEach((item) => {
item.drag_start.x = item.drag_start.x * scale;
item.drag_start.y = item.drag_start.y * scale;
item.drag_end.x = item.drag_end.x * scale;
item.drag_end.y = item.drag_end.y * scale;
item.drag_end.width = item.drag_end.width * scale;
item.drag_end.height = item.drag_end.height * scale;
});
hot_list.value = temp_data;
}, 100);
} else {
ElMessage({
type: 'warning',
message: '请先选择图片',
});
}
};
// 取消回调
const close_event = () => {
dialog_visible.value = false;
};
// 确认回调
const confirm_event = () => {
if (hot_list.value.data.length > 0) {
// 筛选数组hot中所有的link是否有空值如果有则提示出来
if (is_obj_empty(hot_list.value.data)) {
ElMessage({
type: 'warning',
message: '请先设置热区',
});
return;
}
const no_link_list = hot_list.value.data.filter((item) => {
return is_obj_empty(item.link);
});
if (no_link_list.length > 0) {
ElMessage.error('请设置热区链接!');
return;
} else {
modelValue.value = cloneDeep(hot_list.value);
close_event();
}
} else {
ElMessage.error('至少选择一个热区!');
}
};
//#endregion 热区开启关闭确认取消回调 -----------------------------------------------end
</script>
<style lang="scss" scoped>
@import 'index.scss';
</style>