保存一下
This commit is contained in:
@@ -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.js:v14+
|
||||
|
||||
不支持IE
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
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 { 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')
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"experimentalDecorators": true,
|
||||
"target": "ES6",
|
||||
"target": "es2021",
|
||||
"lib": ["es2021", "dom"],
|
||||
"module": "ESNext",
|
||||
"strictPropertyInitialization": false, // 严格属性初始化检查
|
||||
"noUnusedLocals": false, // 检查未使用的局部变量
|
||||
|
||||
Reference in New Issue
Block a user