Colaboración
Sigue la Integración colaborativa para ejecutar el ejemplo, conectar tu aplicación y verificar persistencia y permisos. Los detalles y ejemplos Web/Server están en Inicio rápido de colaboración.
Crea el Unit mediante una API de negocio autenticada que invoque Collaboration Service y carga el ID devuelto en el navegador. Configura HTTP, Session Ticket y WebSocket según Quick Start, persiste con un Database Adapter y protege lectura, JOIN y edición mediante permisos del servidor.
Antes de usar estas API, verifica que el backend admita Unit PDF. Un ejemplo funcional de colaboración con Sheets no demuestra compatibilidad con PDF.
Cargar un PDF colaborativo
Después de crear una unidad PDF mediante la API del servidor o importPdfToUnitIdAsync, cárguela con la Facade de colaboración:
Establece el usuario actual antes de cargar el documento. Consulta Integración de colaboración.
import { UserManagerService } from '@univerjs/core'import '@univerjs-pro/collaboration-client/facade'univer.__getInjector().get(UserManagerService).setCurrentUser({ userID, name, avatar })const pdf = await univerAPI.getCollaboration().loadPdfAsync(unitId)if (!pdf) { throw new Error(`PDF unit not found: ${unitId}`)}console.log(pdf.getId(), pdf.getName())loadPdfAsync carga la instantánea del servidor, prepara la sesión de colaboración y devuelve un FPdf. Devuelve null cuando la unidad no puede asociarse con un PDF.
Usa estado, reenvíos y recuperación de conflictos para indicar el guardado y proteger cambios sin confirmar antes de recargar.
Eliminación de la carga integrada por URL de documentos colaborativos
Nota
A partir de la versión 1.2.0, Univer ha eliminado la carga integrada por URL de documentos colaborativos. Ya no lee automáticamente los parámetros unit y type de la URL para cargar un documento colaborativo.
Si tu aplicación depende de este comportamiento, puedes recuperarlo mediante un plugin personalizado.
Integrar el plugin personalizado
Copia los cuatro archivos siguientes en tu proyecto con esta estructura de directorios:
custom-url-load/├── plugin.ts├── controllers/│ └── data-loader.controller.ts└── services/ ├── url.service.ts └── web-url.service.tsDespués de inicializar el editor y los plugins de colaboración, establece el usuario actual antes de registrar CustomUrlLoadPlugin.
import { UserManagerService } from '@univerjs/core'import { CustomUrlLoadPlugin } from './custom-url-load/plugin'univer.__getInjector().get(UserManagerService).setCurrentUser({ userID, name, avatar })univer.registerPlugin(CustomUrlLoadPlugin)Una vez registrado, el plugin carga el documento, por lo que no necesitas llamar a loadPdfAsync() para el mismo documento. Si tu aplicación ya carga documentos mediante la Facade, puedes mantener el enfoque de la sección anterior y gestionar las URL según las necesidades de tu aplicación.
Código completo del plugin
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'/** * Carga un documento colaborativo desde los parámetros de la 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()) } }}¿Cómo es esta guía?