保存
This commit is contained in:
204
.qoder/repowiki/zh/content/UI组件体系/AppIcon组件.md
Normal file
204
.qoder/repowiki/zh/content/UI组件体系/AppIcon组件.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# AppIcon组件
|
||||
|
||||
<cite>
|
||||
**Referenced Files in This Document **
|
||||
- [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)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [核心数据结构](#核心数据结构)
|
||||
3. [拖拽交互机制](#拖拽交互机制)
|
||||
4. [网格定位原理](#网格定位原理)
|
||||
5. [事件过滤逻辑](#事件过滤逻辑)
|
||||
6. [容器响应式布局](#容器响应式布局)
|
||||
7. [图标重排策略](#图标重排策略)
|
||||
8. [持久化存储](#持久化存储)
|
||||
9. [扩展与最佳实践](#扩展与最佳实践)
|
||||
|
||||
## 简介
|
||||
`AppIcon` 组件是桌面应用系统中的可交互图标单元,实现了基于HTML5 Drag API的拖拽功能。该组件通过精确的坐标计算和网格系统集成,允许用户将图标重新定位到桌面容器的任意有效网格位置。本文档深入解析其技术实现细节,涵盖从拖拽事件处理、坐标换算、网格定位到状态持久化的完整流程。
|
||||
|
||||
**Section sources**
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
|
||||
|
||||
## 核心数据结构
|
||||
|
||||
### IDesktopAppIcon 接口
|
||||
定义了桌面图标的元数据结构,包含名称、图标资源、启动路径及在网格布局中的位置坐标。
|
||||
|
||||
```typescript
|
||||
export interface IDesktopAppIcon {
|
||||
name: string; // 图标显示名称
|
||||
icon: string; // 图标资源路径或标识符
|
||||
path: string; // 关联的应用程序启动路径
|
||||
x: number; // 在grid布局中的列索引(从1开始)
|
||||
y: number; // 在grid布局中的行索引(从1开始)
|
||||
}
|
||||
```
|
||||
|
||||
该设计意图明确:`x` 和 `y` 字段直接映射到CSS Grid的`grid-column`和`grid-row`属性,实现了数据驱动的UI布局。
|
||||
|
||||
**Section sources**
|
||||
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
|
||||
|
||||
## 拖拽交互机制
|
||||
|
||||
`AppIcon` 组件利用原生HTML5 Drag API实现拖拽功能:
|
||||
|
||||
1. **启用拖拽**:通过设置 `draggable="true"` 属性激活元素的拖拽能力。
|
||||
2. **事件监听**:
|
||||
- `@dragstart`:拖拽开始时触发,当前实现为空函数,保留未来扩展空间。
|
||||
- `@dragend`:拖拽结束时触发,执行核心的坐标计算与位置更新逻辑。
|
||||
|
||||
此机制无需依赖第三方库,轻量且兼容性好。
|
||||
|
||||
**Section sources**
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L2-L8)
|
||||
|
||||
## 网格定位原理
|
||||
|
||||
### 动态样式绑定
|
||||
组件通过内联样式动态设置其在CSS Grid中的位置:
|
||||
```vue
|
||||
:style="`grid-column: ${iconInfo.x}/${iconInfo.x + 1}; grid-row: ${iconInfo.y}/${iconInfo.y + 1};`"
|
||||
```
|
||||
此表达式将 `iconInfo.x` 和 `iconInfo.y` 的值转换为Grid的列/行跨度声明,确保图标精准占据一个网格单元。
|
||||
|
||||
### 坐标换算过程
|
||||
`onDragEnd` 事件处理器的核心任务是将鼠标绝对坐标转换为相对网格坐标:
|
||||
|
||||
1. **获取容器边界**:使用 `getBoundingClientRect()` 获取父容器的绝对位置和尺寸。
|
||||
2. **计算相对坐标**:通过 `e.clientX - rect.left` 和 `e.clientY - rect.top` 得到鼠标相对于容器左上角的偏移量。
|
||||
3. **确定目标网格**:利用 `cellRealWidth` 和 `cellRealHeight` 进行除法运算并向上取整(`Math.ceil`),得到目标网格的行列索引。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[拖拽结束] --> B{获取鼠标<br/>clientX/clientY}
|
||||
B --> C{获取容器<br/>getBoundingClientRect}
|
||||
C --> D[计算鼠标相对<br/>容器坐标]
|
||||
D --> E[用cellRealWidth/Height<br/>计算网格索引]
|
||||
E --> F[更新iconInfo.x/y]
|
||||
F --> G[触发UI重渲染]
|
||||
```
|
||||
|
||||
**Diagram sources **
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L10-L38)
|
||||
|
||||
**Section sources**
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L10-L38)
|
||||
|
||||
## 事件过滤逻辑
|
||||
|
||||
为了防止图标被错误地放置在其他图标之上,`onDragEnd` 方法实现了关键的事件过滤:
|
||||
|
||||
1. 使用 `document.elementFromPoint(e.clientX, e.clientY)` 检测鼠标终点位置的DOM元素。
|
||||
2. 如果该元素是另一个 `.icon-container`,则立即返回,不执行任何位置更新。
|
||||
|
||||
此逻辑确保了“空位投放”原则,提升了用户体验,避免了图标的视觉重叠。
|
||||
|
||||
**Section sources**
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L13-L17)
|
||||
|
||||
## 容器响应式布局
|
||||
|
||||
### IGridTemplateParams 接口
|
||||
该接口定义了网格系统的动态参数,是实现响应式布局的关键。
|
||||
|
||||
```typescript
|
||||
export interface IGridTemplateParams {
|
||||
cellExpectWidth: number; // 单元格预设宽度
|
||||
cellExpectHeight: number; // 单元格预设高度
|
||||
cellRealWidth: number; // 单元格实际宽度
|
||||
cellRealHeight: number; // 单元格实际高度
|
||||
gapX: number; // 列间距
|
||||
gapY: number; // 行间距
|
||||
colCount: number; // 总列数
|
||||
rowCount: number; // 总行数
|
||||
}
|
||||
```
|
||||
|
||||
### 实际尺寸计算
|
||||
`useDesktopContainerInit` 函数通过 `ResizeObserver` 监听容器尺寸变化,并动态计算 `cellRealWidth` 和 `cellRealHeight`:
|
||||
|
||||
```javascript
|
||||
const w = containerRect.width - (gridTemplate.gapX * (gridTemplate.colCount - 1));
|
||||
gridTemplate.cellRealWidth = Number((w / gridTemplate.colCount).toFixed(2));
|
||||
```
|
||||
|
||||
此计算考虑了所有列间距的总和,确保了网格单元的实际尺寸能完美填充容器,无边距误差。
|
||||
|
||||
**Section sources**
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L30-L38)
|
||||
|
||||
## 图标重排策略
|
||||
|
||||
当网格的行列数发生变化时,`rearrangeIcons` 函数负责智能地重新分配图标位置:
|
||||
|
||||
1. **优先保留原位**:遍历所有图标,若其原位置在新网格范围内且未被占用,则保留在原位。
|
||||
2. **寻找空位**:对于超出范围或位置冲突的图标,从 `(1,1)` 开始扫描,为其寻找第一个可用的空网格。
|
||||
3. **隐藏溢出图标**:如果所有网格均被占用,则将无法安置的图标放入 `hideAppIcons` 数组。
|
||||
|
||||
此策略保证了用户自定义布局的最大程度保留,同时优雅地处理了空间不足的情况。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant DC as DesktopContainer
|
||||
participant UDCI as useDesktopContainerInit
|
||||
participant RI as rearrangeIcons
|
||||
DC->>UDCI : 监听gridTemplate变化
|
||||
UDCI->>RI : 调用rearrangeIcons()
|
||||
loop 遍历每个图标
|
||||
RI->>RI : 检查是否在新网格内且位置空闲
|
||||
alt 是
|
||||
RI->>RI : 保留在原位
|
||||
else 否
|
||||
RI->>RI : 加入临时队列
|
||||
end
|
||||
end
|
||||
loop 处理临时队列
|
||||
RI->>RI : 扫描网格寻找空位
|
||||
alt 找到空位
|
||||
RI->>RI : 分配新位置
|
||||
else 无空位
|
||||
RI->>RI : 加入hideAppIcons
|
||||
end
|
||||
end
|
||||
RI-->>UDCI : 返回appIcons和hideAppIcons
|
||||
UDCI->>DC : 更新appIconsRef
|
||||
```
|
||||
|
||||
**Diagram sources **
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L77-L80)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L77-L80)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
|
||||
## 持久化存储
|
||||
|
||||
用户的图标布局偏好通过 `localStorage` 实现持久化:
|
||||
|
||||
1. **写入**:使用 `watch` 监听 `appIconsRef.value` 的变化,一旦有更新,立即将整个数组序列化为JSON字符串并存入 `localStorage` 键名为 `'desktopAppIconInfo'` 的条目中。
|
||||
2. **读取**:在初始化时,尝试从 `localStorage` 中读取 `'desktopAppIconInfo'`,若存在则使用存储的位置信息覆盖默认布局。
|
||||
|
||||
这确保了用户关闭页面后再次打开时,仍能看到上次调整好的桌面布局。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L88-L90)
|
||||
|
||||
## 扩展与最佳实践
|
||||
|
||||
### 自定义图标样式
|
||||
可通过修改 `<style scoped>` 中的 `.icon-container` 类来定制图标外观,例如添加背景图片、阴影或动画效果。
|
||||
|
||||
### 扩展拖拽行为
|
||||
可在 `onDragStart` 回调中添加逻辑,如设置拖拽图像 (`e.dataTransfer.setDragImage`) 或传输自定义数据。
|
||||
|
||||
### 避免跨容器拖拽冲突
|
||||
**最佳方案**:为每个可拖拽区域(容器)维护独立的 `gridTemplate` 状态和图标集合。在 `onDragEnd` 事件中,首先确认 `pointTarget` 是否属于当前容器的 `.desktop-icons-container`,如果不是,则忽略此次拖拽操作,防止图标被错误地投放到其他容器中。
|
||||
309
.qoder/repowiki/zh/content/UI组件体系/DesktopContainer组件.md
Normal file
309
.qoder/repowiki/zh/content/UI组件体系/DesktopContainer组件.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# 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)
|
||||
254
.qoder/repowiki/zh/content/UI组件体系/UI组件体系.md
Normal file
254
.qoder/repowiki/zh/content/UI组件体系/UI组件体系.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# UI组件体系
|
||||
|
||||
<cite>
|
||||
**Referenced Files in This Document **
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.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)
|
||||
- [App.vue](file://src/ui/App.vue)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [核心数据模型](#核心数据模型)
|
||||
3. [主容器布局机制](#主容器布局机制)
|
||||
4. [可拖拽图标实现](#可拖拽图标实现)
|
||||
5. [组件层级与数据流](#组件层级与数据流)
|
||||
6. [模板使用示例](#模板使用示例)
|
||||
7. [自定义扩展建议](#自定义扩展建议)
|
||||
|
||||
## 简介
|
||||
本文档深入解析Vue桌面应用的UI组件结构与交互逻辑。重点阐述`DesktopContainer.vue`作为主容器的动态网格布局机制,以及`AppIcon.vue`如何实现可拖拽的应用图标功能。通过分析`IDesktopAppIcon`和`IGridTemplateParams`接口定义,说明图标数据模型和网格参数的设计原理。同时解释从根组件`App.vue`到子组件`DesktopContainer`再到`AppIcon`的父子关系及数据传递方式,并提供实际使用示例与扩展建议。
|
||||
|
||||
## 核心数据模型
|
||||
|
||||
### 桌面应用图标接口 (IDesktopAppIcon)
|
||||
该接口定义了桌面图标的元数据结构,包含名称、图标资源路径、启动路径及其在网格中的位置坐标。
|
||||
|
||||
```typescript
|
||||
export interface IDesktopAppIcon {
|
||||
name: string;
|
||||
icon: string;
|
||||
path: string;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明:**
|
||||
- `name`: 图标显示名称
|
||||
- `icon`: 图标资源路径
|
||||
- `path`: 应用启动路径
|
||||
- `x`: 在网格布局中的列索引(从1开始)
|
||||
- `y`: 在网格布局中的行索引(从1开始)
|
||||
|
||||
此接口用于统一管理所有桌面图标的配置信息,并支持持久化存储至`localStorage`。
|
||||
|
||||
### 网格模板参数接口 (IGridTemplateParams)
|
||||
该接口定义了网格布局的核心计算参数,支持响应式调整。
|
||||
|
||||
```typescript
|
||||
export interface IGridTemplateParams {
|
||||
readonly cellExpectWidth: number
|
||||
readonly cellExpectHeight: number
|
||||
cellRealWidth: number
|
||||
cellRealHeight: number
|
||||
gapX: number
|
||||
gapY: number
|
||||
colCount: number
|
||||
rowCount: number
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明:**
|
||||
- `cellExpectWidth/Height`: 单元格预设宽高
|
||||
- `cellRealWidth/Height`: 实际渲染时的单元格宽高
|
||||
- `gapX/Y`: 列/行间距
|
||||
- `colCount/rowCount`: 当前容器可容纳的总行列数
|
||||
|
||||
这些参数由`ResizeObserver`监听容器尺寸变化后动态计算得出,确保布局自适应。
|
||||
|
||||
**Section sources**
|
||||
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
|
||||
|
||||
## 主容器布局机制
|
||||
|
||||
`DesktopContainer.vue`组件负责构建整个桌面的网格布局系统。其核心是通过组合式API `useDesktopContainerInit` 初始化并维护网格状态。
|
||||
|
||||
### 响应式网格生成
|
||||
组件利用CSS Grid布局特性,通过计算属性`gridStyle`动态生成`grid-template-columns`和`grid-template-rows`样式规则:
|
||||
|
||||
```css
|
||||
gridTemplateColumns: `repeat(${gridTemplate.colCount}, minmax(${gridTemplate.cellExpectWidth}px, 1fr))`
|
||||
```
|
||||
|
||||
### 容器尺寸监听
|
||||
使用`ResizeObserver` API实时监听`.desktop-icons-container`容器的尺寸变化,重新计算:
|
||||
- 可容纳的行列数量 (`colCount`, `rowCount`)
|
||||
- 单元格实际尺寸 (`cellRealWidth`, `cellRealHeight`)
|
||||
|
||||
### 图标初始化与持久化
|
||||
首次加载时从`localStorage`读取历史图标位置信息,结合应用列表进行映射初始化。当图标位置变更时自动同步回本地存储。
|
||||
|
||||
### 超出边界处理
|
||||
当窗口缩放导致图标超出可视区域时,`rearrangeIcons`函数会智能重排图标:
|
||||
- 优先放置于原始位置
|
||||
- 若冲突则寻找最近空位
|
||||
- 实在无法容纳则暂存于`exceedApp`数组
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([组件挂载]) --> Observe["创建ResizeObserver"]
|
||||
Observe --> InitGrid["初始化网格参数"]
|
||||
InitGrid --> LoadStorage["从localStorage加载图标位置"]
|
||||
LoadStorage --> MapIcons["映射应用信息与图标"]
|
||||
MapIcons --> Render["渲染AppIcon组件"]
|
||||
Resize["容器尺寸变化"] --> Recalculate["重新计算行列数"]
|
||||
Recalculate --> Rearrange["调用rearrangeIcons重排"]
|
||||
Rearrange --> UpdateState["更新appIconsRef状态"]
|
||||
UpdateState --> SyncStorage["同步至localStorage"]
|
||||
```
|
||||
|
||||
**Diagram sources **
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||||
|
||||
**Section sources**
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||||
|
||||
## 可拖拽图标实现
|
||||
|
||||
`AppIcon.vue`组件实现了完整的拖拽交互逻辑,允许用户自由调整图标位置。
|
||||
|
||||
### 拖拽事件绑定
|
||||
在模板中为图标容器启用原生HTML5拖拽API:
|
||||
```html
|
||||
<div draggable="true" @dragstart="onDragStart" @dragend="onDragEnd">
|
||||
```
|
||||
|
||||
### 位置计算逻辑
|
||||
在`dragend`事件中执行以下步骤:
|
||||
1. 获取鼠标相对于容器的坐标
|
||||
2. 根据单元格实际尺寸换算为网格坐标(向上取整)
|
||||
3. 更新`iconInfo.x`和`iconInfo.y`属性
|
||||
|
||||
### 防止重复放置
|
||||
通过检查目标元素是否已是其他图标容器来避免无效操作:
|
||||
```javascript
|
||||
if (pointTarget.classList.contains('icon-container')) return
|
||||
```
|
||||
|
||||
### 样式定位
|
||||
使用CSS Grid的`grid-column`和`grid-row`属性将图标精确定位于指定网格单元:
|
||||
```css
|
||||
grid-column: ${x}/${x + 1}; grid-row: ${y}/${y + 1}
|
||||
```
|
||||
|
||||
**Section sources**
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
|
||||
|
||||
## 组件层级与数据流
|
||||
|
||||
整个UI体系遵循清晰的父子组件层级结构,数据沿树状结构单向流动。
|
||||
|
||||
### 组件层级关系
|
||||
```mermaid
|
||||
graph TD
|
||||
A[App.vue] --> B[DesktopContainer.vue]
|
||||
B --> C[AppIcon.vue]
|
||||
```
|
||||
|
||||
### 数据传递路径
|
||||
1. **App.vue** → **DesktopContainer.vue**: 通过直接嵌入`<DesktopContainer/>`标签建立父子关系
|
||||
2. **DesktopContainer.vue** → **AppIcon.vue**:
|
||||
- 使用`v-for`遍历`appIconsRef`数组创建多个实例
|
||||
- 通过`defineProps`接收`iconInfo`和`gridTemplate`两个关键参数
|
||||
- 绑定双击事件`@dblclick="runApp"`实现应用启动
|
||||
|
||||
### 状态管理特点
|
||||
- **集中式初始化**:所有状态在`useDesktopContainerInit`中统一创建
|
||||
- **响应式驱动**:基于Vue的`ref`和`reactive`实现自动更新
|
||||
- **本地持久化**:重要状态如图标位置通过`localStorage`保存
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as App.vue
|
||||
participant DC as DesktopContainer.vue
|
||||
participant AI as AppIcon.vue
|
||||
App->>DC : 渲染组件
|
||||
DC->>DC : 执行useDesktopContainerInit()
|
||||
DC->>DC : 初始化gridTemplate/appIconsRef
|
||||
DC->>AI : v-for循环渲染多个实例
|
||||
DC->>AI : 传递iconInfo和gridTemplate props
|
||||
AI->>AI : 用户拖拽图标
|
||||
AI->>AI : 计算新坐标并更新iconInfo
|
||||
AI->>DC : 触发watch监听
|
||||
DC->>DC : 同步至localStorage
|
||||
```
|
||||
|
||||
**Diagram sources **
|
||||
- [App.vue](file://src/ui/App.vue#L1-L52)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
|
||||
|
||||
**Section sources**
|
||||
- [App.vue](file://src/ui/App.vue#L1-L52)
|
||||
|
||||
## 模板使用示例
|
||||
|
||||
以下是如何在项目中正确使用这些组件的标准范例:
|
||||
|
||||
```vue
|
||||
<!-- 在任意父组件中引入DesktopContainer -->
|
||||
<template>
|
||||
<div class="desktop-wrapper">
|
||||
<DesktopContainer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DesktopContainer from '@/ui/desktop-container/DesktopContainer.vue'
|
||||
</script>
|
||||
```
|
||||
|
||||
每个`AppIcon`实例由`DesktopContainer`内部自动创建,无需手动实例化。只需确保:
|
||||
1. 应用列表数据已正确注入`useDesktopContainerInit`
|
||||
2. 图标资源配置路径准确无误
|
||||
3. localStorage中保留历史位置记录以实现记忆功能
|
||||
|
||||
## 自定义扩展建议
|
||||
|
||||
### 样式定制
|
||||
可通过覆盖SCSS变量或添加自定义类名修改图标外观:
|
||||
```scss
|
||||
.icon-container {
|
||||
@apply bg-transparent hover:bg-blue-100;
|
||||
border-radius: 8px;
|
||||
}
|
||||
```
|
||||
|
||||
### 功能增强
|
||||
- 添加右键菜单支持
|
||||
- 实现图标排序功能(按名称、时间等)
|
||||
- 增加动画过渡效果(拖拽、进入/离开)
|
||||
|
||||
### 性能优化
|
||||
- 对大量图标实施虚拟滚动
|
||||
- 使用Web Worker处理复杂重排算法
|
||||
- 添加防抖机制优化频繁resize场景
|
||||
|
||||
### 接口拓展
|
||||
可继承`IDesktopAppIcon`接口增加新字段:
|
||||
```typescript
|
||||
interface ExtendedIcon extends IDesktopAppIcon {
|
||||
tooltip?: string;
|
||||
category?: 'productivity' | 'entertainment' | 'development';
|
||||
lastLaunched?: Date;
|
||||
}
|
||||
```
|
||||
|
||||
以上扩展应在保持原有接口兼容性的前提下进行,确保不影响现有功能。
|
||||
Reference in New Issue
Block a user