You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 02:22:03