保存一下
This commit is contained in:
@@ -1,6 +1,10 @@
|
|||||||
# vue-desktop
|
# vue-desktop
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
浏览器:Chrome 84+、Edge 84+、Firefox 79+、Safari 14+
|
||||||
|
|
||||||
|
Node.js:v14+
|
||||||
|
|
||||||
|
不支持IE
|
||||||
|
|
||||||
## Recommended IDE Setup
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
|
|||||||
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
|
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
|
||||||
import type { IProcessManage } from '@/core/process/IProcessManage.ts'
|
import type { IProcessManage } from '@/core/process/IProcessManage.ts'
|
||||||
import type { IProcess } from '@/core/process/IProcess.ts'
|
import type { IProcess } from '@/core/process/IProcess.ts'
|
||||||
|
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
|
||||||
|
|
||||||
export default class XSystem {
|
export default class XSystem {
|
||||||
private static _instance: XSystem = new XSystem()
|
private static _instance: XSystem = new XSystem()
|
||||||
@@ -40,10 +41,10 @@ export default class XSystem {
|
|||||||
|
|
||||||
// 运行进程
|
// 运行进程
|
||||||
public async run<T extends IProcess = IProcess>(
|
public async run<T extends IProcess = IProcess>(
|
||||||
proc: string | ProcessInfoImpl,
|
proc: string | IProcessInfo,
|
||||||
constructor?: new (info: ProcessInfoImpl) => T,
|
constructor?: new (info: IProcessInfo) => T,
|
||||||
): Promise<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)) {
|
if (isUndefined(info)) {
|
||||||
throw new Error(`未找到进程信息:${proc}`)
|
throw new Error(`未找到进程信息:${proc}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import DesktopComponent from '@/core/desktop/ui/DesktopComponent.vue'
|
|||||||
import { naiveUi } from '@/core/common/naive-ui/components.ts'
|
import { naiveUi } from '@/core/common/naive-ui/components.ts'
|
||||||
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
|
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
|
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
|
||||||
|
|
||||||
export class DesktopProcess extends ProcessImpl {
|
export class DesktopProcess extends ProcessImpl {
|
||||||
private _desktopRootDom: HTMLElement;
|
private _desktopRootDom: HTMLElement;
|
||||||
@@ -66,7 +67,7 @@ export class DesktopProcess extends ProcessImpl {
|
|||||||
return XSystem.instance.eventManage;
|
return XSystem.instance.eventManage;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(info: ProcessInfoImpl) {
|
constructor(info: IProcessInfo) {
|
||||||
super(info)
|
super(info)
|
||||||
console.log('DesktopProcess')
|
console.log('DesktopProcess')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,10 +79,14 @@ export function useDesktopInit(containerStr: string) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const appIconsRef = ref(appIcons)
|
const appIconsRef = ref(appIcons)
|
||||||
|
const exceedApp = ref<IDesktopAppIcon[]>([])
|
||||||
|
|
||||||
watch(() => [gridTemplate.rowCount, gridTemplate.colCount], ([nRows, nCols], [oRows, oCols]) => {
|
watch(() => [gridTemplate.colCount, gridTemplate.rowCount], ([nCols, nRows], [oCols, oRows]) => {
|
||||||
if (oCols == 1 && oRows == 1) return
|
// if (oCols == 1 && oRows == 1) return
|
||||||
appIconsRef.value = rearrangeIcons(toRaw(appIconsRef.value), nCols, nRows, oCols, oRows)
|
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) => {
|
XSystem.instance.eventManage.addEventListener(DesktopEventEnum.onDesktopAppIconPos, (iconInfo) => {
|
||||||
@@ -98,59 +102,69 @@ export function useDesktopInit(containerStr: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新安排图标位置
|
* 重新安排图标位置
|
||||||
* @param appIcons 图标信息
|
* @param appIconInfos 图标信息
|
||||||
* @param newCols 新的列数
|
* @param maxCol 列数
|
||||||
* @param newRows 新的行数
|
* @param maxRow 行数
|
||||||
* @param oldCols 旧的列数
|
|
||||||
* @param oldRows 旧的行数
|
|
||||||
*/
|
*/
|
||||||
function rearrangeIcons(
|
function rearrangeIcons(
|
||||||
appIcons: IDesktopAppIcon[],
|
appIconInfos: IDesktopAppIcon[],
|
||||||
newCols: number,
|
maxCol: number,
|
||||||
newRows: number,
|
maxRow: number
|
||||||
oldCols: number,
|
): IRearrangeInfo {
|
||||||
oldRows: number
|
|
||||||
): IDesktopAppIcon[] {
|
|
||||||
if (oldCols === newCols && oldRows === newRows) {
|
|
||||||
return appIcons;
|
|
||||||
}
|
|
||||||
const occupied = new Set<string>();
|
const occupied = new Set<string>();
|
||||||
|
|
||||||
function key(x: number, y: number) {
|
function key(x: number, y: number) {
|
||||||
return `${x},${y}`;
|
return `${x},${y}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result: IDesktopAppIcon[] = []
|
const appIcons: IDesktopAppIcon[] = []
|
||||||
const exceed: IDesktopAppIcon[] = []
|
const hideAppIcons: IDesktopAppIcon[] = []
|
||||||
|
const temp: IDesktopAppIcon[] = []
|
||||||
|
|
||||||
for (const appIcon of appIcons) {
|
for (const appIcon of appIconInfos) {
|
||||||
const { x, y } = appIcon;
|
const { x, y } = appIcon;
|
||||||
|
|
||||||
if (x <= newCols && y <= newRows) {
|
if (x <= maxCol && y <= maxRow) {
|
||||||
if (!occupied.has(key(x, y))) {
|
if (!occupied.has(key(x, y))) {
|
||||||
occupied.add(key(x, y))
|
occupied.add(key(x, y))
|
||||||
result.push({ ...appIcon, x, y })
|
appIcons.push({ ...appIcon, x, y })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
exceed.push(appIcon)
|
temp.push(appIcon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const appIcon of exceed) {
|
const max = maxCol * maxRow
|
||||||
// 最后格子也被占 → 从 (1,1) 开始找空位
|
for (const appIcon of temp) {
|
||||||
let placed = false;
|
if (appIcons.length < max) {
|
||||||
for (let c = 1; c <= newCols; c++) {
|
// 最后格子也被占 → 从 (1,1) 开始找空位
|
||||||
for (let r = 1; r <= newRows; r++) {
|
let placed = false;
|
||||||
if (!occupied.has(key(c, r))) {
|
for (let c = 1; c <= maxCol; c++) {
|
||||||
occupied.add(key(c, r));
|
for (let r = 1; r <= maxRow; r++) {
|
||||||
result.push({ ...appIcon, x: c, y: r });
|
if (!occupied.has(key(c, r))) {
|
||||||
placed = true;
|
occupied.add(key(c, r));
|
||||||
break;
|
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[];
|
||||||
}
|
}
|
||||||
|
|||||||
158
src/core/state/Observable.ts
Normal file
158
src/core/state/Observable.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/core/state/useObservableReact.ts
Normal file
32
src/core/state/useObservableReact.ts
Normal 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
|
||||||
|
// }
|
||||||
38
src/core/state/useObservableVue.ts
Normal file
38
src/core/state/useObservableVue.ts
Normal 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
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import ProcessImpl from '../process/impl/ProcessImpl.ts'
|
import ProcessImpl from '../process/impl/ProcessImpl.ts'
|
||||||
import { ProcessInfoImpl } from '@/core/process/impl/ProcessInfoImpl.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;
|
return this._isMounted;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(info: ProcessInfoImpl) {
|
constructor(info: IProcessInfo) {
|
||||||
super(info)
|
super(info)
|
||||||
console.log('BasicSystemProcess')
|
console.log('BasicSystemProcess')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"target": "ES6",
|
"target": "es2021",
|
||||||
|
"lib": ["es2021", "dom"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"strictPropertyInitialization": false, // 严格属性初始化检查
|
"strictPropertyInitialization": false, // 严格属性初始化检查
|
||||||
"noUnusedLocals": false, // 检查未使用的局部变量
|
"noUnusedLocals": false, // 检查未使用的局部变量
|
||||||
|
|||||||
Reference in New Issue
Block a user