vr-uniapp/src/components/model-custom/components/index.vue

868 lines
39 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>
<!-- 左侧模块 -->
<div class="siderbar flex-col">
<card-container class="mb-8">
<el-collapse v-model="activeNames">
<el-collapse-item v-for="(com, i) in components" :key="i" :title="com.title" :name="com.key">
<div class="component flex-row flex-wrap">
<div v-for="item in com.item" :key="item.key" class="item">
<div class="siderbar-item flex-col jc-c align-c gap-4 draggable" draggable="true" @dragstart="dragStart(item, $event)" @dragend="dragEnd">
<img class="img radius-xs" :src="url_computer(item.key)" />
<div>{{ item.name }}</div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</card-container>
<card-container class="mb-8">
<div class="mb-12">内容设置</div>
<slider v-model="center_height" :max="1000">组件高度</slider>
</card-container>
<card-container class="h selected">
<div class="flex-col gap-10 drawer-container">
<div class="flex-row align-c jc-sb">已选组件
<div class="flex-row align-c gap-10">
<span class="clear-selection" @click="show_computer_line">{{ !is_show_component_line ? '显示' : '关闭' }}参考线</span>
<span class="clear-selection" @click="cancel">清除选中</span>
</div>
</div>
<div ref="left_scrollTop" class="drawer-drag-area">
<VueDraggable v-model="diy_data" :animation="500" target=".sort-target" :scroll="true" @sort="on_sort">
<TransitionGroup type="transition" tag="ul" name="fade" class="sort-target flex-col h">
<template v-if="!isEmpty(diy_data)">
<li v-for="(item, index) in diy_data" :key="index" :class="['flex-row gap-y-8 re align-c drawer-drag', { 'drawer-custom-drag-bg': item.show_tabs == '1' }]" @click="on_choose(index, item.show_tabs)" @dblclick="double_click(index)">
<el-icon class="iconfont icon-drag size-16 cr-d" />
<div class="text-line-1 flex align-c" style="width: 70%;">
<template v-if="edit_index == index">
<el-input v-model="item.new_name" placeholder="请输入组件别名" size="small" clearable type="textarea" class="flex-1 do-not-trigger" :rows="1" />
</template>
<template v-else>
<span class="size-12 cr-6 break">{{ !isEmpty(item.new_name) ? item.new_name : item.name }}</span>
</template>
</div>
<div class="abs draggable-icon" :style="item.show_tabs == '1' ? 'opacity: 1;' : 'opacity: 0.5;'">
<el-icon class="iconfont icon-commodity-edit size-16 cr-primary do-not-trigger two-click" @click="on_edit(index)" />
<el-icon class="iconfont icon-close-b size-16" @click.stop="del(index)" />
</div>
</li>
</template>
<div v-else class="w h flex jc-c align-c">
<no-data></no-data>
</div>
</TransitionGroup>
</VueDraggable>
</div>
</div>
</card-container>
</div>
<!-- 视图渲染 -->
<div class="main">
<div class="model-content">
<right-side-operation v-if="typeof select_index === 'number' && !isNaN(select_index)" v-model:index="select_index" v-model:dataLength="diy_data.length" @del="del" @copy="copy" @previous_layer="previous_layer" @underlying_layer="underlying_layer" @top_up="top_up" @bottom_up="bottom_up"></right-side-operation>
<!-- 拖拽区 -->
<div class="model-drag">
<div class="model-wall">
<div ref="imgBoxRef" class="drag-area re dropzone" @dragover.prevent @dragenter.prevent @drop="drop">
<div class="w h" @mousedown.prevent="start_drag" @mousemove.prevent="move_drag" @mouseup.prevent="end_drag">
<DraggableContainer v-if="draggable_container" style="z-index:0" :reference-line-visible="true" :disabled="false" reference-line-color="#ddd" @selectstart.prevent @contextmenu.prevent @dragstart.prevent>
<!-- @mouseover="on_choose(index)" -->
<Vue3DraggableResizable v-for="(item, index) in diy_data" :key="item.id" v-model:w="item.com_data.com_width" v-model:h="item.com_data.com_height" :min-w="0" :min-h="0" :class="{'plug-in-show-component-line': is_show_component_line, 'plug-in-show-tabs': item.show_tabs == '1', 'vdr-handle-z-index': item.com_data.bottom_up == '1' }" :style="{ 'z-index': (diy_data.length - 1) - index }" :init-w="item.com_data.com_width" :init-h="item.com_data.com_height" :x="item.location.x" :y="item.location.y" :parent="true" :draggable="is_draggable" @mousedown.stop="on_choose(index, item.show_tabs)" @click.stop="on_choose(index, item.show_tabs)" @drag-end="dragEndHandle($event, index)" @resizing="resizingHandle($event, item.key, index)" @resize-end="resizingHandle($event, item.key, index)">
<div :class="['main-content', { 'plug-in-border': item.show_tabs == '1' }]">
<template v-if="item.key == 'text'">
<model-text :key="item.id" :value="item.com_data" :source-list="props.sourceList"></model-text>
</template>
<template v-else-if="item.key == 'img'">
<model-image :key="item.id" :value="item.com_data" :source-list="props.sourceList"></model-image>
</template>
<template v-else-if="item.key == 'auxiliary-line'">
<model-lines :key="item.id" :value="item.com_data" :source-list="props.sourceList"></model-lines>
</template>
<template v-else-if="item.key == 'icon'">
<model-icon :key="item.id" :value="item.com_data" :source-list="props.sourceList"></model-icon>
</template>
<template v-else-if="item.key == 'panel'">
<model-panel :key="item.id" :value="item.com_data" :source-list="props.sourceList"></model-panel>
</template>
</div>
</Vue3DraggableResizable>
</DraggableContainer>
<div ref="areaRef" class="area" :style="init_drag_style"></div>
</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>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { cloneDeep, isEmpty } from 'lodash';
import { get_math } from '@/utils';
import { text_com_data, img_com_data, line_com_data, icon_com_data, panel_com_data, isRectangleIntersecting } from "./index-default";
import { SortableEvent, VueDraggable } from 'vue-draggable-plus';
//
const app = getCurrentInstance();
//#region
const emits = defineEmits(['rightUpdate']);
interface Props {
list: diy_content[];
sourceList: object;
}
const props = defineProps<Props>();
//#endregion
//#region 初始值
const activeNames = reactive(['1', '2']);
const components = reactive([
{
title: '组件',
key: '1',
item: [
{
key: 'text',
name: '文本',
new_name: '',
com_data: text_com_data
},
{
key: 'img',
name: '图片',
new_name: '',
com_data: img_com_data,
},
{
key: 'auxiliary-line',
name: '线条',
new_name: '',
com_data: line_com_data,
},
{
key: 'icon',
name: '图标',
new_name: '',
com_data: icon_com_data,
},
{
key: 'panel',
name: '面板',
new_name: '',
com_data: panel_com_data,
},
],
},
]);
const url_computer = (name: string) => {
const new_url = ref(new URL(`../../../assets/images/custom/${name}.png`, import.meta.url).href).value;
return new_url;
};
//#endregion
//#region 组件边线相关
const is_show_component_line = ref(false);
const show_computer_line = () => {
is_show_component_line.value = !is_show_component_line.value;
// set_show_tabs(0);
cancel();
};
//#endregion
//#region 左侧处理逻辑
const select_index = ref<null | number>(null);
// 任何行动都会触发
const on_sort = (item: SortableEvent) => {
let index = item?.newIndex || 0;
// 设置对应的位置为显示
set_show_tabs(index);
};
//#endregion
//#region 中间区域的处理逻辑
const diy_data = toRef(props.list);
// 因为容器变更的话,需要重新计算高度,所以不能默认选中第一个
// onMounted(() => {
// // 如果默认不等于空的话,则默认选中第一个
// if (!isEmpty(diy_data)) {
// on_choose(0, false);
// }
// });
onMounted(() => {
// 监听点击事件
document.addEventListener('click', outerClick);
});
onUnmounted(() => {
// 移除监听事件
document.removeEventListener('click', outerClick);
});
const edit_index = ref(-1);
const on_edit = (index: number) => {
if (edit_index.value === index) {
edit_close_processing(index);
edit_index.value = -1;
} else {
edit_index.value = index;
edit_processing(index);
}
};
// 判断点击的是否是可以点击的区域,其他区域隐藏掉编辑属性
const outerClick = (e: any) => {
if (!e.target.className.includes('do-not-trigger') && !e.target.parentNode.className.includes('do-not-trigger')) {
edit_close_processing(edit_index.value);
edit_index.value = -1;
}
};
const double_click = (index: number) => {
edit_index.value = index;
edit_processing(index);
};
// 编辑时的数据处理
const edit_processing = (index: number) => {
const list = diy_data.value[index];
if (!isEmpty(list) && isEmpty(list.new_name)) {
list.new_name = list.name;
}
};
//编辑关闭前的处理
const edit_close_processing = (index: number) => {
const list = diy_data.value[index];
if (!isEmpty(list) && !isEmpty(list.new_name) && list.new_name === list.name) {
list.new_name = '';
}
};
// 复制
const copy = (index: null | number) => {
if (typeof index === 'number' && !isNaN(index)) {
// 获取当前数据, 复制的时候id更换一下
const new_data = {
...cloneDeep(get_diy_index_data(index)),
id: get_math(),
};
// 在当前位置下插入数据
diy_data.value.splice(index, 0, new_data);
set_show_tabs(index + 1);
}
};
// 删除
const del = (index: null | number) => {
if (typeof index === 'number' && !isNaN(index)) {
app?.appContext.config.globalProperties.$common.message_box('删除后不可恢复,确定继续吗?', 'warning').then(() => {
ElMessage({
type: 'success',
message: '删除成功!',
});
const show_tabs_index = diy_data.value.findIndex((item: any) => item.show_tabs == '1');
// 删除的是当前的这个数据
if (show_tabs_index == index) {
// 调用删除接口,然后,更新数据
diy_data.value.splice(index, 1);
if (diy_data.value.length > 0) {
let new_index: number = index;
// 删除的时候如果大于0则显示上边的数据
if (index > 0) {
new_index = new_index - 1;
}
set_show_tabs(new_index);
} else {
emits('rightUpdate', {});
}
} else {
diy_data.value.splice(index, 1);
}
});
}
};
//前置一层 + 1
const previous_layer = (index: number) => {
if (diy_data.value.length > 0) {
const old_data = get_diy_index_data(index);
// 删除当前位置信息
diy_data.value.splice(index, 1);
// 将数据插入上一层数据中
diy_data.value.splice(index + 1, 0, old_data);
// 设置对应的位置为显示
set_show_tabs(index + 1);
}
}
//后置一层 - 1
const underlying_layer = (index: number) => {
if (diy_data.value.length > 0) {
const old_data = get_diy_index_data(index);
// 删除当前位置信息
diy_data.value.splice(index, 1);
// 将数据插入下一层数据中
diy_data.value.splice(index - 1, 0, old_data);
set_show_tabs(index - 1);
}
}
//组件置顶
const top_up = (index: number) => {
if (!isEmpty(diy_data.value[index])) {
const old_data = get_diy_index_data(index);
// 删除当前位置信息
diy_data.value.splice(index, 1);
// 将数据插入下一层数据中
diy_data.value.splice(0, 0, old_data);
set_show_tabs(0);
}
}
//组件置底
const bottom_up = (index: number) => {
if (!isEmpty(diy_data.value[index])) {
const old_data = get_diy_index_data(index);
const old_length = diy_data.value.length - 1;
// 删除当前位置信息
diy_data.value.splice(index, 1);
// 将数据插入下一层数据中
diy_data.value.splice(old_length, 0, old_data);
set_show_tabs(old_length);
}
}
// 获取当前传递过来的index对应的diy_data中的数据
const get_diy_index_data = (index: number) => {
return (<arrayIndex>diy_data.value)[index.toString()];
};
// 设置当前选中的是那个
const set_show_tabs = (index: number) => {
is_show_component_line.value = false;
diy_data.value.forEach((item, for_index) => {
// 先将全部的设置为false,再将当前选中的设置为true
item.show_tabs = '0';
if (for_index == index) {
select_index.value = for_index;
item.show_tabs = '1';
scroll();
emits('rightUpdate', item);
}
});
};
// 左边已选组件的滚动效果
const left_scrollTop = ref<HTMLElement | null>(null);
const left_activeCard = ref<HTMLElement | null>(null);
// 滚动到指定位置
const scroll = () => {
nextTick(() => {
// 左边已选组件的滚动效果
left_activeCard.value = document.querySelector('.drawer-custom-drag-bg');
if (left_activeCard.value) {
// 获取选中内容的位置
const left_scrollY = left_activeCard.value.offsetTop;
if (left_scrollTop.value) {
// 选中的滚动到指定位置
left_scrollTop.value.scrollTo({ top: left_scrollY - 200, behavior: 'smooth' });
}
}
});
};
// 选中和鼠标按下时候的效果
const on_choose = (index: number, show_tabs: string) => {
// 如果已经选中了, 设置为不可再次触发事件
if (show_tabs != '1') {
// 设置对应的位置为显示
set_show_tabs(index);
}
};
// 清除选中
const cancel = () => {
diy_data.value.forEach((item) => {
item.show_tabs = '0';
});
select_index.value = null;
emits('rightUpdate', {});
};
//#endregion
//#region 容器高度发生变化时的处理
const center_height = defineModel('height', { type: Number, default: 0 });
const drag_area_height = computed(() => center_height.value + 'px');
const draggable_container = ref(true);
let data = reactive<diy_content[]>([]);
watch(() => center_height.value, () => {
data = diy_data.value;
// 从 DOM 中删除组件
draggable_container.value = false;
nextTick(() => {
// 在 DOM 中添加组件
diy_data.value = data.map((item, index) => ({
...item,
show_tabs: '0',
location: {
x: item.location.x,
y: item.location.staging_y,
record_x: item.location.x,
record_y: item.location.staging_y,
staging_y: item.location.staging_y,
},
com_data: {
...item.com_data,
com_height: item.com_data.staging_height,
},
}));
// 容器高度变化时,组件不绑定右侧数据
emits('rightUpdate', {});
draggable_container.value = true;
});
},{ immediate:true, deep: true });
//#endregion
//#region 左侧拖拽过来的处理
let draggedItem = ref<any>({});
const dragStart = (item: any, event: any) => {
// 初始化拖拽的数据
draggedItem.value = {
name: item.name,
show_tabs: '1',
is_enable: '1',
location: { x: 0, y: 0, record_x: 0, record_y: 0, staging_y: 0},
src: item.src,
id: get_math(),
key: item.key,
is_hot: '0',
com_data: {
...cloneDeep(item.com_data),
},
};
// 拖拽的时候清空热区
hot_list.data = [];
};
const dragEnd = () => {
draggedItem.value = {};
};
const drop = (event: any) => {
if (draggedItem.value) {
const com_width = draggedItem.value.com_data.com_width;
const com_height = draggedItem.value.com_data.com_height;
let location_x = event.offsetX;
let location_y = event.offsetY;
// 使用新函数调整位置
const { x: adjustedX, y: adjustedY } = adjustPosition(location_x, location_y, com_width, com_height, 390, center_height.value);
const newItem = {
...draggedItem.value,
location: {
x: adjustedX,
y: adjustedY,
record_x: adjustedX,
record_y: adjustedY,
staging_y: adjustedY,
},
};
diy_data.value.unshift(newItem);
set_show_tabs(diy_data.value.length - 1);
}
};
function adjustPosition(x: number, y: number, width:number, height:number, maxWidth:number, maxHeight:number) {
const halfWidth = width / 2;
const halfHeight = height / 2;
// 确保元素不会超出屏幕范围
x = Math.max(0, Math.min(maxWidth - width, x - halfWidth));
y = Math.max(0, Math.min(maxHeight - height, y - halfHeight));
return { x, y };
}
//#endregion
//#region 区域内拖拽显示
const dragEndHandle = (item: any, index: number) => {
diy_data.value[index].location = { x: item.x, y: item.y, record_x: item.x, record_y: item.y, staging_y: item.y };
};
// {x: number, y: number, w: number, h: number}
const resizingHandle = (new_location: any, key: string, index: number) => {
const { x, y, w, h } = new_location;
diy_data.value[index].location = { x, y, record_x: x, record_y: y, staging_y: y };
const com_data = diy_data.value[index].com_data;
com_data.com_width = w;
com_data.com_height = h;
com_data.staging_height = h;
if (key == 'img') {
const { img_width, img_height } = handleImg(com_data, w, h);
com_data.img_width = img_width;
com_data.img_height = img_height;
} else if (key == 'auxiliary-line') {
const { line_width, line_size } = handleAuxiliaryLine(com_data, w, h);
com_data.line_width = line_width;
com_data.line_size = line_size;
}
};
// 图片大小的计算
const handleImg = (com_data: any, w: number, h: number ) => {
if (com_data.border_show == '1') {
return { img_width: w - com_data.border_size * 2, img_height: h - com_data.border_size * 2 }
} else {
return { img_width: w, img_height: h }
}
};
// 线条的计算
const handleAuxiliaryLine = (com_data: any, w: number, h: number ) => {
if (com_data.line_settings === 'horizontal') {
return { line_width: com_data.com_width, line_size: com_data.com_height - 10 }
} else {
return { line_width: com_data.com_height, line_size: com_data.com_width - 10 }
}
};
//#endregion
//#region 全部拖拽添加
const hot_list = reactive({ data: [] as hotListData[] });
const hot_list_index = ref(0);
const imgBoxRef = 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 is_draggable = ref(true);
// 拖拽生成盒子的开关
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;
if (rect_end.value.width > 5 && rect_end.value.height > 5) {
hot_list.data = [];
}
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.data = [{ name: '热区' + (hot_list.data.length + 1), link: {}, drag_start: cloneDeep(rect_start.value), drag_end: cloneDeep(rect_end.value) }];
diy_data.value.forEach((item: any) => {
item.show_tabs = '0';
});
select_index.value = null;
emits('rightUpdate', {});
}
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_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.data[hot_list_index.value].drag_start);
let clone_drag_end = cloneDeep(hot_list.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,
};
is_draggable.value = false;
// 当前选中区域包含的内容
const rect1 = { x: clone_drag_start.x, y: clone_drag_start.y, width: clone_drag_end.width, height: clone_drag_end.height }
diy_data.value.forEach(item => {
const rect2 = { x: item.location.x, y: item.location.y, width: item.com_data.com_width, height: item.com_data.com_height };
// 如果交集或者包裹返回为1否则为0
item.is_hot = isRectangleIntersecting(rect1, rect2);
});
// 当子元素拖拽方法触发后父元素方法不触发
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);
}
// 计算鼠标移动的距离
const move_x = new_coordinate.x - clone_drag_start.x;
const move_y = new_coordinate.y - clone_drag_start.y;
// 遍历对象,包裹在区域内部的拖拽距离更新
diy_data.value.forEach(item => {
if (item.is_hot == '1') { // 只更新交集和包裹中的数据
let { record_x, record_y} = cloneDeep(item.location);
item.location.x = Math.max(0, record_x + move_x);
item.location.y = Math.max(0, record_y + move_y);
}
});
hot_list.data[hot_list_index.value].drag_start.x = new_coordinate.x;
hot_list.data[hot_list_index.value].drag_start.y = new_coordinate.y;
hot_list.data[hot_list_index.value].drag_end.x = new_coordinate.x + Math.max(clone_drag_end.width, 1);
hot_list.data[hot_list_index.value].drag_end.y = new_coordinate.y + Math.max(clone_drag_end.height, 1);
}
};
document.onmouseup = () => {
is_draggable.value = true;
drag_box_bool.value = false;
// 鼠标抬起的时候将默认值重置为当前x、y坐标
diy_data.value.forEach(item => {
if (item.is_hot == '1') {
const { x, y } = cloneDeep(item.location);
item.location.record_x = x;
item.location.record_y = y;
}
});
};
};
// 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.data[hot_list_index.value].drag_start;
let clone_drag_end = hot_list.data[hot_list_index.value].drag_end;
document.onmousemove = (dragBtnEvent) => {
//用鼠标的位置减去鼠标相对元素的位置,得到元素的位置
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.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.data[hot_list_index.value].drag_start.x = clone_drag_start.x;
hot_list.data[hot_list_index.value].drag_end.y = clone_drag_end.y;
hot_list.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.data[hot_list_index.value].drag_end.y = clone_drag_end.y;
hot_list.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.data[hot_list_index.value].drag_start.x = clone_drag_start.x;
hot_list.data[hot_list_index.value].drag_start.y = clone_drag_start.y;
hot_list.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.data[hot_list_index.value].drag_start.y = clone_drag_start.y;
hot_list.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.data[hot_list_index.value].drag_start.x = clone_drag_start.x;
hot_list.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.data[hot_list_index.value].drag_end.x = clone_drag_end.x;
hot_list.data[hot_list_index.value].drag_end = updateDragEnd(clone_drag_start, clone_drag_end, { x: clone_drag_end.x });
break;
}
}
};
document.onmouseup = () => {
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,
};
};
// 辅助函数用于处理边界
const handleBoundary = (value: number, min: number, max: number) => Math.max(min, Math.min(value, max));
const del_area_event = (index: number) => {
hot_list.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
//#region 绑定上下左右事件
const handleKeyUp = (e: KeyboardEvent) => {
let key_code = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
let x = 0;
let y = 0;
// 键盘控制
if (e.key === 'ArrowUp') { //上键
y = -1;
} else if (e.key === 'ArrowDown') { // 下键
y = 1;
} else if (e.key === 'ArrowLeft') { //左键
x = -1;
} else if (e.key === 'ArrowRight') { //右键
x = 1;
}
e.preventDefault();
// 只有是点击上下左右的时候才会生效
if (key_code.includes(e.key)) {
data_handling(x, y);
}
};
const data_handling = (x: number, y: number) => {
// 遍历对象,内部容器更新
if (hot_list.data.length > 0) {
// 更新热区位置
const { drag_start, drag_end } = hot_list.data[0];
if (isWithinBounds(drag_start.x + x, drag_end.width, 390)) {
hot_list.data[0].drag_start.x += x;
}
if (isWithinBounds(drag_start.y + y, drag_end.height, center_height.value)) {
hot_list.data[0].drag_start.y += y;
}
// 按下按钮的时候判断当前包含哪些组件, 避免后续包裹的或者没有手动拖拽过的无法更新其中组件的内容
const rect1 = { x: drag_start.x, y: drag_start.y, width: drag_end.width, height: drag_end.height }
diy_data.value.forEach(item => {
const rect2 = { x: item.location.x, y: item.location.y, width: item.com_data.com_width, height: item.com_data.com_height };
// 如果交集或者包裹返回为1否则为0
if (isRectangleIntersecting(rect1, rect2) == '1') {
// x 轴不小于0 并且不大于容器宽度
if (isWithinBounds(item.location.x + x, item.com_data.com_width, 390)) {
item.location.x += x;
}
// Y轴不小于0 并且不大于容器高度
if (isWithinBounds(item.location.y + y, item.com_data.com_height, center_height.value)) {
item.location.y += y;
item.location.staging_y += y;
}
}
});
} else {
diy_data.value.forEach(item => {
// 只更新选中的数据
if (item.show_tabs == '1') {
// x 轴不小于0 并且不大于容器宽度
if (isWithinBounds(item.location.x + x, item.com_data.com_width, 390)) {
item.location.x += x;
}
// Y轴不小于0 并且不大于容器高度
if (isWithinBounds(item.location.y + y, item.com_data.com_height, center_height.value)) {
item.location.y += y;
item.location.staging_y += y;
}
}
});
}
};
// coordinate 新的坐标 current_size 当前坐标对应的组件大小(指的是组件的宽高) max_size 容器的最大大小
const isWithinBounds = (coordinate:number, current_size: number, max_size: number) => coordinate >= 0 && coordinate + current_size <= max_size;
onMounted(() => {
// 监听键盘事件
document.addEventListener('keyup', handleKeyUp);
});
onUnmounted(() => {
// 移除监听事件
document.removeEventListener('keyup', handleKeyUp);
});
//#endregion
defineExpose({
diy_data,
});
</script>
<style lang="scss" scoped>
@import 'index.scss';
.model-drag {
overflow-y: scroll;
.model-wall {
width: 39rem;
background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%), linear-gradient(135deg, #e5e5e5 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e5e5e5 75%), linear-gradient(135deg, transparent 75%, #e5e5e5 75%);
background-size: 32px 32px;
background-position: 0 0, 16px 0, 16px -16px, 0 16px;
margin: 0 auto;
.drag-area {
height: v-bind(drag_area_height);
width: 100%;
margin: 0.5rem 0; // 用于将上边框和下边框显示出来
user-select: none;
cursor: crosshair;
}
.main-content {
max-width: 100%;
height: 100%;
overflow: hidden;
}
}
}
.el-input__textarea {
resize: none;
}
</style>