基于 Canvas 的高性能 Vue 3 电子表格组件,在网页中提供类 Excel 的编辑体验。支持完整的单元格编辑、公式计算(相对/绝对/混合引用)、剪切复制粘贴、合并单元格、冻结窗格、数字格式、边框、查找替换、排序与自动填充,全部在单一虚拟化 Canvas 上渲染,可流畅滚动数十万行。纯逻辑核心(公式引擎、排序、查找替换、自动填充)与 Vue/Canvas 完全解耦并具备完整单元测试,便于跨项目复用。
功能特性
核心电子表格
- 动态表格扩展: Excel 风格动态行列扩展;默认 26 列(A-Z)× 200 行,当滚动接近边界、在边缘输入数据、粘贴超出当前范围、或插入行列会将数据挤出边界时,自动扩展行列数
- Canvas 2D 渲染: 高 DPI 自适应(DPR 缩放),虚拟视口渲染,支持动态大小的工作表
- 多 Sheet 工作簿: 标签栏支持新建、重命名、复制、删除、拖拽排序
- 公式引擎: 支持
=SUM()、=AVERAGE()、=COUNT()、=IF()、=VLOOKUP()、=CONCATENATE(),支持依赖追踪与循环引用检测;工具栏与右键菜单提供一键求和/平均值/计数 - 合并单元格: 合并居中、跨列合并、取消合并;合并区域边框按锚点存储、公共边渲染时统一解析
- 冻结窗格: 固定顶部行/左侧列,使其在主体滚动时保持可见。工具栏「冻结窗格」下拉提供「冻结窗格」(以当前选区/活动单元格左上角为冻结点,等同 Excel 的 Freeze Panes)、「冻结首行」、「冻结首列」以及「取消冻结」(仅在已冻结时显示,替换「冻结窗格」项)。冻结状态为按工作表的视图状态(
freeze: { rows, cols }),通过 v-model 持久化;跨冻结线的合并单元格也能正确处理,仅冻结部分固定,非冻结部分随滚动移出 - 丰富单元格格式: 字体、字号(5–72)、加粗、斜体、下划线、删除线、文字颜色、填充颜色、水平/垂直对齐、自动换行
- 边框: 上/下/左/右/外边框/全部/无,5 种预设样式、自定义颜色及按边线型(实线 / 虚线 / 点线);独立边框池存储、公共边渲染时统一解析
- 撤销 / 重做: 单元格、列宽、行高的完整状态快照(50 步)
- 格式刷: 复制并应用单元格格式到任意区域
- 数字格式: Excel 风格数字格式(常规 / 文本 / 数值 / 货币 / 会计 / 百分比 / 科学计数 / 日期 / 时间 / 日期时间 / 持续时间),提供自定义格式对话框;仅影响显示,绝不修改底层单元格值
- 智能数据识别: 输入
100%、1,234、¥1,234.56等带符号数字文本时自动识别为数值并套用对应格式;常见日期时间文本自动识别为日期,与 Excel 输入语义一致 - 排序与排序提醒: 工具栏与右键菜单按单元格展示内容对所有类型(数值 / 日期 / 文本)排序;排序只移动数据、不搬运样式;含合并单元格或公式的区域自动禁用排序;当选区外存在相邻数据时,弹出类 Excel 的「排序提醒」对话框,可选择「扩展选定区域」或「仅对选定区域排序」
- 自动筛选(AutoFilter): 类 Excel 的普通区域自动筛选。工具栏「数据 → 筛选」或
Ctrl+Shift+L启用;选择一个数据单元格(或单行多列选区)后启用,即从该表头行向下智能探测连续数据区;各列表头显示筛选下箭头,点击箭头打开筛选面板(值 / 文本 / 数值 / 日期 / 搜索 / 空值 / 多列 AND)。清除某列条件只清该列、移除 AutoFilter 清全部;仅在 Filter Range 内的列拥有箭头;单选已合并单元格且无筛选态时禁用筛选按钮,见 自动筛选(AutoFilter) - 查找与替换: 工具栏查找按钮或
Ctrl/Cmd+F(含Ctrl/Cmd+H)打开;支持「当前工作表 / 整个工作簿 / 当前选区」三种范围,区分大小写、匹配整个单元格;高亮全部匹配并定位当前项、循环查找;单次与全部替换均接入撤销/重做,仅修改单元格原始value(始终保持字符串),不改动格式 / 边框 / 合并 - 条件格式: 按工作表存储条件格式规则(
SheetState.conditionalFormats)。工具栏「条件格式」下拉提供高亮单元格预设(大于 / 小于 / 介于 / 文本包含 / 重复 / 唯一 / 空白 / 非空白)以及「新建规则」对话框(支持单元格值条件和自定义公式条件)。规则渲染为基础单元格样式之上的临时覆盖层,绝不写回底层value或styleId。规则优先级(数值越小优先级越高)、stopIfTrue语义、单元格值变化时自动失效缓存。插入/删除行列时通过与自动填充相同的shiftFormulaRefs引擎自动调整规则范围与公式引用($B$2绝对引用保持固定)。内置「管理规则」对话框可编辑、排序、删除规则;「清除规则」子菜单支持清除全部或按选区清除。渲染时与基础样式正确合成背景色、文字颜色、粗体、斜体、下划线、删除线。见 条件格式 - 数据验证: 按工作表存储类 Excel 数据验证规则。工具栏「数据验证」按钮打开对话框,设置允许类型(任何值 / 列表 / 整数 / 小数 / 日期 / 时间 / 文本长度 / 自定义公式)、运算符(介于 / 不介于 / 等于 / 大于 ……)与条件值。列表验证在单元格内显示下拉,支持搜索与完整键盘操作(↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc)。支持逐条输入信息提示与出错警告(停止 / 警告 / 信息):验证发生在值写入之前,停止级违规直接拒绝录入,警告/信息级由用户确认后继续。同一范围的多条规则必须全部通过。规则随 v-model 持久化、接入撤销/重做,并在行列增删时自动调整范围。复制/粘贴与自动填充走原子校验-出现停止级违规则整次取消。见 数据验证
- 行列分组与折叠: 类 Excel 的行/列分组(Outline)。选中整行或整列后,工具栏「分组」下拉(位于排序与筛选之间)提供添加分组 / 取消分组 / 清除分组 / 全部展开 / 全部折叠,直接作用于选中轴;行/列右键菜单提供同构的「分组」子菜单。仅支持一层、互不相交的分组,嵌套或部分重叠会被校验拒绝并提示。分组区间在行号/列号带用交替背景色区分,± 折叠按钮浮动于表头带内,不占用独立 gutter。折叠的行/列不参与内容绘制;插入/删除行列时分组范围自动平移、被清空的分组自动删除。分组状态按工作表持久化(
rowOutlines/columnOutlines),增删与折叠均接入撤销/重做。见 行列分组与折叠 - 单元格批注: 类 Excel 的单元格批注(Note/Comment)。右键菜单新建 / 编辑 / 删除;单元格右上角红色三角指示器标识含批注;hover 悬停查看、点按编辑;批注独立于单元格值与样式,采用 Note Pool 存储(Sheet 级 notes 池 + cell.noteId 引用),便于排序 / 插入删除时随 cell 一起移动。批注支持多行文本、可选作者、创建 / 修改时间。可通过
noteAuthorprop 设置当前作者名。见 单元格批注 - 自动填充(填充柄): 类 Excel 的活动选区右下角填充柄,按住向上/下/左/右拖拽即可填充单元格:单值复制、数字/日期/文本数字自动续序列(如
1,2 → 3,4,5)、公式引用按相对/绝对/混合规则平移(如=A1*2 → =A2*2)、源styleId经样式池复用。拖拽期间实时预览、边缘自动滚动、动态扩展工作表、冻结窗格兼容、单次拖拽仅产生一个撤销步骤,见 自动填充 - 只读模式:
editableprop(默认true)一键切换:false时禁止一切数据/格式修改(编辑、粘贴剪切、删除、填充、行列增删调整、排序筛选、CF/DV/批注、合并边框、冻结、格式刷、替换等),只读下编辑类控件(toolbar、右键菜单、对话框确认/插入按钮、公式栏、查找替换栏)真实禁用,保留选中、滚动、复制、查找定位与查看能力。分层守卫纵深防御(UI disabled → 事件闸门 → handler → 底层 mutation),运行时切换立即生效且不污染 undo 历史,见 只读模式
交互体验
- 智能选区: 点击、拖拽、Shift+点击、行/列标题选择、全选按钮
- 双击编辑: 行内编辑器,使用
<textarea>支持多行输入 - 编辑栏: 专用编辑栏,带单元格地址标签及实时公式显示
- 完整键盘导航: 方向键、Tab、Enter、Home、End、Ctrl+Home、Ctrl+End
- 上下文菜单: 智能识别当前区域(单元格、行、列、Sheet 标签),支持多级子菜单
- 行/列操作: 右键菜单插入、删除、剪切、复制、粘贴
- 列宽/行高: 拖拽调整,实时预览,双击自动适配
- 触摸支持: 滑动滚动,双击进入编辑模式
- 响应式: ResizeObserver 监听容器尺寸变化
视觉体验
- 明/暗双主题: 完整的调色板,50+ CSS 自定义属性
- 国际化: 内置中文(zh-CN)与英文(en-US)
- 溢出工具栏: 响应式工具栏,自动将溢出按钮折叠到下拉菜单
- 自定义滚动条: 仿原生滚动条,带箭头按钮及可拖拽滑块
安装
BASH
# pnpm(推荐)
pnpm add xiaodao-spreader
# npm
npm install xiaodao-spreader
# yarn
yarn add xiaodao-spreader
Peer 依赖
vue^3.4.0
快速开始
BASH
# 克隆仓库
git clone https://github.com/xiaodaozhi/xiaodao-spreader.git
cd xiaodao-spreader
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
访问
http://localhost:5173 查看演示应用。基础用法
VUE
<template>
<Spreader
v-model:data="myData"
:row-count="100"
:col-count="12"
theme="light"
locale="zh-CN"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Spreader from 'xiaodao-spreader';
import type { SheetModelData } from 'xiaodao-spreader';
const myData = ref<SheetModelData[]>([
{
name: 'Sheet1',
styles: [{}, { fontSize: 14, fontWeight: 'bold' }],
cells: {
'0,0': { value: '姓名' },
'0,1': { value: '年龄' },
'1,0': { value: '张三', styleId: 1 },
'1,1': { value: '28' },
},
colWidths: { 0: 120, 1: 80 },
},
]);
</script>
Props 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
data (v-model) | SheetModelData[] | [] | 多 Sheet 数据(双向绑定) |
rowCount | number | 200 | 每个 Sheet 的初始行数(可动态扩展) |
colCount | number | 26 | 每个 Sheet 的初始列数(可动态扩展) |
width | number | string | - | 组件宽度(像素;传 string 或省略则自适应) |
height | number | string | - | 组件高度(像素;传 string 或省略则自适应) |
theme | 'light' | 'dark' | 'light' | 主题模式 |
locale | string | 'zh-CN' | 语言: 'zh-CN' | 'en-US' |
noteAuthor | string | ''(空串,显示时回退为 i18n 占位符「未命名」) | 当前批注作者名;为空时存入批注的 author 字段为空字符串,显示时回退为 i18n 占位符 |
editable | boolean | true | 是否允许编辑;false 进入只读模式(详见 只读模式) |
只读模式(Read-only)
通过
editable prop 可将组件切换为只读展示(默认 true,即完全可编辑)。editable={false} 时:- 禁止一切修改:单元格编辑与直接输入、粘贴 / 剪切 / 删除、填充柄、行 / 列增删与宽高调整、排序与筛选、条件格式、数据验证、批注增删改、合并单元格、边框、字体 / 对齐 / 背景 / 文字颜色、数字格式、格式刷、冻结窗格、查找替换、撤销 / 重做均被拦截。
- 保留查看能力:选中与多选区、滚动、冻结查看、复制、单元格内容 / 格式 / 批注查看、查找定位(上一条 / 下一条)不受影响。
- UI 与逻辑一致:所有编辑类控件(toolbar 按钮与下拉、右键菜单项、对话框确认 / 插入按钮、公式栏按钮、查找替换栏按钮与「替换为」输入框)在只读下真实禁用或只读;切到只读的瞬间,已打开的编辑浮层与对话框自动关闭,不会出现「按钮看起来能点、点了没反应」。
- 纵深防御:守卫分四层——① UI
:disabled/ readonly(体验层)→ ② toolbar emit 闸门与事件拦截 → ③ 处理函数首行守卫 → ④ 底层 mutation 守卫(终极防线)。仅靠 DevTools 删除 HTML 的disabled属性无法绕过,数据不可能被改写;只读下不产生任何 undo 快照,undo 历史不被污染。 - 运行时切换即时生效,无需重挂组件;
v-model数据与格式不被改动,切回可编辑后一切如常。
VUE
<template>
<button @click="editable = !editable">
{{ editable ? '切换为只读' : '切换为可编辑' }}
</button>
<Spreader v-model:data="myData" :editable="editable" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Spreader from 'xiaodao-spreader';
import type { SheetModelData } from 'xiaodao-spreader';
const myData = ref<SheetModelData[]>([]);
const editable = ref(true);
</script>
数据模型
SheetModelData
用于 v-model 双向绑定的外部数据格式:
TYPESCRIPT
interface SheetModelData {
name: string;
styles?: CellStyle[];
borders?: BorderStyle[];
cells: Record<string, {
value: string;
styleId?: number;
/** 批注引用:指向 Sheet 级 notes 池中的 CellNote */
noteId?: string;
}>;
merges?: Record<string, SelectionRange>;
freeze?: { rows: number; cols: number };
colWidths?: Record<number, number>;
rowHeights?: Record<number, number>;
// 网格尺寸(colCount/rowCount)不持久化:加载时按内容推导(deriveModelDims),
// 至少 props 默认 26×200,并按数据 / 自定义行列尺寸 / 各类规则范围扩展
/** 单元格批注池:按 noteId 存放 CellNote,cell.noteId 引用之;与 cell value/style 完全独立 */
notes?: Record<string, CellNote>;
}
styles:样式池,styles[0]始终为默认空样式{}。单元格通过下标(styleId)引用样式。borders:边框池,borders[0]始终为默认空边框{}。样式通过下标(borderId)引用边框;省略时由旧版内联边框属性自动迁移。cells:键为"列,行"格式(如"0,0"代表 A1 单元格)。styleId引用styles数组;styleId=0或省略表示默认样式。merges:合并单元格定义,以合并锚点单元格为键。freeze:冻结窗格的视图状态,{ rows, cols }冻结顶部rows行与左侧cols列;{ rows: 0, cols: 0 }表示无冻结。按工作表持久化,切换工作表时恢复。colWidths/rowHeights:稀疏映射,仅存储非默认值。
类型导出
TYPESCRIPT
import type {
CellCoord, // { col: number; row: number }
SelectionRange, // { startCol; startRow; endCol; endRow }
CellData, // { value: string; styleId?: number }
CellStyle, // 类型化样式接口(字体、颜色、边框、对齐等)
BorderStyle, // 四边边框组合 { top; right; bottom; left }
BorderSide, // 单边边框 { width; color; style; owner? }
BorderSource, // 边框来源 'cell' | 'merge'
Range, // { start: number; end: number }
SpreadsheetOptions,
SpreadsheetData,
SheetModelData, // ← 主要数据接口
SheetState, // 内部运行时状态
UndoSnapshot,
ContextMenuItem,
ThemeColors,
Point,
} from 'xiaodao-spreader';
// 运行时工具函数
import { StylePool, resolveStyle, migrateCells, cloneCells } from 'xiaodao-spreader';
架构设计
TEXT
src/
├── index.ts # 库入口: 从 spreader/ 统一 re-export
├── App.vue # 演示应用
└── components/
└── spreader/
├── index.ts # 统一导出口: 组件 + 类型(barrel)
├── theme.css # 全局主题样式表:--sp-* 变量(.spreadsheet-outer / .sp-spreader-overlay 作用域,light + dark 两套)
├── components/
│ ├── spreader.vue # 入口组件:模板 + 样式 + 组合
│ ├── toolbar.vue # 工具栏(带溢出下拉)
│ ├── tabbar.vue # Sheet 标签栏
│ ├── dropdown.vue # 通用下拉组件
│ ├── find-replace-bar.vue # 查找/替换栏 UI
│ ├── note-overlay.vue # 批注查看/编辑浮层(view + edit 双模)
│ └── pickers/
│ ├── border-picker.vue # 边框选择器(按边类型 / 颜色 / 线型 + 子菜单)
│ ├── calc-picker.vue # 求和 / 平均值 / 计数选择器
│ ├── color-palette.vue # 文字 / 填充 / 边框共用的色板(单一数据源)
│ ├── color-picker.vue # 文字 / 填充颜色选择器
│ ├── conditional-format-manager.vue # 条件格式「管理规则」对话框
│ ├── conditional-format-menu.vue # 条件格式工具栏下拉(预设 + 新建 / 管理 / 清除规则)
│ ├── conditional-format-rule-editor.vue # 条件格式「新建 / 编辑规则」对话框
│ ├── data-validation-alert.vue # 数据验证出错警告(Stop / Warning / Information)
│ ├── data-validation-dialog.vue # 数据验证设置对话框
│ ├── data-validation-dropdown.vue # 数据验证列表下拉(搜索 + 虚拟列表 + 键盘)
│ ├── data-validation-input-message.vue # 数据验证输入信息提示气泡(选中单元格时显示)
│ ├── filter-popup.vue # 自动筛选面板(值 / 文本 / 数值 / 日期 + 搜索 + 空值 + 多列 AND)
│ ├── insert-function-dialog.vue # 插入函数对话框
│ ├── merge-picker.vue # 合并单元格选择器
│ ├── number-format-dialog.vue # 数字格式对话框
│ ├── outline-picker.vue # 工具栏「分组」下拉(添加 / 取消 / 清除分组,展开 / 折叠)
│ ├── sort-confirm-dialog.vue # Excel 风格排序提醒对话框
│ └── sort-picker.vue # 排序下拉
├── composables/
│ ├── borders-merge.ts # 边框操作、合并操作、剪贴板、求和/平均/计数
│ ├── core-state.ts # Props、cells/merges/selection、字体度量、导航、行列分组状态
│ ├── find-replace.ts # 查找/替换状态与交互(依赖 Vue)
│ ├── interactions.ts # 渲染器、公式栏、标签栏、右键菜单、滚动条、事件
│ ├── sheets-ops.ts # 行列操作、多 Sheet、v-model emit、refs
│ ├── undo-styles.ts # 撤销/重做、格式刷、字体/对齐/颜色
│ └── float-menu-position.ts # 工具栏下拉菜单共享定位(右锚 + 上下翻向夹紧)
└── core/
├── autofill.ts # 自动填充纯引擎(模式推断、填充柄逻辑,零 Vue 依赖)
├── border-color.ts # 按边边框颜色:颜色与线型解耦、改色计划
├── border-icon.ts # 边框图标单一数据源(toolbar + picker)
├── border-pool.ts # 边框池:去重、注册、解析、迁移、GC
├── border-resolve.ts # 公共边冲突解析(resolveSharedBorder)
├── border-style.ts # 边框线型(实线/虚线/点线):归一化与 dash 图案
├── conditional-formatting.ts # 条件格式引擎(条件求值 + 规则缓存,纯逻辑)
├── constants.ts # 布局常量、i18n 文案、主题配色
├── data-validation.ts # 数据验证引擎(纯逻辑)
├── filter-core.ts # 自动筛选引擎(级联候选值,纯逻辑)
├── find-replace-core.ts # 查找/替换纯算法(零 Vue 依赖,可单测)
├── formula.ts # 公式引擎(解析、求值、依赖追踪)
├── model-dims.ts # 模型尺寸由内容推导(纯逻辑)
├── notes.ts # 批注池管理:CRUD、持久化、Undo 集成
├── number-format.ts # 数字格式引擎(Excel 风格显示格式化)
├── outline-core.ts # 行列分组纯引擎(校验 / 平移 / 折叠,零 Vue 依赖)
├── sort-core.ts # 排序纯算法(零 Vue 依赖,可单测)
├── sort-icon.ts # 排序图标单一数据源(toolbar + picker)
├── style-pool.ts # 样式池:去重、注册、解析、迁移、GC
├── theme.ts # 主题类型与 light/dark 配色(canvas 用);buildOuterStyle 仅剩尺寸
├── types.ts # 全部类型定义
└── utils.ts # 纯工具函数(列标转换、命中测试等)
设计原则
- 稀疏数据模型:仅存储有实际数据的单元格;扩展逻辑范围不会创建空单元格
- 动态范围:每个 Sheet 维护响应式逻辑范围(
colCount/rowCount),从colCount/rowCountprop(默认 26/200)起步,通过ensureCapacity(minCol, minRow)按需增长。扩展使用缓冲步长(8 列 / 32 行)以减少频繁调整开销 - 组合式 API:所有业务逻辑抽取到
composables/,保持单一职责 - Barrel 导出:
spreader/index.ts集中导出组件和全部类型,src/index.ts再统一 re-export,外部统一从xiaodao-spreader引入 - Canvas 2D 渲染:仅绘制可视区域(虚拟渲染),大表流畅
- 无脏标记:在交互结束点手动调用
scheduleRender();requestAnimationFrame自动合并同一帧的多次调用 - 样式池:每个 Sheet 维护
styles: CellStyle[]数组,单元格通过styleId(数组下标)引用样式。相同样式自动去重。运行时通过resolveStyle()/registerStyle()解析样式;GC(compactStyles)仅在保存/导出时执行 - 边框池:边框独立于普通样式,存入每 Sheet 的
borders: BorderStyle[]池,样式通过borderId(数组下标)引用。相邻公共边在渲染时经resolveSharedBorder()统一解析,设置边框不再同步修改相邻单元格 - 共享状态:将
CoreState注入每个 composable,实现跨模块通信而不产生紧耦合 - Reactive 包装:composable 返回值通过
reactive()包装,模板中自动解包 ref/computed
键盘快捷键
导航
| 按键 | 功能 |
|---|---|
↑ ↓ ← → | 移动活动单元格 |
Tab / Shift+Tab | 向右 / 向左移动 |
Enter / Shift+Enter | 向下 / 向上移动 |
Home / End | 跳到首列 / 末列 |
Ctrl+Home / Ctrl+End | 跳到 A1 / 右下角 |
PageUp / PageDown | 向上 / 向下翻页 |
编辑
| 按键 | 功能 |
|---|---|
F2 / 双击 / 直接输入 | 进入编辑模式 |
Enter(编辑中) | 提交并向下移动 |
Tab(编辑中) | 提交并向右移动 |
Escape(编辑中) | 取消编辑 |
Ctrl+Enter | 多行单元格内换行 |
Delete / Backspace | 清除选区内容 |
剪贴板与历史
| 按键 | 功能 |
|---|---|
Ctrl+C / Ctrl+X / Ctrl+V | 复制 / 剪切 / 粘贴 |
Ctrl+Z / Ctrl+Y | 撤销 / 重做 |
Ctrl+A | 全选 |
Ctrl+Shift+L | 切换自动筛选(AutoFilter) |
格式
| 按键 | 功能 |
|---|---|
Ctrl+B | 加粗 |
Ctrl+I | 斜体 |
Ctrl+U | 下划线 |
公式引擎
支持的公式
| 公式 | 语法 | 说明 |
|---|---|---|
SUM | =SUM(A1:B5) | 区间求和 |
AVERAGE | =AVERAGE(A1:B5) | 区间平均值 |
COUNT | =COUNT(A1:B5) | 统计区域内数值单元格数 |
IF | =IF(A1>5, A1*2, 0) | 条件分支;支持比较运算符(> < >= <= = <>)与四则运算 |
VLOOKUP | =VLOOKUP(value, A1:C5, 2, FALSE) | 垂直查找;默认精确匹配,TRUE 为近似匹配 |
CONCATENATE | =CONCATENATE(A1, " ", B1) | 将多个值拼接为字符串 |
| 绝对引用 | $A$1 | 锁定列与行 |
| 混合引用 | $A1、A$1 | 仅锁定列或行 |
依赖追踪
FormulaDeps 类维护双向依赖图:- 正向:
formulaKey→[depKey, ...]: 公式引用了哪些单元格 - 反向:
depKey→Set<formulaKey>: 哪些公式依赖此单元格
单元格变更时,脏标记通过反向图传播。循环引用通过独立的
inProgress 集合检测,返回 #ERROR。粘贴时的引用偏移
shiftFormulaRefs() 在粘贴时自动调整公式中的单元格引用,支持 $ 绝对引用锁定。数字格式
一套 Excel 风格的数字格式引擎(
spreader/core/number-format.ts),控制单元格值的显示方式,绝不修改存储的 value(始终保持原始字符串)。分类
| 分类 | 示例显示 | 格式代码 |
|---|---|---|
| 常规 General | 自动(数值;极大/极小用科学计数法) | ``(空串) |
| 文本 Text | 原样显示 | @ |
| 数值 Number | 1,234.56 | #,##0.00 |
| 百分比 Percent | 12.34% | 0.00% |
| 科学计数 Scientific | 1.23E+03 | 0.00E+00 |
| 货币 Currency | ¥1,234.56 / $1,234.56 | ¥#,##0.00 |
| 货币(取整)Currency (rounded) | ¥1,235 | ¥#,##0 |
| 会计 Accounting | 负数 (¥1,234.56) | ¥#,##0.00;(¥#,##0.00);¥"-" |
| 财务 Financial | 负数 [Red](#,##0.00) | #,##0.00;[Red](#,##0.00) |
| 日期 Date | 2026年8月24日 / 8/24/2026 | yyyy"年"m"月"d"日" |
| 时间 Time | 13:45:30 | h:mm:ss |
| 日期时间 Date & Time | 2026年8月24日 13:45:30 | yyyy"年"m"月"d"日" h:mm:ss |
| 持续时间 Duration | 26:30:00 | [h]:mm:ss |
| 自定义 Custom | 用户自定义 Excel 风格代码 | - |
工作原理
- 存储:格式代码按单元格存入解析后样式的
numberFormat属性(字符串)。省略该属性(或空串)即常规格式。 - 界面:工具栏数字格式下拉框将预设应用到当前选区;「格式…」 项打开
numberFormatDialog.vue,可自定义格式代码、小数位数与千位分隔符。 - 显示:
formatNumber(value, format, locale)解析代码(带缓存),应用千位分隔 / 小数位 / 百分比缩放 / 日期序列号转换,返回显示字符串。非法的日期/持续时间序列号渲染为###。 - 对齐:数值类格式默认右对齐;文本及常规下的非数值保持左对齐,与 Excel 语义一致。
- 国际化:货币符号(
¥/$)、月份与星期名称随locale属性变化。
条件格式
类 Excel 条件格式,按工作表存储在
SheetState.conditionalFormats(ConditionalFormattingRule[])。引擎位于 spreader/core/conditional-formatting.ts,纯逻辑实现、零 Vue/Canvas 依赖,单元测试见 test/conditional-formatting.test.ts。规则结构
每条规则拥有稳定
id、condition(条件)、format(格式覆盖)、一个或多个 ranges(应用范围)、数值型 priority(越小优先级越高)、stopIfTrue(命中后停止后续规则)与 enabled(启用开关)。format 仅携带视觉属性(backgroundColor、color、fontWeight、fontStyle、underline、strikethrough),绝不写回 cell.styleId。支持的条件类型
工具栏下拉和「新建规则」对话框暴露以下预设规则:
| 类型 | Key | 说明 |
|---|---|---|
| 单元格值 | cellIs | 阈值比较:大于、小于、介于、等于 |
| 文本 | textContains / textNotContains / beginsWith / endsWith | 字符串匹配,可勾选区分大小写 |
| 空白 | blank / notBlank | 单元格值为空或仅空白字符 |
| 重复值 | duplicate | 在规则范围内值出现超过一次 |
| 唯一值 | unique | 在规则范围内值恰好出现一次 |
| 公式 | formula | 相对于规则锚点的自定义公式(如 =A1>100),支持绝对引用 $B$2 在范围扩展时保持固定 |
色阶、数据条、图标集、前/后 N 项、高于/低于平均值已在类型系统中预留(
colorScale、dataBar、iconSet、topBottom、aboveBelowAverage),预设 UI 与渲染逻辑规划中。优先级与 Stop-If-True
规则按优先级升序(数字小的先)依次求值。第一条匹配的规则贡献其设置的格式属性;后续规则仅补充尚未被设置的属性。若某规则
stopIfTrue: true,命中后立即停止该单元格的扫描。缓存与失效
重复值 / 唯一值 / TopBottom / AboveBelow 规则需要对规则范围做值频率统计。
CfValueCache 以 range+ruleType 为 key,首次访问时计算并缓存。composables/core-state.ts 中任何 setCellValue 都会在下次渲染前先失效受影响规则的缓存。插入 / 删除传播
CF 引擎的
shiftFormulaRefsSafe 复用公式引擎的 shiftFormulaRefs 函数:因此 $B$2(绝对引用)固定不动,A1(相对引用)正确平移。规则范围随之扩展/收缩;完全被移除的规则自动删除。渲染时样式合成
resolveConditionalFormatting 在 drawCells 内按视口单元格逐个调用,遍历所有范围包含该单元格的启用规则,按优先级合并后生成临时 cfStyle。applyCfFormat 将其合成到基础解析样式上,正确处理背景色、文字颜色、粗体、斜体、下划线、删除线,含合并单元格(CF 对锚点求值后传播到整个合并区域)。UI 组件
| 文件 | 职责 |
|---|---|
conditional-format-menu.vue | 工具栏下拉:高亮单元格预设、空白、重复、唯一、新建规则、管理规则、清除规则子菜单。子菜单采用 Teleport + hover-intent(150ms 延迟)实现跨间隙悬停不丢失 |
conditional-format-rule-editor.vue | 「新建规则」对话框:条件类型下拉(复用 SpDropdown)、阈值输入、格式面板(背景色选择器、文字颜色选择器、粗体/斜体/下划线/删除线,全部复用工具栏控件样式),应用范围自动填入当前选区 |
conditional-format-manager.vue | 「管理规则」对话框:列出当前工作表全部规则,支持拖拽排序(修改 priority)、编辑单条规则、删除单条规则、一键清除全部或清除选区规则 |
数据验证
类 Excel 数据验证,按工作表存储在
SheetState.dataValidations(DataValidationRule[])。引擎位于 spreader/core/data-validation.ts,纯逻辑实现、零 Vue/Canvas 依赖,单元测试见 test/data-validation.test.ts 与 test/data-validation-integration.test.ts。规则结构
每条规则拥有稳定
id、type(类型)、operator(运算符)、条件值 formula1/formula2、一个或多个 ranges(应用范围)、allowBlank(忽略空值)、showDropdown(显示下拉)、输入信息字段(showInputMessage、inputTitle、inputMessage)、出错警告字段(showErrorMessage、errorStyle、errorTitle、errorMessage)与 enabled(启用开关)。支持的类型与运算符
| 类型 | 说明 |
|---|---|
any | 无约束:在对话框选中「任何值」并确定即清除该范围验证 |
list | 单元格内下拉,来源于固定值或单元格范围 |
wholeNumber / decimal | 数值约束 |
date / time | 日期 / 时间约束(ISO、YYYY年M月D日、美式 m/d/yyyy 或 Excel 序列号) |
textLength | 文本长度约束 |
custom | 相对于候选单元格求值的公式,如 =AND(B2>=0,B2<=100) |
所有值类型支持 8 种运算符:
between(介于)、notBetween(不介于)、equal(等于)、notEqual(不等于)、greaterThan(大于)、greaterThanOrEqual(大于等于)、lessThan(小于)、lessThanOrEqual(小于等于)。between/notBetween 需要同时填 formula1(最小值)与 formula2(最大值);其余运算符只用 formula1。列表来源
列表验证的数据来自固定
values({ type: 'values', values: [...] })或范围引用({ type: 'range', range, sheetId });后者在源单元格变化时动态更新。验证流程
验证发生在值写入之前。
commitEdit 返回 Promise<boolean>-false 表示停止级违规拒绝了录入、编辑器状态保留。警告/信息级会调用 showValidationAlert 并等待用户确认后再继续。同一范围有多条规则时必须全部通过(rules.every())。范围索引
空间行分带索引(
DvIndex)将 (row, col) 查询映射到候选规则,O(1) 且零每次输入分配,因此单条覆盖 A1:A100000 的规则依然轻量。原子粘贴 / 自动填充
复制/粘贴与自动填充先算出全部候选值、跑
validateCells,只要出现一条停止级违规就整次取消,不会出现部分提交。插入 / 删除传播
adjustDvRangeForInsert/Delete 与 translateDvRange 复用公式引擎的 shiftFormulaRefs,因此绝对引用固定不动、相对引用正确平移;范围随之扩展/收缩,完全被移除的规则自动删除。UI 组件
| 文件 | 职责 |
|---|---|
data-validation-dialog.vue | 工具栏「数据验证」对话框:类型/运算符/值、列表来源、输入信息、出错警告、应用范围;保存时校验最小值/最大值字面量 |
data-validation-dropdown.vue | 单元格内列表下拉:虚拟列表、搜索框、完整键盘操作(↑ ↓ / Home / End / PageUp / PageDown / Enter / Esc) |
data-validation-alert.vue | 停止 / 警告 / 信息 警告框 |
data-validation-input-message.vue | 悬停提示气泡,显示规则的输入信息 |
持久化与撤销
规则随工作表 v-model(
dataValidations 字段)持久化,在保存/加载/复制工作表中往返,并像其他工作表改动一样接入撤销/重做栈。行列分组与折叠
类 Excel 的行/列分组与折叠(
spreader/core/outline-core.ts 纯函数引擎 + composables/core-state.ts 状态层 + composables/interactions.ts Canvas 绘制层)。数据模型
分组按工作表、按轴存储于
SheetState.rowOutlines / columnOutlines(DimensionOutline[],随 v-model 序列化)。每条分组包含稳定 id、start / end(0-based 闭区间)、level(恒为 1)、collapsed。校验规则
validateGroup 在创建前校验:至少 2 个连续行/列;与既有分组必须完全不相交(部分重叠提示「分组范围与现有分组交叉」);禁止嵌套(MAX_OUTLINE_LEVEL = 1,提示「分组不能嵌套,仅支持一层」)。取消分组要求选区完整覆盖至少一个分组,否则提示调整选区。渲染与折叠
分组区间在行号/列号带用交替背景色区分;± 折叠按钮(约 9px)浮动于表头带内,不预留独立 gutter 分区。折叠的行/列在内容、边框、表头文字绘制时统一跳过(0 尺寸防残影),与滚动、冻结窗格兼容。
入口
- 工具栏「分组」下拉(位于排序与筛选之间):仅在选中整行或整列时可用,菜单项(添加分组 / 取消分组 / 清除分组 / 全部展开 / 全部折叠)直接作用于选中轴,不再二次选择行/列
- 行/列右键菜单「分组」子菜单:按右键轴分发同构的菜单项
传播与撤销
插入/删除行列时分组范围经
addOutlineForInsert / addOutlineForDelete 平移,完全被清空的分组自动删除。分组的创建、取消、清除与折叠均产生撤销步骤;校验失败优先使用宿主注入的应用内提示(showOutlineAlert 钩子),未注入时回退 window.alert。单元测试见 test/outline-core.test.ts。单元格批注
类 Excel 的单元格批注(Note / Comment)功能,独立于单元格值与样式。核心引擎位于
spreader/core/notes.ts,纯逻辑实现;UI 浮层为 components/note-overlay.vue。数据模型
批注采用 Note Pool 存储:Sheet 级
notes 池按 id 存放完整 CellNote 对象,单元格仅保存 noteId 引用。TYPESCRIPT
interface CellNote {
id: string; // 稳定唯一 ID
text: string; // 批注正文(多行文本)
author?: string; // 作者(可选)
createdAt: number; // 创建时间(epoch ms)
updatedAt: number; // 最近修改时间(epoch ms)
}
CellData.noteId: 单元格上的批注引用,指向SheetState.notes池SheetModelData.notes: v-model 序列化时的批注池,键为 noteId
Note Pool 设计使得排序、插入 / 删除行列等操作移动单元格时,
noteId 随 cell 数据一起搬运,批注不会丢失。交互入口
- 右键菜单: 单元格右键菜单提供「新建批注 / 编辑批注 / 删除批注」三项;无批注时隐藏编辑 / 删除,有批注时隐藏新建
- Shift+F2: 在含批注的单元格上切换编辑浮层
- hover 查看: 鼠标悬停含批注的单元格时弹出只读查看浮层
渲染
- 含批注的单元格右上角绘制 6px 红色三角指示器(Canvas 绘制,无 DOM 开销)
- 批注浮层
note-overlay.vue为 Vue DOM 组件,position: absolute驻留于.spreadsheet-wrapper内,被overflow: hidden裁剪到表格区域,不溢出覆盖 toolbar / 行列头 / 冻结区域 / 滚动条 - z-index 分层:非冻结区浮层在 freeze canvas(z-index: 1)之下(z-index: 0),冻结区浮层在 freeze canvas 之上(z-index: 2),滚动条 z-index: 3 永远在最上层
作者名
通过
noteAuthor prop 设置当前作者名。空串时存入批注的 author 字段为空字符串,显示时回退为 i18n 占位符(中文「未命名」/ 英文 Unnamed)。持久化与撤销
- 批注随 Sheet v-model(
notes字段)持久化,保存 / 加载 / 复制工作表均正确往返 - 新建 / 修改 / 删除批注各产生一个撤销步骤,与单元格值、样式、边框等操作共享同一撤销栈
自动填充
一套类 Excel 的填充柄机制(
spreader/core/autofill.ts 纯引擎 + composables/interactions.ts 画布/交互层)。拖动活动选区右下角的小方块即可填充单元格,无 DOM 句柄,全部在 Canvas 上绘制。填充模式
| 源值 | 填充行为 | 示例 |
|---|---|---|
| 单个数字 | 复制 | 42 → 42, 42, 42 |
| 单个文本 | 复制 | foo → foo, foo, foo |
| 单个日期 | +1 天 | 2026-01-01 → 2026-01-02, 2026-01-03 |
| 两个及以上数字,等差 | 线性序列 | 1,2 → 3,4,5 · 2,4 → 6,8,10 |
| 两个及以上日期,等差 | 日期序列 | 2026-01-01, 2026-01-03 → 2026-01-05, 2026-01-07 |
| 文本 + 数字,prefix 相同 | 文本数字序列 | Item1,Item2 → Item3,Item4 |
| 两个单字母 | 字母序列 | A,B → C,D,E |
| 公式 | 引用平移 | =A1*2 → =A2*2, =A3*2 |
| 多列 / 多行整块 | 按列独立推断模式 | A1:B2=[[1,10],[2,20]] → [[3,30],[4,40]] |
公式引用规则
填充柄平移复用既有
shiftFormulaRefs(),未新建第二套公式引擎:| 引用类型 | 语法 | 填充行为 |
|---|---|---|
| 相对引用 | =A1 | 行列随目标单元格偏移 |
| 绝对引用 | =$A$1 | 保持不变 |
| 混合引用(列绝对) | =$A1 | 行调整,列锁定 |
| 混合引用(行绝对) | =A$1 | 列调整,行锁定 |
| 复合引用 | =B1*$F$1 | B1 调整,$F$1 锁定 |
交互
- 填充柄:活动选区右下角 6px 方块,主题主色;当选区与合并单元格部分相交时变灰(禁用填充)。
- 命中测试:独立
isFillHandleHit()在 resize 句柄之后、单元格点击之前判断;悬停显示crosshair光标。 - 状态机:独立
autofilling状态阻止选区拖拽 / 编辑器 / resize 启动;ESC 中途取消。 - 实时预览:拖拽期间目标区域以半透明虚线矩形显示;源选区保持不变;
mouseup前不修改任何单元格数据。 - 边缘自动滚动:拖到距视口边缘 30px 内时启动单一
requestAnimationFrame循环滚动并更新目标区域;mouseup/ ESC / pointercancel 即停止。 - 动态扩展:填充超出当前
rowCount/colCount时先触发ensureCapacity(),折叠进同一撤销步骤。 - 填充后选区:选区更新为源 + 目标的并集。
- 冻结窗格:所有坐标统一走
cellToScreenRect/screenToCell,填充柄、命中测试、预览、最终填充在冻结与主体区域均正确。 - 合并兼容:部分相交的合并禁用填充柄(第一版保守策略);整块合并可正确填充。
- 触摸:与鼠标共用同一
autoFillState与applyAutoFill路径,无独立实现。
架构
TEXT
填充柄 UI (drawFillHandle)
↓
统一命中测试 (isFillHandleHit)
↓
AutoFill 拖拽状态 (autoFillState)
↓
目标区域计算 (computeTargetRange)
↓
纯 AutoFill 模式引擎 (core/autofill.ts)
↓
公式平移 (shiftFormulaRefs)
↓
样式 / 边框 / 单元格写入 (applyAutoFill)
↓
撤销快照 (saveUndo,每次拖拽仅一步)
↓
scheduleRender()
纯引擎(
core/autofill.ts)零 Vue/Canvas 依赖,test/autofill.test.ts 中有 54 个用例完整单测覆盖,便于复用到 Ctrl+D/Ctrl+R、粘贴填充、右键填充、API 自动填充等场景。自动筛选(AutoFilter)
一套类 Excel 的普通区域自动筛选:筛选是「作用于数据区域 Range 的 Sheet 功能」,而非「每个列头自带的筛选功能」。表头箭头在 Canvas 上绘制(无 DOM),筛选面板用 Vue DOM。
启用与切换
- 工具栏「数据 → 筛选」或
Ctrl+Shift+L调用toggleAutoFilter()。 - 未启用 → 创建:选中数据单元格或单行多列选区后启用,即从该表头行向下智能探测连续数据区;完全空白表不创建无效筛选。
- 已启用 → 整体移除:恢复隐藏行、清除全部列条件、移除表头箭头。工具栏按钮高亮态与点击行为严格一致。
- 单选已合并单元格(跨多格合并区域)且无筛选态时,筛选按钮禁用(合并单元格无法作为合法数据区域表头)。
表头箭头
- 仅 Filter Range 的表头行、且列落在范围内才显示;未启用 / 范围外不显示。
- 样式为「右边框带背景的下箭头按钮」:未筛选浅灰底 + 深灰箭头,已筛选蓝底 + 白箭头(区分「已启用」与「该列已筛选」)。
- 点击箭头只打开筛选面板,不触发普通选区;冻结窗格下箭头在冻结表头正确显示。
筛选面板
复用既有引擎:标题显示该列表头内容(非列字母);取消对勾仅在**点「确定」**时生效,即时不影响表格。支持值 / 文本 / 数值 / 日期筛选、搜索、空值、多列 AND;候选值来自 Filter Range 内对应列,其它列已筛选时基于过滤后行生成(Excel 级联行为)。
兼容性
- 隐藏行机制沿用既有 filteredOutRows + 可见行映射,不删除、不复制原始行。
- 行/列增删经
adjustFilterRows/adjustFilterCols同步调整范围;删除整片筛选区域自动取消。 - 排序后重新计算filtered rows,原行索引不丢失。
- 经 v-model 序列化持久化,完整保留范围、箭头、条件与隐藏行。
边框系统
一套独立的边框存储与渲染机制(
spreader/core/border-pool.ts + border-resolve.ts)。- 独立边框池:每个 Sheet 维护
borders: BorderStyle[]池(borders[0]恒为空边框{}),样式通过borderId(数组下标)引用边框,相同边框自动去重。 - 公共边统一解析:相邻单元格的公共边在渲染时经
resolveSharedBorder()解析,宽者优先、同宽取先侧;若仅一侧带owner标记(由边框或选区操作显式写入),则该侧在公共边解析中优先、不受宽度影响,使新设置的选区边界边优先于相邻的旧边。设置边框不再同步修改相邻单元格。 - 合并单元格:合并区域边框统一存储在锚点(左上角),内部边屏蔽,外边界按行/列分段与相邻区域解析,四角补齐角方块。
- 旧数据兼容:旧版内联边框属性(
borderTopWidth等,已废弃)在加载时经migrateBordersInStyles()自动迁移。 - 按边颜色与线型(解耦):二者均挂在每条独立边
BorderSide({ width; color; style })上。颜色为标准 HEX 字符串;''或省略表示「自动」,渲染回退默认色#444。线型style取solid/dashed/dotted;旧数据缺style一律按solid处理,且solid不写style键,使其去重键与历史空边框数据一致。改颜色只动color、保留width/style;改线型只动style、保留width/color。边框下拉框提供两个子菜单:颜色选择器(复用文字色板并含「自动」项,只给已存在的边上色)与线型选择器(实线 / 虚线 / 点线预览,设置当前画笔线型,随后作用于后续边框操作及选区已存在的边,绝不凭空创建边框)。纯逻辑集中在core/border-color.ts(withBorderColor/withBorderWidth/planBorderColorChanges/resolveSelectionBorderColor)与core/border-style.ts(normalizeBorderLineStyle/borderLineDash),与 Vue/Canvas 完全解耦并具备单元测试。渲染器经paintBorderEdge绘制虚线/点线(实线保留原fillRect路径,网格线与选区框不受影响)。
主题与国际化
内置主题
TYPESCRIPT
import type { ThemeColors } from 'xiaodao-spreader';
const lightTheme: ThemeColors = { /* 50+ 颜色字段 */ };
const darkTheme: ThemeColors = { /* 50+ 颜色字段 */ };
主题变量(--sp-*)与作用域
主题颜色通过 CSS 自定义属性(
--sp-*)注入,集中声明在组件样式表 theme.css:CSS
--sp-bg, --sp-gridBg, --sp-gridLine, --sp-selectionBg,
--sp-headerBg, --sp-headerBorder, --sp-headerText,
--sp-formulaBarBg, --sp-formulaBarInputBorder,
--sp-wrapperBg, --sp-cellEditorBorder,
/* ... 以及更多(新增变量须同时提供 light 与 dark 两套取值) */
变量作用域收敛在组件子树内,不污染宿主页面:
- light:定义于
.spreadsheet-outer, .sp-spreader-overlay - dark:定义于
.spreadsheet-outer.dark, .sp-spreader-overlay.dark,theme="dark"时组件根与各 Teleport 浮层根自动挂.dark类,绝不读写<html>的全局类
由于下拉 / 对话框 / 右键菜单等浮层经 Teleport 挂到
<body> 下(脱离组件 DOM 树,无法从组件祖先继承变量),样式表将同一套变量同时声明在 .spreadsheet-outer(组件主体)与 .sp-spreader-overlay(浮层根)两个作用域。消费方必须引入样式表,否则所有组件样式(含浮层)都取不到主题变量:TS
import 'xiaodao-spreader/style.css';
另注:Canvas 内部绘制的网格 / 表头 / 选区配色不依赖 CSS 变量,而是由
ThemeColors(lightTheme / darkTheme)按 theme prop 直接选取。自定义主题
传入
theme="dark" 启用暗色模式,或覆盖 --sp-* 变量实现完全自定义。变量挂在 .spreadsheet-outer 作用域上,覆盖主体时可用包装组件(后代级联),但要连同浮层一起覆盖则需同时写 .sp-spreader-overlay(浮层脱离组件树、不从包装组件继承),也可直接用 theme.css 同优先级规则覆盖:VUE
<template>
<div style="--sp-bg: #1a1a2e; --sp-cellText: #e0e0e0;">
<Spreader v-model:data="myData" />
</div>
</template>
CSS
/* 覆盖 Teleport 浮层(下拉/对话框/右键菜单)时须用该选择器,仅覆盖组件根不够 */
.sp-spreader-overlay {
--sp-bg: #1a1a2e;
--sp-cellText: #e0e0e0;
}
国际化
内置语言:
'zh-CN'、'en-US'。通过 locale 属性传入。右键菜单和工具栏标签自动本地化。开发指南
BASH
# 安装依赖
pnpm install
# 启动开发服务器(自动热更新)
pnpm dev
# 仅做类型检查
pnpm type-check
开发服务器默认运行在
http://localhost:5173。构建发布
BASH
# 生产构建(类型检查 + vite build)
pnpm build
# 预览生产构建
pnpm preview
# 演示/测试构建
pnpm build:demo
构建产物
| 文件 | 说明 |
|---|---|
dist/xiaodao-spreader.es.js | ES 模块(供打包器使用) |
dist/xiaodao-spreader.umd.js | UMD 包(供 <script> 直接引用) |
dist/style.css | 提取的样式表 |
dist/types/ | TypeScript 声明文件 |
CI/CD
项目包含 GitHub Actions 工作流(
.github/workflows/publish.yml),用于自动发布到 npm。触屏与移动端交互
x-spreader 以触屏为第一交互:每个鼠标操作都有对称的触屏路径,并在真机验证。
- 点选 / 长按右键菜单:点击单元格、行头、列头或左上角全选按钮完成选择;在当前选区内长按(450ms)弹出对应右键菜单(单元格 / 行 / 列 / 角落)。选区外长按回落为框选 / 表头拖拽多选,手势零冲突。
- 表头筛选箭头:点击表头单元格上的箭头打开 AutoFilter 面板(
onTouchStart已补筛选按钮命中)。 - 列宽 / 行高拖拽:触摸列头右缘或行头下缘(触屏放宽 8px 热区)拖拽改列宽 / 行高。
- 矩形框选:单元格长按(450ms)后拖动绘制选区;行/列头长按后拖动扩展行/列选区。明显拖动(>8px)转滚动,轻点保持单选。
- 格式刷:从源单元格复制样式后,触屏点目标区域即应用(对齐鼠标
onMouseUp)。 - 点外部提交编辑:点空白画布或公式栏外即提交进行中的单元格 / 公式栏编辑(对齐鼠标
onMouseDown)。 - 底部 tab 栏:长按 tab 按钮或 tab 栏空白区(450ms)弹出 tab 右键菜单。
- 点外部关闭浮层:右键菜单与行高 / 列宽编辑面板(
dimPanel)支持点外部关闭。它们监听touchstart,因为onTouchStart调了preventDefault()会抑制桌面端依赖的合成click/mousedown;其余 picker 均用pointerdown,触屏原生关闭。
路线图
近期
- 更多公式:
COUNT、IF、VLOOKUP、CONCATENATE - 单元格背景颜色选择器
- 数字格式(货币、百分比、日期、小数位数), 见 数字格式
- 条件格式:高亮单元格预设 / 重复值 / 唯一值 / 空白 / 非空白、公式条件、行列增删时公式引用自动平移、管理与清除规则对话框,见 条件格式
- 自动填充拖拽手柄:见 自动填充
- 查找与替换:见 查找与替换
中期
- 冻结窗格(冻结行/列), 见 冻结窗格
- 数据验证:下拉列表、输入约束(任何值 / 列表 / 整数 / 小数 / 日期 / 时间 / 文本长度 / 自定义公式),列表下拉支持搜索与键盘导航,输入信息与出错警告(停止 / 警告 / 信息),粘贴/自动填充原子校验,撤销重做与持久化;见 数据验证
- 行列分组与折叠:见 行列分组与折叠
- 排序与筛选:按展示内容排序,含类 Excel「排序提醒」对话框(扩展选定区域 / 仅对选定区域排序);见 排序与排序提醒
- 自动筛选(AutoFilter):工具栏「筛选」/
Ctrl+Shift+L启用,表头下箭头、智能向下探测数据区、值/文本/数值/日期多类型筛选、清除单列与整体移除分离;见 自动筛选(AutoFilter) - 触屏与移动端交互:选择 / 右键菜单 / 筛选 / 列宽行高 / 框选 / 格式刷 / tab 菜单均补齐对称触屏路径,浮层支持点外部关闭;见 触屏与移动端交互
- 单元格批注:Note Pool 存储、右键菜单 CRUD、hover 查看、编辑浮层、持久化与撤销;见 单元格批注
- 只读模式:
editableprop 分层守卫,UI 与逻辑双重收口;见 只读模式 - 打印布局
远期
- OffscreenCanvas + Web Worker 多线程渲染
- Shared Worker 跨标签页协作
- 插件系统(自定义单元格渲染器)
- Excel / CSV 导入导出
- 图表引擎(嵌入式迷你图表)
技术栈
| 层级 | 技术 | 版本 |
|---|---|---|
| 框架 | Vue 3(Composition API + <script setup>) | ^3.4 |
| 构建 | Vite | ^5.0 |
| 语言 | TypeScript(严格模式) | ~5.4 |
| 渲染 | Canvas 2D API | - |
| 类型检查 | vue-tsc | ^2.2 |
| 包管理 | pnpm | - |
| CSS | Scoped CSS + CSS 自定义属性 | - |
开源协议
本项目基于 MIT 协议 — 详见 LICENSE 文件。

暂无评论