<template>
|
<div style="display: flex; height: 100vh;">
|
<!-- Sidebar -->
|
<div class="sidebar" style="width: 200px; background: #f4f4f4; padding: 10px;">
|
<div
|
v-for="(layout, layoutIndex) in layouts"
|
:key="layoutIndex"
|
class="sidebar-item"
|
@click="selectLayout(layoutIndex)"
|
:class="{ 'selected': selectedLayoutIndex === layoutIndex }"
|
>
|
{{ layout.width }} × {{ layout.height }} × {{ layout.SameCount }}
|
</div>
|
</div>
|
|
<!-- Main Layout Panel -->
|
<div ref="layoutPanel" :class="panelClass" :style="panelStyle">
|
<div
|
v-for="(layout, layoutIndex) in layouts"
|
:key="layoutIndex"
|
class="layout-wrapper"
|
:style="{ display: selectedLayoutIndex === layoutIndex ? 'block' : 'none', top: '-150px' }"
|
>
|
<!-- Layout Info Label -->
|
<div class="layout-info" :style="layoutInfoStyle(layoutIndex)">
|
{{ getCurrentRectInfo(layoutIndex) }}
|
</div>
|
|
<!-- Layout Container -->
|
<div class="layout-container" :style="layoutContainerStyle(layoutIndex)">
|
<!-- 灰色矩形 -->
|
<div
|
v-for="(rect, rectIndex) in layout.rects.filter(r => r.isRemain)"
|
:key="`gray-${rectIndex}`"
|
:ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
|
class="layout-rect"
|
:style="rectStyle(rect, layoutIndex)"
|
@contextmenu.prevent="handleGrayRectRightClick(layoutIndex, rectIndex)"
|
/>
|
|
<!-- 蓝色矩形 -->
|
<div
|
v-for="(rect, rectIndex) in layout.rects.filter(r => !r.isRemain)"
|
:key="`blue-${rectIndex}`"
|
:ref="(el) => { if (el) rectsElements[layoutIndex + '-' + rectIndex] = el }"
|
class="layout-rect"
|
:style="rectStyle(rect, layoutIndex)"
|
@contextmenu.prevent="handleRectRightClick(layoutIndex, rectIndex)"
|
@mousedown="handleRectDragStart(layoutIndex, rectIndex)"
|
@mousemove="handleRectDragging"
|
@mouseup="handleRectDragEnd"
|
@mouseleave="handleRectDragEnd"
|
@click="handleRectClick(layoutIndex, rectIndex)"
|
>
|
<div class="rect-content">
|
<div class="size">{{ rect.w }}×{{ rect.h }}</div>
|
<div v-if="showJiaHao" class="jia-hao">{{ rect.JiaHao }}</div>
|
<div v-if="showProcessId" class="liuchengka">{{ rect.liuchengka }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 提交按钮 -->
|
<button @click="submitLayouts" style="position: fixed; bottom: 20px; right: 20px; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
|
保存调整
|
</button>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, onUnmounted } from 'vue';
|
import request from "@/utils/request";
|
import { useI18n } from "vue-i18n";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
import useUserInfoStore from "@/stores/userInfo";
|
|
const { t } = useI18n();
|
const userStore = useUserInfoStore()
|
const username = userStore.user.userName
|
let clickEventListener = null;
|
const props = defineProps({
|
layoutData: { type: Object, required: true },
|
gw: { type: Number, default: 1000 },
|
gh: { type: Number, default: 1000 },
|
style: { type: String, default: 'width:1000px;height:600px;display:block;background:gray' }
|
});
|
|
const emit = defineEmits(['rectClicked']);
|
const layoutPanel = ref(null);
|
const rectsElements = ref({});
|
const focusIndex = ref(null);
|
const layouts = ref([]);
|
const panelClass = ref('');
|
const panelStyle = ref(props.style);
|
const rectClass = ref('layout-rect');
|
const selectedLayoutIndex = ref(0);
|
const currentRect = ref(null);
|
const dragging = ref(false);
|
const dragStartPos = ref({ x: 0, y: 0 });
|
const dragRect = ref(null);
|
const showJiaHao = ref(false);
|
const showProcessId = ref(false);
|
const themeColor = ref(null);
|
|
const submitLayouts = async () => {
|
layouts.value.forEach(layout => {
|
layout.rects.forEach(rect => {
|
rect.x = Math.round(rect.x);
|
rect.y = Math.round(rect.y);
|
rect.w = Math.round(rect.w);
|
rect.h = Math.round(rect.h);
|
});
|
});
|
const savedProjectNo = localStorage.getItem('projectNo');
|
const processId = savedProjectNo;
|
const Layouts = layouts.value;
|
request.post(`/glassOptimize/updateOptimizeResult/${processId}`, JSON.stringify({ Layouts }), {
|
headers: {
|
'Content-Type': 'application/json'
|
}
|
}).then((res) => {
|
if (res.code == 200 && res.data === true) {
|
ElMessage.success(t('basicData.msg.saveSuccess'));
|
} else {
|
ElMessage.warning(res.msg);
|
}
|
});
|
};
|
|
const fetchSettings = async (username) => {
|
try {
|
const response = await request.post(`/glassOptimize/selectOptimizeParms/${username}`);
|
if (response.code == 200) {
|
if (!response.data) {
|
console.error('响应数据为空');
|
return;
|
}
|
const parsedData = JSON.parse(response.data);
|
if (parsedData.display && parsedData.frameNumber) {
|
showJiaHao.value = parsedData.display.frameNumber;
|
}
|
if (parsedData.display && parsedData.orderNumber) {
|
showProcessId.value = parsedData.display.orderNumber;
|
}
|
if (parsedData.display) {
|
themeColor.value = parsedData.display.themeColor;
|
}
|
} else {
|
console.error('请求失败,状态码:', response.code);
|
}
|
} catch (error) {
|
console.error('请求发生错误:', error);
|
}
|
};
|
|
const showAddDialog = (layoutIndex, rectIndex) => {
|
ElMessageBox.prompt('请输入成品的宽度和高度', '添加成品', {
|
inputType: 'text',
|
inputValidator: (value) => {
|
const values = value.split(',').map(v => v.trim());
|
if (values.length !== 2) {
|
return '请输入两个数字,用逗号分隔';
|
}
|
const [width, height] = values;
|
if (isNaN(width) || isNaN(height)) {
|
return '请输入有效的数字';
|
}
|
if (width <= 0 || height <= 0) {
|
return '宽度和高度必须大于0';
|
}
|
return true;
|
},
|
inputErrorMessage: '输入格式不正确'
|
})
|
.then(({ value }) => {
|
const values = value.split(',').map(v => parseFloat(v.trim()));
|
const newRect = {
|
x: 0,
|
y: 0,
|
w: values[0],
|
h: values[1],
|
isRemain: false
|
};
|
addNewRect(layoutIndex, newRect);
|
})
|
.catch(() => {
|
// 用户取消
|
});
|
};
|
|
const addNewRect = (layoutIndex, newRect) => {
|
const layout = layouts.value[layoutIndex];
|
const bestFitPosition = findBestFitPosition(layoutIndex, newRect);
|
if (bestFitPosition) {
|
newRect.x = bestFitPosition.x;
|
newRect.y = bestFitPosition.y;
|
layout.rects.push(newRect);
|
adjustGrayRectangles(layoutIndex);
|
} else {
|
ElMessage.warning('无法放置,没有足够的空间');
|
}
|
};
|
|
const findBestFitPosition = (layoutIndex, newRect) => {
|
const layout = layouts.value[layoutIndex];
|
const obstacles = layout.rects.filter(r => !r.isRemain);
|
let bestFit = null;
|
let minAreaDifference = Infinity;
|
|
const remainingAreas = calculateRemainingAreas(layout.width, layout.height, obstacles);
|
remainingAreas.forEach(area => {
|
if (newRect.w <= area.w && newRect.h <= area.h) {
|
const areaDifference = Math.abs(area.w * area.h - newRect.w * newRect.h);
|
if (areaDifference < minAreaDifference) {
|
minAreaDifference = areaDifference;
|
bestFit = {
|
x: area.x,
|
y: area.y,
|
w: newRect.w,
|
h: newRect.h
|
};
|
}
|
}
|
});
|
|
return bestFit;
|
};
|
|
const layoutContainerStyle = (layoutIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const scale = Math.min(
|
(props.gw - 100) / layout.width,
|
(props.gh - 100) / layout.height
|
);
|
return {
|
position: 'absolute',
|
left: `${(props.gw - layout.width * scale) / 2}px`,
|
top: `${(props.gh - layout.height * scale) / 2}px`,
|
width: `${layout.width * scale}px`,
|
height: `${layout.height * scale}px`,
|
overflow: 'visible',
|
border: '1px solid #ccc',
|
background: '#fff'
|
};
|
};
|
|
const layoutInfoStyle = (layoutIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const scale = Math.min(
|
(props.gw - 100) / layout.width,
|
(props.gh - 100) / layout.height
|
);
|
return {
|
position: 'absolute',
|
left: `${(props.gw - layout.width * scale) / 2}px`,
|
top: `${(props.gh - layout.height * scale) / 2 - 45}px`,
|
background: 'none',
|
textAlign: 'center',
|
zIndex: 1000
|
};
|
};
|
|
const rectStyle = (rect, layoutIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const scale = Math.min(
|
(props.gw - 100) / layout.width,
|
(props.gh - 100) / layout.height
|
);
|
return {
|
position: 'absolute',
|
left: `${rect.x * scale}px`,
|
top: `${rect.y * scale}px`,
|
width: `${rect.w * scale}px`,
|
height: `${rect.h * scale}px`,
|
backgroundColor: rect.isRemain ? '#f0f0f0' : themeColor.value,
|
border: '1px solid #000',
|
cursor: 'pointer',
|
draggable: !rect.isRemain,
|
zIndex: rect.isRemain ? 1 : 2
|
};
|
};
|
|
const handleRectClick = (layoutIndex, rectIndex) => {
|
focusIndex.value = { layoutIndex, rectIndex };
|
emit('rectClicked', layoutIndex, rectIndex);
|
};
|
|
const handleRectRightClick = (layoutIndex, rectIndex) => {
|
const rect = layouts.value[layoutIndex].rects[rectIndex];
|
if (rect.isRemain) return;
|
|
const contextMenu = document.createElement('div');
|
contextMenu.className = 'context-menu';
|
contextMenu.style.position = 'absolute';
|
contextMenu.style.left = `${event.clientX}px`;
|
contextMenu.style.top = `${event.clientY}px`;
|
contextMenu.style.backgroundColor = '#fff';
|
contextMenu.style.border = '1px solid #ccc';
|
contextMenu.style.padding = '5px';
|
contextMenu.style.zIndex = 1001;
|
|
const rotateItem = document.createElement('div');
|
rotateItem.textContent = '旋转';
|
rotateItem.style.cursor = 'pointer';
|
rotateItem.addEventListener('click', () => {
|
rotateRect(layoutIndex, rectIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveUpAndRotateItem = document.createElement('div');
|
moveUpAndRotateItem.textContent = '向上移动并旋转';
|
moveUpAndRotateItem.style.cursor = 'pointer';
|
moveUpAndRotateItem.addEventListener('click', () => {
|
moveRectAndRotate(layoutIndex, rectIndex, 'up');
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveDownAndRotateItem = document.createElement('div');
|
moveDownAndRotateItem.textContent = '向下移动并旋转';
|
moveDownAndRotateItem.style.cursor = 'pointer';
|
moveDownAndRotateItem.addEventListener('click', () => {
|
moveRectAndRotate(layoutIndex, rectIndex, 'down');
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveLeftAndRotateItem = document.createElement('div');
|
moveLeftAndRotateItem.textContent = '向左移动并旋转';
|
moveLeftAndRotateItem.style.cursor = 'pointer';
|
moveLeftAndRotateItem.addEventListener('click', () => {
|
moveRectAndRotate(layoutIndex, rectIndex, 'left');
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveRightAndRotateItem = document.createElement('div');
|
moveRightAndRotateItem.textContent = '向右移动并旋转';
|
moveRightAndRotateItem.style.cursor = 'pointer';
|
moveRightAndRotateItem.addEventListener('click', () => {
|
moveRectAndRotate(layoutIndex, rectIndex, 'right');
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveUpItem = document.createElement('div');
|
moveUpItem.textContent = '向上移动';
|
moveUpItem.style.cursor = 'pointer';
|
moveUpItem.addEventListener('click', () => {
|
moveRect(layoutIndex, rectIndex, 'up');
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveDownItem = document.createElement('div');
|
moveDownItem.textContent = '向下移动';
|
moveDownItem.style.cursor = 'pointer';
|
moveDownItem.addEventListener('click', () => {
|
moveRect(layoutIndex, rectIndex, 'down');
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveLeftItem = document.createElement('div');
|
moveLeftItem.textContent = '向左移动';
|
moveLeftItem.style.cursor = 'pointer';
|
moveLeftItem.addEventListener('click', () => {
|
moveRect(layoutIndex, rectIndex, 'left');
|
document.body.removeChild(contextMenu);
|
});
|
|
const moveRightItem = document.createElement('div');
|
moveRightItem.textContent = '向右移动';
|
moveRightItem.style.cursor = 'pointer';
|
moveRightItem.addEventListener('click', () => {
|
moveRect(layoutIndex, rectIndex, 'right');
|
document.body.removeChild(contextMenu);
|
});
|
|
const deleteItem = document.createElement('div');
|
deleteItem.textContent = '删除';
|
deleteItem.style.cursor = 'pointer';
|
deleteItem.addEventListener('click', () => {
|
deleteRect(layoutIndex, rectIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
const addItem = document.createElement('div');
|
addItem.textContent = '添加成品';
|
addItem.style.cursor = 'pointer';
|
addItem.addEventListener('click', () => {
|
showAddDialog(layoutIndex, rectIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
const mirrorXItem = document.createElement('div');
|
mirrorXItem.textContent = 'X镜像';
|
mirrorXItem.style.cursor = 'pointer';
|
mirrorXItem.addEventListener('click', () => {
|
mirrorLayoutX(layoutIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
const mirrorYItem = document.createElement('div');
|
mirrorYItem.textContent = 'Y镜像';
|
mirrorYItem.style.cursor = 'pointer';
|
mirrorYItem.addEventListener('click', () => {
|
mirrorLayoutY(layoutIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
contextMenu.appendChild(rotateItem);
|
contextMenu.appendChild(moveUpAndRotateItem);
|
contextMenu.appendChild(moveDownAndRotateItem);
|
contextMenu.appendChild(moveLeftAndRotateItem);
|
contextMenu.appendChild(moveRightAndRotateItem);
|
contextMenu.appendChild(moveUpItem);
|
contextMenu.appendChild(moveDownItem);
|
contextMenu.appendChild(moveLeftItem);
|
contextMenu.appendChild(moveRightItem);
|
contextMenu.appendChild(deleteItem);
|
contextMenu.appendChild(addItem);
|
contextMenu.appendChild(mirrorXItem);
|
contextMenu.appendChild(mirrorYItem);
|
|
document.body.appendChild(contextMenu);
|
};
|
|
const handleGrayRectRightClick = (layoutIndex, rectIndex) => {
|
const rect = layouts.value[layoutIndex].rects[rectIndex];
|
if (!rect.isRemain) return;
|
|
const contextMenu = document.createElement('div');
|
contextMenu.className = 'context-menu';
|
contextMenu.style.position = 'absolute';
|
contextMenu.style.left = `${event.clientX}px`;
|
contextMenu.style.top = `${event.clientY}px`;
|
contextMenu.style.backgroundColor = '#fff';
|
contextMenu.style.border = '1px solid #ccc';
|
contextMenu.style.padding = '5px';
|
contextMenu.style.zIndex = 1001;
|
|
const addItem = document.createElement('div');
|
addItem.textContent = '添加成品';
|
addItem.style.cursor = 'pointer';
|
addItem.addEventListener('click', () => {
|
showAddDialog(layoutIndex, rectIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
const mirrorXItem = document.createElement('div');
|
mirrorXItem.textContent = 'X镜像';
|
mirrorXItem.style.cursor = 'pointer';
|
mirrorXItem.addEventListener('click', () => {
|
mirrorLayoutX(layoutIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
const mirrorYItem = document.createElement('div');
|
mirrorYItem.textContent = 'Y镜像';
|
mirrorYItem.style.cursor = 'pointer';
|
mirrorYItem.addEventListener('click', () => {
|
mirrorLayoutY(layoutIndex);
|
document.body.removeChild(contextMenu);
|
});
|
|
contextMenu.appendChild(addItem);
|
contextMenu.appendChild(mirrorXItem);
|
contextMenu.appendChild(mirrorYItem);
|
|
document.body.appendChild(contextMenu);
|
};
|
|
const handleRectDragStart = (layoutIndex, rectIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[rectIndex];
|
if (rect.isRemain) return;
|
|
dragging.value = true;
|
dragRect.value = { layoutIndex, rectIndex };
|
dragStartPos.value = {
|
x: event.clientX,
|
y: event.clientY
|
};
|
};
|
|
const handleRectDragging = (event) => {
|
if (!dragging.value || !dragRect.value) return;
|
|
const layoutIndex = dragRect.value.layoutIndex;
|
const rectIndex = dragRect.value.rectIndex;
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[rectIndex];
|
const scale = Math.min(
|
(props.gw - 100) / layout.width,
|
(props.gh - 100) / layout.height
|
);
|
|
const deltaX = event.clientX - dragStartPos.value.x;
|
const deltaY = event.clientY - dragStartPos.value.y;
|
|
const newRect = { ...rect };
|
newRect.x += deltaX / scale;
|
newRect.y += deltaY / scale;
|
|
const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
|
let isValidMove = true;
|
|
otherRects.forEach(otherRect => {
|
if (checkOverlap(newRect, otherRect)) {
|
isValidMove = false;
|
}
|
});
|
|
if (newRect.x < 0 || newRect.y < 0 ||
|
newRect.x + newRect.w > layout.width ||
|
newRect.y + newRect.h > layout.height) {
|
isValidMove = false;
|
}
|
|
if (isValidMove) {
|
rect.x = newRect.x;
|
rect.y = newRect.y;
|
dragStartPos.value = {
|
x: event.clientX,
|
y: event.clientY
|
};
|
adjustGrayRectangles(layoutIndex);
|
}
|
};
|
|
const handleRectDragEnd = () => {
|
if (dragRect.value) {
|
const layoutIndex = dragRect.value.layoutIndex;
|
const rectIndex = dragRect.value.rectIndex;
|
const rect = layouts.value[layoutIndex].rects[rectIndex];
|
const layout = layouts.value[layoutIndex];
|
const scale = Math.min(
|
(props.gw - 100) / layout.width,
|
(props.gh - 100) / layout.height
|
);
|
|
rect.x = Math.round(rect.x);
|
rect.y = Math.round(rect.y);
|
adjustAlignmentPosition(layoutIndex, rectIndex);
|
adjustGrayRectangles(layoutIndex);
|
}
|
|
dragging.value = false;
|
dragRect.value = null;
|
};
|
|
const adjustAlignmentPosition = (layoutIndex, rectIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[rectIndex];
|
const otherRects = layout.rects.filter((r, i) => i !== rectIndex);
|
|
const threshold = Math.max(rect.w, rect.h) * 0.1;
|
|
otherRects.forEach(otherRect => {
|
// 水平对齐
|
if (Math.abs(rect.x - otherRect.x) < threshold) {
|
rect.x = Math.round((rect.x + otherRect.x) / 2);
|
}
|
// 水平对齐右侧边缘
|
if (Math.abs((rect.x + rect.w) - (otherRect.x + otherRect.w)) < threshold) {
|
rect.x = Math.round((otherRect.x + otherRect.w - rect.w));
|
}
|
// 垂直对齐
|
if (Math.abs(rect.y - otherRect.y) < threshold) {
|
rect.y = Math.round((rect.y + otherRect.y) / 2);
|
}
|
// 垂直对齐下边缘
|
if (Math.abs((rect.y + rect.h) - (otherRect.y + otherRect.h)) < threshold) {
|
rect.y = Math.round((otherRect.y + otherRect.h - rect.h));
|
}
|
});
|
|
// 确保矩形不会超出布局边界
|
rect.x = Math.max(0, rect.x);
|
rect.y = Math.max(0, rect.y);
|
rect.x = Math.min(rect.x, layout.width - rect.w);
|
rect.y = Math.min(rect.y, layout.height - rect.h);
|
|
// 调整后重新计算灰色余料
|
adjustGrayRectangles(layoutIndex);
|
};
|
|
const mergeAdjacentGrayRects = (rects) => {
|
const grayRects = rects.filter(r => r.isRemain);
|
let merged = [];
|
|
grayRects.sort((a, b) => {
|
if (a.x !== b.x) return a.x - b.x;
|
return a.y - b.y;
|
});
|
|
if (grayRects.length === 0) return;
|
|
merged.push({ ...grayRects[0] });
|
|
for (let i = 1; i < grayRects.length; i++) {
|
const last = merged[merged.length - 1];
|
const current = grayRects[i];
|
|
if (current.x === last.x + last.w &&
|
current.y === last.y &&
|
current.h === last.h) {
|
last.w += current.w;
|
last.x = Math.round(last.x);
|
last.y = Math.round(last.y);
|
last.w = Math.round(last.w);
|
last.h = Math.round(last.h);
|
} else if (current.y === last.y + last.h &&
|
current.x === last.x &&
|
current.w === last.w) {
|
last.h += current.h;
|
last.x = Math.round(last.x);
|
last.y = Math.round(last.y);
|
last.w = Math.round(last.w);
|
last.h = Math.round(last.h);
|
} else {
|
merged.push({
|
x: Math.round(current.x),
|
y: Math.round(current.y),
|
w: Math.round(current.w),
|
h: Math.round(current.h),
|
isRemain: true
|
});
|
}
|
}
|
|
const nonGray = rects.filter(r => !r.isRemain);
|
rects.splice(0, rects.length, ...nonGray, ...merged);
|
};
|
|
const adjustGrayRectangles = (layoutIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const rects = layout.rects;
|
const nonGrayRects = rects.filter(rect => !rect.isRemain);
|
|
const remainingAreas = calculateRemainingAreas(layout.width, layout.height, nonGrayRects);
|
|
const currentGrayRects = rects.filter(r => r.isRemain);
|
currentGrayRects.forEach((_, index) => {
|
if (index >= remainingAreas.length) {
|
rects.splice(index, 1);
|
}
|
});
|
|
remainingAreas.forEach((area, index) => {
|
if (index < currentGrayRects.length) {
|
currentGrayRects[index].x = Math.round(area.x);
|
currentGrayRects[index].y = Math.round(area.y);
|
currentGrayRects[index].w = Math.round(area.w);
|
currentGrayRects[index].h = Math.round(area.h);
|
} else {
|
rects.push({
|
x: Math.round(area.x),
|
y: Math.round(area.y),
|
w: Math.round(area.w),
|
h: Math.round(area.h),
|
isRemain: true
|
});
|
}
|
});
|
|
mergeAdjacentGrayRects(rects);
|
};
|
|
const rotateRect = (layoutIndex, rectIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[rectIndex];
|
const originalState = { ...rect };
|
|
const temp = rect.w;
|
rect.w = rect.h;
|
rect.h = temp;
|
|
const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
|
let isValidRotation = true;
|
|
otherRects.forEach(otherRect => {
|
if (checkOverlap(rect, otherRect)) {
|
isValidRotation = false;
|
}
|
});
|
|
if (rect.x + rect.w > layout.width || rect.y + rect.h > layout.height) {
|
isValidRotation = false;
|
}
|
|
if (isValidRotation) {
|
adjustGrayRectangles(layoutIndex);
|
} else {
|
rect.w = originalState.w;
|
rect.h = originalState.h;
|
ElMessage.warning('无法旋转,存在重叠或超出边界');
|
}
|
};
|
|
const moveRectAndRotate = (layoutIndex, rectIndex, direction) => {
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[rectIndex];
|
const grayRects = layout.rects.filter(r => r.isRemain);
|
|
const temp = rect.w;
|
rect.w = rect.h;
|
rect.h = temp;
|
|
const canPlace = grayRects.some(grayRect => {
|
return grayRect.w >= rect.w && grayRect.h >= rect.h;
|
});
|
|
if (!canPlace) {
|
const temp = rect.w;
|
rect.w = rect.h;
|
rect.h = temp;
|
ElMessage.warning('无法旋转,没有足够的空间');
|
return;
|
}
|
|
adjustGrayRectangles(layoutIndex);
|
moveRect(layoutIndex, rectIndex, direction);
|
};
|
|
const moveRect = (layoutIndex, rectIndex, direction) => {
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[rectIndex];
|
const originalState = { ...rect };
|
|
let maxStep = 0;
|
const obstacles = layout.rects.filter(r => r.isRemain || r !== rect);
|
|
switch (direction) {
|
case 'up':
|
maxStep = getAvailableSpaceUp(rect, layout, obstacles);
|
break;
|
case 'down':
|
maxStep = getAvailableSpaceDown(rect, layout, obstacles);
|
break;
|
case 'left':
|
maxStep = getAvailableSpaceLeft(rect, layout, obstacles);
|
break;
|
case 'right':
|
maxStep = getAvailableSpaceRight(rect, layout, obstacles);
|
break;
|
}
|
|
if (maxStep <= 0) {
|
ElMessage.warning('无法移动,没有足够的空间');
|
return;
|
}
|
|
switch (direction) {
|
case 'up':
|
rect.y -= maxStep;
|
break;
|
case 'down':
|
rect.y += maxStep;
|
break;
|
case 'left':
|
rect.x -= maxStep;
|
break;
|
case 'right':
|
rect.x += maxStep;
|
break;
|
}
|
|
const otherRects = layout.rects.filter(r => !r.isRemain && r !== rect);
|
let isValidMove = true;
|
|
otherRects.forEach(otherRect => {
|
if (checkOverlap(rect, otherRect)) {
|
isValidMove = false;
|
}
|
});
|
|
if (rect.x < 0 || rect.y < 0 ||
|
rect.x + rect.w > layout.width ||
|
rect.y + rect.h > layout.height) {
|
isValidMove = false;
|
}
|
|
if (isValidMove) {
|
adjustGrayRectangles(layoutIndex);
|
} else {
|
rect.x = originalState.x;
|
rect.y = originalState.y;
|
ElMessage.warning('无法移动,存在重叠或超出边界');
|
}
|
};
|
|
const deleteRect = (layoutIndex, rectIndex) => {
|
const layout = layouts.value[layoutIndex];
|
layout.rects.splice(rectIndex, 1);
|
adjustGrayRectangles(layoutIndex);
|
};
|
|
const checkOverlap = (rect1, rect2) => {
|
return !(rect1.x + rect1.w <= rect2.x ||
|
rect1.x >= rect2.x + rect2.w ||
|
rect1.y + rect1.h <= rect2.y ||
|
rect1.y >= rect2.y + rect2.h);
|
};
|
|
const calculateRemainingAreas = (totalWidth, totalHeight, obstacles) => {
|
let remaining = [{ x: 0, y: 0, w: totalWidth, h: totalHeight }];
|
obstacles.forEach(rect => {
|
remaining = cutRemainingAreas(remaining, rect);
|
});
|
return remaining;
|
};
|
|
const cutRemainingAreas = (remainingAreas, obstacle) => {
|
const newRemaining = [];
|
remainingAreas.forEach(area => {
|
if (checkOverlap(area, obstacle)) {
|
if (obstacle.x > area.x) {
|
newRemaining.push({
|
x: area.x,
|
y: area.y,
|
w: obstacle.x - area.x,
|
h: area.h
|
});
|
}
|
if (obstacle.x + obstacle.w < area.x + area.w) {
|
newRemaining.push({
|
x: obstacle.x + obstacle.w,
|
y: area.y,
|
w: area.w - (obstacle.x + obstacle.w - area.x),
|
h: area.h
|
});
|
}
|
if (obstacle.y > area.y) {
|
newRemaining.push({
|
x: area.x,
|
y: area.y,
|
w: area.w,
|
h: obstacle.y - area.y
|
});
|
}
|
if (obstacle.y + obstacle.h < area.y + area.h) {
|
newRemaining.push({
|
x: area.x,
|
y: obstacle.y + obstacle.h,
|
w: area.w,
|
h: area.h - (obstacle.y + obstacle.h - area.y)
|
});
|
}
|
} else {
|
newRemaining.push(area);
|
}
|
});
|
return newRemaining;
|
};
|
|
const getCurrentRectInfo = (layoutIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[focusIndex.value?.rectIndex || 0];
|
if (!rect) return '';
|
const totalRects = layouts.value.length;
|
const currentRectIndex = layoutIndex + 1;
|
const width = layout.width;
|
const height = layout.height;
|
const percentage = ((rect.w / layout.width) * 100).toFixed(1) + '%';
|
return `${currentRectIndex}/${totalRects} ${width}×${height} ×1 ${percentage}`;
|
};
|
|
const selectLayout = (layoutIndex) => {
|
selectedLayoutIndex.value = layoutIndex;
|
};
|
|
const updateLayout = () => {
|
if (!layoutPanel.value) return;
|
layouts.value = props.layoutData.Layouts;
|
};
|
|
const getAvailableSpaceUp = (rect, layout, obstacles) => {
|
let maxSpace = rect.y;
|
obstacles.forEach(obstacle => {
|
if (obstacle.y + obstacle.h < rect.y &&
|
obstacle.x <= rect.x + rect.w &&
|
obstacle.x + obstacle.w >= rect.x) {
|
maxSpace = Math.min(maxSpace, rect.y - (obstacle.y + obstacle.h));
|
}
|
});
|
return maxSpace;
|
};
|
|
const getAvailableSpaceDown = (rect, layout, obstacles) => {
|
let maxSpace = layout.height - (rect.y + rect.h);
|
obstacles.forEach(obstacle => {
|
if (obstacle.y > rect.y + rect.h &&
|
obstacle.x <= rect.x + rect.w &&
|
obstacle.x + obstacle.w >= rect.x) {
|
maxSpace = Math.min(maxSpace, obstacle.y - (rect.y + rect.h));
|
}
|
});
|
return maxSpace;
|
};
|
|
const getAvailableSpaceLeft = (rect, layout, obstacles) => {
|
let maxSpace = rect.x;
|
obstacles.forEach(obstacle => {
|
if (obstacle.x + obstacle.w < rect.x &&
|
obstacle.y <= rect.y + rect.h &&
|
obstacle.y + obstacle.h >= rect.y) {
|
maxSpace = Math.min(maxSpace, rect.x - (obstacle.x + obstacle.w));
|
}
|
});
|
return maxSpace;
|
};
|
|
const getAvailableSpaceRight = (rect, layout, obstacles) => {
|
let maxSpace = layout.width - (rect.x + rect.w);
|
obstacles.forEach(obstacle => {
|
if (obstacle.x > rect.x + rect.w &&
|
obstacle.y <= rect.y + rect.h &&
|
obstacle.y + obstacle.h >= rect.y) {
|
maxSpace = Math.min(maxSpace, obstacle.x - (rect.x + rect.w));
|
}
|
});
|
return maxSpace;
|
};
|
|
let moveInterval = null;
|
|
const handleKeyDown = (event) => {
|
if (!focusIndex.value) return;
|
|
const { layoutIndex, rectIndex } = focusIndex.value;
|
const layout = layouts.value[layoutIndex];
|
const rect = layout.rects[rectIndex];
|
const obstacles = layout.rects.filter(r => r.isRemain || r !== rect);
|
|
switch (event.key) {
|
case 'ArrowUp':
|
event.preventDefault();
|
if (!moveInterval) {
|
moveInterval = setInterval(() => {
|
moveRect(layoutIndex, rectIndex, 'up');
|
}, 50);
|
}
|
break;
|
case 'ArrowDown':
|
event.preventDefault();
|
if (!moveInterval) {
|
moveInterval = setInterval(() => {
|
moveRect(layoutIndex, rectIndex, 'down');
|
}, 50);
|
}
|
break;
|
case 'ArrowLeft':
|
event.preventDefault();
|
if (!moveInterval) {
|
moveInterval = setInterval(() => {
|
moveRect(layoutIndex, rectIndex, 'left');
|
}, 50);
|
}
|
break;
|
case 'ArrowRight':
|
event.preventDefault();
|
if (!moveInterval) {
|
moveInterval = setInterval(() => {
|
moveRect(layoutIndex, rectIndex, 'right');
|
}, 50);
|
}
|
break;
|
}
|
};
|
|
const handleKeyUp = (event) => {
|
if (event.key === 'ArrowUp' || event.key === 'ArrowDown' ||
|
event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
|
if (moveInterval) {
|
clearInterval(moveInterval);
|
moveInterval = null;
|
}
|
}
|
};
|
|
const mirrorLayoutX = (layoutIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const width = layout.width;
|
const rects = [...layout.rects]; // 创建副本避免直接修改
|
|
rects.forEach(rect => {
|
// 计算X镜像后的坐标
|
const newX = width - rect.x - rect.w;
|
const newY = rect.y;
|
|
// 更新矩形位置
|
rect.x = newX;
|
rect.y = newY;
|
});
|
|
// 更新布局
|
layout.rects = rects;
|
adjustGrayRectangles(layoutIndex);
|
};
|
|
const mirrorLayoutY = (layoutIndex) => {
|
const layout = layouts.value[layoutIndex];
|
const height = layout.height;
|
const rects = [...layout.rects]; // 创建副本避免直接修改
|
|
rects.forEach(rect => {
|
// 计算Y镜像后的坐标
|
const newX = rect.x;
|
const newY = height - rect.y - rect.h;
|
|
// 更新矩形位置
|
rect.y = newY;
|
});
|
|
// 更新布局
|
layout.rects = rects;
|
adjustGrayRectangles(layoutIndex);
|
};
|
|
onMounted(() => {
|
fetchSettings(username);
|
updateLayout();
|
|
selectedLayoutIndex.value = 0;
|
|
clickEventListener = (event) => {
|
const contextMenus = document.querySelectorAll('.context-menu');
|
if (contextMenus.length > 0) {
|
contextMenus.forEach(menu => menu.remove());
|
}
|
};
|
document.addEventListener('click', clickEventListener);
|
|
document.addEventListener('keydown', handleKeyDown);
|
document.addEventListener('keyup', handleKeyUp);
|
});
|
|
onUnmounted(() => {
|
rectsElements.value = {};
|
if (clickEventListener) {
|
document.removeEventListener('click', clickEventListener);
|
clickEventListener = null;
|
}
|
document.removeEventListener('keydown', handleKeyDown);
|
document.removeEventListener('keyup', handleKeyUp);
|
});
|
</script>
|
|
<style scoped>
|
.layout-wrapper {
|
position: relative;
|
margin-top: 50px;
|
}
|
|
.layout-rect {
|
user-select: none;
|
}
|
|
.layout-container {
|
position: relative;
|
overflow: visible;
|
}
|
|
.layout-info {
|
color: #444;
|
font-size: 12px;
|
background-color: #ffffff;
|
padding: 5px 10px;
|
border-radius: 3px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
font-weight: bold;
|
}
|
|
.rect-content {
|
display: flex;
|
flex-direction: column;
|
align-items: flex-start;
|
padding: 5px;
|
min-width: 60px;
|
min-height: 20px;
|
white-space: normal;
|
overflow-wrap: break-word;
|
}
|
|
.size {
|
font-size: 12px;
|
color: #444;
|
white-space: normal;
|
word-wrap: break-word;
|
}
|
|
.jia-hao, .liuchengka {
|
font-size: 14px;
|
font-weight: bold;
|
white-space: normal;
|
word-wrap: break-word;
|
}
|
|
.sidebar-item {
|
padding: 10px;
|
cursor: pointer;
|
}
|
|
.sidebar-item.selected {
|
background: #ddd;
|
}
|
|
.context-menu {
|
position: absolute;
|
background-color: #fff;
|
border: 1px solid #ccc;
|
padding: 5px;
|
z-index: 1001;
|
}
|
|
.context-menu div {
|
padding: 5px;
|
cursor: pointer;
|
}
|
|
.context-menu div:hover {
|
background-color: #f0f0f0;
|
}
|
</style>
|