309 lines
10 KiB
Markdown
309 lines
10 KiB
Markdown
# DesktopContainer组件
|
||
|
||
<cite>
|
||
**Referenced Files in This Document**
|
||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||
- [App.vue](file://src/ui/App.vue)
|
||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue)
|
||
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
|
||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
|
||
</cite>
|
||
|
||
## 目录
|
||
1. [简介](#简介)
|
||
2. [核心功能分析](#核心功能分析)
|
||
3. [响应式网格布局初始化](#响应式网格布局初始化)
|
||
4. [ResizeObserver尺寸监听机制](#resizeobserver尺寸监听机制)
|
||
5. [应用图标状态管理与持久化](#应用图标状态管理与持久化)
|
||
6. [模板渲染与事件处理](#模板渲染与事件处理)
|
||
7. [与父组件的数据流关系](#与父组件的数据流关系)
|
||
8. [自定义容器集成示例](#自定义容器集成示例)
|
||
|
||
## 简介
|
||
|
||
`DesktopContainer` 是 Vue 桌面应用的核心容器组件,负责管理桌面图标的布局、状态和交互。该组件通过组合式函数 `useDesktopContainerInit` 实现了动态响应式网格系统,并结合 `localStorage` 提供图标位置的持久化存储能力。作为桌面环境的主视图容器,它与 `App.vue` 父组件构成清晰的数据流结构,为上层应用提供稳定可靠的桌面管理服务。
|
||
|
||
## 核心功能分析
|
||
|
||
`DesktopContainer` 组件承担着桌面环境的核心职责,主要包括:
|
||
- 初始化并维护一个基于 CSS Grid 的响应式布局系统
|
||
- 动态计算网格行列数及单元格实际尺寸以适应容器变化
|
||
- 管理所有桌面应用图标的元数据及其在网格中的坐标位置
|
||
- 通过本地存储实现用户自定义图标准置的持久化
|
||
- 提供标准化的应用启动接口(双击事件)
|
||
- 支持拖拽重排功能并与子组件 `AppIcon` 协同工作
|
||
|
||
该组件的设计体现了关注点分离原则,将复杂的布局逻辑封装在独立的组合式函数中,保持了模板的简洁性和可维护性。
|
||
|
||
**Section sources**
|
||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||
|
||
## 响应式网格布局初始化
|
||
|
||
### useDesktopContainerInit组合式函数
|
||
|
||
`useDesktopContainerInit` 函数是整个桌面布局系统的核心引擎,接收一个 CSS 选择器字符串 `containerStr` 作为参数,用于定位需要监控尺寸变化的 DOM 容器元素。
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class IGridTemplateParams {
|
||
+cellExpectWidth : number
|
||
+cellExpectHeight : number
|
||
+cellRealWidth : number
|
||
+cellRealHeight : number
|
||
+gapX : number
|
||
+gapY : number
|
||
+colCount : number
|
||
+rowCount : number
|
||
}
|
||
class useDesktopContainerInit {
|
||
-container : HTMLElement
|
||
-gridTemplate : IGridTemplateParams
|
||
-ro : ResizeObserver
|
||
-appIconsRef : Ref~Array~
|
||
-exceedApp : Ref~Array~
|
||
+return gridStyle : ComputedRef
|
||
}
|
||
useDesktopContainerInit --> IGridTemplateParams : "uses"
|
||
```
|
||
|
||
**Diagram sources**
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L1-L20)
|
||
|
||
### gridTemplate参数计算逻辑
|
||
|
||
`gridTemplate` 对象采用 `reactive` 声明为响应式数据,包含以下关键属性:
|
||
- `cellExpectWidth` 和 `cellExpectHeight`:单元格期望尺寸(默认90x110px)
|
||
- `gapX` 和 `gapY`:行列间距(默认4px)
|
||
- `colCount` 和 `rowCount`:动态计算的总行列数
|
||
|
||
初始状态下,行列数设为1,随后由 `ResizeObserver` 根据容器实际尺寸重新计算。
|
||
|
||
### gridStyle动态生成机制
|
||
|
||
通过 `computed` 属性 `gridStyle` 动态生成应用于容器的内联样式:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start([开始计算]) --> CalcColumns["构建 gridTemplateColumns<br/>repeat(colCount, minmax(cellExpectWidth + 'px', 1fr))"]
|
||
CalcColumns --> CalcRows["构建 gridTemplateRows<br/>repeat(rowCount, minmax(cellExpectHeight + 'px', 1fr))"]
|
||
CalcRows --> SetGap["设置 gap: gapY + 'px' gapX + 'px'"]
|
||
SetGap --> ReturnStyle["返回样式对象"]
|
||
ReturnStyle --> End([完成])
|
||
```
|
||
|
||
此计算属性确保了每当 `gridTemplate` 中的任何字段发生变化时,都能立即生成正确的 CSS Grid 样式规则。
|
||
|
||
**Section sources**
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||
|
||
## ResizeObserver尺寸监听机制
|
||
|
||
### 尺寸监听流程
|
||
|
||
`ResizeObserver` 被用来监听传入选择器所匹配容器的尺寸变化,其回调函数执行以下步骤:
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant RO as ResizeObserver
|
||
participant CT as Container
|
||
participant GT as gridTemplate
|
||
RO->>CT : getBoundingClientRect()
|
||
CT-->>RO : 返回容器矩形信息
|
||
RO->>GT : 计算 colCount
|
||
RO->>GT : 计算 rowCount
|
||
RO->>GT : 计算 cellRealWidth
|
||
RO->>GT : 计算 cellRealHeight
|
||
```
|
||
|
||
**Diagram sources**
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L38-L52)
|
||
|
||
### 行列数计算公式
|
||
|
||
根据容器当前宽度和高度,使用如下数学公式计算最优行列分布:
|
||
|
||
```
|
||
colCount = floor((width + gapX) / (cellExpectWidth + gapX))
|
||
rowCount = floor((height + gapY) / (cellExpectHeight + gapY))
|
||
```
|
||
|
||
这种算法确保即使在存在间隙的情况下也能最大化利用可用空间。
|
||
|
||
### 实际单元格尺寸调整
|
||
|
||
考虑到间隙对总可用空间的影响,实际单元格尺寸通过以下方式精确计算:
|
||
|
||
```typescript
|
||
const w = containerRect.width - (gapX * (colCount - 1))
|
||
const h = containerRect.height - (gapY * (rowCount - 1))
|
||
cellRealWidth = w / colCount
|
||
cellRealHeight = h / rowCount
|
||
```
|
||
|
||
最终结果保留两位小数,保证视觉上的平滑过渡。
|
||
|
||
**Section sources**
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L38-L52)
|
||
|
||
## 应用图标状态管理与持久化
|
||
|
||
### appIconsRef状态管理
|
||
|
||
`appIconsRef` 是一个 `ref` 类型的响应式数组,存储所有桌面应用图标的配置信息。每个图标对象遵循 `IDesktopAppIcon` 接口规范:
|
||
|
||
```mermaid
|
||
erDiagram
|
||
IDesktopAppIcon {
|
||
string name PK
|
||
string icon
|
||
string path
|
||
int x
|
||
int y
|
||
}
|
||
```
|
||
|
||
**Diagram sources**
|
||
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L1-L15)
|
||
|
||
初始图标数据来源于两个渠道:
|
||
1. 当前运行的应用进程列表(模拟为空数组)
|
||
2. `localStorage` 中保存的历史图标位置信息
|
||
|
||
系统优先使用历史记录中的坐标,若无则按顺序自动分配。
|
||
|
||
### localStorage持久化机制
|
||
|
||
通过 `watch` 监听器实现自动持久化:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[appIconsRef变化] --> B{触发watch}
|
||
B --> C[序列化为JSON字符串]
|
||
C --> D[存入localStorage]
|
||
D --> E[键名为'desktopAppIconInfo']
|
||
```
|
||
|
||
同时,在初始化时从 `localStorage` 读取已有数据,实现跨会话的状态恢复。
|
||
|
||
### 图标重排逻辑
|
||
|
||
当窗口大小导致网格行列数变化时,`rearrangeIcons` 函数会被调用,执行智能重排算法:
|
||
|
||
1. 优先保留原有有效位置的图标
|
||
2. 为移出可视区域的图标寻找新的空闲位置
|
||
3. 若无足够空间,则将其加入 `exceedApp` 隐藏列表
|
||
|
||
该机制确保用户体验的一致性,避免图标因窗口缩放而丢失。
|
||
|
||
**Section sources**
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L60-L94)
|
||
|
||
## 模板渲染与事件处理
|
||
|
||
### v-for循环渲染机制
|
||
|
||
组件模板使用 `v-for` 指令遍历 `appIconsRef` 数组,为每个图标实例化一个 `AppIcon` 子组件:
|
||
|
||
```vue
|
||
<AppIcon
|
||
v-for="(appIcon, i) in appIconsRef"
|
||
:key="i"
|
||
:iconInfo="appIcon"
|
||
:gridTemplate="gridTemplate"
|
||
@dblclick="runApp(appIcon)"
|
||
/>
|
||
```
|
||
|
||
`:key` 使用索引值确保渲染性能,`iconInfo` 和 `gridTemplate` 作为 props 向下传递必要数据。
|
||
|
||
### runApp双击事件扩展点
|
||
|
||
`@dblclick` 事件绑定到 `runApp` 方法,目前为空实现,作为未来功能扩展的预留接口:
|
||
|
||
```typescript
|
||
const runApp = (appIcon: IDesktopAppIcon) => {}
|
||
```
|
||
|
||
此处可集成应用启动逻辑,如进程管理器调用、窗口创建等。
|
||
|
||
### AppIcon组件协同
|
||
|
||
`AppIcon` 组件接收父级传递的 `gridTemplate` 参数,结合自身 `x/y` 坐标计算绝对位置:
|
||
|
||
```css
|
||
grid-column: ${x}/${x + 1};
|
||
grid-row: ${y}/${y + 1};
|
||
```
|
||
|
||
并实现拖拽功能,在释放时根据鼠标位置更新坐标,形成闭环控制。
|
||
|
||
**Section sources**
|
||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
|
||
|
||
## 与父组件的数据流关系
|
||
|
||
### 挂载关系分析
|
||
|
||
`DesktopContainer` 被直接嵌入 `App.vue` 的模板结构中,位于 `.desktop-bg` 容器内部:
|
||
|
||
```mermaid
|
||
graph TB
|
||
A[App.vue] --> B[desktop-root]
|
||
B --> C[desktop-bg]
|
||
C --> D[DesktopContainer]
|
||
B --> E[task-bar]
|
||
```
|
||
|
||
这种层级结构明确了其作为主内容区核心组件的地位。
|
||
|
||
### 数据流路径
|
||
|
||
数据流动遵循典型的 Vue 单向数据流模式:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[onMounted Hook] --> B[查询DOM容器]
|
||
B --> C[启动ResizeObserver]
|
||
C --> D[触发尺寸计算]
|
||
D --> E[更新gridTemplate]
|
||
E --> F[computed生成gridStyle]
|
||
F --> G[模板应用样式]
|
||
H[localStorage读取] --> I[初始化appIconsRef]
|
||
I --> J[渲染AppIcon列表]
|
||
J --> K[用户交互]
|
||
K --> L[更新appIconsRef]
|
||
L --> M[watch触发持久化]
|
||
```
|
||
|
||
整个过程无需向上通信,完全由组合式函数内部闭环处理。
|
||
|
||
**Section sources**
|
||
- [App.vue](file://src/ui/App.vue#L1-L52)
|
||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||
|
||
## 自定义容器集成示例
|
||
|
||
要在其他环境中复用 `DesktopContainer`,可通过修改 `containerStr` 参数指定不同的监听目标:
|
||
|
||
```typescript
|
||
// 在自定义组件中使用
|
||
const { appIconsRef, gridStyle, gridTemplate } = useDesktopContainerInit('#custom-desktop-container')
|
||
```
|
||
|
||
对应的 HTML 结构需包含匹配的选择器:
|
||
|
||
```html
|
||
<div id="custom-desktop-container" :style="gridStyle">
|
||
<!-- AppIcon will be rendered here -->
|
||
</div>
|
||
```
|
||
|
||
注意确保目标元素具有明确的尺寸(宽高),否则 `ResizeObserver` 无法正确计算布局参数。此外,建议保持原有的 CSS 类名以继承样式定义。
|
||
|
||
**Section sources**
|
||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94) |