# JOffice **Repository Path**: holysheng/J-Office ## Basic Information - **Project Name**: JOffice - **Description**: JOffice是一个基于现代Web技术构建的在线电子表格应用,类似于Google Sheets或Microsoft Excel的网页版。它提供了丰富的电子表格功能,同时保持轻量级和高性能的特点。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-03-08 - **Last Updated**: 2026-04-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 架构总览 ## 数据流向 ### 1. 用户交互流(View → Controller → Render) ``` 用户操作(点击/拖拽/滚动) ↓ OfficeView 捕获原生事件 ↓ EventBus.emit (VIEW.CANVAS.* 事件) ↓ CanvasHandler 处理事件 ↓ SelectionManager 更新选区状态 ↓ RenderManager.render() 重新渲染 ``` ### 2. 数据变更流(MVVM 响应式) ``` 用户操作(编辑/格式化) ↓ EventHandler 处理业务逻辑 ↓ 修改 Cell 属性(cell.content = ...) ↓ Cell.emitCellChanged(自动触发) ↓ EventBus.emit (SHEET.CELL_CHANGED 事件) ↓ SheetHandler.onCellChanged ↓ RenderManager.render() 重新渲染 ``` ### 3. 行列变更流 ``` Sheet 代理方法(insertLineNode/deleteLineNode) ↓ 修改 Lines 链表 + cells 数组 ↓ Sheet.emitLineNodeUpdate ↓ EventBus.emit (SHEET.LINE_CHANGED 事件) ↓ SheetHandler.onLineChanged ↓ RenderManager.render() 重新渲染 ``` ### 核心设计原则 * **事件解耦**:通过 EventBus 实现观察者模式,各模块间松耦合 * **MVVM 响应式**:Cell 属性变更自动触发渲染,无需手动调用 * **单向数据流**:View → Controller → Model → Event → Render * **职责分离**:SheetHandler 统一监听 Model 事件,CanvasHandler 处理用户交互 # 插件系统 ## 插件基础 * **IPlugin 抽象类**:所有插件的基类,定义了插件的基本接口 - `contentType`:内容类型标识(如 "text"、"checkbox") - `install(context)`:安装插件,接收 WorkBook 上下文 - `onClick(event)` / `onDoubleClick(event)`:处理鼠标事件 - `hide()`:隐藏插件 UI - `dispose()`:清理资源 - `renderCell(ctx, cell, sheet)`:渲染单元格内容(模板方法) - `doRenderCell(ctx, cell, sheet)`:子类重写此方法实现具体渲染(受保护) - `renderSelection(ctx, rect, ...)`:渲染选中框 ## PluginManager:插件管理器 * **注册机制**:调用插件的 `install` 方法并传入当前上下文(workBook 实例) * **事件分发**:CanvasHandler 触发插件管理器的鼠标事件,插件管理器将事件分发给所有已注册的插件,每个插件可自行决定是否响应该事件 * **存储结构**:使用 `Map` 存储插件,以 contentType 为键,提高查找效率 * **生命周期管理**:自动调用插件的 `dispose` 方法清理资源 ## 公共绘制函数 * **drawCellText(ctx, cell, fontOverrides)**:绘制单元格文本,支持自定义字体、对齐、删除线等 * **drawCellBackground(ctx, cell, color)**:绘制单元格背景颜色 * **drawCellBorder(ctx, cell)**:绘制单元格边框 * **drawSelection(ctx, rect, ...)**:绘制选中框 ## TextEditor:内置文本编辑插件 ### EditHistory:编辑历史管理器 * **职责**:继承 HistoryStack,管理 TextEditor 内部的文本输入历史 * **功能**: - 维护文本内容的历史记录数组和光标位置 - 通过索引切换历史记录并恢复对应的光标位置 - 接管系统的撤销、重做、粘贴事件(阻止默认行为) - 支持跨平台快捷键(Mac 的 Cmd 和 Windows 的 Ctrl) ### 光标工具 CursorTools * **document.createTreeWalker()**:创建 TreeWalker 对象(传入目标 DOM 节点、要遍历的节点类型,如文本节点),遍历元素的所有子节点,将遍历到的文本内容和偏移量以数组形式存储 * **window.getSelection()**:获取当前的 Selection 对象,从中可以获取 Range 对象来确定当前光标或选区位置 * **document.createRange()**:根据 createTreeWalker 遍历得到的文本数组定位到目标位置,通过 Range 对象设置新的选区或光标位置 ### TextEditor * **双击响应**:PluginManager 会分发双击事件给插件,TextEditor 响应该事件,根据事件中传入的 Cell 参数设置编辑器的样式并显示编辑器 * **事件处理**:编辑器内的鼠标和键盘事件由 EditHistory 接管处理,同时需要阻止事件继续冒泡到 Canvas 层 * **样式应用**:使用 Flex 布局实现水平和垂直对齐,同步单元格的字体、颜色、背景等样式 ## CheckBox:勾选框单元格插件 * **继承关系**:继承 TextEditor,拥有所有文本渲染能力 * **渲染逻辑**: - 重写 `doRenderCell` 添加左侧勾选框图标 - 调整文本渲染位置(留出 checkbox 空间) - 使用公共函数 `drawCellText`、`drawCellBackground`、`drawCellBorder` 进行渲染 * **状态管理**:单元格勾选状态存储在 `Cell.pluginData` 中("1" 表示勾选,其他表示未勾选) * **交互逻辑**: - 单击图标区域:切换勾选状态 - 双击非图标区域:进入编辑模式(编辑器留出图标空间) * **样式效果**:勾选后文本显示删除线和灰色 # Model 层 ## Sheet:工作表类 * MVC 架构中的 Model 层核心数据模型,管理单个工作表的所有数据和逻辑 * 二维数组存储:使用 Cell[][] 存储所有单元格 * 链表结构:使用 Lines 链表管理行列信息(headRow、headColumn) * 事件代理:所有 Lines 的增删改操作都通过 Sheet 代理,统一发射 LINE_CHANGED 事件 * 提供单元格查找方法:getCellByIndex、getCellByNode、getCellByPoint、getCellsBetween * 代理方法:deleteLineNode、insertLineNodeBefore、insertLineNodeAfter、updateLineNodeSize、growLine ## Cell:单元格类(策略模式 + MVVM 响应式) * 使用策略模式实现不同类型单元格的行为,通过 ICellStrategy 接口定义计算策略 * 三种策略:BaseCellStrategy(基础单元格)、MainCellStrategy(合并单元格主格)、SubordinateCellStrategy(从属单元格) * MainCellStrategy:存储合并范围的结束单元格上下文,计算合并后的宽高和名称(如 A1:C3) * SubordinateCellStrategy:存储主单元格上下文,访问坐标/尺寸等属性时抛出错误,引导访问主单元格 * 单元格支持动态切换策略(合并/取消合并),通过 merge 和 unMerge 方法实现 * **MVVM 响应式设计**:所有属性(content、backgroundColor、font、border、contentType、pluginData)修改时自动触发 CELL_CHANGED 事件 * **Proxy 代理**:font 和 border 使用 Proxy 拦截嵌套属性修改(cell.font.fontSize = 18 也会触发事件) * **批处理机制**:使用 requestAnimationFrame 合并多次变更,减少事件触发次数 ## Lines:线段集合类 * 双向链表结构,管理行或列的 LineNode 集合 * 维护 head 和 tail 节点引用,提供 length(节点数量)和 totalSize(总尺寸)属性 * 提供链表操作:grow(末尾添加)、insertBefore、insertAfter、delete * 提供查询方法:getNodeByPoint(二分查找,根据坐标点查找节点)、getNodeByIndex(根据索引查找) ## LineNode:链表节点类 * 包含 size(节点大小)、pre(前驱节点)、next(后继节点)属性 * 添加索引缓存机制:index 属性使用 _cachedIndex 缓存,避免频繁递归计算 * 提供 startPoint 和 endPoint 属性(左闭右开区间) * invalidateIndexCache 方法:在节点插入、删除或链接变化时使缓存失效 * jump 方法:跳跃指定数量的节点,用于快速定位 # Controllers 层 ## WorkBook:工作簿核心类 * MVC 架构的总调度者,协调 Model、View、Controller 的初始化 * 管理多个工作表(sheets)和当前激活的工作表(activeIndex) * 创建并初始化 View、EventBus、PluginManager、SelectionManager、EventHandler * 提供插件机制:pluginManager 属性对外暴露,支持插件注册 * 提供 getSelection 方法:将 SelectionManager 的选区范围转换为矩形列表,供 RenderManager 使用 * dispose 方法:清理所有资源,包括 EventBus、插件、事件处理器、视图 ## SelectionManager:选区管理器 * 管理单元格选择状态,支持单选区和多选区(Ctrl+点击) * 主选区(_selectionRange):最后一次选择的区域 * 多选区列表(_ranges):包含所有选区,主选区是最后一个元素 * 拖拽状态:_dragStartRow 和 _dragStartColumn 记录拖拽起点 * 缓存机制:_cachedCells 缓存选中的单元格列表,_cacheInvalidated 标记缓存有效性 * startSelection:开始选区操作,根据 clearMultiSelection 参数决定是否清除多选 * updateSelection:拖拽过程中更新选区范围(取起点和当前点的最小/最大值) * getSelectedCells:获取所有选中的单元格,使用缓存机制提高性能 ## RenderManager:渲染管理器 * 负责 Canvas 的清空、绘制网格、单元格和选中框 * 极简化直接绘制:Model 层已经是响应式的,RenderManager 只负责绘制,不管理状态 * render 方法:使用 requestAnimationFrame 调度渲染,避免重复渲染(_renderScheduled 标记) * calculateVisibleRange:根据滚动位置和画布尺寸计算需要绘制的行列范围 * drawGrid:直接绘制网格线,不创建对象 * drawContent:遍历可见单元格,根据 contentType 获取对应插件进行渲染,跳过从属单元格 * 选中框渲染:通过 WorkBook.getSelection 获取选区矩形列表,使用 text 插件的 renderSelection 方法绘制 ## EventHandler:事件处理器基类 * CanvasHandler:处理画布的点击、拖拽、滚动、调整大小等事件 * 双击检测:通过 isDblClickArea 函数判断当前点击与上次单击是否在同一单元格内 * onMouseDown:设置选区,根据修饰键(macOS 的 Command 或 Windows 的 Ctrl)决定是否清除多选 * onDrawMove:拖拽框选过程中,更新选区范围并实时渲染 * onResize:画布尺寸变更时,重置标题和滚动容器,触发渲染(可能触发两次:canvas 尺寸变化 + 滚动条出现) ## HistoryManager:全局历史管理器 * 管理 Cell 内容、格式、行列等全局操作的撤销/重做历史 * **架构设计**: - **HistoryStack**:通用的历史记录栈,管理索引和记录 - **IChange**:命令模式接口,封装操作,包括 commit/undo/redo 方法 - **CellChange**:记录单元格属性变更(内容、格式、样式等) - **LineChange**:记录行列操作(删除、插入、调整大小) - **HistoryManager**:业务层协调器,管理变更列表、发布事件 * **对外 API**: - `canUndo` / `canRedo`:查询是否可撤销/重做 - `historyCount` / `currentIndex`:查询历史记录数量和当前索引 - `add(changes)`:添加变更记录 - `undo()` / `redo()`:执行撤销/重做 - `clear()`:清空历史 * **与 EditHistory 的区别**: - HistoryManager:管理全局级别的 Sheet 操作(单元格编辑、行列操作等) - EditHistory:管理 TextEditor 内部的文本输入历史(光标移动、字符删除等) # View 层 ## OfficeView:画布视图类 * 管理 Canvas 元素、滚动容器、行列标题容器 * 双击检测:onClick 方法内部判断时间间隔,位置相近由外部函数(_dblClickAreaChecker)判定 * 事件委托:在 _$scrollWrapper 上监听鼠标事件,根据目标元素分发到不同处理函数(onTitleClickAll、onRowTitleClick、onColumnTitleClick、onMouseDown) * resetTitle:根据行列尺寸重新生成标题 HTML,设置滚动容器尺寸(通过 CSS 变量) * onScroll:滚动时同步更新标题栏位置,发射 SCROLL 事件 * Resize 优化:tryUseCachedImage 方法在两个维度都缩小时使用缓存图像,避免重新渲染 * DPR 支持:resetCanvasSize 方法应用 devicePixelRatio 缩放,确保高清屏幕显示清晰 ## ToolBarView:工具栏视图类 * 根据 ToolBarConfig 配置创建工具栏界面元素 * 支持多种工具类型:button、textButton、activeButton、group、radioGroup、dropdown * onToolButtonClick:普通按钮点击发射 TOOLBAR.CLICK 事件 * onToolActiveCheck:激活按钮切换状态(添加/移除 active 类),发射 TOOLBAR.CHECK 事件 * onRadioGroupClick:单选按钮组点击时,取消其他按钮的激活状态,只保留当前点击的按钮 * onDropdownChange:下拉菜单变更时更新配置中的 value 值,发射 TOOLBAR.DROPDOWN_CHANGE 事件