Vue实现Windows XP桌面:多选应用移动覆盖问题排查
在线Windows XP桌面多选应用移动时的覆盖问题
问题概述
开发在线版Windows XP桌面时,实现桌面网格多选应用移动功能遇到逻辑错误:仅在同时移动至少3个应用时触发——将3个以上应用移动到已有应用的位置时,会覆盖其中一个应用。调试发现明明条件返回true,却未进入对应的if语句。
现象展示
- 初始状态:桌面有5个应用
- 操作后:选中3个应用并移动到第1列的0、1、2行时,其中一个应用消失
技术实现说明
前端基于Vue开发,桌面网格用二维数组表示:空插槽为null,非空插槽存储应用的title、icon、selected、id等信息。通过updateGrid函数重新渲染网格,当应用要放置的插槽被占用时,会调用moveAppDown函数处理,但部分场景下该函数的if判断未触发,即使条件为true。
相关代码
桌面网格模板
<div class="desktop-row" v-for="(row, rowIndex) in desktopGridLayout" :key="rowIndex"> <div class="desktop-slot" v-for="(slot, slotIndex) in row" :key="slotIndex" ref="appSlots"> <div class="app-wrapper" v-if="slot" @mousedown="registerGrab($event, slot)" @touchstart="registerGrab($event, slot)" @click="selectApp($event, slot)" :class="{ selected: slot.selected }" :id="`slot${slot.id}`" @dblclick="slot.edit = true"> <div class="image-container"> <img :src="slot.icon" :alt="slot.title"> <div class="select-overlay" :style="{ maskImage: `url(${slot.icon})` }"> </div> </div> <input type="text" v-if="slot.edit" v-model="slot.title"> <h3 v-else>{{slot.title}}</h3> </div> </div> </div>
应用数据示例
apps: [ { id: 0, title: "Kosz bardzo długa nazwa", icon: "icons/Recycle Bin (empty).png", row: 0, col: 0, selected: false, edit: false, }, { id: 1, title: "Mój komputer", icon: "icons/My Computer.png", row: 1, col: 0, selected: false, edit: false, } ],
网格更新函数
updateGrid(numOfRows = this.desktopGridLayout.length, numOfCols = this.desktopGridLayout[0].length) { const newGrid = [] for (let i = 0; i < numOfRows; i++) { newGrid.push([]) for (let j = 0; j < numOfCols; j++) { newGrid[i].push(null) } } this.apps.sort((a,b) => { let rowDiff = a.row - b.row let colDiff = a.col - b.col if (colDiff === 0) { return rowDiff } return colDiff }).sort(function (x, y) { return (x === y) ? 0 : x ? 1 : -1; }).forEach(app => { let row = app.row let col = app.col if (row >= newGrid.length ) { row = newGrid.length - 1 } else if (row < 0) { row = 0 } if (col >= newGrid[0].length ) { col = newGrid[0].length - 1 } else if (col < 0) { col = 0 } if (newGrid[row][col] !== null) { this.moveAppDown(row, col, newGrid) } console.log(`Selected app finished on (${row}, ${col}) ${newGrid[row][col]}`) newGrid[row][col] = { title: app.title, icon: app.icon, id: app.id, selected: app.selected } this.apps[this.apps.indexOf(app)] = { ...app, row: row, col: col } }) this.desktopGridLayout = newGrid }
应用下移处理函数
moveAppDown(rowIndex, colIndex, grid = this.desktopGridLayout) { let newColIndex = colIndex; let newRowIndex = rowIndex + 1; if (newRowIndex >= grid.length) { newRowIndex = 0 newColIndex = colIndex + 1 if (newColIndex >= grid[0].length) { const emptySlotRow = grid.find(row => row.some(col => col === null)) newRowIndex = grid.indexOf(emptySlotRow) newColIndex = grid[newRowIndex].indexOf(null) } } if (grid[newRowIndex][newColIndex] !== null) { console.log("entering reccurention") this.moveAppDown(newRowIndex, newColIndex) } const app = this.apps.filter(app => app ? app.id === grid[rowIndex][colIndex].id : false)[0] console.log(`Moved app finished on (${newRowIndex}, ${newColIndex})`, grid[newRowIndex][newColIndex], grid[newRowIndex][newColIndex] !== null) grid[newRowIndex][newColIndex] = grid[rowIndex][colIndex] grid[rowIndex][colIndex] = null this.apps[this.apps.indexOf(app)] = { ...app, row: newRowIndex, col: newColIndex } }
拖拽处理函数
handleGrab(mouseX, mouseY) { const app = this.apps.filter(app => app.id === this.grabActive)[0] const appWrapper = app.element.querySelector(".app-wrapper") appWrapper.style.position = "absolute"; appWrapper.style.top = mouseY + this.grabOffset.y + "px" appWrapper.style.left = mouseX + this.grabOffset.x + "px" appWrapper.style.zIndex = 500 // 同步移动选中的其他应用 this.apps.filter(app => app.selected && app.id !== this.grabActive).forEach(selectedApp => { const yOffset = selectedApp.element.getBoundingClientRect().y - app.element.getBoundingClientRect().y const xOffset = selectedApp.element.getBoundingClientRect().x - app.element.getBoundingClientRect().x const selectedAppWrapper = selectedApp.element.querySelector(".app-wrapper") selectedAppWrapper.style.position = "absolute"; selectedAppWrapper.style.top = mouseY + this.grabOffset.y + yOffset + "px" selectedAppWrapper.style.left = mouseX + this.grabOffset.x + xOffset + "px" selectedAppWrapper.style.zIndex = 500 }) const xDiff = mouseX - this.mouseStartingPosition.x const yDiff = mouseY - this.mouseStartingPosition.y if (Math.abs(xDiff) > 10 || Math.abs(yDiff) > 10) { this.blockNextClick = true let nextRowIndex = app.row let nextColIndex = app.col if (xDiff < -((this.iconSize.width / 5) * 3 + this.iconSize.gap)) { this.mouseStartingPosition.x -= this.iconSize.width + this.iconSize.gap nextColIndex = nextColIndex - 1 > 0 ? nextColIndex - 1 : 0 } else if (xDiff > ((this.iconSize.width / 5) * 3 + this.iconSize.gap)) { this.mouseStartingPosition.x += this.iconSize.width + this.iconSize.gap nextColIndex = nextColIndex + 1 >= this.desktopGridLayout[0].length ? this.desktopGridLayout[0].length - 1 : nextColIndex + 1 } if (yDiff < -((this.iconSize.height / 5) * 3 + this.iconSize.gap)) { this.mouseStartingPosition.y -= this.iconSize.height + this.iconSize.gap nextRowIndex = nextRowIndex - 1 > 0 ? nextRowIndex - 1 : 0 } else if (yDiff > ((this.iconSize.height / 5) * 3 + this.iconSize.gap)) { this.mouseStartingPosition.y += this.iconSize.height + this.iconSize.gap nextRowIndex = nextRowIndex + 1 >= this.desktopGridLayout.length ? this.desktopGridLayout.length - 1 : nextRowIndex + 1 } if(app.row !== nextRowIndex || app.col !== nextColIndex) { // 更新选中应用的位置 this.apps = this.apps.map(selectedApp => { let nextSelectedRowIndex = nextRowIndex - app.row let nextSelectedColIndex = nextColIndex - app.col return { ...selectedApp, row: selectedApp.selected || selectedApp.id === app.id ? selectedApp.row + nextSelectedRowIndex : selectedApp.row, col: selectedApp.selected || selectedApp.id === app.id ? selectedApp.col + nextSelectedColIndex : selectedApp.col } }) } } }
补充信息
完整组件及项目可查看GitHub仓库
调试时的控制台截图:
内容的提问来源于stack exchange,提问作者Wiktor Fajkowski
相关产品推荐
相关产品推荐

