Headless Univer
Headless Univer は、対話型エディター UI を持たない Univer の内容ランタイムです。ブラウザー版と内容モデル、コマンド、計算、Facade API を共有します。Node.js での内容処理やブラウザー内の UI を使わない処理に利用でき、登録する製品・機能プラグインによって利用可能な機能が決まります。
Node.js でブックを作成・読み込みし、セルや書式、行列を編集し、数式を計算して Univer スナップショットを保存できます。フィルター、並べ替え、入力規則、ピボット、グラフには対応する内容プラグインが必要です。グラフデータの変更と描画は別です。
以下はプラグインモードを使います。@univerjs/core と製品の内容プラグインは Headless 層に含まれますが、@univerjs/ui、製品の *-ui、標準ブラウザープリセットは含めません。@univerjs/engine-render は共有依存として登録し、Canvas やエディターはマウントしません。詳細はパッケージの境界を参照してください。
ローカルスナップショット処理に共同編集は不要です。共同編集サンプルにはクライアント、サーバー、認証が必要です。Office 変換と画像出力にはそれぞれ Server SDK と描画ランタイムを接続します。/facade の読み込みだけでは機能プラグインの登録や UI API の Headless 化は行われません。
インストール
pnpm add @univerjs/core @univerjs/engine-render @univerjs/engine-formula @univerjs/docs @univerjs/sheets @univerjs/sheets-formula @univerjs/sheets-numfmtnpm install @univerjs/core @univerjs/engine-render @univerjs/engine-formula @univerjs/docs @univerjs/sheets @univerjs/sheets-formula @univerjs/sheets-numfmtyarn add @univerjs/core @univerjs/engine-render @univerjs/engine-formula @univerjs/docs @univerjs/sheets @univerjs/sheets-formula @univerjs/sheets-numfmtbun add @univerjs/core @univerjs/engine-render @univerjs/engine-formula @univerjs/docs @univerjs/sheets @univerjs/sheets-formula @univerjs/sheets-numfmt使い方
import { LocaleType, mergeLocales, Univer } from '@univerjs/core'import { FUniver } from '@univerjs/core/facade'import { UniverDocsPlugin } from '@univerjs/docs'import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'import { UniverRenderEnginePlugin } from '@univerjs/engine-render'import { UniverSheetsPlugin } from '@univerjs/sheets'import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'import SheetsJaJP from '@univerjs/sheets/locale/ja-JP'import '@univerjs/engine-formula/facade'import '@univerjs/sheets/facade'import '@univerjs/sheets-formula/facade'import '@univerjs/sheets-numfmt/facade'const univer = new Univer({ locale: LocaleType.JA_JP, locales: { [LocaleType.JA_JP]: mergeLocales( SheetsJaJP, ), },})univer.registerPlugin(UniverRenderEnginePlugin)univer.registerPlugin(UniverFormulaEnginePlugin)univer.registerPlugin(UniverDocsPlugin)univer.registerPlugin(UniverSheetsPlugin)univer.registerPlugin(UniverSheetsFormulaPlugin)univer.registerPlugin(UniverSheetsNumfmtPlugin)const univerAPI = FUniver.newAPI(univer)univerAPI.createWorkbook({})Node.js 環境での共同編集の例
Node.js を共同編集クライアントとして使用し、サーバー上の既存のワークブックを読み込み、Facade API で内容を変更して、同じドキュメントを開いている他のクライアントに同期できます。実行前に、共同編集サービス、クライアントライセンス、アクセス可能なドキュメント ID を用意してください。
サンプルファイル
次の構成でファイルを配置します。
src/sheets/node/├── main.ts├── univer.ts└── worker.tsuniver.ts:Univer を作成し、各機能のプラグイン、共同編集クライアント、必要に応じて RPC メインプロセスプラグインを登録します。worker.ts:計算側の数式エンジン、リモート数式、フィルター、ピボットテーブルの各プラグインを登録し、RPC でメインプロセスと通信します。main.ts:共同編集ドキュメントを読み込み、権限、基本的なシート操作、条件付き書式、データ入力規則、テーブル、ピボットテーブル、スパークライン、画像、グラフ、図形、グループ化、ハイパーリンクの API を順に示します。
両方のファイルの your-license を同じライセンスに置き換え、サーバー URL、Cookie、ドキュメント ID、ユーザー ID も実際の値に変更してください。main.ts の権限の例では共同編集者と保護範囲も設定するため、実際の業務ユーザーと、必要な管理権限を持つ認証情報を使用してください。
import { UniverCollaborationPlugin } from '@univerjs-pro/collaboration'import { UniverCollaborationClientPlugin } from '@univerjs-pro/collaboration-client'import { NodeCollaborationSocketService, UniverCollaborationClientNodePlugin,} from '@univerjs-pro/collaboration-client-node'import { UniverProFormulaEnginePlugin } from '@univerjs-pro/engine-formula'import { UniverLicensePlugin } from '@univerjs-pro/license'import { UniverSheetsChartPlugin } from '@univerjs-pro/sheets-chart'import { UniverSheetsOutlinePlugin } from '@univerjs-pro/sheets-outline'import { UniverSheetsPivotTablePlugin } from '@univerjs-pro/sheets-pivot'import { UniverSheetsShapePlugin } from '@univerjs-pro/sheets-shape'import { UniverSheetSparklinePlugin } from '@univerjs-pro/sheets-sparkline'import { IAuthzIoService, IImageIoService, IUndoRedoService, LocaleType, Univer } from '@univerjs/core'import { UniverDataValidationPlugin } from '@univerjs/data-validation'import { UniverDocsPlugin } from '@univerjs/docs'import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'import { UniverDrawingPlugin } from '@univerjs/drawing'import { UniverRenderEnginePlugin } from '@univerjs/engine-render'import { UniverNetworkPlugin } from '@univerjs/network'import { UniverRPCNodeMainPlugin } from '@univerjs/rpc-node'import { UniverSheetsPlugin } from '@univerjs/sheets'import { UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting'import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'import { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing'import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link'import { UniverSheetsNotePlugin } from '@univerjs/sheets-note'import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort'import { UniverSheetsTablePlugin } from '@univerjs/sheets-table'import { UniverThreadCommentPlugin } from '@univerjs/thread-comment'import path from 'node:path'// ここで使用する Facade API は packages/facade/src/apis/everything.ts に含まれます// 一部のプラグインは実装が不完全なため、その API はここに含まれていません。import '@univerjs/engine-formula/facade'import '@univerjs/sheets/facade'import '@univerjs/sheets-formula/facade'import '@univerjs/sheets-numfmt/facade'import '@univerjs/sheets-conditional-formatting/facade'import '@univerjs/sheets-data-validation/facade'import '@univerjs/sheets-filter/facade'import '@univerjs/sheets-hyper-link/facade'import '@univerjs/sheets-note/facade'import '@univerjs/sheets-table/facade'import '@univerjs/sheets-drawing/facade'import '@univerjs/sheets-thread-comment/facade'import '@univerjs-pro/engine-shape/facade'import '@univerjs-pro/sheets-pivot/facade'import '@univerjs-pro/sheets-sparkline/facade'import '@univerjs-pro/sheets-chart/facade'import '@univerjs-pro/sheets-shape/facade'import '@univerjs-pro/sheets-outline/facade'import '@univerjs-pro/collaboration-client/facade'export interface ICreateUniverOnNodeOptions { useComputingWorker?: boolean}export function createUniverOnNode(options: ICreateUniverOnNodeOptions = {}): Univer { const { useComputingWorker = false } = options const univer = new Univer({ locale: LocaleType.JA_JP, locales: {}, override: [ [IAuthzIoService, null], [IUndoRedoService, null], ], }) univer.registerPlugin(UniverLicensePlugin, { license: 'your-license', }) registerBasicPlugins(univer, useComputingWorker) registerSharedPlugins(univer) if (useComputingWorker) { registerRPCPlugin(univer) } registerDocPlugins(univer) registerSheetPlugins(univer, useComputingWorker) registerCollaborationPlugins(univer) return univer}function registerBasicPlugins(univer: Univer, useComputingWorker: boolean): void { univer.registerPlugin(UniverProFormulaEnginePlugin, { notExecuteFormula: useComputingWorker })}function registerSharedPlugins(univer: Univer): void { univer.registerPlugin(UniverRenderEnginePlugin) univer.registerPlugin(UniverThreadCommentPlugin) univer.registerPlugin(UniverDrawingPlugin, { override: [[IImageIoService, null]], })}function registerDocPlugins(univer: Univer): void { univer.registerPlugin(UniverDocsPlugin) univer.registerPlugin(UniverDocsDrawingPlugin)}function registerSheetPlugins(univer: Univer, useComputingWorker: boolean): void { univer.registerPlugin(UniverSheetsPlugin, { notExecuteFormula: useComputingWorker }) univer.registerPlugin(UniverSheetsFormulaPlugin, { notExecuteFormula: useComputingWorker }) univer.registerPlugin(UniverSheetsNumfmtPlugin) univer.registerPlugin(UniverSheetsConditionalFormattingPlugin) univer.registerPlugin(UniverDataValidationPlugin) univer.registerPlugin(UniverSheetsDataValidationPlugin) univer.registerPlugin(UniverSheetsFilterPlugin) univer.registerPlugin(UniverSheetsHyperLinkPlugin) univer.registerPlugin(UniverSheetsNotePlugin) univer.registerPlugin(UniverSheetsDrawingPlugin) univer.registerPlugin(UniverSheetsSortPlugin) univer.registerPlugin(UniverSheetsTablePlugin) univer.registerPlugin(UniverSheetsChartPlugin) univer.registerPlugin(UniverSheetsShapePlugin) univer.registerPlugin(UniverSheetsPivotTablePlugin, { notExecuteFormula: useComputingWorker ? true : undefined, }) univer.registerPlugin(UniverSheetSparklinePlugin) univer.registerPlugin(UniverSheetsOutlinePlugin)}function registerRPCPlugin(univer: Univer): void { // ビルド成果物の実際の場所に合わせて worker のパスを設定します。 const childPath = path.join(__dirname, 'worker.js') univer.registerPlugin(UniverRPCNodeMainPlugin, { workerSrc: childPath })}function registerCollaborationPlugins(univer: Univer): void { const serverUrl = 'https://your-univer-server.example.com' univer.registerPlugin(UniverNetworkPlugin) univer.registerPlugin(UniverCollaborationPlugin) univer.registerPlugin(UniverCollaborationClientPlugin, { socketService: NodeCollaborationSocketService, enableOfflineEditing: false, enableSingleActiveInstanceLock: false, snapshotServerUrl: `${serverUrl}/universer-api/snapshot`, collabSubmitChangesetUrl: `${serverUrl}/universer-api/comb`, collabWebSocketUrl: `${serverUrl}/universer-api/comb/connect`, wsSessionTicketUrl: `${serverUrl}/universer-api/user/session-ticket`, uploadFileServerUrl: `${serverUrl}/universer-api/stream/file/upload`, signUrlServerUrl: `${serverUrl}/universer-api/file/{fileID}/sign-url`, downloadEndpointUrl: `${serverUrl}`, authzUrl: `${serverUrl}/universer-api/authz`, sendChangesetTimeout: 200, retryConnectingInterval: 1000, customHeaders: { // 例:Univer サーバーが Cookie 認証を必要とする場合、ここで 'cookie' ヘッダーを設定できます。 cookie: 'your-cookie', // ... その他のヘッダー }, }) univer.registerPlugin(UniverCollaborationClientNodePlugin)}import { UniverProFormulaEnginePlugin } from '@univerjs-pro/engine-formula/worker'import { UniverLicensePlugin } from '@univerjs-pro/license'import { UniverSheetsPivotTablePlugin } from '@univerjs-pro/sheets-pivot/worker'import { LocaleType, Univer } from '@univerjs/core'import { UniverRPCNodeWorkerPlugin } from '@univerjs/rpc-node/worker'import { UniverSheetsPlugin } from '@univerjs/sheets/worker'import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter/worker'import { UniverRemoteSheetsFormulaPlugin } from '@univerjs/sheets-formula/worker'const univer = new Univer({ locale: LocaleType.JA_JP,})univer.registerPlugin(UniverLicensePlugin, { license: 'your-license',})univer.registerPlugin(UniverSheetsPlugin, { onlyRegisterFormulaRelatedMutations: true })univer.registerPlugin(UniverProFormulaEnginePlugin)univer.registerPlugin(UniverRPCNodeWorkerPlugin)univer.registerPlugin(UniverRemoteSheetsFormulaPlugin)univer.registerPlugin(UniverSheetsFilterPlugin)univer.registerPlugin(UniverSheetsPivotTablePlugin, { notExecuteFormula: false,})import type { FConnectorShape } from '@univerjs-pro/engine-shape/facade'import type { FSheetShape } from '@univerjs-pro/sheets-shape/facade'import { ChartBorderDashType } from '@univerjs-pro/engine-chart'import { CellValueType } from '@univerjs/core'import { FUniver } from '@univerjs/core/facade'import { createUniverOnNode } from './univer'async function run(): Promise<void> { const univer = createUniverOnNode({ useComputingWorker: true }) const univerAPI = FUniver.newAPI(univer) const workbook = await univerAPI.getCollaboration().loadSheetAsync('your-unit-id') if (!workbook) throw new Error('共同編集ワークブックを読み込めませんでした') await TestSheetsPermissionAPI(univerAPI) TestSheetsFacadeAPI(univerAPI) TestSheetsConditionalFormattingAPI(univerAPI) await TestSheetsDataValidationFacadeAPI(univerAPI) await TestSheetsTableFacadeAPI(univerAPI) await TestSheetsPivotFacadeAPI(univerAPI) TestSheetsSparklineFacadeAPI(univerAPI) await TestSheetsDrawingAPI(univerAPI) await TestSheetsChartFacadeAPI(univerAPI) await TestSheetsShapeFacadeAPI(univerAPI) await TestSheetsOutlineAPI(univerAPI) await TestSheetsHyperlinkAPI(univerAPI) await univerAPI.getCollaboration().flush(workbook.getId()) console.log('サンプルの変更を同期しました。クライアントは接続を維持します')}async function TestSheetsPermissionAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.getActiveSheet() // ワークブックに共同編集者を追加します const workbookPermission = fWorkbook.getWorkbookPermission() await workbookPermission.setCollaborators([ { user: { userID: 'systemUser1', name: 'システムユーザー 1', avatar: '' }, role: univerAPI.Enum.UnitRole.Owner, }, { user: { userID: 'your-user-id', name: 'Node.js クライアント', avatar: '' }, role: univerAPI.Enum.UnitRole.Editor, }, { user: { userID: 'editorUser1', name: '編集ユーザー 1', avatar: '' }, role: univerAPI.Enum.UnitRole.Editor, }, { user: { userID: 'readerUser1', name: '閲覧ユーザー 1', avatar: '' }, role: univerAPI.Enum.UnitRole.Reader, }, ]) const fRange = fWorksheet.getRange('A1:B2') const rangePermission = fRange.getRangePermission() const rule = await rangePermission.protect({ name: 'テスト用保護ルール', allowedUsers: ['editorUser1'], allowViewByOthers: true, })}function TestSheetsFacadeAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return // ワークブック名を設定します fWorkbook.setName('テストワークブック - Node 共同編集クライアント') const fWorksheet = fWorkbook.getActiveSheet() // セル A1 に値を設定します fWorksheet .getRange('A1') .setValue(`こんにちは!Node 共同編集クライアントからのメッセージです。現在の時刻:${new Date().toString()}。`) // C2:E4 のセルを結合します const fRange2 = fWorksheet.getRange('C2:E4') if (fRange2.isPartOfMerge() || fRange2.isMerged()) { fRange2.breakApart() } fRange2.merge().setValue(1234).setNumberFormat('#,##0.00') // セル A1 に定義名を作成し、A6 で使用します const definedNameBuilder = fWorkbook .newDefinedNameBuilder() .setRef(`${fWorksheet.getSheetName()}!$A$1`) .setName('MyDefinedName') .build() fWorkbook.insertDefinedNameBuilder(definedNameBuilder) fWorksheet.getRange('A6').setValue('=MyDefinedName') // 新しいシートを挿入します const newSheet = fWorkbook.insertSheet('Node クライアントの新規シート', { index: 1, sheet: { rowCount: 100, columnCount: 20, cellData: { 0: { 0: { v: 1, }, }, 1: { 1: { v: 2, }, }, 2: { 2: { v: 3, }, }, 3: { 3: { v: 4, }, }, }, }, }) // 3 行目の後に 3 行挿入します(インデックスは 0 始まり)。 newSheet.insertRowsAfter(2, 3) const sheets = fWorkbook.getSheets() if (sheets.length) { // 最初のシートの名前を変更し、行数と列数を設定します sheets[0].setName('名前変更後のシート 1').setRowCount(100).setColumnCount(10) // 最初のシートの先頭行と先頭列を固定します sheets[0].setFrozenRows(1).setFrozenColumns(1) // 最初のシートのグリッド線を赤に設定します sheets[0].setGridLinesColor('#FF0000') // 最初のシートのデータ範囲を取得し、A1 形式で出力します const dataRange = sheets[0].getDataRange() console.warn(`名前変更後のシート 1 のデータ範囲(A1 形式):${dataRange.getA1Notation()}`) // 5 秒後にデータ範囲の内容を消去します setTimeout(() => { dataRange.clearContent() }, 5000) }}function TestSheetsConditionalFormattingAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const newSheet = fWorkbook.insertSheet('条件付き書式シート', { sheet: { cellData: { 0: { 0: { v: 1, t: CellValueType.NUMBER }, 1: { v: 2, t: CellValueType.NUMBER }, 2: { v: 3, t: CellValueType.NUMBER }, 3: { v: 4, t: CellValueType.NUMBER }, }, 1: { 0: { v: 2, t: CellValueType.NUMBER }, 1: { v: 3, t: CellValueType.NUMBER }, 2: { v: 4, t: CellValueType.NUMBER }, 3: { v: 5, t: CellValueType.NUMBER }, }, 2: { 0: { v: 3, t: CellValueType.NUMBER }, 1: { v: 4, t: CellValueType.NUMBER }, 2: { v: 5, t: CellValueType.NUMBER }, 3: { v: 6, t: CellValueType.NUMBER }, }, 3: { 0: { v: 4, t: CellValueType.NUMBER }, 1: { v: 5, t: CellValueType.NUMBER }, 2: { v: 6, t: CellValueType.NUMBER }, 3: { v: 7, t: CellValueType.NUMBER }, }, }, }, }) const fRange = newSheet.getRange('A1:D4') const rule = newSheet .newConditionalFormattingRule() .whenNumberGreaterThan(3) .setRanges([fRange.getRange()]) .setItalic(true) .setBackground('red') .setFontColor('green') .build() newSheet.addConditionalFormattingRule(rule)}async function TestSheetsDataValidationFacadeAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('データ入力規則シート', { sheet: { rowCount: 30, columnCount: 10, cellData: { 0: { 0: { v: '点数' }, 1: { v: '状態' }, 2: { v: '地域' }, 3: { v: '承認済み' }, 4: { v: '数式条件' }, 5: { v: '開始日' }, 6: { v: 'コピーした点数' }, 7: { v: '地域の候補' }, }, 1: { 0: { v: 95, t: CellValueType.NUMBER }, 1: { v: '未処理' }, 2: { v: '東部' }, 3: { v: 'はい' }, 4: { v: '合格' }, 5: { v: '2026-01-02' }, 6: { v: 60, t: CellValueType.NUMBER }, 7: { v: '東部' }, }, 2: { 0: { v: 42, t: CellValueType.NUMBER }, 1: { v: '完了' }, 2: { v: '西部' }, 3: { v: 'いいえ' }, 4: { v: '合格' }, 5: { v: '2025-12-31' }, 6: { v: 55, t: CellValueType.NUMBER }, 7: { v: '北部' }, }, 3: { 0: { v: 101, t: CellValueType.NUMBER }, 1: { v: '不明' }, 2: { v: '中部' }, 3: { v: '未定' }, 4: { v: '合格' }, 5: { v: '2026-06-01' }, 6: { v: 75, t: CellValueType.NUMBER }, 7: { v: '南部' }, }, 4: { 0: { v: 60, t: CellValueType.NUMBER }, 1: { v: '保留' }, 2: { v: '北部' }, 3: { v: 'はい' }, 4: { v: '合格' }, 5: { v: '2024-01-01' }, 6: { v: 90, t: CellValueType.NUMBER }, }, 5: { 0: { v: 70, t: CellValueType.NUMBER }, 1: { v: '未処理' }, 2: { v: '南部' }, 3: { v: 'いいえ' }, 4: { v: '合格' }, 5: { v: '2026-03-01' }, 6: { v: 45, t: CellValueType.NUMBER }, }, }, }, }) const scoreRange = fWorksheet.getRange('A2:A6') scoreRange.setDataValidation( univerAPI .newDataValidation() .requireNumberBetween(1, 100, true) .setAllowInvalid(false) .setAllowBlank(false) .setOptions({ showErrorMessage: true, errorTitle: '点数が無効です', error: '1~100 の整数で点数を入力してください。', showInputMessage: true, promptTitle: '点数', prompt: '1~100 の整数を使用してください。', }) .build(), ) fWorksheet.getRange('B2:B6').setDataValidation( univerAPI .newDataValidation() .requireValueInList(['未処理', '保留', '完了'], false, true) .setAllowBlank(false) .setOptions({ showErrorMessage: true, error: '未処理、保留、完了のいずれかを選択してください。', }) .build(), ) fWorksheet.getRange('C2:C6').setDataValidation( univerAPI .newDataValidation() .requireValueInRange(fWorksheet.getRange('H2:H4'), false, true) .setAllowBlank(true) .setOptions({ showErrorMessage: true, error: 'H2:H4 から地域を選択してください。', }) .build(), ) fWorksheet.getRange('D2:D6').setDataValidation(univerAPI.newDataValidation().requireCheckbox('はい', 'いいえ').build()) fWorksheet.getRange('E2:E6').setDataValidation( univerAPI .newDataValidation() .requireFormulaSatisfied('=A2>=50') .setOptions({ showErrorMessage: true, error: '列 A の点数は 50 以上である必要があります。', }) .build(), ) fWorksheet.getRange('F2:F6').setDataValidation( univerAPI .newDataValidation() .requireDateOnOrAfter(new Date('2026-01-01')) .setOptions({ showErrorMessage: true, error: '2026-01-01 以降の日付を指定してください。', }) .build(), ) const scoreRule = scoreRange.getDataValidation() scoreRule?.setCriteria( univerAPI.Enum.DataValidationType.DECIMAL, [univerAPI.Enum.DataValidationOperator.BETWEEN, '50', '100'], false, ) scoreRule?.setOptions({ error: '50~100 の点数を入力してください。' }) scoreRule?.setRanges([fWorksheet.getRange('A2:A5')]) const copiedRule = scoreRule ?.copy() .requireNumberGreaterThanOrEqualTo(60, true) .setOptions({ error: '60 以上を入力してください。' }) .build() if (copiedRule) { fWorksheet.getRange('G2:G6').setDataValidation(copiedRule) } // console.warn('A2:F6 のデータ入力規則:', fWorksheet.getRange('A2:F6').getDataValidations()); // console.warn('A2:A6 のデータ検証状態:', await fWorksheet.getRange('A2:A6').getValidatorStatus()); // console.warn('シートのデータ検証状態:', await fWorksheet.getValidatorStatusAsync()); // console.warn('ワークブックのデータ検証状態:', await fWorkbook.getValidatorStatus()); // console.warn('範囲のデータ検証エラー:', await fWorksheet.getRange('A2:F6').getDataValidationErrorAsync()); // console.warn('シートのデータ検証エラー:', await fWorksheet.getAllDataValidationErrorAsync()); // console.warn('ワークブックのデータ検証エラー:', await fWorkbook.getAllDataValidationErrorAsync()); fWorksheet.getRange('D2:D6').setDataValidation(null) fWorksheet.getRange('G2:G6').getDataValidation()?.delete()}async function TestSheetsTableFacadeAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('テーブルシート', { sheet: { rowCount: 30, columnCount: 8, cellData: { 0: { 0: { v: '地域' }, 1: { v: '商品' }, 2: { v: '売上高' }, 3: { v: '担当者' }, }, 1: { 0: { v: '東部' }, 1: { v: 'ノートパソコン' }, 2: { v: 1200, t: CellValueType.NUMBER }, 3: { v: 'Alice' }, }, 2: { 0: { v: '西部' }, 1: { v: 'キーボード' }, 2: { v: 850, t: CellValueType.NUMBER }, 3: { v: 'Bob' }, }, 3: { 0: { v: '東部' }, 1: { v: 'マウス' }, 2: { v: 420, t: CellValueType.NUMBER }, 3: { v: 'Alice' }, }, 4: { 0: { v: '北部' }, 1: { v: 'モニター' }, 2: { v: 1600, t: CellValueType.NUMBER }, 3: { v: 'Carol' }, }, 5: { 0: { v: '南部' }, 1: { v: 'ドッキングステーション' }, 2: { v: 720, t: CellValueType.NUMBER }, 3: { v: 'David' }, }, 8: { 0: { v: '名前' }, 1: { v: '点数' }, 2: { v: 'チーム' }, }, 9: { 0: { v: 'Ada' }, 1: { v: 92, t: CellValueType.NUMBER }, 2: { v: 'A' }, }, 10: { 0: { v: 'Grace' }, 1: { v: 88, t: CellValueType.NUMBER }, 2: { v: 'B' }, }, 11: { 0: { v: 'Linus' }, 1: { v: 95, t: CellValueType.NUMBER }, 2: { v: 'A' }, }, }, }, }) const salesTableId = 'node-sales-table' const salesTableAdded = await fWorksheet.addTable( 'NodeSalesTable', fWorksheet.getRange('A1:D6').getRange(), salesTableId, { tableStyleId: 'table-default-4', filters: [ { filterType: univerAPI.Enum.TableColumnFilterTypeEnum.manual, values: ['東部', '北部'], }, ], }, ) if (salesTableAdded) { await fWorksheet.setTableFilter(salesTableId, 2, { filterType: univerAPI.Enum.TableColumnFilterTypeEnum.condition, filterInfo: { conditionType: univerAPI.Enum.TableConditionTypeEnum.Number, compareType: univerAPI.Enum.TableNumberCompareTypeEnum.GreaterThan, expectedValue: 800, }, }) await fWorksheet.resetFilter(salesTableId, 2) await fWorksheet.setTableRange(salesTableId, fWorksheet.getRange('A1:D7').getRange()) await fWorksheet.setTableName(salesTableId, 'NodeSalesTableRenamed') await fWorksheet.addTableTheme(salesTableId, { name: 'node-table-style', headerRowStyle: { bg: { rgb: '#145F82', }, cl: { rgb: '#FFFFFF', }, }, firstRowStyle: { bg: { rgb: '#E0F2FE', }, }, secondRowStyle: { bg: { rgb: '#FFFFFF', }, }, }) } const scoreTableId = await fWorkbook.addTable( fWorksheet.getSheetId(), 'NodeScoreTable', fWorksheet.getRange('A9:C12').getRange(), 'node-score-table', { tableStyleId: 'table-default-2', }, ) if (scoreTableId) { await fWorkbook.setTableFilter(scoreTableId, 1, { filterType: univerAPI.Enum.TableColumnFilterTypeEnum.condition, filterInfo: { conditionType: univerAPI.Enum.TableConditionTypeEnum.Number, compareType: univerAPI.Enum.TableNumberCompareTypeEnum.GreaterThanOrEqual, expectedValue: 90, }, }) await fWorkbook.removeTable(scoreTableId) }}async function TestSheetsPivotFacadeAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('ピボットテーブルシート', { sheet: { rowCount: 40, columnCount: 12, cellData: { 0: { 0: { v: '地域' }, 1: { v: 'カテゴリ' }, 2: { v: '商品' }, 3: { v: '四半期' }, 4: { v: '売上' }, 5: { v: '数量' }, }, 1: { 0: { v: '東部' }, 1: { v: 'ハードウェア' }, 2: { v: 'キーボード' }, 3: { v: '第 1 四半期' }, 4: { v: 1200, t: CellValueType.NUMBER }, 5: { v: 12, t: CellValueType.NUMBER }, }, 2: { 0: { v: '東部' }, 1: { v: 'ハードウェア' }, 2: { v: 'モニター' }, 3: { v: '第 2 四半期' }, 4: { v: 1800, t: CellValueType.NUMBER }, 5: { v: 9, t: CellValueType.NUMBER }, }, 3: { 0: { v: '西部' }, 1: { v: 'ソフトウェア' }, 2: { v: 'エディター' }, 3: { v: '第 1 四半期' }, 4: { v: 950, t: CellValueType.NUMBER }, 5: { v: 19, t: CellValueType.NUMBER }, }, 4: { 0: { v: '西部' }, 1: { v: 'ハードウェア' }, 2: { v: 'ドッキングステーション' }, 3: { v: '第 2 四半期' }, 4: { v: 720, t: CellValueType.NUMBER }, 5: { v: 8, t: CellValueType.NUMBER }, }, 5: { 0: { v: '北部' }, 1: { v: 'ソフトウェア' }, 2: { v: '分析ツール' }, 3: { v: '第 1 四半期' }, 4: { v: 1600, t: CellValueType.NUMBER }, 5: { v: 16, t: CellValueType.NUMBER }, }, 6: { 0: { v: '南部' }, 1: { v: 'ハードウェア' }, 2: { v: 'マウス' }, 3: { v: '第 2 四半期' }, 4: { v: 480, t: CellValueType.NUMBER }, 5: { v: 24, t: CellValueType.NUMBER }, }, }, }, }) const unitId = fWorkbook.getId() const subUnitId = fWorksheet.getSheetId() const sourceInfo = { unitId, subUnitId, sheetName: fWorksheet.getSheetName(), range: fWorksheet.getRange('A1:F7').getRange(), } const anchorCellInfo = { unitId, subUnitId, row: 0, col: 7, } const fPivotTable = await fWorkbook.addPivotTable( sourceInfo, univerAPI.Enum.PositionTypeEnum.Existing, anchorCellInfo, ) if (!fPivotTable) return await fPivotTable.addField(0, univerAPI.Enum.PivotTableFiledAreaEnum.Row, 0) await fPivotTable.addField(3, univerAPI.Enum.PivotTableFiledAreaEnum.Column, 0) await fPivotTable.addField(4, univerAPI.Enum.PivotTableFiledAreaEnum.Value, 0) await fPivotTable.addField(5, univerAPI.Enum.PivotTableFiledAreaEnum.Value, 1) await fPivotTable.setOptions({ repeatRowLabels: true, showRowGrandTotal: true, showColGrandTotal: true, }) const rowFieldIds = fPivotTable.getFieldIdsByArea(univerAPI.Enum.PivotTableFiledAreaEnum.Row) if (rowFieldIds[0]) { await fPivotTable.setLabelSort(rowFieldIds[0], { type: univerAPI.Enum.PivotDataFieldSortOperatorEnum.ascending, }) await fPivotTable.setLabelManualFilter(rowFieldIds[0], ['東部', '北部', '西部']) } const valueFieldIds = fPivotTable.getFieldIdsByArea(univerAPI.Enum.PivotTableFiledAreaEnum.Value) if (valueFieldIds[0]) { await fPivotTable.setFieldSetting(valueFieldIds[0], { displayName: '売上合計', format: '$#,##0', subtotalType: univerAPI.Enum.PivotSubtotalTypeEnum.sum, }) } if (valueFieldIds[1]) { await fPivotTable.setSubtotalType(valueFieldIds[1], univerAPI.Enum.PivotSubtotalTypeEnum.average) } if (rowFieldIds[0] && valueFieldIds[0]) { await fPivotTable.setValueFilter(rowFieldIds[0], { operator: univerAPI.Enum.PivotFilterOperatorEnum.valueGreaterThan, expected: 700, valueFieldId: valueFieldIds[0], }) } if (valueFieldIds.length > 1) { await fPivotTable.updateValuePosition(univerAPI.Enum.PivotTableValuePositionEnum.Column, 0) } const pivotTableId = fPivotTable.getId() const pivotTableById = fWorkbook.getPivotTableById(pivotTableId) const pivotTableByCell = fWorksheet.getPivotTableByCell(anchorCellInfo.row, anchorCellInfo.col) const sourceFieldsInfo = fPivotTable.getSourceFieldsInfo() const pivotTableRangeInfo = fPivotTable.getPivotTableRangeInfo() console.warn( `ピボットテーブルを作成:${pivotTableId}、ID で取得:${Boolean(pivotTableById)}、セルで取得:${Boolean(pivotTableByCell)}、ソースフィールド数:${sourceFieldsInfo.length}、範囲数:${pivotTableRangeInfo?.length ?? 0}`, )}function TestSheetsSparklineFacadeAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('スパークラインシート', { sheet: { rowCount: 20, columnCount: 8, cellData: { 0: { 0: { v: 12, t: CellValueType.NUMBER }, 1: { v: 18, t: CellValueType.NUMBER }, 2: { v: 9, t: CellValueType.NUMBER }, 3: { v: 24, t: CellValueType.NUMBER }, 4: { v: 16, t: CellValueType.NUMBER }, }, 1: { 0: { v: 8, t: CellValueType.NUMBER }, 1: { v: 13, t: CellValueType.NUMBER }, 2: { v: 21, t: CellValueType.NUMBER }, 3: { v: 14, t: CellValueType.NUMBER }, 4: { v: 28, t: CellValueType.NUMBER }, }, 2: { 0: { v: 5, t: CellValueType.NUMBER }, 1: { v: 9, t: CellValueType.NUMBER }, 2: { v: 7, t: CellValueType.NUMBER }, 3: { v: 15, t: CellValueType.NUMBER }, 4: { v: 11, t: CellValueType.NUMBER }, }, 3: { 0: { v: 20, t: CellValueType.NUMBER }, 1: { v: 12, t: CellValueType.NUMBER }, 2: { v: 30, t: CellValueType.NUMBER }, 3: { v: 18, t: CellValueType.NUMBER }, 4: { v: 25, t: CellValueType.NUMBER }, }, }, }, }) const firstSparkline = fWorksheet.addSparkline( [fWorksheet.getRange('A1:E1').getRange()], [fWorksheet.getRange('F1').getRange()], univerAPI.Enum.SparklineTypeEnum.LINE_CHART, ) const secondSparkline = fWorksheet.addSparkline( [fWorksheet.getRange('A2:E2').getRange()], [fWorksheet.getRange('F2').getRange()], univerAPI.Enum.SparklineTypeEnum.LINE_CHART, ) fWorksheet.composeSparkline([fWorksheet.getRange('F1:F2').getRange()]) const sparklineGroup = fWorksheet.getSparklineGroupByCell(0, 5) sparklineGroup?.setConfig({ type: univerAPI.Enum.SparklineTypeEnum.BAR_CHART, }) sparklineGroup?.changeDataSource( [fWorksheet.getRange('A3:E3').getRange(), fWorksheet.getRange('A4:E4').getRange()], [fWorksheet.getRange('G1').getRange(), fWorksheet.getRange('G2').getRange()], ) fWorksheet.unComposeSparkline([fWorksheet.getRange('G1:G2').getRange()]) fWorksheet.getSparklineByCell(0, 6)?.removeSparkline()}async function TestSheetsOutlineAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('グループ化シート', { sheet: { rowCount: 20, columnCount: 8, }, }) // 行 2~6 と行 3~4 にグループを追加します(インデックスは 0 始まり)。 fWorksheet.addRowOutline(1, 5).addRowOutline(2, 2) // 列 B~E と列 C~D にグループを追加します(インデックスは 0 始まり)。 fWorksheet.addColumnOutline(1, 2) const rowOutlines = fWorksheet.getDimensionOutlines(univerAPI.Enum.DimensionOutlineAxis.ROW) fWorksheet.setDimensionOutlineCollapsed(rowOutlines[0].id, true) await new Promise((resolve) => setTimeout(resolve, 2000)) fWorksheet.removeDimensionOutline(rowOutlines[1].id) fWorksheet.clearDimensionOutlines(univerAPI.Enum.DimensionOutlineAxis.COLUMN, 1, 3)}async function TestSheetsDrawingAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('描画シート', { sheet: { rowCount: 20, columnCount: 8, }, }) const images = fWorksheet.getImages() console.warn(`現在のシートの画像数:${images.length}。`) const activeImages = fWorksheet.getActiveImages() console.warn(`現在のシートの選択中の画像数:${activeImages.length}。`) const image = await fWorksheet .newOverGridImage() .setSource('https://avatars.githubusercontent.com/u/61444807?s=48&v=4', univerAPI.Enum.ImageSourceType.URL) .setColumn(5) .setRow(5) .setWidth(500) .setHeight(300) .buildAsync() fWorksheet.insertImages([image]) // 画像の幅を 100px、高さを 50px に更新します await new Promise((resolve) => setTimeout(resolve, 2000)) const insertedImage = fWorksheet.getImageById(image.drawingId)! const newImage = await insertedImage.toBuilder().setWidth(100).setHeight(50).buildAsync() fWorksheet.updateImages([newImage]) // 2 秒後に画像を削除します // await new Promise((resolve) => setTimeout(resolve, 2000)); // fWorksheet.deleteImages([insertedImage]);}async function TestSheetsChartFacadeAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('グラフシート', { sheet: { rowCount: 20, columnCount: 8, cellData: { 0: { 0: { v: 10, t: CellValueType.NUMBER }, 1: { v: 20, t: CellValueType.NUMBER }, 2: { v: 30, t: CellValueType.NUMBER }, 3: { v: 40, t: CellValueType.NUMBER }, }, 1: { 0: { v: 15, t: CellValueType.NUMBER }, 1: { v: 25, t: CellValueType.NUMBER }, 2: { v: 35, t: CellValueType.NUMBER }, 3: { v: 45, t: CellValueType.NUMBER }, }, 2: { 0: { v: 12, t: CellValueType.NUMBER }, 1: { v: 22, t: CellValueType.NUMBER }, 2: { v: 32, t: CellValueType.NUMBER }, 3: { v: 42, t: CellValueType.NUMBER }, }, 3: { 0: { v: 18, t: CellValueType.NUMBER }, 1: { v: 28, t: CellValueType.NUMBER }, 2: { v: 38, t: CellValueType.NUMBER }, 3: { v: 48, t: CellValueType.NUMBER }, }, }, }, }) // A1:D4 をデータソースにして縦棒グラフを作成します。 const chartInfo = fWorksheet .newChart(univerAPI.Enum.ChartTypeString.Column) .setSource({ range: 'A1:D4', orientation: univerAPI.Enum.ChartSourceOrientation.Columns }) .setPosition('B2') .setSize(500, 320) .setTitle({ text: 'Node グラフ Facade 縦棒グラフ', position: univerAPI.Enum.ChartTitlePositionEnum.Top, }) .setLegend({ position: univerAPI.Enum.ChartLegendPositionEnum.Bottom, selectMode: univerAPI.Enum.ChartSelectModeEnum.Multiple, }) .setAppearance({ background: { color: '#FFFFFF' }, border: { color: '#D1D5DB' }, }) .setXAxisTitle('四半期') .setYAxisTitle('値') .setAxisPointer({ indicatorLineType: ChartBorderDashType.Dashed, indicatorLineColor: '#1D4ED8', indicatorLabelColor: '#DBEAFE', indicatorLabelTextColor: '#1E3A8A', }) .setAllSeriesStyle({ border: { color: '#1D4ED8', opacity: 0.7, width: 1, dashType: ChartBorderDashType.Solid, }, }) .build() const columnChart = await fWorksheet.insertChart(chartInfo) const updatedColumnConfig = columnChart .toBuilder(univerAPI.Enum.ChartTypeString.Column) .setTitle('Node グラフ Facade 更新後の縦棒グラフ') .setLegend({ position: univerAPI.Enum.ChartLegendPositionEnum.Right }) .setSeries(0, { color: '#DC2626' }) .build() await columnChart.update(updatedColumnConfig) await columnChart.setDataSource({ range: 'A2:D4', orientation: univerAPI.Enum.ChartSourceOrientation.Rows }) const lineChartInfo = fWorksheet .newChart(univerAPI.Enum.ChartTypeString.Line) .setSource('A1:D4') .setAbsolutePosition(560, 120) .setSize(520, 320) .setTitle('Node グラフ Facade 折れ線グラフ') .setLegend({ position: univerAPI.Enum.ChartLegendPositionEnum.Top }) .setAllSeriesStyle({ point: { shape: univerAPI.Enum.ChartLinePointShape.Circle, color: '#16A34A', size: 8, }, }) .setXAxis({ title: '期間', titleStyle: { color: '#374151', fontSize: 12 }, }) .setYAxis({ title: '金額', titleStyle: { color: '#374151', fontSize: 12 }, }) .build() await fWorksheet.insertChart(lineChartInfo) const pieChartInfo = fWorksheet .newChart(univerAPI.Enum.ChartTypeString.Pie) .setSource('A1:D2') .setAbsolutePosition(560, 480) .setSize(420, 300) .setTitle('Node グラフ Facade 円グラフ') .setLegend({ position: univerAPI.Enum.ChartLegendPositionEnum.Left }) .setDoughnutHole(0.45) .setSliceBorderColor('#111827') .setPaddingAngleEnabled(true) .setRosePie(true) .setLabelLineVisible(true) .build() const pieChart = await fWorksheet.insertChart(pieChartInfo) await pieChart.remove()}async function TestSheetsShapeFacadeAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('図形シート', { sheet: { rowCount: 20, columnCount: 8, }, }) // 四角形を作成し、セル B2 に固定します。 const rectShape = fWorksheet.insertShape({ shapeType: univerAPI.Enum.ShapeTypeEnum.Rect, }) as FSheetShape | null if (!rectShape) throw new Error('四角形を挿入できませんでした。') rectShape .setPosition(1, 1, 0, 0) .setSize(220, 120) .setSolidFill('#DBEAFE', 0.9) .setStrokeLineType(univerAPI.Enum.ShapeLineTypeEnum.SolidLine) .setStrokeColor('#1D4ED8') .setStrokeWidth(2) .setStrokeLineDashType(univerAPI.Enum.ShapeLineDashEnum.Solid) .setStrokeLineJoinType(univerAPI.Enum.ShapeLineJoinEnum.Round) .setStrokeLineCapType(univerAPI.Enum.ShapeLineCapEnum.Round) .setStrokeOpacity(0.9) rectShape .getText() .setText('シート図形 Facade のサンプル') .setHorizontalAlign(univerAPI.Enum.HorizontalAlign.CENTER) .setVerticalAlign(univerAPI.Enum.VerticalAlign.MIDDLE) // 絶対ピクセル座標で、グラデーション塗りつぶしの楕円を作成します。 const ellipseShape = fWorksheet.insertShape({ shapeType: univerAPI.Enum.ShapeTypeEnum.Ellipse, }) as FSheetShape | null if (!ellipseShape) throw new Error('楕円を挿入できませんでした。') ellipseShape .setAbsolutePosition(420, 120) .setSize(180, 120) .setGradientFill( univerAPI.Enum.ShapeGradientTypeEnum.Linear, [ { position: 0, color: '#DCFCE7' }, { position: 1, color: '#22C55E' }, ], 0, ) .setStrokeColor('#15803D') .setStrokeWidth(2) // 四角形と楕円を結ぶコネクタを作成します。 const connectorShape = fWorksheet.insertShape({ shapeType: univerAPI.Enum.ShapeTypeEnum.BentConnector3, }) if (!connectorShape || !connectorShape.isConnectorShape()) throw new Error('コネクタを挿入できませんでした。') ;(connectorShape as FConnectorShape) .setAbsolutePosition(250, 155) .setSize(220, 80) .setStrokeColor('#374151') .setStrokeWidth(3) .setStrokeLineDashType(univerAPI.Enum.ShapeLineDashEnum.Dash) .setStartArrow(univerAPI.Enum.ShapeArrowTypeEnum.OvalArrow, univerAPI.Enum.ShapeArrowSizeEnum.Small) .setEndArrow(univerAPI.Enum.ShapeArrowTypeEnum.Arrow, univerAPI.Enum.ShapeArrowSizeEnum.Large) // 一時的な図形を作成し、Facade の削除 API を示します。 const tempShape = fWorksheet.insertShape({ shapeType: univerAPI.Enum.ShapeTypeEnum.Diamond, }) if (!tempShape) throw new Error('一時的な図形を挿入できませんでした。') tempShape.setAbsolutePosition(120, 320).setSize(120, 120).setNoneFill().setStrokeColor('#DC2626').setStrokeWidth(2) const shapes = fWorksheet.getShapes() console.warn('シートの図形数:', shapes.length) console.warn('四角形の検索結果:', fWorksheet.getShape(rectShape.getId())?.getId()) rectShape .setAbsolutePosition(120, 140) .setSize(260, 140) .setSolidFill('#FEE2E2', 0.85) .setStrokeColor('#B91C1C') .setStrokeWidth(4) const rectSites = rectShape.getConnectionSites() const ellipseSites = ellipseShape.getConnectionSites() ;(connectorShape as FConnectorShape) .bindStart(rectShape.getId(), rectSites[0]?.index ?? 0) .bindEnd(ellipseShape.getId(), ellipseSites[2]?.index ?? ellipseSites[0]?.index ?? 0) tempShape.remove()}async function TestSheetsHyperlinkAPI(univerAPI: FUniver) { const fWorkbook = univerAPI.getActiveWorkbook() if (!fWorkbook) return const fWorksheet = fWorkbook.insertSheet('ハイパーリンクシート', { sheet: { rowCount: 20, columnCount: 8, }, }) // セル A1 に Univer へのハイパーリンクを作成します const fRange = fWorksheet.getRange('A1') await fRange.setHyperLink('https://univer.ai/', 'Univer') // A2 にアクティブシートの B2:D4 へのハイパーリンクを作成します const fRange2 = fWorksheet.getRange('A2') const rangeUrl = fWorksheet.getRange('B2:D4').getUrl() await fRange2.setHyperLink(rangeUrl, 'B2:D4 へのリンク') // A3 に別のシートへのハイパーリンクを作成します const sheets = fWorkbook.getSheets() if (sheets.length > 2) { const anotherSheetUrl = sheets[1].getUrl() const fRange3 = fWorksheet.getRange('A3') await fRange3.setHyperLink(anotherSheetUrl, `${sheets[1].getSheetName()} へのリンク`) } // A4 に定義名へのハイパーリンクを作成します const fRange4 = fWorksheet.getRange('A4') const definedNameHyperlinkUrl = fWorkbook.getUrlOfDefineName('MyDefinedName') await fRange4.setHyperLink(definedNameHyperlinkUrl, 'MyDefinedName へのリンク') // A1 のハイパーリンクを Google に変更します await fRange.updateHyperLink('https://www.google.com/', 'Google') // A2 のハイパーリンクを削除します const hyperlinks = fRange2.getHyperLinks() if (hyperlinks.length > 0) { await fRange2.cancelHyperLink(hyperlinks[0]) } // 2 秒後に A1 のすべてのハイパーリンクを削除します await new Promise((resolve) => setTimeout(resolve, 2000)) fRange.cancelHyperLink()}run().catch((error: unknown) => { console.error(error) process.exitCode = 1})計算 worker を有効にする
createUniverOnNode() の既定値は useComputingWorker: false で、現在のプロセスで数式を計算します。上記の main.ts は useComputingWorker: true を明示的に渡し、計算 worker を有効にしています。
- メインプロセスの
UniverProFormulaEnginePlugin、UniverSheetsPlugin、UniverSheetsFormulaPlugin、UniverSheetsPivotTablePluginにすべてnotExecuteFormula: trueを設定し、RPC を通じて子プロセスに計算を委譲します。 UniverRPCNodeMainPluginがworkerSrcで指定されたビルド済みのworker.jsを起動します。- 子プロセスは
UniverRPCNodeWorkerPluginとUniverRemoteSheetsFormulaPluginを通じてデータを受信し、計算します。
UniverSheetsPivotTablePlugin の設定には 3 つのモードがあります。worker を使うメインプロセスでは true、worker 内では false を設定します。worker を使わない場合は省略するか undefined を指定し、現在のプロセスにピボットテーブルのサービスと計算処理の両方を登録します。そのため、メインプロセスでは useComputingWorker を直接渡さず、useComputingWorker ? true : undefined を使用します。
この worker は Node.js の child_process.fork() で起動します。プロジェクトで使用しているビルドツールで main.ts と worker.ts を別々のエントリーポイントとしてビルドし、workerSrc をビルド後の worker ファイルに設定してください。サンプルでは worker.js がメインエントリーの出力と同じディレクトリにあることを想定しています。パス、拡張子、モジュール形式は実際の出力に合わせてください。ファイルの場所には CommonJS の __dirname を使っているため、ESM プロジェクトでは対応するパス解決方法に変更してください。
実行時の注意点
loadSheetAsync() はサーバー上の既存ドキュメントを読み込むため、createWorkbook() を呼び出す必要はありません。共同編集サービスは customHeaders の Cookie などの認証情報からユーザーを識別し、権限を確認します。接続の memberID はサーバーから返されます。
この例ではブラウザのオフラインキャッシュと複数タブ間のインスタンスロックを無効にしているため、アカウントごとのキャッシュ分離を目的とした setCurrentUser() の呼び出しは不要です。後からコメントなどローカルの現在のユーザーに依存する機能を使用する場合は、UserManagerService で業務ユーザーを設定できます。これはサーバー認証の代わりにはなりません。
サンプルは実行後も共同編集接続を維持します。ブラウザで同じドキュメントを開くと変更を確認できます。flush() はキュー内の変更に対するサーバーの確認を待ちます。単発のスクリプトにする場合は、すべての非同期処理と同期が完了してから univer.dispose() でインスタンスを解放してください。
このガイドはいかがですか?