保存
This commit is contained in:
193
.qoder/repowiki/zh/content/响应式布局系统/响应式布局系统.md
Normal file
193
.qoder/repowiki/zh/content/响应式布局系统/响应式布局系统.md
Normal file
@@ -0,0 +1,193 @@
|
||||
<cite>
|
||||
**本文档中引用的文件**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||||
- [basic.css](file://src/css/basic.css)
|
||||
- [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)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [核心组件分析](#核心组件分析)
|
||||
3. [响应式网格布局机制](#响应式网格布局机制)
|
||||
4. [图标位置管理与持久化](#图标位置管理与持久化)
|
||||
5. [视觉呈现与基础样式](#视觉呈现与基础样式)
|
||||
6. [依赖关系图](#依赖关系图)
|
||||
|
||||
## 简介
|
||||
|
||||
本技术文档深入剖析了基于CSS Grid的动态桌面布局系统。该系统以`useDesktopContainerInit`这一核心Vue组合式函数(Hook)为基础,实现了高度响应式的桌面容器功能。通过集成ResizeObserver API、Vue的响应式系统以及localStorage,该系统能够根据容器尺寸动态调整网格布局,并智能地重新排列桌面图标,同时将用户自定义的布局状态持久化存储。整体架构结合了现代前端框架特性与原生Web API,构建了一个灵活、可扩展且用户体验良好的虚拟桌面环境。
|
||||
|
||||
## 核心组件分析
|
||||
|
||||
系统的核心逻辑封装在`useDesktopContainerInit`函数中,该函数作为Vue 3的组合式API被`DesktopContainer.vue`组件所调用。其主要职责是初始化并管理整个桌面容器的状态,包括网格模板参数、计算样式以及桌面图标数据。该函数返回一个包含`gridTemplate`、`appIconsRef`和`gridStyle`三个关键属性的对象,为上层组件提供完整的布局控制能力。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class useDesktopContainerInit {
|
||||
+container : HTMLElement
|
||||
+gridTemplate : IGridTemplateParams
|
||||
+gridStyle : ComputedRef~Object~
|
||||
+ro : ResizeObserver
|
||||
+appIconsRef : Ref~Array<IDesktopAppIcon>~
|
||||
+exceedApp : Ref~Array<IDesktopAppIcon>~
|
||||
+useDesktopContainerInit(containerStr : string) : Object
|
||||
}
|
||||
class IGridTemplateParams {
|
||||
+cellExpectWidth : number
|
||||
+cellExpectHeight : number
|
||||
+cellRealWidth : number
|
||||
+cellRealHeight : number
|
||||
+gapX : number
|
||||
+gapY : number
|
||||
+colCount : number
|
||||
+rowCount : number
|
||||
}
|
||||
class IDesktopAppIcon {
|
||||
+name : string
|
||||
+icon : string
|
||||
+path : string
|
||||
+x : number
|
||||
+y : number
|
||||
}
|
||||
useDesktopContainerInit --> IGridTemplateParams : "包含"
|
||||
useDesktopContainerInit --> IDesktopAppIcon : "管理"
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
|
||||
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts#L3-L14)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||||
|
||||
## 响应式网格布局机制
|
||||
|
||||
### 动态网格计算流程
|
||||
|
||||
系统的响应式能力源于对`ResizeObserver`的巧妙运用。当`DesktopContainer`组件挂载时,`useDesktopContainerInit`会创建一个`ResizeObserver`实例,并将其绑定到指定的容器元素(如`.desktop-icons-container`)。每当容器的尺寸发生变化,观察者回调就会被触发,执行一系列精确的计算来更新网格布局。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[容器尺寸变化] --> B{ResizeObserver 触发}
|
||||
B --> C[获取容器实际宽高]
|
||||
C --> D[计算列数 colCount]
|
||||
D --> E[计算行数 rowCount]
|
||||
E --> F[计算单元格实际宽度 cellRealWidth]
|
||||
F --> G[计算单元格实际高度 cellRealHeight]
|
||||
G --> H[更新 gridTemplate 响应式对象]
|
||||
H --> I[computed 自动更新 gridStyle]
|
||||
I --> J[DOM 中的 style 属性更新]
|
||||
J --> K[浏览器重绘,应用新布局]
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L37-L58)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L37-L58)
|
||||
|
||||
### `gridStyle` 计算属性详解
|
||||
|
||||
`gridStyle`是一个由`computed`创建的计算属性,它直接决定了桌面容器的CSS Grid样式。该属性依赖于`gridTemplate`中的`colCount`、`rowCount`、`cellExpectWidth`、`cellExpectHeight`、`gapX`和`gapY`等值。其核心作用是将这些数值动态地转换为标准的CSS Grid声明:
|
||||
|
||||
- **`gridTemplateColumns`**: 使用`repeat()`函数生成指定数量的列轨道,每列的最小值为预设宽度(`cellExpectWidth`),最大值为`1fr`,确保了列的弹性伸缩。
|
||||
- **`gridTemplateRows`**: 与列同理,生成指定数量的行轨道。
|
||||
- **`gap`**: 设置行与列之间的间距。
|
||||
|
||||
这种设计使得布局的任何变化都能立即反映在UI上,实现了真正的数据驱动视图。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L25-L35)
|
||||
|
||||
## 图标位置管理与持久化
|
||||
|
||||
### 图标初始定位与重排算法
|
||||
|
||||
系统通过`appIconsRef`这个`ref`对象来管理所有桌面图标的集合。在初始化时,函数会尝试从`localStorage`中读取之前保存的图标位置信息(键名为`desktopAppIconInfo`)。对于每个应用,它首先检查是否有历史记录,如果有则使用历史坐标;如果没有,则根据当前的网格行列数进行默认的蛇形排列。
|
||||
|
||||
当网格的行列数因容器大小改变而发生变化时,`watch`监听器会被激活,调用`rearrangeIcons`函数对图标进行智能重排。该算法的核心逻辑如下:
|
||||
1. 遍历现有图标,优先保留那些仍在新网格范围内的图标。
|
||||
2. 对于超出新网格范围或需要移动的图标,尝试在新的网格空间内寻找空闲的位置进行放置。
|
||||
3. 如果没有足够的空间,则将无法显示的图标放入`exceedApp`数组中(可用于后续的“更多”菜单展示)。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Container as DesktopContainer.vue
|
||||
participant Hook as useDesktopContainerInit.ts
|
||||
participant Observer as ResizeObserver
|
||||
participant Storage as localStorage
|
||||
Observer->>Hook : resize事件触发
|
||||
Hook->>Hook : 计算新colCount, rowCount
|
||||
Hook->>Hook : 更新gridTemplate响应式对象
|
||||
Hook->>Hook : watch检测到变化
|
||||
Hook->>Hook : 调用rearrangeIcons()
|
||||
Hook->>Hook : 返回新的appIcons和hideAppIcons
|
||||
Hook->>Hook : 更新appIconsRef.value
|
||||
Hook->>Storage : 将appIconsRef.value序列化并存入localStorage
|
||||
Hook-->>Container : 提供更新后的appIconsRef和gridStyle
|
||||
Container->>Container : Vue自动更新DOM
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L78-L94)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L78-L94)
|
||||
|
||||
### 布局持久化实现
|
||||
|
||||
为了保证用户的个性化设置不丢失,系统利用`localStorage`实现了布局的持久化。通过另一个`watch`监听器,每当`appIconsRef`的值发生改变(无论是因为重排还是用户拖拽),都会立即将最新的图标数组序列化为JSON字符串,并存储到`localStorage`中。当下次页面加载时,初始化代码会优先读取这段存储的数据,从而恢复用户上次的桌面布局。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L88-L92)
|
||||
|
||||
## 视觉呈现与基础样式
|
||||
|
||||
### AppIcon 组件的样式绑定
|
||||
|
||||
`AppIcon.vue`组件负责渲染单个桌面图标。它通过`style`属性直接绑定了`grid-column`和`grid-row`这两个CSS Grid属性,其值来源于`iconInfo`对象的`x`和`y`坐标。例如,`grid-column: 2 / 3`表示该图标占据第2列。这种绑定方式使得图标的物理位置完全由其数据模型决定,实现了布局的动态化。
|
||||
|
||||
**Section sources**
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L2-L8)
|
||||
|
||||
### basic.css 的基础样式作用
|
||||
|
||||
`basic.css`文件提供了整个应用的基础样式规则,为动态布局奠定了视觉基调。它包含了:
|
||||
- **盒模型重置**: 统一使用`border-box`,简化尺寸计算。
|
||||
- **根元素变量**: 定义了字体、颜色、间距等CSS自定义属性,便于全局主题管理。
|
||||
- **基础元素样式**: 对`body`、`a`、`button`等元素进行了基础美化。
|
||||
- **实用工具类**: 如`.container`用于创建居中的内容区域。
|
||||
- **响应式支持**: 包含了针对减少动画偏好的媒体查询。
|
||||
|
||||
虽然`DesktopContainer`和`AppIcon`组件使用了`scoped`样式,但`basic.css`提供的全局基础样式确保了整个应用的一致性和可用性。
|
||||
|
||||
**Section sources**
|
||||
- [basic.css](file://src/css/basic.css#L1-L134)
|
||||
|
||||
## 依赖关系图
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[basic.css] --> |提供基础样式| B(DesktopContainer.vue)
|
||||
C[useDesktopContainerInit.ts] --> |导出核心逻辑| B
|
||||
B --> |使用| C
|
||||
B --> |渲染| D(AppIcon.vue)
|
||||
D --> |接收props| C
|
||||
C --> |读写| E[localStorage]
|
||||
C --> |监听| F[ResizeObserver]
|
||||
F --> |响应| G[容器尺寸变化]
|
||||
C --> |类型定义| H[IGridTemplateParams.ts]
|
||||
C --> |类型定义| I[IDesktopAppIcon.ts]
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1-L23)
|
||||
- [AppIcon.vue](file://src/ui/desktop-container/AppIcon.vue#L1-L52)
|
||||
- [basic.css](file://src/css/basic.css#L1-L134)
|
||||
124
.qoder/repowiki/zh/content/响应式布局系统/图标重排与持久化.md
Normal file
124
.qoder/repowiki/zh/content/响应式布局系统/图标重排与持久化.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 图标重排与持久化
|
||||
|
||||
<cite>
|
||||
**Referenced Files in This Document **
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
|
||||
- [IDesktopAppIcon.ts](file://src/ui/types/IDesktopAppIcon.ts)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [appIconsRef的创建过程](#appiconsref的创建过程)
|
||||
2. [localStorage数据同步机制](#localstorage数据同步机制)
|
||||
3. [网格变化监听与重排响应](#网格变化监听与重排响应)
|
||||
4. [rearrangeIcons算法详解](#rearrangeicons算法详解)
|
||||
5. [布局状态持久化策略](#布局状态持久化策略)
|
||||
|
||||
## appIconsRef的创建过程
|
||||
|
||||
`appIconsRef` 是一个 Vue 响应式引用,用于管理桌面图标的布局状态。其创建过程始于 `useDesktopContainerInit` 函数的调用,该函数接收容器选择器字符串作为参数并初始化核心布局逻辑。
|
||||
|
||||
在初始化过程中,系统首先从 `localStorage` 中读取键为 `desktopAppIconInfo` 的存储项,尝试恢复之前保存的图标位置信息。若存在历史数据,则解析为 `oldAppIcons` 数组;否则使用空数组作为默认值。随后,系统遍历当前可用的应用程序列表(`appInfos`),为每个应用创建对应的桌面图标对象。
|
||||
|
||||
对于每个新生成的图标,系统优先检查是否存在同名的历史图标记录。如果存在,则继承其坐标(x, y);若不存在,则根据当前网格的行列数按索引自动分配初始坐标:
|
||||
- 列坐标 x = 当前索引 % 行数 + 1
|
||||
- 行坐标 y = floor(当前索引 / 行数) + 1
|
||||
|
||||
最终,这些图标数据被封装为响应式引用 `appIconsRef`,供视图层绑定使用。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L74-L94)
|
||||
|
||||
## localStorage数据同步机制
|
||||
|
||||
系统通过双向数据绑定机制实现 `appIconsRef` 与 `localStorage` 的实时同步。当用户对桌面图标进行拖拽、重排等操作导致布局变更时,Vue 的响应式系统会触发相应的监听器,将最新状态持久化到本地存储中。
|
||||
|
||||
具体而言,系统注册了一个针对 `appIconsRef.value` 的 `watch` 监听器。每当图标数组内容发生变化(如新增、删除或位置调整),该监听器便会执行回调函数,将更新后的 `appIcons` 数组序列化为 JSON 字符串,并通过 `localStorage.setItem('desktopAppIconInfo', ...)` 方法写入浏览器本地存储。
|
||||
|
||||
此机制确保了用户在刷新页面或重新打开应用后,能够恢复上次关闭时的桌面布局,实现了跨会话的个性化配置记忆功能。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L89-L92)
|
||||
|
||||
## 网格变化监听与重排响应
|
||||
|
||||
为了适应不同屏幕尺寸和窗口大小的变化,系统利用 `ResizeObserver` API 实时监测桌面容器的尺寸变动,并动态计算最优的网格列数(`colCount`)和行数(`rowCount`)。当这些参数发生改变时,系统需要智能地重新排列所有图标以避免重叠或溢出。
|
||||
|
||||
为此,系统设置了一个复合监听器 `watch(() => [gridTemplate.colCount, gridTemplate.rowCount], ...)`, 专门监控 `colCount` 和 `rowCount` 的联合变化。一旦检测到新的网格维度,监听器立即调用 `rearrangeIcons` 函数,传入当前图标列表及新的行列限制,执行自动重排逻辑。
|
||||
|
||||
该监听器包含优化判断:若新旧行列数完全一致,则直接返回,避免不必要的重排计算,提升性能效率。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L86-L88)
|
||||
|
||||
## rearrangeIcons算法详解
|
||||
|
||||
`rearrangeIcons` 函数是整个图标管理系统的核心算法,负责处理图标冲突、寻找空闲位置以及管理超出可视范围的图标。其输入为原始图标数组和目标网格的最大行列数,输出为包含正常显示图标和隐藏图标的结构体。
|
||||
|
||||
### 冲突检测与占用标记
|
||||
|
||||
算法首先创建一个 `Set<string>` 类型的 `occupied` 集合,用于记录已被占用的网格单元。通过辅助函数 `key(x, y)` 将二维坐标转换为唯一字符串标识(如 `"1,2"`),实现高效的哈希查找。
|
||||
|
||||
### 分阶段处理流程
|
||||
|
||||
1. **第一阶段:保留有效位置**
|
||||
- 遍历所有图标,筛选出位于当前网格范围内的图标(即 `x ≤ maxCol && y ≤ maxRow`)
|
||||
- 检查目标位置是否已被占用,若未占用则将其加入结果数组 `appIcons` 并标记为已占用
|
||||
- 对于位置无效或冲突的图标,则暂存至临时数组 `temp`
|
||||
|
||||
2. **第二阶段:填补空位**
|
||||
- 遍历 `temp` 数组中的待安置图标
|
||||
- 若当前已放置图标数量小于网格总容量(`maxCol * maxRow`),则从左上角 `(1,1)` 开始逐行扫描,寻找第一个空闲位置进行安置
|
||||
- 一旦找到合适位置,立即跳出内层循环,继续处理下一个图标
|
||||
|
||||
3. **第三阶段:处理溢出图标**
|
||||
- 若网格已满且仍有剩余图标无法安置,则将其归类至 `hideAppIcons` 数组
|
||||
- 这些图标将在 UI 层面被隐藏,防止界面混乱
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([开始重排]) --> ValidatePosition["验证图标位置有效性"]
|
||||
ValidatePosition --> InRange{"是否在网格范围内?"}
|
||||
InRange --> |是| CheckOccupied["检查位置是否被占用"]
|
||||
InRange --> |否| ToTemp["加入临时数组 temp"]
|
||||
CheckOccupied --> IsFree{"位置空闲?"}
|
||||
IsFree --> |是| PlaceIcon["放置图标并标记占用"]
|
||||
IsFree --> |否| ToTemp
|
||||
PlaceIcon --> NextIcon["处理下一个图标"]
|
||||
ToTemp --> NextIcon
|
||||
NextIcon --> AllProcessed{"所有图标处理完毕?"}
|
||||
AllProcessed --> |否| ValidatePosition
|
||||
AllProcessed --> |是| FillEmpty["填补空位"]
|
||||
FillEmpty --> HasSpace{"仍有空位?"}
|
||||
HasSpace --> |是| FindSlot["从(1,1)开始寻找空位"]
|
||||
HasSpace --> |否| HideExcess["隐藏超出图标"]
|
||||
FindSlot --> CanPlace{"能否放置?"}
|
||||
CanPlace --> |是| UpdateAppIcons["更新 appIcons 数组"]
|
||||
CanPlace --> |否| HideExcess
|
||||
UpdateAppIcons --> MoreTemp{"temp 数组为空?"}
|
||||
MoreTemp --> |否| FillEmpty
|
||||
MoreTemp --> |是| ReturnResult["返回结果: appIcons + hideAppIcons"]
|
||||
HideExcess --> ReturnResult
|
||||
```
|
||||
|
||||
**Diagram sources **
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
|
||||
## 布局状态持久化策略
|
||||
|
||||
系统的布局持久化策略建立在 Vue 的响应式系统与浏览器本地存储的协同工作之上。`appIconsRef` 作为单一数据源(Single Source of Truth),集中管理所有图标的坐标信息。任何对图标的修改操作(无论是用户交互还是程序逻辑)都会反映到该引用上。
|
||||
|
||||
通过 `watch` 监听器,系统实现了从内存状态到持久化存储的单向同步。这种设计具有以下优势:
|
||||
|
||||
- **自动同步**:无需手动调用保存方法,所有变更自动记录
|
||||
- **原子性保证**:每次写入都是完整的数组快照,避免部分更新导致的数据不一致
|
||||
- **跨会话恢复**:页面刷新后可通过 `localStorage.getItem('desktopAppIconInfo')` 重建初始状态
|
||||
- **容错处理**:使用 `JSON.parse(... || '[]')` 确保解析失败时返回安全默认值
|
||||
|
||||
该策略构成了完整的“读取→运行→修改→保存”闭环,保障了用户体验的一致性和数据的安全性。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L89-L92)
|
||||
221
.qoder/repowiki/zh/content/响应式布局系统/布局初始化逻辑.md
Normal file
221
.qoder/repowiki/zh/content/响应式布局系统/布局初始化逻辑.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# 布局初始化逻辑
|
||||
|
||||
<cite>
|
||||
**Referenced Files in This Document**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [核心组件分析](#核心组件分析)
|
||||
3. [生命周期与容器初始化](#生命周期与容器初始化)
|
||||
4. [DOM查询机制与containerStr参数](#dom查询机制与containerstr参数)
|
||||
5. [网格模板初始状态设计](#网格模板初始状态设计)
|
||||
6. [ResizeObserver响应式尺寸监听](#resizeobserver响应式尺寸监听)
|
||||
7. [图标重排逻辑](#图标重排逻辑)
|
||||
8. [依赖关系图](#依赖关系图)
|
||||
|
||||
## 简介
|
||||
`useDesktopContainerInit` 是一个 Vue 3 组合式 API Hook,用于在桌面容器组件挂载时初始化其布局系统。该 Hook 负责建立基于 CSS Grid 的动态网格布局,并通过 `ResizeObserver` 实现响应式行为,确保桌面图标能根据容器尺寸自动调整排列方式。
|
||||
|
||||
## 核心组件分析
|
||||
|
||||
### useDesktopContainerInit 功能概览
|
||||
此 Hook 封装了桌面容器的核心初始化逻辑,包括:
|
||||
- 容器元素的 DOM 查询与绑定
|
||||
- 网格布局参数的初始化与响应式管理
|
||||
- 容器尺寸变化的监听与处理
|
||||
- 桌面图标的持久化存储与位置管理
|
||||
- 图标超出可视区域时的隐藏策略
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14-L94)
|
||||
|
||||
## 生命周期与容器初始化
|
||||
|
||||
### onMounted 中的初始化流程
|
||||
`useDesktopContainerInit` 利用 Vue 的 `onMounted` 生命周期钩子,在组件挂载完成后立即执行容器初始化操作。这一时机确保了对应的 DOM 元素已经存在于页面中,可以安全地进行查询和观察。
|
||||
|
||||
在 `onMounted` 回调中,Hook 首先通过 `document.querySelector(containerStr)` 获取指定的容器元素引用,并将其赋值给局部变量 `container`。随后,立即调用 `ResizeObserver` 的 `observe` 方法开始监听该容器的尺寸变化。
|
||||
|
||||
当组件被卸载时,`onUnmounted` 钩子会确保清理工作正确执行:停止对容器的观察(`unobserve`)并断开 `ResizeObserver` 实例(`disconnect`),防止内存泄漏。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Component as "DesktopContainer组件"
|
||||
participant Hook as "useDesktopContainerInit"
|
||||
participant DOM as "浏览器DOM"
|
||||
participant Observer as "ResizeObserver"
|
||||
Component->>Hook : setup()
|
||||
Hook->>Hook : 初始化gridTemplate等响应式数据
|
||||
Component->>Component : 渲染完成
|
||||
Component->>Hook : onMounted触发
|
||||
Hook->>DOM : querySelector(containerStr)
|
||||
DOM-->>Hook : 返回容器元素
|
||||
Hook->>Observer : new ResizeObserver(callback)
|
||||
Hook->>Observer : observe(container)
|
||||
Note over Hook,Observer : 开始监听容器尺寸变化
|
||||
loop 每次窗口或容器大小改变
|
||||
Observer->>Observer : 触发回调
|
||||
Observer->>Hook : 执行回调函数
|
||||
Hook->>Hook : 重新计算gridTemplate参数
|
||||
Hook->>Hook : 更新gridStyle
|
||||
end
|
||||
Component->>Component : 组件即将销毁
|
||||
Component->>Hook : onUnmounted触发
|
||||
Hook->>Observer : unobserve(container)
|
||||
Hook->>Observer : disconnect()
|
||||
Note over Hook,Observer : 清理资源,防止内存泄漏
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L70-L85)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L70-L85)
|
||||
|
||||
## DOM查询机制与containerStr参数
|
||||
|
||||
### containerStr 参数的作用
|
||||
`containerStr` 是传递给 `useDesktopContainerInit` 函数的一个字符串参数,它代表一个 CSS 选择器。该选择器用于定位需要初始化的桌面容器 DOM 元素。
|
||||
|
||||
在当前实现中,`DesktopContainer.vue` 组件传入的值为 `.desktop-icons-container`,这是一个类选择器,指向模板中具有该类名的 `<div>` 元素。这种设计使得 Hook 具有良好的通用性,可以在不同的容器上复用,只需传入相应的选择器即可。
|
||||
|
||||
```typescript
|
||||
// 在 DesktopContainer.vue 中的调用示例
|
||||
const { appIconsRef, gridStyle, gridTemplate } = useDesktopContainerInit('.desktop-icons-container')
|
||||
```
|
||||
|
||||
**Section sources**
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L10-L13)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L14)
|
||||
|
||||
## 网格模板初始状态设计
|
||||
|
||||
### gridTemplate 初始值的设计意图
|
||||
`gridTemplate` 对象使用 Vue 的 `reactive` 函数创建,使其成为一个响应式对象。其初始状态的设计体现了以下几个关键考量:
|
||||
|
||||
| 属性 | 默认值 | 设计意图 |
|
||||
|------|--------|----------|
|
||||
| `cellExpectWidth` | 90 | 预设每个单元格的理想宽度(像素),作为网格列宽的基础 |
|
||||
| `cellExpectHeight` | 110 | 预设每个单元格的理想高度(像素),作为网格行高的基础 |
|
||||
| `gapX` / `gapY` | 4 | 设置行列之间的间隙,提供视觉呼吸空间,避免图标紧贴 |
|
||||
| `colCount` / `rowCount` | 1 | 初始行列数设为1,表示最小的网格结构,将在首次ResizeObserver回调中被实际尺寸覆盖 |
|
||||
|
||||
这些默认值共同定义了一个合理的起始布局,即使在 `ResizeObserver` 回调执行前,也能保证界面有一个基本的、可预测的显示状态。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class IGridTemplateParams {
|
||||
+readonly cellExpectWidth : number
|
||||
+readonly cellExpectHeight : number
|
||||
+cellRealWidth : number
|
||||
+cellRealHeight : number
|
||||
+gapX : number
|
||||
+gapY : number
|
||||
+colCount : number
|
||||
+rowCount : number
|
||||
}
|
||||
class useDesktopContainerInit {
|
||||
-container : HTMLElement
|
||||
-gridTemplate : IGridTemplateParams
|
||||
-gridStyle : ComputedRef
|
||||
-ro : ResizeObserver
|
||||
+useDesktopContainerInit(containerStr : string) : Object
|
||||
}
|
||||
useDesktopContainerInit --> IGridTemplateParams : "包含"
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L4-L20)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L16-L32)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L16-L32)
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L4-L20)
|
||||
|
||||
## ResizeObserver响应式尺寸监听
|
||||
|
||||
### 回调中的尺寸计算逻辑
|
||||
`ResizeObserver` 的回调函数是实现响应式布局的核心。每当容器的尺寸发生变化时,该回调都会被触发,执行以下关键步骤:
|
||||
|
||||
1. **获取容器实际尺寸**:通过 `container.getBoundingClientRect()` 获取容器当前的精确几何信息。
|
||||
2. **计算行列数量**:利用容器宽度和预设单元格宽度(含间隙)计算出应显示的列数和行数。
|
||||
```javascript
|
||||
colCount = Math.floor((width + gapX) / (expectWidth + gapX))
|
||||
```
|
||||
3. **计算实际单元格尺寸**:在确定了行列数后,重新分配容器内部空间,计算出每个单元格的实际宽高,确保网格完全填充容器且无多余空白。
|
||||
```javascript
|
||||
realWidth = (totalWidth - totalGapX) / colCount
|
||||
```
|
||||
|
||||
这种“期望尺寸 -> 计算行列 -> 反推实际尺寸”的模式,既保证了图标的相对大小一致性,又实现了完美的空间利用率。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L45-L65)
|
||||
|
||||
## 图标重排逻辑
|
||||
|
||||
### rearrangeIcons 函数的工作机制
|
||||
当网格的行列数发生变化时,`watch` 监听器会触发 `rearrangeIcons` 函数,负责重新安排所有桌面图标的可见性和位置。
|
||||
|
||||
该函数的主要逻辑如下:
|
||||
1. 创建一个 `Set` 来记录已被占用的网格坐标 `(x,y)`。
|
||||
2. 遍历所有图标,优先将位于新网格范围内的图标保留在原位。
|
||||
3. 对于超出新网格范围的图标,尝试在网格内寻找空闲位置进行安置。
|
||||
4. 如果网格已满,则将无法放置的图标放入 `hideAppIcons` 数组,这些图标将被隐藏。
|
||||
|
||||
这确保了用户界面的连续性,尽可能保留用户的原有布局习惯。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[开始重排图标] --> B{遍历所有图标}
|
||||
B --> C["检查(x,y)是否在maxCol/maxRow范围内"]
|
||||
C --> |是| D["检查该位置是否已被占用"]
|
||||
D --> |否| E["将图标加入appIcons数组<br/>标记位置为已占用"]
|
||||
D --> |是| F["跳过,不添加"]
|
||||
C --> |否| G["将图标暂存到temp数组"]
|
||||
G --> H{遍历temp数组}
|
||||
H --> I["检查appIcons数组是否已满"]
|
||||
I --> |否| J["在网格中寻找第一个空位"]
|
||||
J --> K["将图标放入空位<br/>标记位置为已占用"]
|
||||
K --> L["加入appIcons数组"]
|
||||
I --> |是| M["加入hideAppIcons数组"]
|
||||
M --> N[结束]
|
||||
L --> N
|
||||
E --> N
|
||||
F --> N
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
|
||||
## 依赖关系图
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "UI层"
|
||||
DesktopContainerVue["DesktopContainer.vue"]
|
||||
AppIconVue["AppIcon.vue"]
|
||||
UseHook["useDesktopContainerInit.ts"]
|
||||
end
|
||||
subgraph "类型定义"
|
||||
IGridTemplate["IGridTemplateParams.ts"]
|
||||
IDesktopAppIcon["IDesktopAppIcon.ts"]
|
||||
end
|
||||
DesktopContainerVue --> UseHook : "调用"
|
||||
UseHook --> IGridTemplate : "导入接口"
|
||||
UseHook --> IDesktopAppIcon : "导入接口"
|
||||
DesktopContainerVue --> AppIconVue : "使用组件"
|
||||
UseHook --> AppIconVue : "通过返回值传递数据"
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
|
||||
140
.qoder/repowiki/zh/content/响应式布局系统/网格参数计算机制.md
Normal file
140
.qoder/repowiki/zh/content/响应式布局系统/网格参数计算机制.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# 网格参数计算机制
|
||||
|
||||
<cite>
|
||||
**本文档引用文件**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue)
|
||||
- [basic.css](file://src/css/basic.css)
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [引言](#引言)
|
||||
2. [核心数据结构定义](#核心数据结构定义)
|
||||
3. [ResizeObserver中的动态网格计算逻辑](#resizeobserver中的动态网格计算逻辑)
|
||||
4. [实际单元格尺寸的精确控制](#实际单元格尺寸的精确控制)
|
||||
5. [gridStyle样式对象的生成与绑定](#gridstyle样式对象的生成与绑定)
|
||||
6. [图标重排机制分析](#图标重排机制分析)
|
||||
7. [总结](#总结)
|
||||
|
||||
## 引言
|
||||
本项目通过Vue 3组合式API实现了一个响应式的桌面图标容器布局系统。其核心在于利用`ResizeObserver`监听容器尺寸变化,动态计算并调整CSS Grid布局的行列数及单元格大小。该机制确保在不同屏幕尺寸和窗口缩放情况下,桌面图标能够自适应排列,同时保持良好的视觉一致性与交互体验。
|
||||
|
||||
## 核心数据结构定义
|
||||
系统通过接口`IGridTemplateParams`定义了网格布局所需的核心参数集合:
|
||||
|
||||
```typescript
|
||||
export interface IGridTemplateParams {
|
||||
readonly cellExpectWidth: number; // 预期单元格宽度
|
||||
readonly cellExpectHeight: number; // 预期单元格高度
|
||||
cellRealWidth: number; // 实际单元格宽度
|
||||
cellRealHeight: number; // 实际单元格高度
|
||||
gapX: number; // 列间距
|
||||
gapY: number; // 行间距
|
||||
colCount: number; // 总列数
|
||||
rowCount: number; // 总行数
|
||||
}
|
||||
```
|
||||
|
||||
这些参数构成了整个动态网格计算的基础,其中预期尺寸为设计基准值,而实际尺寸则由运行时环境动态决定。
|
||||
|
||||
**Section sources**
|
||||
- [IGridTemplateParams.ts](file://src/ui/types/IGridTemplateParams.ts#L3-L20)
|
||||
|
||||
## ResizeObserver中的动态网格计算逻辑
|
||||
当容器尺寸发生变化时,`ResizeObserver`回调函数会触发重新计算流程。关键步骤如下:
|
||||
|
||||
1. **获取容器几何信息**:通过`getBoundingClientRect()`获取当前容器的实际宽高。
|
||||
2. **计算列数(colCount)**:
|
||||
```ts
|
||||
gridTemplate.colCount = Math.floor((containerRect.width + gridTemplate.gapX) / (gridTemplate.cellExpectWidth + gridTemplate.gapX));
|
||||
```
|
||||
3. **计算行数(rowCount)**:
|
||||
```ts
|
||||
gridTemplate.rowCount = Math.floor((containerRect.height + gridTemplate.gapY) / (gridTemplate.cellExpectHeight + gridTemplate.gapY));
|
||||
```
|
||||
|
||||
此算法的本质是将容器总宽度(或高度)加上一个间隙值,再除以“单个单元格宽度+列间距”,从而避免因浮点误差导致最后一列无法完整显示的问题。使用`Math.floor`向下取整保证结果为有效整数。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L39-L40)
|
||||
|
||||
## 实际单元格尺寸的精确控制
|
||||
在确定了行列数量后,系统进一步计算每个单元格的实际像素尺寸,以充分利用可用空间并消除边缘空白。
|
||||
|
||||
### 计算公式推导
|
||||
- **可用总宽度** = 容器宽度 - 所有列间隙之和
|
||||
即:`w = containerRect.width - gapX * (colCount - 1)`
|
||||
- **每列实际宽度** = 可用总宽度 ÷ 列数
|
||||
即:`cellRealWidth = w / colCount`
|
||||
|
||||
同理可得行方向上的计算:
|
||||
- `h = containerRect.height - gapY * (rowCount - 1)`
|
||||
- `cellRealHeight = h / rowCount`
|
||||
|
||||
### 浮点数精度控制
|
||||
由于浏览器渲染对小数像素的支持有限,直接使用浮点值可能导致布局抖动或错位。因此系统采用`toFixed(2)`保留两位小数,并通过`Number()`转换回数值类型,既保证精度又提升渲染稳定性。
|
||||
|
||||
```ts
|
||||
gridTemplate.cellRealWidth = Number((w / gridTemplate.colCount).toFixed(2))
|
||||
gridTemplate.cellRealHeight = Number((h / gridTemplate.rowCount).toFixed(2))
|
||||
```
|
||||
|
||||
这种处理方式平衡了空间利用率与视觉平滑性。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L41-L42)
|
||||
|
||||
## gridStyle样式对象的生成与绑定
|
||||
`gridStyle`是一个基于Vue `computed`的响应式计算属性,负责将`gridTemplate`中的参数转化为标准的CSS Grid样式规则。
|
||||
|
||||
### 样式对象结构
|
||||
```ts
|
||||
const gridStyle = computed(() => ({
|
||||
gridTemplateColumns: `repeat(${gridTemplate.colCount}, minmax(${gridTemplate.cellExpectWidth}px, 1fr))`,
|
||||
gridTemplateRows: `repeat(${gridTemplate.rowCount}, minmax(${gridTemplate.cellExpectHeight}px, 1fr))`,
|
||||
gap: `${gridTemplate.gapX}px ${gridTemplate.gapY}px`
|
||||
}))
|
||||
```
|
||||
|
||||
### 关键特性说明
|
||||
- **`minmax()`函数应用**:确保每列最小宽度不低于`cellExpectWidth`,但允许在空间充足时扩展至等分的`1fr`比例。
|
||||
- **动态重复语法**:`repeat(colCount, ...)`自动构建指定数量的轨道定义。
|
||||
- **双向间隙设置**:`gap`属性分别设置横向与纵向间距。
|
||||
|
||||
该样式对象最终通过`:style="gridStyle"`绑定到`.desktop-icons-container`元素上,实现视图层的实时更新。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[容器尺寸变化] --> B{ResizeObserver触发}
|
||||
B --> C[计算colCount/rrowCount]
|
||||
C --> D[计算cellRealWidth/Height]
|
||||
D --> E[更新gridTemplate响应式对象]
|
||||
E --> F[gridStyle重新计算]
|
||||
F --> G[DOM样式自动更新]
|
||||
G --> H[完成布局重绘]
|
||||
```
|
||||
|
||||
**Diagram sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L20-L30)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1)
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L20-L30)
|
||||
- [DesktopContainer.vue](file://src/ui/desktop-container/DesktopContainer.vue#L1)
|
||||
|
||||
## 图标重排机制分析
|
||||
每当行列数发生变更时,系统会调用`rearrangeIcons`函数对所有图标进行位置重分配,确保其不超出可视范围且无重叠。
|
||||
|
||||
### 重排策略
|
||||
1. **优先保留原有坐标**:若图标的原位置仍在新网格范围内且未被占用,则保留原位。
|
||||
2. **空位填充机制**:对于越界或冲突的图标,遍历网格寻找首个可用空位(从左上角开始)。
|
||||
3. **溢出图标管理**:当网格已满时,超出部分存入`hideAppIcons`数组,可用于后续提示用户。
|
||||
|
||||
该机制保障了用户体验的一致性,避免图标因窗口缩放而丢失或错乱。
|
||||
|
||||
**Section sources**
|
||||
- [useDesktopContainerInit.ts](file://src/ui/desktop-container/useDesktopContainerInit.ts#L102-L156)
|
||||
|
||||
## 总结
|
||||
本系统的动态网格计算机制充分体现了响应式设计的思想。通过结合`ResizeObserver`、Vue响应式系统与CSS Grid布局,实现了从容器尺寸→行列数量→单元格尺寸→样式绑定→图标定位的完整闭环。特别是在实际尺寸计算中对间隙总和的扣除与浮点精度的控制,展现了对细节的高度关注。整体架构清晰、逻辑严谨,具备良好的可维护性与扩展性。
|
||||
Reference in New Issue
Block a user