保存一下

This commit is contained in:
2025-08-25 16:42:07 +08:00
parent 50a06568d4
commit 95a73ef524
10 changed files with 149 additions and 52 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title> <title>vue-desktop</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -1,7 +1,15 @@
/**
* 桌面应用图标信息
*/
export interface IDesktopAppIcon { export interface IDesktopAppIcon {
/** 图标name */
name: string; name: string;
/** 图标 */
icon: string; icon: string;
/** 图标路径 */
path: string; path: string;
col: number; /** 图标在grid布局中的列 */
row: number; x: number;
/** 图标在grid布局中的行 */
y: number;
} }

View File

@@ -3,9 +3,9 @@
*/ */
export interface IGridTemplateParams { export interface IGridTemplateParams {
/** 单元格预设宽度 */ /** 单元格预设宽度 */
cellExpectWidth: number readonly cellExpectWidth: number
/** 单元格预设高度 */ /** 单元格预设高度 */
cellExpectHeight: number readonly cellExpectHeight: number
/** 单元格实际宽度 */ /** 单元格实际宽度 */
cellRealWidth: number cellRealWidth: number
/** 单元格实际高度 */ /** 单元格实际高度 */

View File

@@ -4,11 +4,11 @@
class="w-full h-full pos-relative" class="w-full h-full pos-relative"
> >
<div class="desktop-root" @contextmenu="onContextMenu"> <div class="desktop-root" @contextmenu="onContextMenu">
<div class="w-full h-full flex-1 p-2 pos-relative"> <div class="desktop-bg">
<div class="desktop-container" <div class="desktop-container"
:style="gridStyle"> :style="gridStyle">
<AppIcon v-for="(appIcon, i) in appIconsRef" :key="i" <AppIcon v-for="(appIcon, i) in appIconsRef" :key="i"
:icon="appIcon" :gridTemplate="gridTemplate" /> :iconInfo="appIcon" :gridTemplate="gridTemplate" />
</div> </div>
</div> </div>
<div class="task-bar"></div> <div class="task-bar"></div>
@@ -22,8 +22,9 @@ import XSystem from '@/core/XSystem.ts'
import { notificationApi } from '@/core/common/naive-ui/discrete-api.ts' import { notificationApi } from '@/core/common/naive-ui/discrete-api.ts'
import { configProviderProps } from '@/core/common/naive-ui/theme.ts' import { configProviderProps } from '@/core/common/naive-ui/theme.ts'
import { DesktopEventEnum } from '@/core/events/EventTypes.ts' import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
import { useDesktopInit } from '@/core/desktop/ui/useDesktopInit.ts' import { useDesktopInit } from '@/core/desktop/ui/hooks/useDesktopInit.ts'
import AppIcon from '@/core/desktop/ui/components/AppIcon.vue' import AppIcon from '@/core/desktop/ui/components/AppIcon.vue'
import { watch } from 'vue'
const props = defineProps<{ process: DesktopProcess }>() const props = defineProps<{ process: DesktopProcess }>()
@@ -47,15 +48,25 @@ const onContextMenu = (e: MouseEvent) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
$taskBarHeight: 40px;
.desktop-root { .desktop-root {
@apply w-full h-full flex flex-col; @apply w-full h-full flex flex-col;
.desktop-bg {
@apply w-full h-full flex-1 p-2 pos-relative;
background-image: url("imgs/desktop-bg-2.jpeg");
background-repeat: no-repeat;
background-size: cover;
height: calc(100% - #{$taskBarHeight});
}
.desktop-container { .desktop-container {
@apply w-full h-full flex-1 grid grid-auto-flow-col pos-relative; @apply w-full h-full flex-1 grid grid-auto-flow-col pos-relative;
} }
.task-bar { .task-bar {
@apply w-full h-[40px] bg-gray-200; @apply w-full bg-gray-200;
height: $taskBarHeight;
flex-shrink: 0; flex-shrink: 0;
} }
} }

View File

@@ -1,20 +1,22 @@
<template> <template>
<div <div
class="icon-container" class="icon-container"
:style="`grid-column: ${icon.col}/${icon.col + 1};grid-row: ${icon.row}/${icon.row + 1};`" :style="`grid-column: ${iconInfo.x}/${iconInfo.x + 1};grid-row: ${iconInfo.y}/${iconInfo.y + 1};`"
draggable="true" draggable="true"
@dragstart="onDragStart" @dragstart="onDragStart"
@dragend="onDragEnd" @dragend="onDragEnd"
> >
{{ icon.name }} {{ iconInfo.name }}
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { IDesktopAppIcon } from '@/core/desktop/types/IDesktopAppIcon.ts' import type { IDesktopAppIcon } from '@/core/desktop/types/IDesktopAppIcon.ts'
import type { IGridTemplateParams } from '@/core/desktop/types/IGridTemplateParams.ts' import type { IGridTemplateParams } from '@/core/desktop/types/IGridTemplateParams.ts'
import XSystem from '@/core/XSystem.ts'
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
const { icon, gridTemplate } = defineProps<{ icon: IDesktopAppIcon, gridTemplate: IGridTemplateParams }>() const { iconInfo, gridTemplate } = defineProps<{ iconInfo: IDesktopAppIcon, gridTemplate: IGridTemplateParams }>()
const onDragStart = (e: DragEvent) => {} const onDragStart = (e: DragEvent) => {}
@@ -38,8 +40,10 @@ const onDragEnd = (e: DragEvent) => {
const gridX = Math.ceil(mouseX / gridTemplate.cellRealWidth) const gridX = Math.ceil(mouseX / gridTemplate.cellRealWidth)
const gridY = Math.ceil(mouseY / gridTemplate.cellRealHeight) const gridY = Math.ceil(mouseY / gridTemplate.cellRealHeight)
icon.col = gridX iconInfo.x = gridX
icon.row = gridY iconInfo.y = gridY
XSystem.instance.eventManages.notifyEvent(DesktopEventEnum.onDesktopAppIconPos, iconInfo)
} }
</script> </script>
@@ -47,6 +51,6 @@ const onDragEnd = (e: DragEvent) => {
.icon-container { .icon-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@apply flex flex-col items-center justify-center rounded bg-gray-200; @apply flex flex-col items-center justify-center bg-gray-200;
} }
</style> </style>

View File

@@ -6,7 +6,10 @@ import {
onMounted, onMounted,
onUnmounted, onUnmounted,
reactive, reactive,
ref,
toRaw,
toRefs, toRefs,
toValue,
useTemplateRef, useTemplateRef,
watch, watch,
watchEffect, watchEffect,
@@ -58,27 +61,32 @@ export function useDesktopInit(containerStr: string) {
// 有桌面图标的app // 有桌面图标的app
const appInfos = XSystem.instance.processManages.processInfos.filter(processInfo => !processInfo.isJustProcess) const appInfos = XSystem.instance.processManages.processInfos.filter(processInfo => !processInfo.isJustProcess)
const oldAppIcons: IDesktopAppIcon[] = JSON.parse(localStorage.getItem('desktopAppIconInfo') || '[]')
const appIcons: IDesktopAppIcon[] = appInfos.map((processInfo, index) => { const appIcons: IDesktopAppIcon[] = appInfos.map((processInfo, index) => {
const oldAppIcon = oldAppIcons.find(oldAppIcon => oldAppIcon.name === processInfo.name)
// 左上角坐标原点,从上到下从左到右 索引从1开始 // 左上角坐标原点,从上到下从左到右 索引从1开始
const x = Math.floor(index / gridTemplate.rowCount) + 1 const x = Math.floor(index / gridTemplate.rowCount) + 1
const y = index % gridTemplate.rowCount + 1 const y = index % gridTemplate.rowCount + 1
return { return {
name: processInfo.name, name: processInfo.name,
icon: processInfo.icon, icon: processInfo.icon,
path: processInfo.startName, path: processInfo.startName,
col: x, x: oldAppIcon ? oldAppIcon.x : x,
row: y y: oldAppIcon ? oldAppIcon.y : y
} }
}) })
const appIconsRef = reactive(appIcons)
watch(() => [gridTemplate.rowCount, gridTemplate.colCount], () => { const appIconsRef = ref(appIcons)
appIconsRef.forEach((appIcon, index) => {
const x = Math.floor(index / gridTemplate.rowCount) + 1 watch(() => [gridTemplate.rowCount, gridTemplate.colCount], ([nRows, nCols], [oRows, oCols]) => {
const y = index % gridTemplate.rowCount + 1 if (oCols == 1 && oRows == 1) return
appIcon.col = x appIconsRef.value = rearrangeIcons(toRaw(appIconsRef.value), nCols, nRows, oCols, oRows)
appIcon.row = y
}) })
XSystem.instance.eventManages.addEventListener(DesktopEventEnum.onDesktopAppIconPos, (iconInfo) => {
localStorage.setItem('desktopAppIconInfo', JSON.stringify(toValue(appIconsRef.value)))
}) })
return { return {
@@ -87,3 +95,62 @@ export function useDesktopInit(containerStr: string) {
gridStyle gridStyle
} }
} }
/**
*
* @param appIcons
* @param newCols
* @param newRows
* @param oldCols
* @param oldRows
*/
function rearrangeIcons(
appIcons: IDesktopAppIcon[],
newCols: number,
newRows: number,
oldCols: number,
oldRows: number
): IDesktopAppIcon[] {
if (oldCols === newCols && oldRows === newRows) {
return appIcons;
}
const occupied = new Set<string>();
function key(x: number, y: number) {
return `${x},${y}`;
}
const result: IDesktopAppIcon[] = []
const exceed: IDesktopAppIcon[] = []
for (const appIcon of appIcons) {
const { x, y } = appIcon;
if (x <= newCols && y <= newRows) {
if (!occupied.has(key(x, y))) {
occupied.add(key(x, y))
result.push({ ...appIcon, x, y })
}
} else {
exceed.push(appIcon)
}
}
for (const appIcon of exceed) {
// 最后格子也被占 → 从 (1,1) 开始找空位
let placed = false;
for (let c = 1; c <= newCols; c++) {
for (let r = 1; r <= newRows; r++) {
if (!occupied.has(key(c, r))) {
occupied.add(key(c, r));
result.push({ ...appIcon, x: c, y: r });
placed = true;
break;
}
}
if (placed) break;
}
}
return result;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,17 +1,5 @@
import type { IEventMap } from '@/core/events/IEventBuilder.ts' import type { IEventMap } from '@/core/events/IEventBuilder.ts'
import type { IDesktopAppIcon } from '@/core/desktop/types/IDesktopAppIcon.ts'
/**
* 桌面进程事件枚举
* @description
* <p>onDesktopRootDomResize - 桌面根dom尺寸改变</p>
* <p>onDesktopProcessInitialize - 桌面进程初始化完成</p>
*/
export enum DesktopEventEnum {
/** 桌面进程初始化完成 */
onDesktopRootDomResize = 'onDesktopRootDomResize',
/** 桌面进程初始化完成 */
onDesktopProcessInitialize = 'onDesktopProcessInitialize'
}
/** /**
* 基础系统进程事件枚举 * 基础系统进程事件枚举
@@ -28,19 +16,6 @@ export enum BasicSystemEventEnum {
onThemeChange = 'onThemeChange' onThemeChange = 'onThemeChange'
} }
/**
* 桌面进程的事件
* @description
* <p>onDesktopRootDomResize - 桌面根dom尺寸改变</p>
* <p>onDesktopProcessInitialize - 桌面进程初始化完成</p>
*/
export interface IDesktopEvent extends IEventMap {
/** 桌面根dom尺寸改变 */
[DesktopEventEnum.onDesktopRootDomResize]: (width: number, height: number) => void
/** 桌面进程初始化完成 */
[DesktopEventEnum.onDesktopProcessInitialize]: () => void
}
/** /**
* 系统进程的事件 * 系统进程的事件
* @description * @description
@@ -54,4 +29,35 @@ export interface IBasicSystemEvent extends IEventMap {
[BasicSystemEventEnum.onThemeChange]: (theme: string) => void [BasicSystemEventEnum.onThemeChange]: (theme: string) => void
} }
/**
* 桌面进程事件枚举
* @description
* <p>onDesktopRootDomResize - 桌面根dom尺寸改变</p>
* <p>onDesktopProcessInitialize - 桌面进程初始化完成</p>
*/
export enum DesktopEventEnum {
/** 桌面进程初始化完成 */
onDesktopRootDomResize = 'onDesktopRootDomResize',
/** 桌面进程初始化完成 */
onDesktopProcessInitialize = 'onDesktopProcessInitialize',
/** 桌面应用图标位置改变 */
onDesktopAppIconPos = 'onDesktopAppIconPos'
}
/**
* 桌面进程的事件
* @description
* <p>onDesktopRootDomResize - 桌面根dom尺寸改变</p>
* <p>onDesktopProcessInitialize - 桌面进程初始化完成</p>
*/
export interface IDesktopEvent extends IEventMap {
/** 桌面根dom尺寸改变 */
[DesktopEventEnum.onDesktopRootDomResize]: (width: number, height: number) => void
/** 桌面进程初始化完成 */
[DesktopEventEnum.onDesktopProcessInitialize]: () => void
/** 桌面应用图标位置改变 */
[DesktopEventEnum.onDesktopAppIconPos]: (iconInfo: IDesktopAppIcon) => void
}
export interface IAllEvent extends IDesktopEvent, IBasicSystemEvent {} export interface IAllEvent extends IDesktopEvent, IBasicSystemEvent {}

View File

@@ -16,5 +16,6 @@
"noUnusedParameters": false, // 检查未使用的参数 "noUnusedParameters": false, // 检查未使用的参数
"noImplicitReturns": true, // 检查函数所有路径是否都有返回值 "noImplicitReturns": true, // 检查函数所有路径是否都有返回值
"noImplicitOverride": true, // 检查子类是否正确覆盖了父类方法 "noImplicitOverride": true, // 检查子类是否正确覆盖了父类方法
"allowSyntheticDefaultImports": true // 允许使用默认导入
} }
} }