| New file |
| | |
| | | <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"
|
| | | >
|
| | | <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
|
| | |
|
| | |
|
| | | 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); // 新增:控制jia-hao的显示状态
|
| | | 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);
|
| | | console.log(parsedData.display.frameNumber)
|
| | | if (parsedData.display && parsedData.display.frameNumber) {
|
| | | showJiaHao.value = parsedData.display.frameNumber;
|
| | |
|
| | | }
|
| | | if (parsedData.display && parsedData.display.orderNumber) {
|
| | | showProcessId.value = parsedData.display.orderNumber;
|
| | | |
| | | }
|
| | | if (parsedData.display ) {
|
| | | themeColor.value = parsedData.display.themeColor;
|
| | | |
| | | }
|
| | | |
| | | |
| | | |
| | | console.log( parsedData);
|
| | | } 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];
|
| | | layout.rects.push(newRect);
|
| | | adjustGrayRectangles(layoutIndex);
|
| | | };
|
| | |
|
| | | const layoutContainerStyle = (layoutIndex) => {
|
| | | const containerWidth = (props.gw - 210) / 2;
|
| | | const containerHeight = (props.gh - 100) / 3;
|
| | | 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);
|
| | | });
|
| | |
|
| | | 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);
|
| | |
|
| | | 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);
|
| | | });
|
| | |
|
| | | contextMenu.appendChild(addItem);
|
| | |
|
| | | 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) {
|
| | | // 不调整宽度
|
| | | }
|
| | | 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) {
|
| | | // 不调整高度
|
| | | }
|
| | | });
|
| | | };
|
| | |
|
| | | 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 };
|
| | |
|
| | | // 计算剩余空间
|
| | | const remainingAreas = calculateRemainingAreas(layout.width, layout.height, layout.rects.filter(r => !r.isRemain));
|
| | | |
| | | // 根据方向计算可移动的最大步长
|
| | | let maxStep = 0;
|
| | | switch (direction) {
|
| | | case 'up':
|
| | | maxStep = rect.y;
|
| | | break;
|
| | | case 'down':
|
| | | maxStep = layout.height - (rect.y + rect.h);
|
| | | break;
|
| | | case 'left':
|
| | | maxStep = rect.x;
|
| | | break;
|
| | | case 'right':
|
| | | maxStep = layout.width - (rect.x + rect.w);
|
| | | break;
|
| | | }
|
| | |
|
| | | // 移动步长,根据剩余空间动态调整
|
| | | const stepSize = maxStep;
|
| | | const actualStep = Math.min(maxStep, stepSize);
|
| | |
|
| | | // 移动矩形
|
| | | switch (direction) {
|
| | | case 'up':
|
| | | rect.y -= actualStep;
|
| | | break;
|
| | | case 'down':
|
| | | rect.y += actualStep;
|
| | | break;
|
| | | case 'left':
|
| | | rect.x -= actualStep;
|
| | | break;
|
| | | case 'right':
|
| | | rect.x +=actualStep;
|
| | | 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;
|
| | | };
|
| | |
|
| | |
|
| | | let clickEventListener = null;
|
| | | onMounted(() => {
|
| | | fetchSettings(username);
|
| | | setTimeout(updateLayout, 500);
|
| | |
|
| | | selectedLayoutIndex.value = 0;
|
| | |
|
| | | // 添加全局点击事件监听器
|
| | | clickEventListener = (event) => {
|
| | | // 检查是否存在右键菜单
|
| | | const contextMenus = document.querySelectorAll('.context-menu');
|
| | | if (contextMenus.length > 0) {
|
| | | // 移除所有右键菜单
|
| | | contextMenus.forEach(menu => menu.remove());
|
| | | }
|
| | | };
|
| | | document.addEventListener('click', clickEventListener);
|
| | |
|
| | | });
|
| | |
|
| | | onUnmounted(() => {
|
| | | rectsElements.value = {};
|
| | | // 移除全局点击事件监听器
|
| | | if (clickEventListener) {
|
| | | document.removeEventListener('click', clickEventListener);
|
| | | }
|
| | | });
|
| | | </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: grid;
|
| | | grid-template-columns: 1fr;
|
| | | grid-template-rows: 1fr;
|
| | | padding: 5px;
|
| | | }
|
| | |
|
| | | .size {
|
| | | grid-row: 1;
|
| | | grid-column: 1;
|
| | | color: #444;
|
| | | font-size: 12px;
|
| | | }
|
| | |
|
| | | .jia-hao .liuchengka {
|
| | | grid-row: 2;
|
| | | grid-column: 1;
|
| | | margin: auto;
|
| | | font-size: 14px;
|
| | | font-weight: bold;
|
| | | }
|
| | |
|
| | | .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>
|