初始化
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
36
.gitignore
vendored
36
.gitignore
vendored
@@ -1,10 +1,30 @@
|
|||||||
# ---> Vue
|
# Logs
|
||||||
# gitignore template for Vue.js projects
|
logs
|
||||||
#
|
*.log
|
||||||
# Recommended template: Node.gitignore
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
# TODO: where does this rule come from?
|
node_modules
|
||||||
docs/_book
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
# TODO: where does this rule come from?
|
/cypress/videos/
|
||||||
test/
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|||||||
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
6
.vscode/extensions.json
vendored
Normal file
6
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
31
README.md
31
README.md
@@ -1,2 +1,33 @@
|
|||||||
# vue-desktop
|
# vue-desktop
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
41
package.json
Normal file
41
package.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "vue-desktop",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vueuse/core": "^13.6.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
|
"vue": "^3.5.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node22": "^22.0.2",
|
||||||
|
"@types/lodash": "^4.17.20",
|
||||||
|
"@types/node": "^22.16.5",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
||||||
|
"@vue/tsconfig": "^0.7.0",
|
||||||
|
"naive-ui": "^2.42.0",
|
||||||
|
"npm-run-all2": "^8.0.4",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"sass": "^1.90.0",
|
||||||
|
"typescript": "~5.8.0",
|
||||||
|
"unocss": "^66.4.2",
|
||||||
|
"vite": "^7.0.6",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.0",
|
||||||
|
"vue-tsc": "^3.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
3122
pnpm-lock.yaml
generated
Normal file
3122
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
18
src/App.vue
Normal file
18
src/App.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div id="root" ref="root-dom" class="w-100vw h-100vh"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import XSystem from '@/core/XSystem.ts'
|
||||||
|
import { onMounted, useTemplateRef } from 'vue'
|
||||||
|
|
||||||
|
const dom = useTemplateRef<HTMLDivElement>('root-dom')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
XSystem.instance.initialization(dom.value!);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
63
src/core/XSystem.ts
Normal file
63
src/core/XSystem.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import ProcessManages from './process/ProcessManages.ts'
|
||||||
|
import AppProcess from './process/AppProcess.ts'
|
||||||
|
import type { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||||
|
import { isUndefined } from 'lodash'
|
||||||
|
import { BasicSystemProcess } from '@/core/system/BasicSystemProcess.ts'
|
||||||
|
import { DesktopProcess } from '@/core/desktop/DesktopProcess.ts'
|
||||||
|
import type { IAllEvent } from '@/core/events/EventTypes.ts'
|
||||||
|
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
|
||||||
|
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
|
||||||
|
|
||||||
|
export default class XSystem {
|
||||||
|
private static _instance: XSystem = new XSystem()
|
||||||
|
|
||||||
|
private _processManages: ProcessManages = new ProcessManages()
|
||||||
|
private _eventManages: IEventBuilder<IAllEvent> = new EventBuilderImpl()
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
console.log('XSystem')
|
||||||
|
}
|
||||||
|
|
||||||
|
public static get instance() {
|
||||||
|
return this._instance
|
||||||
|
}
|
||||||
|
public get processManages() {
|
||||||
|
return this._processManages
|
||||||
|
}
|
||||||
|
public get eventManages() {
|
||||||
|
return this._eventManages
|
||||||
|
}
|
||||||
|
|
||||||
|
public initialization(dom: HTMLDivElement) {
|
||||||
|
this.run('basic-system', BasicSystemProcess).then(() => {
|
||||||
|
this.run('desktop', DesktopProcess).then((proc) => {
|
||||||
|
console.log(proc)
|
||||||
|
proc.mount(dom)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行进程
|
||||||
|
public async run<T extends AppProcess = AppProcess>(
|
||||||
|
proc: string | AppProcessInfo,
|
||||||
|
constructor?: new (info: AppProcessInfo) => T,
|
||||||
|
): Promise<T> {
|
||||||
|
let info = typeof proc === 'string' ? this._processManages.findProcessInfoByName(proc) : proc
|
||||||
|
if (isUndefined(info)) {
|
||||||
|
throw new Error(`未找到进程信息:${proc}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是单例应用
|
||||||
|
if (info.singleton) {
|
||||||
|
let process = this._processManages.findProcessByName(info.name)
|
||||||
|
if (process) {
|
||||||
|
return process as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建进程
|
||||||
|
let process = isUndefined(constructor) ? new AppProcess(info) : new constructor(info)
|
||||||
|
|
||||||
|
return process as T
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/core/apps/setting/app.json
Normal file
18
src/core/apps/setting/app.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "setting",
|
||||||
|
"title": "设置",
|
||||||
|
"description": "设置",
|
||||||
|
"icon": "iconfont icon-setting",
|
||||||
|
"startName": "main",
|
||||||
|
"singleton": true,
|
||||||
|
"isJustProcess": false,
|
||||||
|
"windowFormConfigs": [
|
||||||
|
{
|
||||||
|
"name": "main",
|
||||||
|
"title": "设置",
|
||||||
|
"icon": "iconfont icon-setting",
|
||||||
|
"width": 800,
|
||||||
|
"height": 600
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
src/core/apps/setting/main.vue
Normal file
9
src/core/apps/setting/main.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
设置APP页面
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
10
src/core/common/naive-ui/components.ts
Normal file
10
src/core/common/naive-ui/components.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {
|
||||||
|
create,
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NConfigProvider,
|
||||||
|
} from 'naive-ui'
|
||||||
|
|
||||||
|
export const naiveUi = create({
|
||||||
|
components: [NButton, NCard, NConfigProvider]
|
||||||
|
})
|
||||||
21
src/core/common/naive-ui/discrete-api.ts
Normal file
21
src/core/common/naive-ui/discrete-api.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createDiscreteApi } from 'naive-ui'
|
||||||
|
import { configProviderProps } from './theme.ts'
|
||||||
|
|
||||||
|
const { message, notification, dialog, loadingBar, modal } = createDiscreteApi(
|
||||||
|
['message', 'dialog', 'notification', 'loadingBar', 'modal'],
|
||||||
|
{
|
||||||
|
configProviderProps: configProviderProps,
|
||||||
|
notificationProviderProps: {
|
||||||
|
placement: 'bottom-right',
|
||||||
|
max: 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const { messageApi, notificationApi, dialogApi, loadingBarApi, modalApi } = {
|
||||||
|
messageApi: message,
|
||||||
|
notificationApi: notification,
|
||||||
|
dialogApi: dialog,
|
||||||
|
loadingBarApi: loadingBar,
|
||||||
|
modalApi: modal
|
||||||
|
}
|
||||||
15
src/core/common/naive-ui/theme.ts
Normal file
15
src/core/common/naive-ui/theme.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { type ConfigProviderProps, darkTheme, dateZhCN, type GlobalTheme, lightTheme, zhCN } from 'naive-ui'
|
||||||
|
|
||||||
|
const lTheme: GlobalTheme = {
|
||||||
|
...lightTheme,
|
||||||
|
common: {
|
||||||
|
...lightTheme.common,
|
||||||
|
primaryColor: '#0070f3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const configProviderProps: ConfigProviderProps = {
|
||||||
|
theme: lTheme,
|
||||||
|
dateLocale: dateZhCN,
|
||||||
|
locale: zhCN,
|
||||||
|
}
|
||||||
29
src/core/common/types/Version.ts
Normal file
29
src/core/common/types/Version.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 版本信息
|
||||||
|
*/
|
||||||
|
export interface Version {
|
||||||
|
/**
|
||||||
|
* 公司名称
|
||||||
|
*/
|
||||||
|
company: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号
|
||||||
|
*/
|
||||||
|
major: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子版本号
|
||||||
|
*/
|
||||||
|
minor: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修订号
|
||||||
|
*/
|
||||||
|
build: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私有版本号
|
||||||
|
*/
|
||||||
|
private: number
|
||||||
|
}
|
||||||
97
src/core/desktop/DesktopProcess.ts
Normal file
97
src/core/desktop/DesktopProcess.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import AppProcess from '@/core/process/AppProcess.ts'
|
||||||
|
import type { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||||
|
import XSystem from '@/core/XSystem.ts'
|
||||||
|
import { BasicSystemProcess } from '@/core/system/BasicSystemProcess.ts'
|
||||||
|
import { createApp, h } from 'vue'
|
||||||
|
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'
|
||||||
|
|
||||||
|
export class DesktopProcess extends AppProcess {
|
||||||
|
private _desktopRootDom: HTMLElement;
|
||||||
|
private _isMounted: boolean = false;
|
||||||
|
private _width: number = 0;
|
||||||
|
private _height: number = 0;
|
||||||
|
private _pendingResize: boolean = false;
|
||||||
|
|
||||||
|
public get desktopRootDom() {
|
||||||
|
return this._desktopRootDom;
|
||||||
|
}
|
||||||
|
public get isMounted() {
|
||||||
|
return this._isMounted;
|
||||||
|
}
|
||||||
|
public get basicSystemProcess() {
|
||||||
|
return XSystem.instance.processManages.findProcessByName<BasicSystemProcess>('basic-system')
|
||||||
|
}
|
||||||
|
|
||||||
|
public get width() {
|
||||||
|
return this._width;
|
||||||
|
}
|
||||||
|
public set width(value: number) {
|
||||||
|
if (this._height === value) return;
|
||||||
|
if (!this._isMounted) return;
|
||||||
|
this._width = value;
|
||||||
|
this._desktopRootDom.style.width = value >= 0 ? `${value}px` : '100%';
|
||||||
|
|
||||||
|
this.scheduleResizeEvent()
|
||||||
|
}
|
||||||
|
public get height() {
|
||||||
|
return this._height;
|
||||||
|
}
|
||||||
|
public set height(value: number) {
|
||||||
|
if (this._height === value) return;
|
||||||
|
if (!this._isMounted) return;
|
||||||
|
this._height = value;
|
||||||
|
this._desktopRootDom.style.height = value >= 0 ? `${value}px` : '100%';
|
||||||
|
|
||||||
|
this.scheduleResizeEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleResizeEvent() {
|
||||||
|
if (this._pendingResize) return;
|
||||||
|
|
||||||
|
this._pendingResize = true;
|
||||||
|
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
if (this._pendingResize) {
|
||||||
|
this._pendingResize = false;
|
||||||
|
console.log('onDesktopRootDomResize')
|
||||||
|
this.eventManages.notifyEvent(DesktopEventEnum.onDesktopRootDomResize, this._width, this._height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private get eventManages() {
|
||||||
|
return XSystem.instance.eventManages;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(info: AppProcessInfo) {
|
||||||
|
super(info)
|
||||||
|
console.log('DesktopProcess')
|
||||||
|
}
|
||||||
|
|
||||||
|
public mount(dom: HTMLDivElement) {
|
||||||
|
if (this._isMounted) return;
|
||||||
|
this._width = window.innerWidth
|
||||||
|
this._height = window.innerHeight
|
||||||
|
window.addEventListener(
|
||||||
|
'resize',
|
||||||
|
debounce(() => {
|
||||||
|
this.width = window.innerWidth
|
||||||
|
this.height = window.innerHeight
|
||||||
|
}, 300),
|
||||||
|
)
|
||||||
|
|
||||||
|
dom.style.zIndex = '0';
|
||||||
|
dom.style.width = `${this._width}px`
|
||||||
|
dom.style.height = `${this._height}px`
|
||||||
|
this._desktopRootDom = dom;
|
||||||
|
|
||||||
|
const app = createApp(DesktopComponent, { process: this })
|
||||||
|
app.use(naiveUi)
|
||||||
|
app.mount(dom)
|
||||||
|
|
||||||
|
this._isMounted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/core/desktop/DesktopProcessInfo.ts
Normal file
15
src/core/desktop/DesktopProcessInfo.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||||
|
|
||||||
|
export const DesktopProcessInfo = new AppProcessInfo({
|
||||||
|
name: 'desktop',
|
||||||
|
title: '桌面',
|
||||||
|
version: {
|
||||||
|
company: 'XZG',
|
||||||
|
major: 1,
|
||||||
|
minor: 0,
|
||||||
|
build: 0,
|
||||||
|
private: 0
|
||||||
|
},
|
||||||
|
singleton: true,
|
||||||
|
isJustProcess: true
|
||||||
|
})
|
||||||
7
src/core/desktop/types/IconType.ts
Normal file
7
src/core/desktop/types/IconType.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export interface IconType {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
path: string;
|
||||||
|
col: number;
|
||||||
|
row: number;
|
||||||
|
}
|
||||||
101
src/core/desktop/ui/DesktopComponent.vue
Normal file
101
src/core/desktop/ui/DesktopComponent.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<n-config-provider :config-provider-props="configProviderProps" class="w-full h-full pos-relative">
|
||||||
|
<div ref="desktopRootDom" class="desktop-root">
|
||||||
|
<div class="desktop-container">
|
||||||
|
<div v-for="icon in iconArr" class="icon-container">{{ icon.icon }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="task-bar"></div>
|
||||||
|
</div>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DesktopProcess } from '@/core/desktop/DesktopProcess.ts'
|
||||||
|
import XSystem from '@/core/XSystem.ts'
|
||||||
|
import { notificationApi } from '@/core/common/naive-ui/discrete-api.ts'
|
||||||
|
import { configProviderProps } from '@/core/common/naive-ui/theme.ts'
|
||||||
|
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
|
||||||
|
import { useIconDrag } from '@/core/desktop/utils/useIconDrag.ts'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import type { IconType } from '@/core/desktop/types/IconType.ts'
|
||||||
|
import { useDesktopInit } from '@/core/desktop/ui/useDesktopInit.ts'
|
||||||
|
|
||||||
|
const props = defineProps<{process: DesktopProcess}>()
|
||||||
|
console.log(props.process)
|
||||||
|
|
||||||
|
const iconArr: IconType[] = [
|
||||||
|
{
|
||||||
|
name: '文件管理器',
|
||||||
|
icon: '🗂',
|
||||||
|
path: '/',
|
||||||
|
col: 1,
|
||||||
|
row: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '浏览器',
|
||||||
|
icon: '🌐',
|
||||||
|
path: '/',
|
||||||
|
col: 1,
|
||||||
|
row: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '记事本',
|
||||||
|
icon: '📄',
|
||||||
|
path: '/',
|
||||||
|
col: 1,
|
||||||
|
row: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '音乐播放器',
|
||||||
|
icon: '🎵',
|
||||||
|
path: '/',
|
||||||
|
col: 1,
|
||||||
|
row: 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
XSystem.instance.eventManages.addEventListener(DesktopEventEnum.onDesktopRootDomResize, (width, height) => {
|
||||||
|
console.log(width, height)
|
||||||
|
notificationApi.create({ title: '桌面通知', content: `桌面尺寸变化${width}x${height}}`, duration: 2000 })
|
||||||
|
})
|
||||||
|
|
||||||
|
const iconsInit = () => {
|
||||||
|
const icons = document.querySelectorAll<HTMLDivElement>('div.icon-container')
|
||||||
|
const container = document.querySelector<HTMLDivElement>('.desktop-container')!
|
||||||
|
icons.forEach((icon: HTMLDivElement) => {
|
||||||
|
useIconDrag(icon, container)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
const container = document.querySelector<HTMLDivElement>('.desktop-container')!
|
||||||
|
console.log(container.getBoundingClientRect())
|
||||||
|
// iconsInit()
|
||||||
|
const { col, row } = useDesktopInit(container)
|
||||||
|
console.log(col.value, row.value)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$icon-width: 80px;
|
||||||
|
$icon-height: 110px;
|
||||||
|
|
||||||
|
.desktop-root {
|
||||||
|
@apply w-full h-full flex flex-col;
|
||||||
|
|
||||||
|
.desktop-container {
|
||||||
|
@apply w-full flex-1 grid gap-4 grid-auto-flow-col p-4 pos-relative;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax($icon-width, 1fr));
|
||||||
|
grid-template-rows: repeat(auto-fill, minmax($icon-height, 1fr));
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
width: $icon-width;
|
||||||
|
height: $icon-height;
|
||||||
|
@apply flex flex-col items-center justify-center rounded text-white bg-gray-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-bar {
|
||||||
|
@apply w-full h-[40px] bg-gray-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
src/core/desktop/ui/useDesktopInit.ts
Normal file
44
src/core/desktop/ui/useDesktopInit.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import XSystem from '@/core/XSystem.ts'
|
||||||
|
import type { IconType } from '@/core/desktop/types/IconType.ts'
|
||||||
|
import { nextTick, onUnmounted, reactive, toRefs } from 'vue'
|
||||||
|
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
|
||||||
|
import { useDraggable } from '@vueuse/core'
|
||||||
|
|
||||||
|
export function useDesktopInit(container: HTMLElement) {
|
||||||
|
const gridTemplate = reactive({
|
||||||
|
cellWidth: 90,
|
||||||
|
cellHeight: 110,
|
||||||
|
gap: 10,
|
||||||
|
col: 1,
|
||||||
|
row: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const ro = new ResizeObserver(entries => {
|
||||||
|
const entry= entries[0]
|
||||||
|
const containerRect = entry.contentRect
|
||||||
|
gridTemplate.col = Math.floor(containerRect.width / gridTemplate.cellWidth);
|
||||||
|
gridTemplate.row = Math.floor(containerRect.height / (gridTemplate.cellHeight));
|
||||||
|
console.log(1111)
|
||||||
|
})
|
||||||
|
ro.observe(container)
|
||||||
|
onUnmounted(() => {
|
||||||
|
ro.unobserve(container)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 有桌面图标的app
|
||||||
|
const apps = XSystem.instance.processManages.processInfos.filter(processInfo => !processInfo.isJustProcess)
|
||||||
|
console.log(apps)
|
||||||
|
const icons: IconType[] = apps.map(processInfo => {
|
||||||
|
return {
|
||||||
|
name: processInfo.name,
|
||||||
|
icon: processInfo.icon,
|
||||||
|
path: processInfo.startName,
|
||||||
|
col: 0,
|
||||||
|
row: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(gridTemplate),
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/core/desktop/utils/useIconDrag.ts
Normal file
64
src/core/desktop/utils/useIconDrag.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { useDraggable } from '@vueuse/core'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export function useIconDrag(el: HTMLElement, container: HTMLElement) {
|
||||||
|
let offsetX = 0
|
||||||
|
let offsetY = 0
|
||||||
|
let containerRect = container.getBoundingClientRect()
|
||||||
|
|
||||||
|
el.addEventListener('mousedown', (e) => {
|
||||||
|
el.classList.add('dragging')
|
||||||
|
|
||||||
|
let rect = el.getBoundingClientRect()
|
||||||
|
console.log(rect)
|
||||||
|
offsetX = e.clientX - rect.left
|
||||||
|
offsetY = e.clientY - rect.top
|
||||||
|
|
||||||
|
// 临时脱离 grid,用绝对定位移动
|
||||||
|
el.style.position = "absolute";
|
||||||
|
el.style.left = rect.left - containerRect.left + "px";
|
||||||
|
el.style.top = rect.top - containerRect.top + "px";
|
||||||
|
el.style.gridRow = "auto";
|
||||||
|
el.style.gridColumn = "auto";
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", onMouseMove);
|
||||||
|
document.addEventListener("mouseup", onMouseUp);
|
||||||
|
})
|
||||||
|
|
||||||
|
function onMouseMove(e: MouseEvent) {
|
||||||
|
if (!el) return;
|
||||||
|
el.style.left = e.clientX - containerRect.left - offsetX + "px";
|
||||||
|
el.style.top = e.clientY - containerRect.top - offsetY + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseUp(e: MouseEvent) {
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const cellWidth = 90 + 16; // 图标宽度 + gap
|
||||||
|
const cellHeight = 110 + 16;
|
||||||
|
|
||||||
|
// 计算所在行列
|
||||||
|
let col = Math.round((e.clientX - containerRect.left) / cellWidth) + 1;
|
||||||
|
let row = Math.round((e.clientY - containerRect.top) / cellHeight) + 1;
|
||||||
|
|
||||||
|
// 限制在 grid 内
|
||||||
|
const maxCols = Math.floor(containerRect.width / cellWidth);
|
||||||
|
const maxRows = Math.floor(containerRect.height / cellHeight);
|
||||||
|
col = Math.max(1, Math.min(maxCols, col));
|
||||||
|
row = Math.max(1, Math.min(maxRows, row));
|
||||||
|
|
||||||
|
console.log(col, row)
|
||||||
|
|
||||||
|
// 放回 grid
|
||||||
|
el.style.position = "relative";
|
||||||
|
el.style.left = "";
|
||||||
|
el.style.top = "";
|
||||||
|
el.style.gridRow = `${row}`;
|
||||||
|
el.style.gridColumn = `${col}`;
|
||||||
|
|
||||||
|
el.classList.remove("dragging");
|
||||||
|
|
||||||
|
document.removeEventListener("mousemove", onMouseMove);
|
||||||
|
document.removeEventListener("mouseup", onMouseUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/core/events/EventTypes.ts
Normal file
32
src/core/events/EventTypes.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { IEventMap } from '@/core/events/IEventBuilder.ts'
|
||||||
|
|
||||||
|
export enum DesktopEventEnum {
|
||||||
|
onDesktopRootDomResize = 'onDesktopRootDomResize'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DesktopEventParams = {
|
||||||
|
[DesktopEventEnum.onDesktopRootDomResize]: (width: number, height: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BasicSystemEventEnum {
|
||||||
|
onAuthChange = 'onAuthChange',
|
||||||
|
onThemeChange = 'onThemeChange'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BasicSystemEventParams = {
|
||||||
|
[BasicSystemEventEnum.onAuthChange]: () => {},
|
||||||
|
[BasicSystemEventEnum.onThemeChange]: (theme: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AllEventParams = BasicSystemEventParams & DesktopEventParams
|
||||||
|
|
||||||
|
export interface IDesktopEvent extends IEventMap {
|
||||||
|
[DesktopEventEnum.onDesktopRootDomResize]: (width: number, height: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBasicSystemEvent extends IEventMap {
|
||||||
|
[BasicSystemEventEnum.onAuthChange]: () => {},
|
||||||
|
[BasicSystemEventEnum.onThemeChange]: (theme: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAllEvent extends IDesktopEvent, IBasicSystemEvent {}
|
||||||
45
src/core/events/IEventBuilder.ts
Normal file
45
src/core/events/IEventBuilder.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 事件定义
|
||||||
|
* @interface IEventMap 事件定义 键是事件名称,值是事件处理函数
|
||||||
|
*/
|
||||||
|
export interface IEventMap {
|
||||||
|
[key: string]: (...args: any[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件管理器接口定义
|
||||||
|
*/
|
||||||
|
export interface IEventBuilder<Events extends IEventMap> {
|
||||||
|
/**
|
||||||
|
* 添加事件监听
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param handler 事件处理函数
|
||||||
|
* @param options 配置项 { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
addEventListener<E extends keyof Events, F extends Events[E]>(
|
||||||
|
eventName: E,
|
||||||
|
handler: F,
|
||||||
|
options?: {
|
||||||
|
immediate?: boolean
|
||||||
|
immediateArgs?: Parameters<F>
|
||||||
|
once?: boolean
|
||||||
|
},
|
||||||
|
): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除事件监听
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param handler 事件处理函数
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
removeEventListener<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发事件
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param args 参数
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
notifyEvent<E extends keyof Events, F extends Events[E]>(eventName: E, ...args: Parameters<F>): void
|
||||||
|
}
|
||||||
94
src/core/events/impl/EventBuilderImpl.ts
Normal file
94
src/core/events/impl/EventBuilderImpl.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import type { IEventBuilder, IEventMap } from '@/core/events/IEventBuilder.ts'
|
||||||
|
|
||||||
|
interface HandlerWrapper<T extends (...args: any[]) => any> {
|
||||||
|
fn: T
|
||||||
|
once: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventBuilderImpl<Events extends IEventMap>
|
||||||
|
implements IEventBuilder<Events>
|
||||||
|
{
|
||||||
|
private _eventHandlers = new Map<keyof Events, Set<HandlerWrapper<Events[keyof Events]>>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加事件监听器
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param handler 监听器
|
||||||
|
* @param options { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
|
||||||
|
* @example
|
||||||
|
* eventBus.addEventListener('noArgs', () => {})
|
||||||
|
* eventBus.addEventListener('greet', name => {}, { immediate: true, immediateArgs: ['abc'] })
|
||||||
|
* eventBus.addEventListener('onResize', (w, h) => {}, { immediate: true, immediateArgs: [1, 2] })
|
||||||
|
*/
|
||||||
|
addEventListener<E extends keyof Events, F extends Events[E]>(
|
||||||
|
eventName: E,
|
||||||
|
handler: F,
|
||||||
|
options?: {
|
||||||
|
immediate?: boolean;
|
||||||
|
immediateArgs?: Parameters<F>;
|
||||||
|
once?: boolean;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (!handler) return
|
||||||
|
if (!this._eventHandlers.has(eventName)) {
|
||||||
|
this._eventHandlers.set(eventName, new Set<HandlerWrapper<F>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
const set = this._eventHandlers.get(eventName)!
|
||||||
|
if (![...set].some((wrapper) => wrapper.fn === handler)) {
|
||||||
|
set.add({ fn: handler, once: options?.once ?? false })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.immediate) {
|
||||||
|
try {
|
||||||
|
handler(...(options.immediateArgs ?? []))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除事件监听器
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param handler 监听器
|
||||||
|
* @example
|
||||||
|
* eventBus.removeEventListener('noArgs', () => {})
|
||||||
|
*/
|
||||||
|
removeEventListener<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F) {
|
||||||
|
const set = this._eventHandlers.get(eventName)
|
||||||
|
if (!set) return
|
||||||
|
|
||||||
|
for (const wrapper of set) {
|
||||||
|
if (wrapper.fn === handler) {
|
||||||
|
set.delete(wrapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知事件
|
||||||
|
* @param eventName 事件名称
|
||||||
|
* @param args 参数
|
||||||
|
* @example
|
||||||
|
* eventBus.notifyEvent('noArgs')
|
||||||
|
* eventBus.notifyEvent('greet', 'Alice')
|
||||||
|
* eventBus.notifyEvent('onResize', 1, 2)
|
||||||
|
*/
|
||||||
|
notifyEvent<E extends keyof Events, F extends Events[E]>(eventName: E, ...args: Parameters<F>) {
|
||||||
|
if (!this._eventHandlers.has(eventName)) return
|
||||||
|
|
||||||
|
const set = this._eventHandlers.get(eventName)!
|
||||||
|
for (const wrapper of set) {
|
||||||
|
try {
|
||||||
|
wrapper.fn(...args)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrapper.once) {
|
||||||
|
set.delete(wrapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/core/process/AppProcess.ts
Normal file
42
src/core/process/AppProcess.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
import XSystem from '../XSystem.ts'
|
||||||
|
import type { AppProcessInfo } from '../process/AppProcessInfo.ts'
|
||||||
|
import WindowForm from '../window/WindowForm.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进程
|
||||||
|
*/
|
||||||
|
export default class AppProcess {
|
||||||
|
private readonly _id: string = uuidV4();
|
||||||
|
private readonly _processInfo: AppProcessInfo;
|
||||||
|
// 当前进程的窗体集合
|
||||||
|
private _windowForms: Map<string, WindowForm> = new Map();
|
||||||
|
|
||||||
|
public get id() {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
public get processInfo() {
|
||||||
|
return this._processInfo;
|
||||||
|
}
|
||||||
|
public get windowForms() {
|
||||||
|
return this._windowForms;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(info: AppProcessInfo) {
|
||||||
|
console.log(`AppProcess: ${info.name}`)
|
||||||
|
this._processInfo = info;
|
||||||
|
|
||||||
|
const startName = info.startName;
|
||||||
|
|
||||||
|
XSystem.instance.processManages.addProcess(this);
|
||||||
|
// 通过设置 isJustProcess 为 true,则不会创建窗体
|
||||||
|
if (!info.isJustProcess) {
|
||||||
|
this.openWindowForm(startName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public openWindowForm(startName: string) {
|
||||||
|
const window = new WindowForm(this, startName);
|
||||||
|
this._windowForms.set(window.id, window);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/core/process/AppProcessInfo.ts
Normal file
100
src/core/process/AppProcessInfo.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import type { Version } from '../common/types/Version.ts'
|
||||||
|
import type { AppProcessInfoParams } from '../process/types/AppProcessInfoParams.ts'
|
||||||
|
import type { WindowFormConfig } from '../window/types/WindowFormConfig.ts'
|
||||||
|
|
||||||
|
export class AppProcessInfo {
|
||||||
|
/**
|
||||||
|
* 应用进程名称
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用进程标题
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用进程描述
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _description: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用进程图标
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _icon: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用进程启动入口
|
||||||
|
* 对应windowFrom参数name
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _startName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用版本信息
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _version: Version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用是否只存在一个进程
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _singleton: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否只是一个进程
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _isJustProcess: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进程所有的窗口配置信息
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _windowFormConfigs: Array<WindowFormConfig>;
|
||||||
|
|
||||||
|
constructor(info: AppProcessInfoParams) {
|
||||||
|
this._name = info.name;
|
||||||
|
this._title = info.title || '';
|
||||||
|
this._description = info.description || '';
|
||||||
|
this._icon = <string> info.icon;
|
||||||
|
this._startName = info.startName || '';
|
||||||
|
this._version = info.version || { company: 'XZG', major: 1, minor: 0, build: 0, private: 0 };
|
||||||
|
this._singleton = info.singleton;
|
||||||
|
this._isJustProcess = info.isJustProcess;
|
||||||
|
this._windowFormConfigs = info.windowFormConfigs || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get name() {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
public get title() {
|
||||||
|
return this._title;
|
||||||
|
}
|
||||||
|
public get description() {
|
||||||
|
return this._description;
|
||||||
|
}
|
||||||
|
public get icon() {
|
||||||
|
return this._icon;
|
||||||
|
}
|
||||||
|
public get startName() {
|
||||||
|
return this._startName;
|
||||||
|
}
|
||||||
|
public get version() {
|
||||||
|
return this._version;
|
||||||
|
}
|
||||||
|
public get singleton() {
|
||||||
|
return this._singleton;
|
||||||
|
}
|
||||||
|
public get isJustProcess() {
|
||||||
|
return this._isJustProcess;
|
||||||
|
}
|
||||||
|
public get windowFormConfigs() {
|
||||||
|
return this._windowFormConfigs;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/core/process/ProcessManages.ts
Normal file
78
src/core/process/ProcessManages.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import type AppProcess from './AppProcess.ts'
|
||||||
|
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||||
|
import { BasicSystemProcessInfo } from '@/core/system/BasicSystemProcessInfo.ts'
|
||||||
|
import { DesktopProcessInfo } from '@/core/desktop/DesktopProcessInfo.ts'
|
||||||
|
import type { AppProcessInfoParams } from '@/core/process/types/AppProcessInfoParams.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进程管理
|
||||||
|
*/
|
||||||
|
export default class ProcessManages {
|
||||||
|
private _processPool: Map<string, AppProcess> = new Map<string, AppProcess>();
|
||||||
|
private _processInfos: AppProcessInfo[] = [];
|
||||||
|
|
||||||
|
public get processInfos() {
|
||||||
|
return this._processInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
console.log('ProcessManages')
|
||||||
|
this.loadAppProcessInfos();
|
||||||
|
}
|
||||||
|
// TODO 加载所有进程信息
|
||||||
|
public loadAppProcessInfos() {
|
||||||
|
console.log('加载所有进程信息')
|
||||||
|
// 添加内置进程
|
||||||
|
const apps = import.meta.glob<AppProcessInfoParams>('../apps/**/*.json', { eager: true })
|
||||||
|
const internalProcessInfos: AppProcessInfo[] = Object.values(apps).map(data => new AppProcessInfo(data))
|
||||||
|
|
||||||
|
this._processInfos.push(BasicSystemProcessInfo)
|
||||||
|
this._processInfos.push(DesktopProcessInfo)
|
||||||
|
|
||||||
|
this._processInfos.push(...internalProcessInfos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加进程
|
||||||
|
public addProcess(process: AppProcess) {
|
||||||
|
this._processPool.set(process.id, process);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过进程id查找进程
|
||||||
|
* @param id 进程id
|
||||||
|
*/
|
||||||
|
public findProcessById(id: string) {
|
||||||
|
return this._processPool.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过进程名称查找进程
|
||||||
|
* @param name 进程名称
|
||||||
|
*/
|
||||||
|
public findProcessByName<T extends AppProcess = AppProcess>(name: string) {
|
||||||
|
const pools = [...this._processPool.values()];
|
||||||
|
return pools.find(proc => proc.processInfo.name === name) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据进程id删除进程
|
||||||
|
* @param id 进程id
|
||||||
|
*/
|
||||||
|
public removeProcess(id: string): void;
|
||||||
|
/**
|
||||||
|
* 根据进程删除进程
|
||||||
|
* @param process 进程信息
|
||||||
|
*/
|
||||||
|
public removeProcess(process: AppProcess): void;
|
||||||
|
public removeProcess(params: string | AppProcess) {
|
||||||
|
const id = typeof params === 'string' ? params : params.id;
|
||||||
|
this._processPool.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过进程名称查找进程信息
|
||||||
|
*/
|
||||||
|
public findProcessInfoByName(name: string) {
|
||||||
|
return this._processInfos.find(info => info.name === name);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/core/process/types/AppProcessEvent.ts
Normal file
14
src/core/process/types/AppProcessEvent.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* 进程的事件
|
||||||
|
* <p>onProcessExit - 进程退出</p>
|
||||||
|
* <p>onProcessWindowFormOpen - 进程的窗体打开</p>
|
||||||
|
* <p>onProcessWindowFormExit - 进程的窗体退出</p>
|
||||||
|
* <p>onProcessWindowFormFocus - 进程的窗体获取焦点</p>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
type AppProcessEvent =
|
||||||
|
'onProcessExit' |
|
||||||
|
'onProcessWindowFormOpen' |
|
||||||
|
'onProcessWindowFormExit' |
|
||||||
|
'onProcessWindowFormFocus' |
|
||||||
|
'onProcessWindowFormBlur'
|
||||||
26
src/core/process/types/AppProcessInfoParams.ts
Normal file
26
src/core/process/types/AppProcessInfoParams.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Version } from '../../common/types/Version.ts'
|
||||||
|
import type { WindowFormConfig } from '../../window/types/WindowFormConfig.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用进程入参信息
|
||||||
|
*/
|
||||||
|
export interface AppProcessInfoParams {
|
||||||
|
/** 应用进程名称 */
|
||||||
|
name: string;
|
||||||
|
/** 应用进程标题 */
|
||||||
|
title?: string;
|
||||||
|
/** 应用进程描述 */
|
||||||
|
description?: string;
|
||||||
|
/** 应用进程图标 */
|
||||||
|
icon?: string;
|
||||||
|
/** 应用进程启动入口 */
|
||||||
|
startName?: string;
|
||||||
|
/** 应用版本信息 */
|
||||||
|
version?: Version;
|
||||||
|
/** 应用是否只存在一个进程 */
|
||||||
|
singleton: boolean;
|
||||||
|
/** 是否只是一个进程, 没有UI */
|
||||||
|
isJustProcess: boolean;
|
||||||
|
/** 进程所有的窗口配置信息 */
|
||||||
|
windowFormConfigs?: WindowFormConfig[];
|
||||||
|
}
|
||||||
18
src/core/system/BasicSystemProcess.ts
Normal file
18
src/core/system/BasicSystemProcess.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import AppProcess from '../process/AppProcess.ts'
|
||||||
|
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础系统进程
|
||||||
|
*/
|
||||||
|
export class BasicSystemProcess extends AppProcess{
|
||||||
|
private _isMounted: boolean = false;
|
||||||
|
|
||||||
|
public get isMounted() {
|
||||||
|
return this._isMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(info: AppProcessInfo) {
|
||||||
|
super(info)
|
||||||
|
console.log('BasicSystemProcess')
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/core/system/BasicSystemProcessInfo.ts
Normal file
18
src/core/system/BasicSystemProcessInfo.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础系统进程信息
|
||||||
|
*/
|
||||||
|
export const BasicSystemProcessInfo = new AppProcessInfo({
|
||||||
|
name: 'basic-system',
|
||||||
|
title: '基础系统进程',
|
||||||
|
isJustProcess: true,
|
||||||
|
version: {
|
||||||
|
company: 'XZG',
|
||||||
|
major: 1,
|
||||||
|
minor: 0,
|
||||||
|
build: 0,
|
||||||
|
private: 0
|
||||||
|
},
|
||||||
|
singleton: true
|
||||||
|
});
|
||||||
17
src/core/utils/Singleton.ts
Normal file
17
src/core/utils/Singleton.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/** 单例模式
|
||||||
|
* 确保一个类只有一个实例,并提供一个全局访问点
|
||||||
|
* @param constructor
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export function Singleton<T extends { new (...args: any[]): any }>(constructor: T): T {
|
||||||
|
let instance: any;
|
||||||
|
|
||||||
|
return new Proxy(constructor, {
|
||||||
|
construct(target, argsList, newTarget) {
|
||||||
|
if (!instance) {
|
||||||
|
instance = Reflect.construct(target, argsList, newTarget);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
22
src/core/window/WindowForm.ts
Normal file
22
src/core/window/WindowForm.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
import type AppProcess from '../process/AppProcess.ts'
|
||||||
|
import XSystem from '../XSystem.ts'
|
||||||
|
import type { AppProcessInfo } from '../process/AppProcessInfo.ts'
|
||||||
|
|
||||||
|
export default class WindowForm {
|
||||||
|
private readonly _id: string = uuidV4();
|
||||||
|
private readonly _procId: string;
|
||||||
|
|
||||||
|
public get id() {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
public get proc() {
|
||||||
|
return XSystem.instance.processManages.findProcessById(this._procId)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(proc: AppProcess, startName: string) {
|
||||||
|
this._procId = proc.id;
|
||||||
|
console.log('WindowForm')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
src/core/window/types/WindowFormConfig.ts
Normal file
60
src/core/window/types/WindowFormConfig.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* 窗体配置
|
||||||
|
*/
|
||||||
|
export interface WindowFormConfig {
|
||||||
|
/**
|
||||||
|
* 窗体名称
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* 窗体标题
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
/**
|
||||||
|
* 窗体图标
|
||||||
|
*/
|
||||||
|
icon?: string;
|
||||||
|
top?: number;
|
||||||
|
left?: number;
|
||||||
|
/**
|
||||||
|
* 窗体宽度
|
||||||
|
*/
|
||||||
|
width?: number;
|
||||||
|
widthAuto?: boolean;
|
||||||
|
/**
|
||||||
|
* 窗体高度
|
||||||
|
*/
|
||||||
|
height?: number;
|
||||||
|
heightAuto?: boolean;
|
||||||
|
/**
|
||||||
|
* 窗体最小宽度
|
||||||
|
*/
|
||||||
|
minWidth?: number;
|
||||||
|
/**
|
||||||
|
* 窗体最小高度
|
||||||
|
*/
|
||||||
|
minHeight?: number;
|
||||||
|
/**
|
||||||
|
* 窗体最大宽度
|
||||||
|
*/
|
||||||
|
maxWidth?: number;
|
||||||
|
/**
|
||||||
|
* 窗体最大高度
|
||||||
|
*/
|
||||||
|
maxHeight?: number;
|
||||||
|
/**
|
||||||
|
* 窗体透明度
|
||||||
|
*/
|
||||||
|
opacity?: number;
|
||||||
|
windowStyle?: string;
|
||||||
|
windowState?: number;
|
||||||
|
resizeMode?: number;
|
||||||
|
topMost?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否显示在任务栏
|
||||||
|
*/
|
||||||
|
showInTaskbar?: boolean;
|
||||||
|
showTitleBarIcon?: boolean;
|
||||||
|
showTitleBarText?: boolean;
|
||||||
|
hideTitleBar?: boolean;
|
||||||
|
}
|
||||||
135
src/css/basic.css
Normal file
135
src/css/basic.css
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/* ===== 基础重置 ===== */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box; /* 使用更直观的盒模型 */
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 根元素设置 ===== */
|
||||||
|
:root {
|
||||||
|
/* 字体设置 */
|
||||||
|
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||||
|
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
|
'Liberation Mono', 'Courier New', monospace;
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
--color-primary: #3a86ff;
|
||||||
|
--color-secondary: #8338ec;
|
||||||
|
--color-accent: #ff006e;
|
||||||
|
--color-text: #333;
|
||||||
|
--color-light: #f8f9fa;
|
||||||
|
--color-dark: #212529;
|
||||||
|
--color-gray: #6c757d;
|
||||||
|
|
||||||
|
/* 响应式基础大小 */
|
||||||
|
--base-font-size: 16px;
|
||||||
|
--spacing-unit: 1rem;
|
||||||
|
|
||||||
|
/* 过渡动画 */
|
||||||
|
--transition-speed: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 基础HTML元素样式 ===== */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth; /* 平滑滚动 */
|
||||||
|
font-size: var(--base-font-size);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
color: var(--color-text);
|
||||||
|
background-color: var(--color-light);
|
||||||
|
-webkit-font-smoothing: antialiased; /* 字体抗锯齿 */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 排版元素 ===== */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: var(--spacing-unit);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: var(--spacing-unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-secondary);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 表单元素 ===== */
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
font: inherit; /* 继承字体设置 */
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 图片和媒体 ===== */
|
||||||
|
img,
|
||||||
|
picture,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 实用工具类 ===== */
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 calc(var(--spacing-unit) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 响应式媒体查询 ===== */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
:root {
|
||||||
|
--base-font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main.ts
Normal file
16
src/main.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
import './css/basic.css'
|
||||||
|
|
||||||
|
// import App from './App.vue'
|
||||||
|
|
||||||
|
// const app = createApp(App)
|
||||||
|
//
|
||||||
|
// app.use(createPinia())
|
||||||
|
//
|
||||||
|
// app.mount('#app')
|
||||||
|
|
||||||
|
import XSystem from '@/core/XSystem.ts'
|
||||||
|
XSystem.instance.initialization(document.querySelector('#app')!);
|
||||||
12
src/stores/counter.ts
Normal file
12
src/stores/counter.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
||||||
16
tsconfig.app.json
Normal file
16
tsconfig.app.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"target": "ES6",
|
||||||
|
"module": "ESNext",
|
||||||
|
"strictPropertyInitialization": false // 严格属性初始化检查
|
||||||
|
}
|
||||||
|
}
|
||||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*",
|
||||||
|
"eslint.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
9
uno.config.ts
Normal file
9
uno.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// uno.config.ts
|
||||||
|
import { defineConfig, transformerDirectives, transformerVariantGroup } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
transformers: [
|
||||||
|
transformerVariantGroup(), // 允许像 "hover:(bg-red-500 text-white)" 这种写法
|
||||||
|
transformerDirectives(), // 解析 @apply / @screen / @unocss
|
||||||
|
],
|
||||||
|
})
|
||||||
23
vite.config.ts
Normal file
23
vite.config.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
vueJsx(),
|
||||||
|
vueDevTools(),
|
||||||
|
UnoCSS()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user