ai-sheet-sdk
v0.1.11
Published
基于 [Univer](https://github.com/dream-num/univer) 的在线表格 SDK,提供可编辑和只读预览两种模式。通过 IR(Intermediate Representation)中间格式与后端交互,支持懒加载、自动保存、增量保存和 RTL 文本。
Readme
AI Sheet SDK
基于 Univer 的在线表格 SDK,提供可编辑和只读预览两种模式。通过 IR(Intermediate Representation)中间格式与后端交互,支持懒加载、自动保存、增量保存和 RTL 文本。
目录结构
src/
├── sdk/ # SDK 入口 & 对外组件
│ ├── index.ts # 导出入口
│ ├── AISheet.tsx # 可编辑表格组件
│ ├── AISheetPreview.tsx # 只读预览组件
│ ├── createDefaultProvider.ts # 默认 HTTP DataProvider
│ └── types.ts # 对外类型定义
├── ir/ # IR 中间表示层
│ ├── types.ts # IR 类型定义(与后端 DTO 对齐)
│ ├── univerTransform.ts # IR ↔ Univer 双向转换
│ ├── univerTransform.test.ts
│ ├── diffCompute.ts # 增量保存 diff 计算
│ └── diffCompute.test.ts
├── components/ # Univer 底层封装
│ ├── UniverSheet.tsx # 可编辑 Univer 实例
│ └── UniverPreviewSheet.tsx # 只读 Univer 实例
├── api/
│ └── irApi.ts # 内部 API 层(demo 页面使用)
├── pages/ # Demo 页面
│ ├── HomePage.tsx
│ ├── SheetPage.tsx
│ ├── SheetPreviewPage.tsx
│ └── MockSheetPage.tsx
└── mock/
└── rtl-mock-data.ts # RTL 演示数据快速开始
安装依赖
pnpm install本地开发
pnpm dev开发服务器在 http://localhost:7091 启动,/api 代理到 http://localhost:8080(需后端服务运行)。
Demo 页面路由:
| 路径 | 说明 |
|------|------|
| / | 首页,输入 fileId 跳转 |
| /sheet/:fileId | 可编辑表格 |
| /sheet/:fileId/preview | 只读预览 |
| /mock | RTL 演示(纯前端 mock 数据) |
构建 SDK
pnpm build:sdk产物输出到 dist/,包含 ai-sheet-sdk.js 和 ai-sheet-sdk.css。
运行测试
pnpm test # 单次运行
pnpm test:watch # 监听模式作为 SDK 使用
安装
pnpm add ai-sheet-sdkPeer 依赖:react ^18 || ^19、react-dom ^18 || ^19。
基本用法
import { AISheet, createDefaultProvider } from 'ai-sheet-sdk';
import 'ai-sheet-sdk/style.css';
const provider = createDefaultProvider('/api/sheet/ir', 'your-token');
function MySheet() {
return (
<AISheet
fileId="01KJ9Y71S0MD68JCF8FYWXNRM5"
dataProvider={provider}
onReady={(api) => console.log('ready', api)}
onSave={(status) => console.log('save:', status)}
/>
);
}只读预览
import { AISheetPreview, createDefaultProvider } from 'ai-sheet-sdk';
import 'ai-sheet-sdk/style.css';
const provider = createDefaultProvider('/api/sheet/ir');
function MyPreview() {
return (
<AISheetPreview
fileId="01KJ9Y71S0MD68JCF8FYWXNRM5"
dataProvider={provider}
/>
);
}在 Vue 项目中嵌入
通过 createRoot 桥接 React 组件:
<script setup lang="ts">
import { AISheet, createDefaultProvider } from 'ai-sheet-sdk';
import 'ai-sheet-sdk/style.css';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { onMounted, onUnmounted, ref } from 'vue';
const container = ref<HTMLDivElement>();
let root: ReturnType<typeof createRoot>;
onMounted(() => {
root = createRoot(container.value!);
const provider = createDefaultProvider('/api/sheet/ir', 'token');
root.render(
React.createElement(AISheet, {
fileId: 'your-file-id',
dataProvider: provider,
})
);
});
onUnmounted(() => root?.unmount());
</script>
<template>
<div ref="container" style="width: 100%; height: 100%" />
</template>核心概念
DataProvider
数据源通过 DataProvider 接口抽象,宿主应用可以使用内置的 createDefaultProvider(基于 axios 的 REST 实现),也可以自行实现:
interface DataProvider {
// 必选
getMeta(fileId: string): Promise<WorkbookMeta>;
getStyles(fileId: string): Promise<StylePalette>;
getCells(fileId, sheet, r0, r1, c0, c1): Promise<CellRangeResponse>;
// 可选 — 提供后启用保存功能
saveIR?(fileId, request: SaveIRRequest): Promise<SaveResponse>;
diffSaveIR?(fileId, request: DiffSaveRequest): Promise<SaveResponse>;
evaluateFormulas?(fileId: string): Promise<void>;
}createDefaultProvider(apiBaseUrl, token?) 对应的后端接口:
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /{fileId}/meta | 获取工作簿元数据 |
| GET | /{fileId}/styles | 获取样式表 |
| GET | /{fileId}/cells?sheet&r0&r1&c0&c1 | 获取单元格范围 |
| POST | /{fileId}/save | 全量保存 |
| PATCH | /{fileId}/diff-save | 增量保存 |
| POST | /{fileId}/evaluate | 触发服务端公式计算 |
后端响应格式为 { code: number, data: T, message?: string },SDK 自动解包。
IR 数据格式
IR(Intermediate Representation)是前后端之间的数据协议:
WorkbookMeta — 工作簿元数据
{
fileId: string;
fileName: string;
fileSize: number;
sheets: SheetMeta[];
version?: number; // 用于增量保存的乐观锁
}SheetMeta — 工作表元数据
{
index: number;
name: string;
rowCount: number;
colCount: number;
colWidths: Record<number, number>; // 列宽(Excel 字符宽度,非像素)
rowHeights: Record<number, number>; // 行高(磅值,非像素)
mergedCells: MergedRegion[];
}IRCellData — 单元格
{
row: number;
col: number;
value: string | number | boolean;
type: 'STRING' | 'NUMERIC' | 'BOOLEAN' | 'FORMULA' | 'BLANK';
formulaValue?: string; // type 为 FORMULA 时的公式表达式
styleIndex: number; // 引用 StylePalette.styles 的索引,-1 表示无样式
}StylePalette — 样式表
{
styles: CellStyleDef[]; // 索引式样式数组
}IR ↔ Univer 转换规则
| IR 属性 | 方向 | Univer 属性 | 转换公式 |
|---------|------|------------|---------|
| colWidths (字符宽度) | → | columnData[col].w (像素) | × 8 |
| rowHeights (磅) | → | rowData[row].h (像素) | × 1.33 |
| CellStyleDef.bold | ↔ | IStyleData.bl | BooleanNumber.TRUE / FALSE |
| CellStyleDef.fontSize | ↔ | IStyleData.fs | 直接映射 |
| CellStyleDef.fontColor | ↔ | IStyleData.cl.rgb | 直接映射 |
| CellStyleDef.bgColor | ↔ | IStyleData.bg.rgb | 直接映射 |
| CellStyleDef.numberFormat | ↔ | IStyleData.n.pattern | 直接映射 |
组件 Props 参考
AISheet
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| fileId | string | — | 文件 ID(必填) |
| dataProvider | DataProvider | — | 数据源(必填) |
| readonly | boolean | false | 只读模式 |
| autoSaveDelay | number | 3000 | 自动保存防抖延迟(ms) |
| onReady | (api: FUniver) => void | — | Univer 初始化完成回调 |
| onSave | (status) => void | — | 保存状态回调:'saving' / 'saved' / 'error' |
| onError | (error: Error) => void | — | 加载错误回调 |
| ref | Ref<AISheetRef> | — | 通过 ref.getUniverAPI() 获取底层 API |
AISheetPreview
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| fileId | string | — | 文件 ID(必填) |
| dataProvider | DataProvider | — | 数据源(必填) |
| onReady | (api: FUniver) => void | — | 初始化完成回调 |
| onError | (error: Error) => void | — | 加载错误回调 |
| ref | Ref<AISheetRef> | — | 通过 ref.getUniverAPI() 获取底层 API |
技术栈
- 表格引擎:Univer
^0.15.5 - 框架:React 18/19
- HTTP:axios
- 构建:Vite(库模式)
- 测试:Vitest + Testing Library
- 类型:TypeScript 5.9
