Headless Univer
Headless Univer — среда выполнения Univer для работы с содержимым без интерактивного интерфейса редактора. Она использует общие с браузерным редактором модели, команды, вычисления и Facade API. Обработка возможна в Node.js и в браузере без UI. Доступные возможности определяются зарегистрированными плагинами продуктов и функций.
В Node.js можно создавать и загружать книги, менять ячейки, форматы, строки и столбцы, вычислять формулы и сохранять снимки Univer. Фильтры, сортировка, проверка данных, сводные таблицы и диаграммы требуют своих плагинов содержимого. Изменение данных диаграммы не означает её отрисовку.
Пример использует режим плагинов. @univerjs/core и плагины содержимого продукта входят в Headless; @univerjs/ui, пакеты продукта *-ui и стандартные браузерные пресеты сюда не входят. @univerjs/engine-render остаётся общей зависимостью без создания Canvas и монтирования редактора. См. границы пакетов Headless.
Локальные снимки не требуют сервиса совместной работы. Совместный пример дополнительно требует клиента, сервера и аутентификации. Для конвертации Office и снимков экрана нужны соответственно Server SDK и среда рендеринга. Импорт /facade сам по себе не регистрирует плагины и не превращает UI API в Headless API.
Установка
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 SheetsRuRU from '@univerjs/sheets/locale/ru-RU'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.RU_RU, locales: { [LocaleType.RU_RU]: mergeLocales( SheetsRuRU, ), },})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 может работать как клиент совместного редактирования: загружать существующую книгу с сервера, изменять её содержимое через API Facade и синхронизировать изменения с другими клиентами, открывшими тот же документ. Перед запуском подготовьте сервис совместного редактирования, клиентскую лицензию и 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'// Используемые здесь API Facade входят в 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.RU_RU, 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.RU_RU,})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 строки после третьей строки (индексация с 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]) // Меняем ширину изображения на 100 px, а высоту на 50 px 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: 'Столбчатая диаграмма Facade в Node', 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('Обновлённая столбчатая диаграмма Facade в Node') .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('Линейная диаграмма Facade в Node') .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('Круговая диаграмма Facade в Node') .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) // Создаём временную фигуру для демонстрации API удаления Facade. 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, }, }) // Создаём гиперссылку на Univer в ячейке A1 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]) } // Удаляем все гиперссылки в A1 через 2 секунды 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запускает собранный файлworker.js, указанный вworkerSrc.- Дочерний процесс получает данные и выполняет вычисления через
UniverRPCNodeWorkerPluginиUniverRemoteSheetsFormulaPlugin.
UniverSheetsPivotTablePlugin различает три режима: true в основном процессе при использовании worker, false в worker и отсутствие параметра или значение undefined без worker, чтобы зарегистрировать и сервисы сводных таблиц, и вычислительную логику в текущем процессе. Поэтому основной процесс использует useComputingWorker ? true : undefined, а не передаёт useComputingWorker напрямую.
Node.js запускает этот worker через child_process.fork(). Используйте инструмент сборки проекта, чтобы собрать main.ts и worker.ts как отдельные точки входа, и задайте в workerSrc путь к собранному файлу worker. В примере предполагается, что worker.js расположен в одном каталоге с выходным файлом основной точки входа; путь, расширение и формат модулей должны соответствовать результату сборки. Для поиска файла используется __dirname из CommonJS; в проектах ESM нужен соответствующий способ разрешения пути.
Примечания по запуску
loadSheetAsync() загружает существующий документ с сервера, поэтому вызывать createWorkbook() не требуется. Сервис совместного редактирования определяет пользователя и проверяет права по данным аутентификации, например Cookie в customHeaders. Сервер возвращает memberID соединения.
В примере отключены офлайн-кеш браузера и блокировка экземпляра между вкладками, поэтому вызывать setCurrentUser() для разделения кеша по учётным записям не нужно. Если позже понадобятся функции, зависящие от локального текущего пользователя, например комментарии, его можно задать через UserManagerService. Это не заменяет серверную аутентификацию.
После выполнения пример сохраняет соединение совместного редактирования. Откройте тот же документ в браузере, чтобы увидеть изменения. flush() ожидает подтверждения сервера для изменений в очереди. Для одноразового скрипта вызовите univer.dispose() после завершения всех асинхронных операций и синхронизации.
Насколько полезно это руководство?