Совместное редактирование
Несколько пользователей могут редактировать одну доску и видеть изменения друг друга. Выполните интеграцию совместного редактирования, подключив серверную часть, аутентификацию, права доступа и хранилище, затем настройте лицензию.
Настройка и загрузка документа
Добавьте настройки клиента Collaboration в существующий редактор Boards по инструкции Быстрый старт совместного редактирования. Пример использует Sheets; ваш сервер должен создать и сохранить документ Boards, а затем вернуть его ID браузеру.
Задайте текущего пользователя до загрузки документа. См. раздел Интеграция совместного редактирования.
import '@univerjs-pro/boards/facade'import { UserManagerService } from '@univerjs/core'import '@univerjs-pro/collaboration-client/facade'univer.__getInjector().get(UserManagerService).setCurrentUser({ userID, name, avatar })const document = await univerAPI.getCollaboration().loadBoardAsync('your-document-id')if (!document) throw new Error('Document could not be loaded')При повторном открытии или публикации ссылки на документ используйте тот же ID. Не заменяйте загрузку серверного документа созданием пустого локального документа.
Используйте состояние, повторные отправки и восстановление конфликтов для индикации сохранения и защиты неподтверждённых изменений перед перезагрузкой.
Удаление встроенной загрузки совместных документов по URL
Обратите внимание
Начиная с версии 1.2.0, Univer больше не содержит встроенной логики загрузки совместных документов по URL. Параметры unit и type больше не считываются автоматически для загрузки совместного документа.
Если ваше приложение зависит от этого поведения, его можно восстановить с помощью собственного плагина.
Подключение собственного плагина
Скопируйте четыре файла ниже в проект, сохранив следующую структуру каталогов:
custom-url-load/├── plugin.ts├── controllers/│ └── data-loader.controller.ts└── services/ ├── url.service.ts └── web-url.service.tsПосле инициализации редактора и плагинов совместной работы задайте текущего пользователя, а затем зарегистрируйте CustomUrlLoadPlugin.
import { UserManagerService } from '@univerjs/core'import { CustomUrlLoadPlugin } from './custom-url-load/plugin'univer.__getInjector().get(UserManagerService).setCurrentUser({ userID, name, avatar })univer.registerPlugin(CustomUrlLoadPlugin)После регистрации плагин сам загружает документ, поэтому вызывать loadBoardAsync() для того же документа не требуется. Если приложение уже загружает документы через Facade, можно сохранить подход из предыдущего раздела и обрабатывать URL в соответствии с требованиями приложения.
Полный код плагина
import { UniverCollaborationClientPlugin } from '@univerjs-pro/collaboration-client'import { DependentOn, Inject, Injector, Plugin } from '@univerjs/core'import { CollaborationDataLoaderController } from './controllers/data-loader.controller'import { IURLService } from './services/url.service'import { WebURLService } from './services/web-url.service'@DependentOn(UniverCollaborationClientPlugin)export class CustomUrlLoadPlugin extends Plugin { static override pluginName = 'CUSTOM_URL_LOAD_PLUGIN' constructor( _config: undefined, @Inject(Injector) protected override readonly _injector: Injector ) { super() } override onStarting(): void { this._injector.add([IURLService, { useClass: WebURLService }]) this._injector.add([CollaborationDataLoaderController]) this.disposeWithMe(this._injector.get(IURLService)) this.disposeWithMe(this._injector.get(CollaborationDataLoaderController)) }}import { DataLoaderService } from '@univerjs-pro/collaboration-client'import { ILogService, Inject, RxDisposable } from '@univerjs/core'import { IURLService } from '../services/url.service'/** * Загружает совместный документ по параметрам URL. */export class CollaborationDataLoaderController extends RxDisposable { constructor( @IURLService private readonly _urlService: IURLService, @ILogService private readonly _logService: ILogService, @Inject(DataLoaderService) private readonly _dataLoaderService: DataLoaderService ) { super() const unitId = this._urlService.getParam('unit') const type = this._urlService.getParam('type') if (!unitId || !type) { return } this._dataLoaderService.loadUnit(unitId, Number(type)) .catch((error: unknown) => { this._logService.error('[CollaborationDataLoaderController]', error) }) }}import type { IDisposable } from '@univerjs/core'import type { Observable } from 'rxjs'import { createIdentifier } from '@univerjs/core'export interface IURLService extends IDisposable { getParam: (key: string) => string | undefined setParam: (key: string, value: string, useReplace?: boolean) => void removeParam: (key: string, useReplace?: boolean) => void readonly urlChange$: Observable<void>}export const IURLService = createIdentifier<IURLService>('demo.custom-url-load.url-service')import type { IURLService } from './url.service'import { RxDisposable } from '@univerjs/core'import { fromEvent, map, shareReplay, takeUntil } from 'rxjs'export class WebURLService extends RxDisposable implements IURLService { readonly urlChange$ = fromEvent(window, 'popstate').pipe( map(() => undefined), takeUntil(this.dispose$), shareReplay({ bufferSize: 1, refCount: true }) ) getParam(key: string): string | undefined { return new URL(window.location.href).searchParams.get(key) ?? undefined } setParam(key: string, value: string, useReplace = false): void { const url = new URL(window.location.href) url.searchParams.set(key, value) this._updateUrl(url, useReplace) } removeParam(key: string, useReplace = false): void { const url = new URL(window.location.href) url.searchParams.delete(key) this._updateUrl(url, useReplace) } private _updateUrl(url: URL, useReplace: boolean): void { if (useReplace) { window.history.replaceState(window.history.state, '', url.toString()) } else { window.history.pushState(window.history.state, '', url.toString()) } }}Проверка синхронизации и прав доступа
Откройте один ID в двух независимых сеансах браузера. Измените текст или переместите элемент в одном сеансе и проверьте второй. Убедитесь, что содержимое сохраняется после перезагрузки страницы, повторного подключения и перезапуска сервиса. С помощью учётных записей читателя и пользователя без доступа проверьте, что сервер отклоняет запрещённые операции чтения и записи.
История изменений и импорт и экспорт требуют отдельной настройки: совместное редактирование не включает их автоматически.
Насколько полезно это руководство?