保存一下

This commit is contained in:
2025-08-26 17:04:31 +08:00
parent 26f68d89bb
commit 89b5e9e0be
9 changed files with 292 additions and 42 deletions

View File

@@ -1,6 +1,10 @@
# vue-desktop
This template should help get you started developing with Vue 3 in Vite.
浏览器Chrome 84+、Edge 84+、Firefox 79+、Safari 14+
Node.jsv14+
不支持IE
## Recommended IDE Setup

View File

@@ -9,6 +9,7 @@ import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
import type { IProcessManage } from '@/core/process/IProcessManage.ts'
import type { IProcess } from '@/core/process/IProcess.ts'
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
export default class XSystem {
private static _instance: XSystem = new XSystem()
@@ -40,10 +41,10 @@ export default class XSystem {
// 运行进程
public async run<T extends IProcess = IProcess>(
proc: string | ProcessInfoImpl,
constructor?: new (info: ProcessInfoImpl) => T,
proc: string | IProcessInfo,
constructor?: new (info: IProcessInfo) => T,
): Promise<T> {
let info = typeof proc === 'string' ? this._processManage.findProcessInfoByName(proc) : proc
let info = typeof proc === 'string' ? this._processManage.findProcessInfoByName(proc)! : proc
if (isUndefined(info)) {
throw new Error(`未找到进程信息:${proc}`)
}

View File

@@ -7,6 +7,7 @@ import DesktopComponent from '@/core/desktop/ui/DesktopComponent.vue'
import { naiveUi } from '@/core/common/naive-ui/components.ts'
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
import { debounce } from 'lodash'
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
export class DesktopProcess extends ProcessImpl {
private _desktopRootDom: HTMLElement;
@@ -66,7 +67,7 @@ export class DesktopProcess extends ProcessImpl {
return XSystem.instance.eventManage;
}
constructor(info: ProcessInfoImpl) {
constructor(info: IProcessInfo) {
super(info)
console.log('DesktopProcess')
}

View File

@@ -79,10 +79,14 @@ export function useDesktopInit(containerStr: string) {
})
const appIconsRef = ref(appIcons)
const exceedApp = ref<IDesktopAppIcon[]>([])
watch(() => [gridTemplate.rowCount, gridTemplate.colCount], ([nRows, nCols], [oRows, oCols]) => {
if (oCols == 1 && oRows == 1) return
appIconsRef.value = rearrangeIcons(toRaw(appIconsRef.value), nCols, nRows, oCols, oRows)
watch(() => [gridTemplate.colCount, gridTemplate.rowCount], ([nCols, nRows], [oCols, oRows]) => {
// if (oCols == 1 && oRows == 1) return
if (oCols === nCols && oRows === nRows) return
const { appIcons, hideAppIcons } = rearrangeIcons(toRaw(appIconsRef.value), nCols, nRows)
appIconsRef.value = appIcons
exceedApp.value = hideAppIcons
})
XSystem.instance.eventManage.addEventListener(DesktopEventEnum.onDesktopAppIconPos, (iconInfo) => {
@@ -98,59 +102,69 @@ export function useDesktopInit(containerStr: string) {
/**
* 重新安排图标位置
* @param appIcons 图标信息
* @param newCols 新的列数
* @param newRows 新的行数
* @param oldCols 旧的列数
* @param oldRows 旧的行数
* @param appIconInfos 图标信息
* @param maxCol 列数
* @param maxRow 行数
*/
function rearrangeIcons(
appIcons: IDesktopAppIcon[],
newCols: number,
newRows: number,
oldCols: number,
oldRows: number
): IDesktopAppIcon[] {
if (oldCols === newCols && oldRows === newRows) {
return appIcons;
}
appIconInfos: IDesktopAppIcon[],
maxCol: number,
maxRow: number
): IRearrangeInfo {
const occupied = new Set<string>();
function key(x: number, y: number) {
return `${x},${y}`;
}
const result: IDesktopAppIcon[] = []
const exceed: IDesktopAppIcon[] = []
const appIcons: IDesktopAppIcon[] = []
const hideAppIcons: IDesktopAppIcon[] = []
const temp: IDesktopAppIcon[] = []
for (const appIcon of appIcons) {
for (const appIcon of appIconInfos) {
const { x, y } = appIcon;
if (x <= newCols && y <= newRows) {
if (x <= maxCol && y <= maxRow) {
if (!occupied.has(key(x, y))) {
occupied.add(key(x, y))
result.push({ ...appIcon, x, y })
appIcons.push({ ...appIcon, x, y })
}
} else {
exceed.push(appIcon)
temp.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;
const max = maxCol * maxRow
for (const appIcon of temp) {
if (appIcons.length < max) {
// 最后格子也被占 → 从 (1,1) 开始找空位
let placed = false;
for (let c = 1; c <= maxCol; c++) {
for (let r = 1; r <= maxRow; r++) {
if (!occupied.has(key(c, r))) {
occupied.add(key(c, r));
appIcons.push({ ...appIcon, x: c, y: r });
placed = true;
break;
}
}
if (placed) break;
}
if (placed) break;
} else {
// 放不下了
hideAppIcons.push(appIcon)
}
}
return result;
return {
appIcons,
hideAppIcons
};
}
interface IRearrangeInfo {
/** 正常的桌面图标信息 */
appIcons: IDesktopAppIcon[];
/** 隐藏的桌面图标信息(超出屏幕显示的) */
hideAppIcons: IDesktopAppIcon[];
}

View File

@@ -0,0 +1,158 @@
type Listener<T> = (state: T) => void
type KeyListener<T, K extends keyof T> = (changed: Pick<T, K>) => void
/**
* 从给定类型 T 中排除所有函数类型的属性,只保留非函数类型的属性
* @template T - 需要处理的原始类型
* @returns 一个新的类型,该类型只包含原始类型中非函数类型的属性
*/
type NonFunctionProperties<T> = {
[K in keyof T]: T[K] extends Function ? never : T[K]
}
/**
* 创建一个可观察对象,用于管理状态和事件。
* @template T - 需要处理的状态类型
* @example
* interface AppState {
* count: number
* isOpen: boolean
* title: string
* }
*
* const app = new Observable<AppState>({
* count: 0,
* isOpen: false,
* title: "Demo"
* })
*
* // 全量订阅
* app.subscribe(state => console.log("全量:", state))
*
* // 单字段订阅
* app.subscribeKey("count", changes => console.log("count 变化:", changes))
*
* // 多字段订阅
* app.subscribeKey(["count", "isOpen"], changes =>
* console.log("count/isOpen 回调:", changes)
* )
*
* // 直接修改属性
* app.count = 1
* app.isOpen = true
* app.title = "New Title"
*
* // 输出示例:
* // 全量: { count: 1, isOpen: true, title: 'New Title' }
* // count 变化: { count: 1 }
* // count/isOpen 回调: { count: 1, isOpen: true }
*/
export class Observable<T extends object> {
private listeners = new Set<WeakRef<Listener<T>>>()
private keyListeners = new Map<keyof T, Set<WeakRef<Function>>>()
private registry = new FinalizationRegistry((ref: WeakRef<any>) => {
this.listeners.delete(ref)
this.keyListeners.forEach(set => set.delete(ref))
})
private pendingKeys = new Set<keyof T>()
private notifyScheduled = false
constructor(initialState: NonFunctionProperties<T>) {
Object.assign(this, initialState)
// Proxy 拦截属性修改
return new Proxy(this, {
set: (target, prop: string, value) => {
const key = prop as keyof T
(target as any)[key] = value
// 每次赋值都加入 pendingKeys
this.pendingKeys.add(key)
this.scheduleNotify()
return true
},
get: (target, prop: string) => (target as any)[prop]
})
}
/** 安排微任务通知 */
private scheduleNotify() {
if (!this.notifyScheduled) {
this.notifyScheduled = true
Promise.resolve().then(() => this.flushNotify())
}
}
/** 执行通知:全量 + 单/多字段通知 */
private flushNotify() {
const keys = Array.from(this.pendingKeys)
this.pendingKeys.clear()
this.notifyScheduled = false
// 全量通知一次
for (const ref of this.listeners) {
const fn = ref.deref()
if (fn) fn(this as unknown as T)
else this.listeners.delete(ref)
}
// 单/多字段通知(合并函数)
const fnMap = new Map<Function, (keyof T)[]>()
for (const key of keys) {
const set = this.keyListeners.get(key)
if (!set) continue
for (const ref of set) {
const fn = ref.deref()
if (!fn) {
set.delete(ref)
continue
}
if (!fnMap.has(fn)) fnMap.set(fn, [])
const arr = fnMap.get(fn)!
if (!arr.includes(key)) arr.push(key)
}
}
// 调用每个函数一次,并返回订阅字段的当前值
fnMap.forEach((subKeys, fn) => {
const result: Partial<T> = {}
subKeys.forEach(k => (result[k] = (this as any)[k]))
fn(result)
})
}
/** 全量订阅 */
subscribe(fn: Listener<T>) {
const ref = new WeakRef(fn)
this.listeners.add(ref)
this.registry.register(fn, ref)
return () => {
this.listeners.delete(ref)
this.registry.unregister(fn)
}
}
/** 单字段或多字段订阅 */
subscribeKey<K extends keyof T>(keys: K | K[], fn: KeyListener<T, K>) {
const keyArray = Array.isArray(keys) ? keys : [keys]
const refs: WeakRef<Function>[] = []
for (const key of keyArray) {
if (!this.keyListeners.has(key)) this.keyListeners.set(key, new Set())
const ref = new WeakRef(fn)
this.keyListeners.get(key)!.add(ref)
this.registry.register(fn, ref)
refs.push(ref)
}
return () => {
for (let i = 0; i < keyArray.length; i++) {
const set = this.keyListeners.get(keyArray[i])
if (set) set.delete(refs[i])
}
this.registry.unregister(fn)
}
}
}

View File

@@ -0,0 +1,32 @@
// import { useEffect, useState } from "react"
// import { Observable } from "./Observable"
//
// export function useObservable<T extends object, K extends keyof T>(
// observable: Observable<T>,
// keys?: K | K[]
// ): Pick<T, K> | T {
// const keyArray = keys ? (Array.isArray(keys) ? keys : [keys]) : null
//
// const [state, setState] = useState<Partial<T>>(() => {
// if (keyArray) {
// const init: Partial<T> = {}
// keyArray.forEach(k => (init[k] = (observable as any)[k]))
// return init
// }
// return { ...(observable as any) }
// })
//
// useEffect(() => {
// const unsubscribe = keyArray
// ? observable.subscribeKey(keyArray as K[], changed => {
// setState(prev => ({ ...prev, ...changed }))
// })
// : observable.subscribe(s => setState({ ...s }))
//
// return () => {
// unsubscribe()
// }
// }, [observable, ...(keyArray || [])])
//
// return state as any
// }

View File

@@ -0,0 +1,38 @@
import type { Observable } from '@/core/state/Observable.ts'
import { onUnmounted, ref, type Ref } from 'vue'
/**
* vue使用自定义的 observable
* @param observable
* @param keys
* @returns
* @example
* const app = new Observable({ count: 0, isOpen: false, title: "Demo" })
* // 多字段订阅
* const state = useObservable(app, ["count", "isOpen"])
* // state.value.count / state.value.isOpen 响应式
*/
export function useObservable<T extends object, K extends keyof T>(
observable: Observable<T>,
keys?: K[] | K
): Ref<Pick<T, K> | T> {
const state = ref({} as any) // 响应式
const keyArray = keys ? (Array.isArray(keys) ? keys : [keys]) : null
// 订阅回调
const unsubscribe = keyArray
? observable.subscribeKey(keyArray as K[], changed => {
// 更新响应式 state
Object.assign(state.value, changed)
})
: observable.subscribe(s => {
state.value = { ...s }
})
onUnmounted(() => {
unsubscribe()
})
return state
}

View File

@@ -1,5 +1,6 @@
import ProcessImpl from '../process/impl/ProcessImpl.ts'
import { ProcessInfoImpl } from '@/core/process/impl/ProcessInfoImpl.ts'
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
/**
* 基础系统进程
@@ -11,7 +12,7 @@ export class BasicSystemProcess extends ProcessImpl{
return this._isMounted;
}
constructor(info: ProcessInfoImpl) {
constructor(info: IProcessInfo) {
super(info)
console.log('BasicSystemProcess')
}

View File

@@ -9,7 +9,8 @@
"@/*": ["./src/*"]
},
"experimentalDecorators": true,
"target": "ES6",
"target": "es2021",
"lib": ["es2021", "dom"],
"module": "ESNext",
"strictPropertyInitialization": false, // 严格属性初始化检查
"noUnusedLocals": false, // 检查未使用的局部变量