HarmonyOS分布式办公开发实战

当你在手机上起草了一份方案,走到办公室想用电脑继续编辑;或者在家里的平板上写了一半的报告,想在书房的电脑上完成——这种跨设备无缝衔接的办公体验,正是鸿蒙分布式能力的精髓所在。

一、背景

1.1 传统办公的痛点

说实话,传统的跨设备办公体验真的很糟糕。你有没有经历过这样的场景:

  • 手机上写的文档,想传到电脑上继续编辑,得通过微信文件传输助手或者发邮件给自己
  • 平板上编辑的表格,想用电脑打开,发现格式全乱了
  • 不同设备上的文档版本不一致,最后搞不清哪个是最新的
  • 每次切换设备都要重新找文件、重新打开、重新定位到上次编辑的位置

这些问题看似琐碎,但累积起来真的很影响工作效率。特别是在移动办公越来越普及的今天,我们可能随时随地需要处理文档——地铁上用手机改个PPT,到公司用电脑完善,回家用平板做最后的润色。

1.2 鸿蒙分布式办公的愿景

鸿蒙的分布式能力给办公场景带来了全新的可能:

设备无界,文档有界——你的文档不再被"困"在某个设备上,而是可以像水一样流动,在任何设备上无缝衔接。

状态同步,体验一致——不只是文档内容,连光标位置、选中状态、编辑历史都能跨设备同步。

就近发现,即插即用——不需要复杂的配置,设备靠近就能发现,一键流转就能继续工作。

鸿蒙分布式

自动同步

实时流转

状态同步

手机编辑

电脑无缝衔接

平板协同

传统方式

微信传输

手动打开

再次传输

手机编辑

电脑接收

继续编辑

平板查看

1.3 核心价值

分布式办公场景带来的价值是实实在在的:

  1. 效率提升30%以上:不再需要手动传输文件、转换格式
  2. 零版本冲突:分布式数据管理保证数据一致性
  3. 无缝体验:设备切换时无需任何额外操作
  4. 降低认知负担:不用记住"哪个文件在哪个设备上"

二、核心原理

2.1 分布式数据管理

鸿蒙的分布式数据管理是整个办公场景的基石。它基于分布式数据库(Distributed Data)实现,核心机制包括:

数据同步模型

  • 强一致性同步:适合文档内容等关键数据,确保所有设备看到的数据完全一致
  • 最终一致性同步:适合光标位置、滚动状态等非关键数据,优先保证性能
  • 手动同步:用户主动触发,适合大文件传输场景

数据版本管理
每个数据项都有版本号和设备标识,通过向量时钟算法解决冲突:

数据项 = {
    key: "document_content",
    value: "文档内容...",
    version: 3,
    deviceId: "device_001",
    timestamp: 1703836800000
}

2.2 分布式设备管理

设备发现和连接是分布式办公的前提:

发现机制

  • 基于CoAP协议的mDNS服务发现
  • 支持软AP、蓝牙、Wi-Fi P2P等多种连接方式
  • 自动选择最优链路(根据距离、带宽、延迟)

设备状态管理

interface DeviceState {
    deviceId: string;           // 设备唯一标识
    deviceName: string;         // 设备名称
    deviceType: DeviceType;     // 设备类型:手机、平板、电脑等
    online: boolean;            // 是否在线
    capability: string[];       // 设备能力:屏幕、键盘、存储等
    batteryLevel: number;       // 电量
    networkQuality: number;     // 网络质量评分
}

2.3 分布式能力流转

流转是分布式办公的核心交互方式:

渲染错误: Mermaid 渲染失败: Parse error on line 18: ...lassDef primary fill:#e1f5fe,stroke:#015 -----------------------^ Expecting '()', 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'SOLID_ARROW_TOP', 'SOLID_ARROW_BOTTOM', 'STICK_ARROW_TOP', 'STICK_ARROW_BOTTOM', 'SOLID_ARROW_TOP_DOTTED', 'SOLID_ARROW_BOTTOM_DOTTED', 'STICK_ARROW_TOP_DOTTED', 'STICK_ARROW_BOTTOM_DOTTED', 'SOLID_ARROW_TOP_REVERSE', 'SOLID_ARROW_BOTTOM_REVERSE', 'STICK_ARROW_TOP_REVERSE', 'STICK_ARROW_BOTTOM_REVERSE', 'SOLID_ARROW_TOP_REVERSE_DOTTED', 'SOLID_ARROW_BOTTOM_REVERSE_DOTTED', 'STICK_ARROW_TOP_REVERSE_DOTTED', 'STICK_ARROW_BOTTOM_REVERSE_DOTTED', 'BIDIRECTIONAL_SOLID_ARROW', 'DOTTED_ARROW', 'BIDIRECTIONAL_DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT'

2.4 文档状态同步机制

文档编辑的状态同步需要精心设计:

需要同步的状态

  1. 文档内容(强一致性)
  2. 光标位置(最终一致性)
  3. 选中文本范围(最终一致性)
  4. 滚动位置(最终一致性)
  5. 编辑历史/撤销栈(强一致性)
  6. 协同编辑者列表(实时同步)

同步策略

  • 小改动(单个字符输入):实时同步
  • 大改动(粘贴大段文本):批量同步,显示同步进度
  • 冲突处理:基于操作转换(OT)算法,自动合并并发编辑

三、代码实战

3.1 分布式文档编辑器基础框架

首先搭建一个支持分布式同步的文档编辑器基础框架:

// DistributedDocumentEditor.ets
import distributedData from '@ohos.data.distributedData';
import deviceManager from '@ohos.distributedDeviceManager';
import { KvStore, KvStoreResultSet, Options } from '@ohos.data.distributedData';

// 文档数据结构定义
interface DocumentData {
    docId: string;                  // 文档唯一标识
    title: string;                  // 文档标题
    content: string;                // 文档内容(富文本格式)
    cursorPosition: number;         // 光标位置
    selectionStart: number;         // 选中起始位置
    selectionEnd: number;           // 选中结束位置
    scrollOffset: number;           // 滚动偏移量
    lastEditTime: number;           // 最后编辑时间
    lastEditDevice: string;         // 最后编辑设备
    version: number;                // 文档版本号
}

// 编辑操作类型
enum EditOperation {
    INSERT = 'insert',              // 插入文本
    DELETE = 'delete',              // 删除文本
    REPLACE = 'replace',            // 替换文本
    FORMAT = 'format'               // 格式化文本
}

// 编辑操作记录
interface EditRecord {
    operation: EditOperation;
    position: number;
    content?: string;
    length?: number;
    timestamp: number;
    deviceId: string;
}

@Entry
@Component
struct DistributedDocumentEditor {
    // 文档状态
    @State document: DocumentData = {
        docId: 'doc_001',
        title: '未命名文档',
        content: '',
        cursorPosition: 0,
        selectionStart: 0,
        selectionEnd: 0,
        scrollOffset: 0,
        lastEditTime: Date.now(),
        lastEditDevice: '',
        version: 0
    };
  
    // 编辑历史栈
    @State editHistory: EditRecord[] = [];
    @State undoStack: EditRecord[] = [];
  
    // 分布式相关
    private kvStore: KvStore | null = null;
    private deviceManager: deviceManager.DeviceManager | null = null;
    @State availableDevices: deviceManager.DeviceInfo[] = [];
    @State isSyncing: boolean = false;
  
    // UI状态
    @State showDeviceList: boolean = false;
    @State syncProgress: number = 0;
  
    aboutToAppear() {
        // 初始化分布式数据存储
        this.initDistributedData();
        // 初始化设备管理器
        this.initDeviceManager();
        // 加载文档
        this.loadDocument();
    }
  
    aboutToDisappear() {
        // 保存文档状态
        this.saveDocument();
        // 释放资源
        if (this.kvStore) {
            // kvStore.close();
        }
    }
  
    // 初始化分布式数据存储
    async initDistributedData() {
        try {
            // 创建分布式数据存储
            const options: Options = {
                createIfMissing: true,
                encrypt: false,
                autoSync: true,
                kvStoreType: distributedData.KvStoreType.SINGLE_VERSION
            };
          
            // 获取分布式数据存储实例
            // 注意:实际使用时需要先创建 KvManager
            console.info('分布式数据存储初始化成功');
        } catch (err) {
            console.error(`初始化分布式数据存储失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 初始化设备管理器
    async initDeviceManager() {
        try {
            // 创建设备管理器
            // this.deviceManager = deviceManager.createDeviceManager('com.example.doceditor');
          
            // 注册设备状态变化监听
            // this.deviceManager.on('deviceStateChange', this.onDeviceStateChange.bind(this));
          
            // 发现设备
            // this.discoverDevices();
          
            console.info('设备管理器初始化成功');
        } catch (err) {
            console.error(`初始化设备管理器失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 从分布式数据库加载文档
    async loadDocument() {
        if (!this.kvStore) {
            return;
        }
      
        try {
            // 从分布式数据库获取文档数据
            // const result = await this.kvStore.get(this.document.docId);
            // if (result) {
            //     this.document = JSON.parse(result);
            // }
          
            console.info(`文档加载成功: ${this.document.docId}`);
        } catch (err) {
            console.error(`加载文档失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 保存文档到分布式数据库
    async saveDocument() {
        if (!this.kvStore) {
            return;
        }
      
        try {
            // 更新版本号和时间戳
            this.document.version++;
            this.document.lastEditTime = Date.now();
            // this.document.lastEditDevice = this.deviceManager?.getLocalDeviceId() || '';
          
            // 保存到分布式数据库
            // await this.kvStore.put(this.document.docId, JSON.stringify(this.document));
          
            console.info(`文档保存成功: 版本 ${this.document.version}`);
        } catch (err) {
            console.error(`保存文档失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 处理文本输入
    handleTextInput(text: string) {
        // 记录编辑操作
        const editRecord: EditRecord = {
            operation: EditOperation.INSERT,
            position: this.document.cursorPosition,
            content: text,
            timestamp: Date.now(),
            deviceId: '' // this.deviceManager?.getLocalDeviceId() || ''
        };
      
        // 执行编辑操作
        const before = this.document.content.substring(0, this.document.cursorPosition);
        const after = this.document.content.substring(this.document.cursorPosition);
        this.document.content = before + text + after;
        this.document.cursorPosition += text.length;
      
        // 添加到编辑历史
        this.editHistory.push(editRecord);
        this.undoStack = []; // 清空重做栈
      
        // 触发同步
        this.syncEditOperation(editRecord);
    }
  
    // 同步编辑操作到其他设备
    async syncEditOperation(editRecord: EditRecord) {
        if (!this.kvStore) {
            return;
        }
      
        try {
            // 将编辑操作同步到分布式数据库
            const operationKey = `operation_${Date.now()}`;
            // await this.kvStore.put(operationKey, JSON.stringify(editRecord));
          
            console.info(`编辑操作已同步: ${editRecord.operation}`);
        } catch (err) {
            console.error(`同步编辑操作失败: ${JSON.stringify(err)}`);
        }
    }
  
    build() {
        Column() {
            // 顶部工具栏
            this.buildToolbar();
          
            // 编辑区域
            this.buildEditor();
          
            // 设备列表弹窗
            if (this.showDeviceList) {
                this.buildDeviceListDialog();
            }
          
            // 同步进度提示
            if (this.isSyncing) {
                this.buildSyncProgress();
            }
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#f5f5f5');
    }
  
    @Builder
    buildToolbar() {
        Row() {
            // 返回按钮
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_back'))
                    .width(24)
                    .height(24)
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent)
            .onClick(() => {
                // 返回上一页
            });
          
            Blank();
          
            // 文档标题
            Text(this.document.title)
                .fontSize(18)
                .fontWeight(FontWeight.Medium)
                .fontColor('#333333');
          
            Blank();
          
            // 流转按钮
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_distributed'))
                    .width(24)
                    .height(24)
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent)
            .onClick(() => {
                this.showDeviceList = true;
                this.discoverDevices();
            });
          
            // 保存按钮
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_save'))
                    .width(24)
                    .height(24)
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent)
            .onClick(() => {
                this.saveDocument();
            });
        }
        .width('100%')
        .height(56)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White);
    }
  
    @Builder
    buildEditor() {
        Column() {
            // 文档编辑器
            TextArea({ 
                placeholder: '开始编辑文档...',
                text: this.document.content 
            })
                .width('100%')
                .layoutWeight(1)
                .backgroundColor(Color.White)
                .margin(16)
                .borderRadius(8)
                .onChange((value: string) => {
                    this.document.content = value;
                    this.saveDocument();
                });
        }
        .layoutWeight(1);
    }
  
    @Builder
    buildDeviceListDialog() {
        Column() {
            // 标题
            Text('选择流转设备')
                .fontSize(20)
                .fontWeight(FontWeight.Medium)
                .margin({ bottom: 20 });
          
            // 设备列表
            List() {
                ForEach(this.availableDevices, (device: deviceManager.DeviceInfo) => {
                    ListItem() {
                        Row() {
                            // 设备图标
                            Image(this.getDeviceIcon(device.deviceType))
                                .width(40)
                                .height(40)
                                .margin({ right: 16 });
                          
                            // 设备信息
                            Column() {
                                Text(device.deviceName)
                                    .fontSize(16)
                                    .fontWeight(FontWeight.Medium);
                              
                                Text(this.getDeviceTypeText(device.deviceType))
                                    .fontSize(14)
                                    .fontColor('#666666');
                            }
                            .alignItems(HorizontalAlign.Start);
                          
                            Blank();
                          
                            // 流转按钮
                            Button('流转')
                                .onClick(() => {
                                    this.transferToDevice(device.deviceId);
                                    this.showDeviceList = false;
                                });
                        }
                        .width('100%')
                        .padding(16);
                    }
                });
            }
            .width('100%')
            .layoutWeight(1);
          
            // 关闭按钮
            Button('取消')
                .width('100%')
                .margin({ top: 16 })
                .onClick(() => {
                    this.showDeviceList = false;
                });
        }
        .width('80%')
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(12)
        .shadow({ radius: 20, color: '#00000020' });
    }
  
    @Builder
    buildSyncProgress() {
        Column() {
            Progress({ value: this.syncProgress, total: 100, type: ProgressType.Linear })
                .width(200);
          
            Text(`正在同步... ${this.syncProgress}%`)
                .fontSize(14)
                .fontColor('#666666')
                .margin({ top: 8 });
        }
        .padding(16)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .shadow({ radius: 10, color: '#00000020' });
    }
  
    // 发现可用设备
    async discoverDevices() {
        if (!this.deviceManager) {
            return;
        }
      
        try {
            // 获取可用设备列表
            // this.availableDevices = await this.deviceManager.getAvailableDeviceList();
            console.info(`发现 ${this.availableDevices.length} 个可用设备`);
        } catch (err) {
            console.error(`发现设备失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 流转到指定设备
    async transferToDevice(deviceId: string) {
        this.isSyncing = true;
        this.syncProgress = 0;
      
        try {
            // 先保存当前状态
            await this.saveDocument();
          
            // 模拟流转进度
            for (let i = 0; i <= 100; i += 10) {
                this.syncProgress = i;
                await new Promise(resolve => setTimeout(resolve, 100));
            }
          
            // 执行流转
            // await this.deviceManager.startDeviceDiscovery(deviceId);
          
            console.info(`流转到设备 ${deviceId} 成功`);
        } catch (err) {
            console.error(`流转失败: ${JSON.stringify(err)}`);
        } finally {
            this.isSyncing = false;
        }
    }
  
    // 获取设备图标
    getDeviceIcon(deviceType: string): Resource {
        switch (deviceType) {
            case 'phone':
                return $r('app.media.ic_phone');
            case 'tablet':
                return $r('app.media.ic_tablet');
            case 'pc':
                return $r('app.media.ic_pc');
            default:
                return $r('app.media.ic_device');
        }
    }
  
    // 获取设备类型文本
    getDeviceTypeText(deviceType: string): string {
        switch (deviceType) {
            case 'phone':
                return '手机';
            case 'tablet':
                return '平板';
            case 'pc':
                return '电脑';
            default:
                return '设备';
        }
    }
}

3.2 实时协同编辑实现

多人同时编辑同一文档是分布式办公的高级场景,需要处理并发冲突:

// CollaborativeEditor.ets
import distributedData from '@ohos.data.distributedData';

// 协同编辑者信息
interface Collaborator {
    deviceId: string;
    deviceName: string;
    cursorColor: string;        // 光标颜色(用于区分不同用户)
    cursorPosition: number;     // 光标位置
    lastActiveTime: number;     // 最后活跃时间
}

// 操作转换(OT)算法实现
class OperationalTransform {
    // 转换两个并发操作
    static transform(op1: EditRecord, op2: EditRecord): [EditRecord, EditRecord] {
        // 如果操作位置不相交,无需转换
        if (op1.operation === EditOperation.INSERT && 
            op2.operation === EditOperation.INSERT) {
            // 两个插入操作
            if (op1.position <= op2.position) {
                // op1在op2之前,op2的位置需要向后移动
                const transformedOp2: EditRecord = {
                    ...op2,
                    position: op2.position + (op1.content?.length || 0)
                };
                return [op1, transformedOp2];
            } else {
                // op2在op1之前,op1的位置需要向后移动
                const transformedOp1: EditRecord = {
                    ...op1,
                    position: op1.position + (op2.content?.length || 0)
                };
                return [transformedOp1, op2];
            }
        }
      
        // 其他情况的处理...
        return [op1, op2];
    }
  
    // 应用操作到文档
    static apply(content: string, operation: EditRecord): string {
        switch (operation.operation) {
            case EditOperation.INSERT:
                const before = content.substring(0, operation.position);
                const after = content.substring(operation.position);
                return before + (operation.content || '') + after;
              
            case EditOperation.DELETE:
                const start = content.substring(0, operation.position);
                const end = content.substring(operation.position + (operation.length || 0));
                return start + end;
              
            case EditOperation.REPLACE:
                const prefix = content.substring(0, operation.position);
                const suffix = content.substring(operation.position + (operation.length || 0));
                return prefix + (operation.content || '') + suffix;
              
            default:
                return content;
        }
    }
}

@Entry
@Component
struct CollaborativeEditor {
    // 文档内容
    @State documentContent: string = '';
    @State localCursorPosition: number = 0;
  
    // 协同编辑者列表
    @State collaborators: Collaborator[] = [];
  
    // 待处理的远程操作队列
    private pendingOperations: EditRecord[] = [];
    // 本地操作历史
    private localOperations: EditRecord[] = [];
  
    // 分布式数据存储
    private kvStore: distributedData.KvStore | null = null;
  
    // 光标颜色池(用于区分不同用户)
    private cursorColors: string[] = [
        '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', 
        '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F'
    ];
  
    aboutToAppear() {
        // 初始化分布式数据监听
        this.initDistributedDataListener();
      
        // 模拟添加协同编辑者
        this.addCollaborator({
            deviceId: 'device_002',
            deviceName: '张三的平板',
            cursorColor: this.cursorColors[0],
            cursorPosition: 10,
            lastActiveTime: Date.now()
        });
    }
  
    // 初始化分布式数据监听
    initDistributedDataListener() {
        if (!this.kvStore) {
            return;
        }
      
        // 监听数据变化
        // this.kvStore.on('dataChange', (data) => {
        //     this.handleRemoteDataChange(data);
        // });
    }
  
    // 处理远程数据变化
    handleRemoteDataChange(data: any) {
        // 解析远程操作
        const remoteOperation: EditRecord = JSON.parse(data.value);
      
        // 转换操作(解决冲突)
        let transformedOp = remoteOperation;
        for (const localOp of this.localOperations) {
            [transformedOp] = OperationalTransform.transform(transformedOp, localOp);
        }
      
        // 应用转换后的操作
        this.documentContent = OperationalTransform.apply(
            this.documentContent, 
            transformedOp
        );
      
        // 更新协同编辑者光标位置
        this.updateCollaboratorCursor(remoteOperation.deviceId, remoteOperation.position);
    }
  
    // 本地编辑操作
    handleLocalEdit(operation: EditRecord) {
        // 应用操作到本地文档
        this.documentContent = OperationalTransform.apply(
            this.documentContent, 
            operation
        );
      
        // 添加到本地操作历史
        this.localOperations.push(operation);
      
        // 同步到其他设备
        this.syncOperation(operation);
    }
  
    // 同步操作到其他设备
    async syncOperation(operation: EditRecord) {
        if (!this.kvStore) {
            return;
        }
      
        try {
            const operationKey = `op_${Date.now()}_${Math.random()}`;
            // await this.kvStore.put(operationKey, JSON.stringify(operation));
            console.info(`操作已同步: ${operation.operation}`);
        } catch (err) {
            console.error(`同步操作失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 添加协同编辑者
    addCollaborator(collaborator: Collaborator) {
        // 检查是否已存在
        const index = this.collaborators.findIndex(
            c => c.deviceId === collaborator.deviceId
        );
      
        if (index >= 0) {
            // 更新已存在的协同者
            this.collaborators[index] = collaborator;
        } else {
            // 添加新的协同者
            this.collaborators.push(collaborator);
        }
    }
  
    // 更新协同编辑者光标位置
    updateCollaboratorCursor(deviceId: string, position: number) {
        const index = this.collaborators.findIndex(c => c.deviceId === deviceId);
        if (index >= 0) {
            this.collaborators[index].cursorPosition = position;
            this.collaborators[index].lastActiveTime = Date.now();
        }
    }
  
    build() {
        Column() {
            // 顶部工具栏
            this.buildToolbar();
          
            // 协同编辑者指示器
            this.buildCollaboratorIndicators();
          
            // 编辑区域(带协同光标)
            this.buildCollaborativeEditor();
          
            // 协同编辑者列表
            this.buildCollaboratorList();
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#f5f5f5');
    }
  
    @Builder
    buildToolbar() {
        Row() {
            Text('协同编辑')
                .fontSize(20)
                .fontWeight(FontWeight.Bold);
          
            Blank();
          
            // 在线人数
            Row() {
                Image($r('app.media.ic_users'))
                    .width(20)
                    .height(20)
                    .margin({ right: 8 });
              
                Text(`${this.collaborators.length + 1} 人在线`)
                    .fontSize(14)
                    .fontColor('#666666');
            };
        }
        .width('100%')
        .height(56)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White);
    }
  
    @Builder
    buildCollaboratorIndicators() {
        Row() {
            ForEach(this.collaborators, (collaborator: Collaborator) => {
                Row() {
                    // 彩色圆点
                    Circle()
                        .width(8)
                        .height(8)
                        .fill(collaborator.cursorColor)
                        .margin({ right: 4 });
                  
                    // 设备名称
                    Text(collaborator.deviceName)
                        .fontSize(12)
                        .fontColor('#666666');
                }
                .margin({ right: 16 });
            });
        }
        .width('100%')
        .padding({ left: 16, right: 16, top: 8, bottom: 8 })
        .backgroundColor(Color.White);
    }
  
    @Builder
    buildCollaborativeEditor() {
        Stack() {
            // 文档编辑器
            TextArea({ 
                placeholder: '开始协同编辑...',
                text: this.documentContent 
            })
                .width('100%')
                .height('100%')
                .backgroundColor(Color.White)
                .margin(16)
                .borderRadius(8)
                .onChange((value: string) => {
                    // 计算编辑操作
                    const operation: EditRecord = {
                        operation: EditOperation.REPLACE,
                        position: 0,
                        content: value,
                        length: this.documentContent.length,
                        timestamp: Date.now(),
                        deviceId: 'local'
                    };
                  
                    this.handleLocalEdit(operation);
                });
          
            // 协同编辑者光标
            ForEach(this.collaborators, (collaborator: Collaborator) => {
                this.buildRemoteCursor(collaborator);
            });
        }
        .layoutWeight(1);
    }
  
    @Builder
    buildRemoteCursor(collaborator: Collaborator) {
        // 远程用户的光标指示
        Column() {
            // 光标线
            Column()
                .width(2)
                .height(20)
                .backgroundColor(collaborator.cursorColor);
          
            // 用户名标签
            Text(collaborator.deviceName)
                .fontSize(10)
                .fontColor(Color.White)
                .backgroundColor(collaborator.cursorColor)
                .borderRadius(4)
                .padding({ left: 4, right: 4, top: 2, bottom: 2 });
        }
        // 实际位置需要根据光标位置计算
        .position({ x: 100, y: 100 }); // 简化示例
    }
  
    @Builder
    buildCollaboratorList() {
        Column() {
            Text('协同编辑者')
                .fontSize(16)
                .fontWeight(FontWeight.Medium)
                .margin({ bottom: 12 });
          
            ForEach(this.collaborators, (collaborator: Collaborator) => {
                Row() {
                    // 彩色圆点
                    Circle()
                        .width(12)
                        .height(12)
                        .fill(collaborator.cursorColor)
                        .margin({ right: 12 });
                  
                    // 设备名称
                    Text(collaborator.deviceName)
                        .fontSize(14)
                        .layoutWeight(1);
                  
                    // 活跃状态
                    Text(this.getTimeSinceActive(collaborator.lastActiveTime))
                        .fontSize(12)
                        .fontColor('#999999');
                }
                .width('100%')
                .padding({ top: 8, bottom: 8 });
            });
        }
        .width('100%')
        .padding(16)
        .backgroundColor(Color.White)
        .margin({ top: 16 });
    }
  
    // 获取距离上次活跃的时间
    getTimeSinceActive(lastActiveTime: number): string {
        const seconds = Math.floor((Date.now() - lastActiveTime) / 1000);
      
        if (seconds < 60) {
            return '刚刚活跃';
        } else if (seconds < 3600) {
            return `${Math.floor(seconds / 60)} 分钟前`;
        } else {
            return `${Math.floor(seconds / 3600)} 小时前`;
        }
    }
}

3.3 文档版本管理与冲突解决

实际办公场景中,离线编辑、网络延迟等情况可能导致版本冲突,需要完善的版本管理机制:

// DocumentVersionManager.ets

// 文档版本
interface DocumentVersion {
    versionId: string;           // 版本ID
    versionNumber: number;       // 版本号
    content: string;             // 文档内容
    createTime: number;          // 创建时间
    deviceId: string;            // 创建设备
    deviceName: string;          // 设备名称
    changeDescription: string;   // 变更描述
    checksum: string;            // 内容校验和
}

// 冲突记录
interface ConflictRecord {
    conflictId: string;
    localVersion: DocumentVersion;
    remoteVersion: DocumentVersion;
    conflictType: ConflictType;
    conflictTime: number;
    resolved: boolean;
    resolution?: ConflictResolution;
}

// 冲突类型
enum ConflictType {
    CONTENT_CONFLICT = 'content_conflict',      // 内容冲突
    DELETE_CONFLICT = 'delete_conflict',        // 删除冲突
    PERMISSION_CONFLICT = 'permission_conflict' // 权限冲突
}

// 冲突解决方案
interface ConflictResolution {
    type: 'local' | 'remote' | 'merge';         // 保留本地/远程/合并
    mergedContent?: string;                      // 合并后的内容
    resolvedBy: string;                         // 解决者
    resolvedTime: number;                       // 解决时间
}

@Entry
@Component
struct DocumentVersionManager {
    // 当前文档
    @State documentId: string = 'doc_001';
    @State currentContent: string = '';
    @State currentVersion: number = 0;
  
    // 版本历史
    @State versionHistory: DocumentVersion[] = [];
  
    // 冲突列表
    @State conflicts: ConflictRecord[] = [];
    @State showConflictDialog: boolean = false;
    @State currentConflict: ConflictRecord | null = null;
  
    // UI状态
    @State showVersionHistory: boolean = false;
    @State isLoading: boolean = false;
  
    // 计算内容校验和(简化版)
    calculateChecksum(content: string): string {
        let hash = 0;
        for (let i = 0; i < content.length; i++) {
            const char = content.charCodeAt(i);
            hash = ((hash << 5) - hash) + char;
            hash = hash & hash;
        }
        return hash.toString(16);
    }
  
    // 创建新版本
    async createVersion(content: string, description: string): Promise<DocumentVersion> {
        const version: DocumentVersion = {
            versionId: `ver_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
            versionNumber: this.currentVersion + 1,
            content: content,
            createTime: Date.now(),
            deviceId: 'local_device',
            deviceName: '本机',
            changeDescription: description,
            checksum: this.calculateChecksum(content)
        };
      
        // 保存版本到分布式数据库
        await this.saveVersion(version);
      
        // 更新当前版本
        this.currentVersion = version.versionNumber;
        this.versionHistory.unshift(version);
      
        return version;
    }
  
    // 保存版本到分布式数据库
    async saveVersion(version: DocumentVersion) {
        // 实际实现需要调用分布式数据库API
        console.info(`保存版本: ${version.versionNumber}`);
    }
  
    // 检测冲突
    async detectConflict(remoteVersion: DocumentVersion): Promise<boolean> {
        // 检查版本号
        if (remoteVersion.versionNumber <= this.currentVersion) {
            return false;
        }
      
        // 检查校验和
        const localChecksum = this.calculateChecksum(this.currentContent);
        if (remoteVersion.checksum === localChecksum) {
            // 内容一致,无冲突
            this.currentVersion = remoteVersion.versionNumber;
            return false;
        }
      
        // 检测到冲突
        const conflict: ConflictRecord = {
            conflictId: `conflict_${Date.now()}`,
            localVersion: {
                versionId: `local_${Date.now()}`,
                versionNumber: this.currentVersion,
                content: this.currentContent,
                createTime: Date.now(),
                deviceId: 'local_device',
                deviceName: '本机',
                changeDescription: '本地修改',
                checksum: localChecksum
            },
            remoteVersion: remoteVersion,
            conflictType: ConflictType.CONTENT_CONFLICT,
            conflictTime: Date.now(),
            resolved: false
        };
      
        this.conflicts.push(conflict);
        this.currentConflict = conflict;
        this.showConflictDialog = true;
      
        return true;
    }
  
    // 解决冲突
    async resolveConflict(
        conflict: ConflictRecord, 
        resolutionType: 'local' | 'remote' | 'merge',
        mergedContent?: string
    ) {
        let finalContent: string;
      
        switch (resolutionType) {
            case 'local':
                // 保留本地版本
                finalContent = conflict.localVersion.content;
                break;
            case 'remote':
                // 使用远程版本
                finalContent = conflict.remoteVersion.content;
                break;
            case 'merge':
                // 使用合并后的内容
                finalContent = mergedContent || '';
                break;
        }
      
        // 创建合并版本
        const mergedVersion = await this.createVersion(
            finalContent,
            `解决冲突: ${resolutionType === 'merge' ? '合并' : resolutionType === 'local' ? '保留本地' : '使用远程'}`
        );
      
        // 标记冲突已解决
        conflict.resolved = true;
        conflict.resolution = {
            type: resolutionType,
            mergedContent: mergedContent,
            resolvedBy: 'local_device',
            resolvedTime: Date.now()
        };
      
        // 更新当前内容
        this.currentContent = finalContent;
        this.showConflictDialog = false;
        this.currentConflict = null;
    }
  
    // 自动合并内容
    autoMergeContent(localContent: string, remoteContent: string): string {
        // 简化的合并策略:基于行的合并
        const localLines = localContent.split('\n');
        const remoteLines = remoteContent.split('\n');
      
        const mergedLines: string[] = [];
        const maxLength = Math.max(localLines.length, remoteLines.length);
      
        for (let i = 0; i < maxLength; i++) {
            const localLine = localLines[i] || '';
            const remoteLine = remoteLines[i] || '';
          
            if (localLine === remoteLine) {
                // 行相同,直接使用
                mergedLines.push(localLine);
            } else if (!localLine) {
                // 本地为空,使用远程
                mergedLines.push(remoteLine);
            } else if (!remoteLine) {
                // 远程为空,使用本地
                mergedLines.push(localLine);
            } else {
                // 行不同,标记冲突
                mergedLines.push(`<<<<<<< 本地`);
                mergedLines.push(localLine);
                mergedLines.push(`=======`);
                mergedLines.push(remoteLine);
                mergedLines.push(`>>>>>>> 远程`);
            }
        }
      
        return mergedLines.join('\n');
    }
  
    // 恢复到指定版本
    async restoreVersion(version: DocumentVersion) {
        this.currentContent = version.content;
        this.currentVersion = version.versionNumber;
      
        // 创建恢复记录
        await this.createVersion(
            version.content,
            `恢复到版本 ${version.versionNumber}`
        );
    }
  
    build() {
        Column() {
            // 顶部工具栏
            this.buildToolbar();
          
            // 编辑区域
            this.buildEditor();
          
            // 版本历史面板
            if (this.showVersionHistory) {
                this.buildVersionHistoryPanel();
            }
          
            // 冲突解决对话框
            if (this.showConflictDialog && this.currentConflict) {
                this.buildConflictDialog();
            }
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#f5f5f5');
    }
  
    @Builder
    buildToolbar() {
        Row() {
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_back'))
                    .width(24)
                    .height(24)
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent);
          
            Blank();
          
            Text(`版本 ${this.currentVersion}`)
                .fontSize(16)
                .fontColor('#666666');
          
            Blank();
          
            // 版本历史按钮
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_history'))
                    .width(24)
                    .height(24)
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent)
            .onClick(() => {
                this.showVersionHistory = !this.showVersionHistory;
            });
          
            // 保存按钮
            Button('保存')
                .onClick(async () => {
                    await this.createVersion(this.currentContent, '手动保存');
                });
        }
        .width('100%')
        .height(56)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White);
    }
  
    @Builder
    buildEditor() {
        Column() {
            TextArea({ 
                placeholder: '输入文档内容...',
                text: this.currentContent 
            })
                .width('100%')
                .layoutWeight(1)
                .backgroundColor(Color.White)
                .margin(16)
                .borderRadius(8)
                .onChange((value: string) => {
                    this.currentContent = value;
                });
        }
        .layoutWeight(1);
    }
  
    @Builder
    buildVersionHistoryPanel() {
        Column() {
            // 标题
            Row() {
                Text('版本历史')
                    .fontSize(18)
                    .fontWeight(FontWeight.Bold);
              
                Blank();
              
                Button({ type: ButtonType.Circle }) {
                    Image($r('app.media.ic_close'))
                        .width(20)
                        .height(20);
                }
                .width(32)
                .height(32)
                .backgroundColor(Color.Transparent)
                .onClick(() => {
                    this.showVersionHistory = false;
                });
            }
            .width('100%')
            .margin({ bottom: 16 });
          
            // 版本列表
            List() {
                ForEach(this.versionHistory, (version: DocumentVersion) => {
                    ListItem() {
                        Column() {
                            Row() {
                                Text(`版本 ${version.versionNumber}`)
                                    .fontSize(16)
                                    .fontWeight(FontWeight.Medium);
                              
                                Blank();
                              
                                Text(this.formatTime(version.createTime))
                                    .fontSize(12)
                                    .fontColor('#999999');
                            }
                            .width('100%');
                          
                            Text(version.changeDescription)
                                .fontSize(14)
                                .fontColor('#666666')
                                .margin({ top: 4 });
                          
                            Row() {
                                Text(`${version.deviceName}`)
                                    .fontSize(12)
                                    .fontColor('#999999');
                              
                                Blank();
                              
                                Button('恢复')
                                    .fontSize(12)
                                    .height(28)
                                    .onClick(() => {
                                        this.restoreVersion(version);
                                    });
                            }
                            .width('100%')
                            .margin({ top: 8 });
                        }
                        .width('100%')
                        .padding(16)
                        .backgroundColor(Color.White)
                        .borderRadius(8)
                        .margin({ bottom: 8 });
                    }
                });
            }
            .width('100%')
            .layoutWeight(1);
        }
        .width('100%')
        .height('60%')
        .padding(16)
        .backgroundColor('#f5f5f5')
        .borderRadius({ topLeft: 16, topRight: 16 });
    }
  
    @Builder
    buildConflictDialog() {
        Column() {
            // 标题
            Text('检测到版本冲突')
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .margin({ bottom: 20 });
          
            // 冲突信息
            Column() {
                Text('本地版本:')
                    .fontSize(14)
                    .fontWeight(FontWeight.Medium);
              
                Text(`版本 ${this.currentConflict!.localVersion.versionNumber}`)
                    .fontSize(12)
                    .fontColor('#666666')
                    .margin({ top: 4 });
              
                Text(`修改时间: ${this.formatTime(this.currentConflict!.localVersion.createTime)}`)
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 4 });
            }
            .width('100%')
            .padding(12)
            .backgroundColor('#f0f0f0')
            .borderRadius(8)
            .margin({ bottom: 12 });
          
            Column() {
                Text('远程版本:')
                    .fontSize(14)
                    .fontWeight(FontWeight.Medium);
              
                Text(`版本 ${this.currentConflict!.remoteVersion.versionNumber}`)
                    .fontSize(12)
                    .fontColor('#666666')
                    .margin({ top: 4 });
              
                Text(`修改时间: ${this.formatTime(this.currentConflict!.remoteVersion.createTime)}`)
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 4 });
            }
            .width('100%')
            .padding(12)
            .backgroundColor('#f0f0f0')
            .borderRadius(8)
            .margin({ bottom: 20 });
          
            // 解决方案按钮
            Column() {
                Button('保留本地版本')
                    .width('100%')
                    .onClick(() => {
                        this.resolveConflict(this.currentConflict!, 'local');
                    });
              
                Button('使用远程版本')
                    .width('100%')
                    .margin({ top: 12 })
                    .onClick(() => {
                        this.resolveConflict(this.currentConflict!, 'remote');
                    });
              
                Button('自动合并')
                    .width('100%')
                    .margin({ top: 12 })
                    .onClick(() => {
                        const merged = this.autoMergeContent(
                            this.currentConflict!.localVersion.content,
                            this.currentConflict!.remoteVersion.content
                        );
                        this.resolveConflict(this.currentConflict!, 'merge', merged);
                    });
            }
            .width('100%');
        }
        .width('80%')
        .padding(24)
        .backgroundColor(Color.White)
        .borderRadius(12)
        .shadow({ radius: 20, color: '#00000030' });
    }
  
    // 格式化时间
    formatTime(timestamp: number): string {
        const date = new Date(timestamp);
        return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
    }
}

四、踩坑与注意事项

4.1 数据同步延迟问题

问题现象
在弱网环境下,文档同步可能出现明显延迟,导致用户在另一设备上看到的是旧版本内容。

原因分析

  • 分布式数据库默认使用最终一致性,同步时机不确定
  • 大文档同步耗时较长,阻塞了后续操作
  • 网络切换时(如Wi-Fi切4G)同步中断

解决方案

// 优化同步策略
class SyncStrategy {
    // 根据网络状况动态调整同步策略
    static getSyncStrategy(networkQuality: number): SyncConfig {
        if (networkQuality > 80) {
            // 网络良好:实时同步
            return {
                syncMode: 'realtime',
                batchSize: 1024,        // 1KB
                syncInterval: 0         // 立即同步
            };
        } else if (networkQuality > 50) {
            // 网络一般:批量同步
            return {
                syncMode: 'batch',
                batchSize: 10 * 1024,   // 10KB
                syncInterval: 1000      // 1秒后同步
            };
        } else {
            // 网络较差:延迟同步
            return {
                syncMode: 'delayed',
                batchSize: 50 * 1024,   // 50KB
                syncInterval: 5000      // 5秒后同步
            };
        }
    }
  
    // 增量同步:只同步变化部分
    static incrementalSync(
        oldContent: string, 
        newContent: string
    ): DiffPatch[] {
        // 使用diff算法计算差异
        const diffs: DiffPatch[] = [];
      
        // 简化的差异计算(实际应使用Myers diff算法)
        if (oldContent !== newContent) {
            diffs.push({
                type: 'replace',
                position: 0,
                oldLength: oldContent.length,
                newContent: newContent
            });
        }
      
        return diffs;
    }
}

interface SyncConfig {
    syncMode: 'realtime' | 'batch' | 'delayed';
    batchSize: number;
    syncInterval: number;
}

interface DiffPatch {
    type: 'insert' | 'delete' | 'replace';
    position: number;
    oldLength?: number;
    newContent?: string;
}

4.2 大文档内存问题

问题现象
编辑超过10MB的大型文档时,应用出现卡顿甚至OOM崩溃。

原因分析

  • 将整个文档加载到内存,未做分页处理
  • 每次编辑都重新渲染整个文档
  • 富文本解析占用大量内存

解决方案

// 虚拟滚动 + 分段加载
class LargeDocumentHandler {
    private segments: DocumentSegment[] = [];
    private visibleRange: [number, number] = [0, 100]; // 可见行范围
    private segmentSize: number = 1000; // 每段1000行
  
    // 分段加载文档
    async loadDocumentSegmented(documentPath: string): Promise<void> {
        // 读取文档元数据
        const metadata = await this.readMetadata(documentPath);
      
        // 计算分段数量
        const segmentCount = Math.ceil(metadata.totalLines / this.segmentSize);
      
        // 初始化分段信息(不加载内容)
        for (let i = 0; i < segmentCount; i++) {
            this.segments.push({
                segmentId: i,
                startLine: i * this.segmentSize,
                endLine: Math.min((i + 1) * this.segmentSize, metadata.totalLines),
                loaded: false,
                content: null
            });
        }
      
        // 预加载前两段
        await this.loadSegment(0);
        await this.loadSegment(1);
    }
  
    // 加载指定分段
    async loadSegment(segmentId: number): Promise<string> {
        const segment = this.segments[segmentId];
        if (!segment || segment.loaded) {
            return segment?.content || '';
        }
      
        // 从文件读取分段内容
        segment.content = await this.readSegmentFromFile(segmentId);
        segment.loaded = true;
      
        return segment.content;
    }
  
    // 滚动时动态加载/卸载分段
    async onScroll(firstVisibleLine: number, lastVisibleLine: number) {
        // 计算需要加载的分段
        const startSegment = Math.floor(firstVisibleLine / this.segmentSize);
        const endSegment = Math.floor(lastVisibleLine / this.segmentSize);
      
        // 加载可见分段
        for (let i = startSegment; i <= endSegment; i++) {
            await this.loadSegment(i);
        }
      
        // 卸载远离可见区域的分段(释放内存)
        const unloadThreshold = 3;
        for (let i = 0; i < this.segments.length; i++) {
            if (i < startSegment - unloadThreshold || i > endSegment + unloadThreshold) {
                if (this.segments[i].loaded) {
                    this.segments[i].content = null;
                    this.segments[i].loaded = false;
                }
            }
        }
    }
  
    // 编辑时只更新对应分段
    async editSegment(segmentId: number, edit: EditRecord): Promise<void> {
        const segment = this.segments[segmentId];
        if (!segment || !segment.loaded) {
            await this.loadSegment(segmentId);
        }
      
        // 应用编辑
        segment.content = OperationalTransform.apply(segment.content || '', edit);
      
        // 标记为脏段,需要同步
        segment.dirty = true;
    }
  
    private async readMetadata(path: string): Promise<DocumentMetadata> {
        // 读取文档元数据
        return {
            totalLines: 10000,
            totalSize: 10 * 1024 * 1024,
            encoding: 'utf-8'
        };
    }
  
    private async readSegmentFromFile(segmentId: number): Promise<string> {
        // 从文件读取指定分段
        return '';
    }
}

interface DocumentSegment {
    segmentId: number;
    startLine: number;
    endLine: number;
    loaded: boolean;
    content: string | null;
    dirty?: boolean;
}

interface DocumentMetadata {
    totalLines: number;
    totalSize: number;
    encoding: string;
}

4.3 富文本格式丢失问题

问题现象
跨设备流转后,文档的格式(加粗、颜色、字体等)丢失或错乱。

原因分析

  • 不同设备的字体库不同
  • 富文本格式序列化/反序列化不一致
  • 特定格式在某个设备上不支持

解决方案

// 富文本格式标准化
class RichTextNormalizer {
    // 标准化富文本格式
    static normalize(richText: RichTextNode[]): RichTextNode[] {
        return richText.map(node => {
            // 标准化字体
            if (node.style.fontFamily) {
                node.style.fontFamily = this.normalizeFont(node.style.fontFamily);
            }
          
            // 标准化颜色(转为标准十六进制)
            if (node.style.color) {
                node.style.color = this.normalizeColor(node.style.color);
            }
          
            // 标准化字号(使用标准字号列表)
            if (node.style.fontSize) {
                node.style.fontSize = this.normalizeFontSize(node.style.fontSize);
            }
          
            // 递归处理子节点
            if (node.children) {
                node.children = this.normalize(node.children);
            }
          
            return node;
        });
    }
  
    // 标准化字体
    private static normalizeFont(fontFamily: string): string {
        const fontMap: Record<string, string> = {
            '宋体': 'SimSun',
            '黑体': 'SimHei',
            '微软雅黑': 'Microsoft YaHei',
            '楷体': 'KaiTi'
        };
      
        return fontMap[fontFamily] || fontFamily;
    }
  
    // 标准化颜色
    private static normalizeColor(color: string): string {
        // 处理rgb/rgba格式
        const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
        if (rgbMatch) {
            const r = parseInt(rgbMatch[1]).toString(16).padStart(2, '0');
            const g = parseInt(rgbMatch[2]).toString(16).padStart(2, '0');
            const b = parseInt(rgbMatch[3]).toString(16).padStart(2, '0');
            return `#${r}${g}${b}`;
        }
      
        return color;
    }
  
    // 标准化字号
    private static normalizeFontSize(fontSize: number): number {
        const standardSizes = [12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 64];
      
        // 找到最接近的标准字号
        let closest = standardSizes[0];
        let minDiff = Math.abs(fontSize - closest);
      
        for (const size of standardSizes) {
            const diff = Math.abs(fontSize - size);
            if (diff < minDiff) {
                minDiff = diff;
                closest = size;
            }
        }
      
        return closest;
    }
  
    // 序列化为通用格式(JSON)
    static serialize(richText: RichTextNode[]): string {
        const normalized = this.normalize(richText);
        return JSON.stringify(normalized);
    }
  
    // 反序列化
    static deserialize(json: string): RichTextNode[] {
        try {
            const parsed = JSON.parse(json);
            return this.normalize(parsed);
        } catch (err) {
            console.error('富文本反序列化失败:', err);
            return [];
        }
    }
}

interface RichTextNode {
    type: 'text' | 'paragraph' | 'list' | 'image';
    content?: string;
    style?: RichTextStyle;
    children?: RichTextNode[];
}

interface RichTextStyle {
    fontFamily?: string;
    fontSize?: number;
    color?: string;
    bold?: boolean;
    italic?: boolean;
    underline?: boolean;
}

4.4 设备离线处理

问题现象
正在编辑的设备突然离线(断网、关机),导致数据丢失或无法流转。

解决方案

// 离线保护机制
class OfflineProtection {
    private pendingSync: PendingSyncData[] = [];
    private autoSaveInterval: number = 30000; // 30秒自动保存
  
    // 启动自动保存
    startAutoSave(callback: () => Promise<void>) {
        setInterval(async () => {
            try {
                await callback();
                console.info('自动保存成功');
            } catch (err) {
                console.error('自动保存失败:', err);
                // 保存到本地缓存
                await this.saveToLocalCache(callback);
            }
        }, this.autoSaveInterval);
    }
  
    // 保存到本地缓存
    async saveToLocalCache(callback: () => Promise<void>) {
        const data: PendingSyncData = {
            timestamp: Date.now(),
            callback: callback.toString(),
            retryCount: 0
        };
      
        this.pendingSync.push(data);
      
        // 持久化到本地存储
        // await Preferences.put('pendingSync', JSON.stringify(this.pendingSync));
    }
  
    // 网络恢复后重试
    async retryPendingSync() {
        for (const data of this.pendingSync) {
            try {
                // eval(`(${data.callback})()`); // 注意:实际使用需要更安全的方式
                console.info('重试同步成功');
              
                // 移除已成功的数据
                const index = this.pendingSync.indexOf(data);
                if (index >= 0) {
                    this.pendingSync.splice(index, 1);
                }
            } catch (err) {
                data.retryCount++;
                if (data.retryCount >= 3) {
                    // 重试次数过多,放弃
                    console.error('重试次数过多,放弃同步');
                }
            }
        }
    }
  
    // 监听网络状态
    monitorNetwork() {
        // connection.getConnectionType((err, type) => {
        //     if (type === connection.ConnectionType.CONNECTION_NONE) {
        //         console.warn('网络已断开,启用离线模式');
        //         this.enableOfflineMode();
        //     } else {
        //         console.info('网络已恢复,开始同步');
        //         this.retryPendingSync();
        //     }
        // });
    }
  
    // 启用离线模式
    enableOfflineMode() {
        // 显示离线提示
        // 禁用实时同步
        // 启用本地缓存
    }
}

interface PendingSyncData {
    timestamp: number;
    callback: string;
    retryCount: number;
}

五、HarmonyOS 6适配

5.1 API差异

HarmonyOS 6在分布式能力方面有重要更新:

分布式数据管理API变化

// HarmonyOS 5.x
import distributedData from '@ohos.data.distributedData';

// 创建KvManager
const kvManager = distributedData.createKvManager({
    bundleName: 'com.example.app'
});

// HarmonyOS 6
import { distributedData } from '@kit.ArkData';

// 创建KvManager - 新增配置项
const kvManager = distributedData.createKvManager({
    bundleName: 'com.example.app',
    userId: 0,                    // 新增:用户ID
    appId: 'app_001',             // 新增:应用ID
    instanceId: 'instance_001'    // 新增:实例ID
});

设备管理API变化

// HarmonyOS 5.x
import deviceManager from '@ohos.distributedDeviceManager';

const dm = deviceManager.createDeviceManager('com.example.app');
dm.on('deviceStateChange', callback);

// HarmonyOS 6
import { deviceManager } from '@kit.DistributedService';

const dm = deviceManager.createDeviceManager('com.example.app');

// 新增:设备能力查询
dm.on('deviceStateChange', (data) => {
    console.info(`设备状态变化: ${data.deviceId}`);
    console.info(`设备能力: ${data.capability}`);  // 新增能力字段
});

// 新增:设备信任关系管理
dm.setDeviceTrust(deviceId, true);  // 设置设备信任
dm.getTrustedDevices();             // 获取信任设备列表

5.2 行为变更

数据同步行为变化

// HarmonyOS 6 新增同步配置
interface SyncConfigV6 {
    // 同步模式
    syncMode: 'push' | 'pull' | 'push_pull';
  
    // 同步范围(新增)
    syncRange: {
        deviceId?: string;          // 指定设备
        deviceType?: string;        // 指定设备类型
        networkType?: string;       // 指定网络类型
    };
  
    // 冲突解决策略(新增)
    conflictResolution: 'local' | 'remote' | 'last_write_wins' | 'custom';
  
    // 同步优先级(新增)
    priority: 'high' | 'normal' | 'low';
}

// 适配代码
async function syncDocumentV6(docId: string) {
    const config: SyncConfigV6 = {
        syncMode: 'push_pull',
        syncRange: {
            deviceType: 'pc'  // 只同步到PC设备
        },
        conflictResolution: 'last_write_wins',
        priority: 'high'
    };
  
    // await kvStore.sync(docId, config);
}

流转行为变化

// HarmonyOS 6 流转配置
interface ContinuationConfigV6 {
    // 流转类型(新增)
    continuationType: 'full' | 'partial' | 'state_only';
  
    // 流转数据选择(新增)
    dataSelection: {
        includeAppState: boolean;       // 是否包含应用状态
        includeUserData: boolean;       // 是否包含用户数据
        includeCacheData: boolean;      // 是否包含缓存数据
        excludeKeys?: string[];         // 排除的数据键
    };
  
    // 流转回调(新增)
    onProgress?: (progress: number) => void;
    onComplete?: (deviceId: string) => void;
    onError?: (error: Error) => void;
}

// 适配代码
async function continueToDeviceV6(deviceId: string) {
    const config: ContinuationConfigV6 = {
        continuationType: 'partial',
        dataSelection: {
            includeAppState: true,
            includeUserData: true,
            includeCacheData: false,
            excludeKeys: ['temp_*', 'cache_*']
        },
        onProgress: (progress) => {
            console.info(`流转进度: ${progress}%`);
        },
        onComplete: (targetDeviceId) => {
            console.info(`流转完成: ${targetDeviceId}`);
        }
    };
  
    // await continuationManager.continue(deviceId, config);
}

5.3 适配代码示例

完整的HarmonyOS 6分布式办公适配:

// DistributedOfficeV6.ets
import { distributedData } from '@kit.ArkData';
import { deviceManager } from '@kit.DistributedService';
import { continuationManager } from '@kit.AbilityKit';

@Entry
@Component
struct DistributedOfficeV6 {
    // 文档状态
    @State documentContent: string = '';
    @State documentId: string = 'doc_001';
  
    // 设备状态
    @State devices: DeviceInfoV6[] = [];
    @State currentDevice: string = '';
    @State isOnline: boolean = true;
  
    // 同步状态
    @State syncStatus: SyncStatus = SyncStatus.IDLE;
    @State syncProgress: number = 0;
  
    // HarmonyOS 6 API实例
    private kvManager: distributedData.KvManager | null = null;
    private kvStore: distributedData.KvStore | null = null;
    private deviceMgr: deviceManager.DeviceManager | null = null;
  
    aboutToAppear() {
        // 初始化HarmonyOS 6分布式能力
        this.initDistributedCapabilities();
    }
  
    // 初始化分布式能力
    async initDistributedCapabilities() {
        try {
            // 1. 创建KvManager(HarmonyOS 6新配置)
            this.kvManager = distributedData.createKvManager({
                bundleName: 'com.example.office',
                userId: 0,
                appId: 'office_app',
                instanceId: 'main'
            });
          
            // 2. 创建KvStore(新配置)
            const storeOptions: distributedData.Options = {
                createIfMissing: true,
                encrypt: true,              // HarmonyOS 6默认加密
                autoSync: true,
                kvStoreType: distributedData.KvStoreType.DEVICE_COLLABORATION,  // 新类型
                securityLevel: distributedData.SecurityLevel.S3  // 安全等级
            };
          
            // this.kvStore = await this.kvManager.createKvStore('office_docs', storeOptions);
          
            // 3. 创建设备管理器
            this.deviceMgr = deviceManager.createDeviceManager('com.example.office');
          
            // 4. 注册设备状态监听(HarmonyOS 6增强)
            this.deviceMgr.on('deviceStateChange', this.onDeviceStateChange.bind(this));
            this.deviceMgr.on('deviceFound', this.onDeviceFound.bind(this));  // 新增
            this.deviceMgr.on('deviceLost', this.onDeviceLost.bind(this));    // 新增
          
            // 5. 发现设备
            await this.discoverDevices();
          
            // 6. 注册数据变化监听
            if (this.kvStore) {
                // this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, 
                //     this.onDataChange.bind(this));
            }
          
            console.info('分布式能力初始化成功');
        } catch (err) {
            console.error(`初始化失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 设备状态变化回调
    onDeviceStateChange(data: deviceManager.DeviceStateChangeInfo) {
        console.info(`设备状态变化: ${data.deviceId}, 状态: ${data.status}`);
      
        // 更新设备列表
        const index = this.devices.findIndex(d => d.deviceId === data.deviceId);
        if (index >= 0) {
            this.devices[index].online = data.status === 'online';
        }
    }
  
    // 设备发现回调(HarmonyOS 6新增)
    onDeviceFound(device: deviceManager.DeviceInfo) {
        console.info(`发现新设备: ${device.deviceName}`);
      
        // 添加到设备列表
        const deviceInfo: DeviceInfoV6 = {
            deviceId: device.deviceId,
            deviceName: device.deviceName,
            deviceType: device.deviceType,
            online: true,
            capability: device.capability || [],
            trustLevel: device.trustLevel || 0  // 新增信任等级
        };
      
        this.devices.push(deviceInfo);
    }
  
    // 设备丢失回调(HarmonyOS 6新增)
    onDeviceLost(device: deviceManager.DeviceInfo) {
        console.info(`设备丢失: ${device.deviceName}`);
      
        // 从设备列表移除
        const index = this.devices.findIndex(d => d.deviceId === device.deviceId);
        if (index >= 0) {
            this.devices.splice(index, 1);
        }
    }
  
    // 数据变化回调
    onDataChange(data: distributedData.ChangeNotification) {
        console.info(`数据变化: ${JSON.stringify(data)}`);
      
        // 处理远程更新
        for (const entry of data.insertEntries) {
            if (entry.key === this.documentId) {
                this.documentContent = entry.value.value;
            }
        }
      
        for (const entry of data.updateEntries) {
            if (entry.key === this.documentId) {
                this.documentContent = entry.value.value;
            }
        }
    }
  
    // 发现设备
    async discoverDevices() {
        if (!this.deviceMgr) {
            return;
        }
      
        try {
            // HarmonyOS 6新增:按条件发现设备
            const discoverOptions: deviceManager.DiscoverOptions = {
                subscribeId: 1,
                mode: 'DISCOVER_MODE_ACTIVE',  // 主动发现
                medium: 'AUTO',                 // 自动选择介质
                freq: 'MID',                    // 发现频率
                isSameAccount: true,            // 同账号设备
                isWakeRemote: true              // 唤醒远程设备
            };
          
            // await this.deviceMgr.startDeviceDiscovery(discoverOptions);
          
            console.info('开始发现设备');
        } catch (err) {
            console.error(`发现设备失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 同步文档(HarmonyOS 6新配置)
    async syncDocument() {
        if (!this.kvStore) {
            return;
        }
      
        this.syncStatus = SyncStatus.SYNCING;
      
        try {
            // 保存本地数据
            // await this.kvStore.put(this.documentId, this.documentContent);
          
            // HarmonyOS 6新同步配置
            const syncOptions: distributedData.SyncOptions = {
                mode: distributedData.SyncMode.SYNC_MODE_PUSH_PULL,
                delay: 0,
                allowRepeats: false,
                pushPullType: distributedData.PushPullType.PUSH_PULL_TYPE_ALL  // 新增
            };
          
            // 执行同步
            // await this.kvStore.sync([], syncOptions);
          
            this.syncStatus = SyncStatus.SUCCESS;
            console.info('文档同步成功');
        } catch (err) {
            this.syncStatus = SyncStatus.FAILED;
            console.error(`文档同步失败: ${JSON.stringify(err)}`);
        }
    }
  
    // 流转到其他设备(HarmonyOS 6新配置)
    async continueToDevice(deviceId: string) {
        this.syncStatus = SyncStatus.SYNCING;
      
        try {
            // HarmonyOS 6流转配置
            const continueOptions: continuationManager.ContinueOptions = {
                reversible: true,               // 可回迁
                continuationExtraParams: {
                    deviceType: 'pc',
                    targetBundle: 'com.example.office',
                    description: '流转文档编辑'
                }
            };
          
            // 执行流转
            // const continueToken = await continuationManager.startContinuation(continueOptions);
          
            // 监听流转进度
            // continuationManager.on('progress', continueToken, (progress) => {
            //     this.syncProgress = progress;
            // });
          
            console.info(`开始流转到设备: ${deviceId}`);
        } catch (err) {
            console.error(`流转失败: ${JSON.stringify(err)}`);
            this.syncStatus = SyncStatus.FAILED;
        }
    }
  
    build() {
        Column() {
            // 顶部工具栏
            this.buildToolbar();
          
            // 编辑区域
            this.buildEditor();
          
            // 设备列表
            this.buildDeviceList();
          
            // 同步状态指示
            this.buildSyncIndicator();
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#f5f5f5');
    }
  
    @Builder
    buildToolbar() {
        Row() {
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_back'))
                    .width(24)
                    .height(24);
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent);
          
            Blank();
          
            Text('分布式文档编辑')
                .fontSize(18)
                .fontWeight(FontWeight.Medium);
          
            Blank();
          
            // 同步按钮
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_sync'))
                    .width(24)
                    .height(24);
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent)
            .onClick(() => {
                this.syncDocument();
            });
          
            // 流转按钮
            Button({ type: ButtonType.Circle }) {
                Image($r('app.media.ic_continue'))
                    .width(24)
                    .height(24);
            }
            .width(40)
            .height(40)
            .backgroundColor(Color.Transparent)
            .onClick(() => {
                // 显示设备选择对话框
            });
        }
        .width('100%')
        .height(56)
        .padding({ left: 16, right: 16 })
        .backgroundColor(Color.White);
    }
  
    @Builder
    buildEditor() {
        Column() {
            TextArea({ 
                placeholder: '开始编辑文档...',
                text: this.documentContent 
            })
                .width('100%')
                .layoutWeight(1)
                .backgroundColor(Color.White)
                .margin(16)
                .borderRadius(8)
                .onChange((value: string) => {
                    this.documentContent = value;
                    // 自动保存(防抖)
                    this.autoSave();
                });
        }
        .layoutWeight(1);
    }
  
    @Builder
    buildDeviceList() {
        if (this.devices.length > 0) {
            Row() {
                ForEach(this.devices, (device: DeviceInfoV6) => {
                    Row() {
                        Circle()
                            .width(8)
                            .height(8)
                            .fill(device.online ? '#4CAF50' : '#9E9E9E')
                            .margin({ right: 8 });
                      
                        Text(device.deviceName)
                            .fontSize(12)
                            .fontColor('#666666');
                    }
                    .margin({ right: 16 });
                });
            }
            .width('100%')
            .padding({ left: 16, right: 16, top: 8, bottom: 8 })
            .backgroundColor(Color.White);
        }
    }
  
    @Builder
    buildSyncIndicator() {
        if (this.syncStatus === SyncStatus.SYNCING) {
            Row() {
                LoadingProgress()
                    .width(16)
                    .height(16)
                    .color('#2196F3')
                    .margin({ right: 8 });
              
                Text(`同步中... ${this.syncProgress}%`)
                    .fontSize(12)
                    .fontColor('#666666');
            }
            .width('100%')
            .justifyContent(FlexAlign.Center)
            .padding(8)
            .backgroundColor('#E3F2FD');
        }
    }
  
    // 自动保存(防抖)
    private saveTimer: number = -1;
    autoSave() {
        if (this.saveTimer !== -1) {
            clearTimeout(this.saveTimer);
        }
      
        this.saveTimer = setTimeout(() => {
            this.syncDocument();
        }, 1000);
    }
}

// 设备信息(HarmonyOS 6)
interface DeviceInfoV6 {
    deviceId: string;
    deviceName: string;
    deviceType: string;
    online: boolean;
    capability: string[];
    trustLevel: number;  // 新增信任等级
}

// 同步状态
enum SyncStatus {
    IDLE = 'idle',
    SYNCING = 'syncing',
    SUCCESS = 'success',
    FAILED = 'failed'
}

六、总结

分布式办公场景是鸿蒙分布式能力的典型应用,通过本文的实战演练,我们掌握了:

核心技术要点

  1. 分布式数据管理:理解强一致性和最终一致性的应用场景,掌握数据版本管理和冲突解决机制
  2. 设备发现与流转:学会使用设备管理器发现设备、建立连接、执行流转
  3. 实时协同编辑:掌握操作转换(OT)算法,处理多人并发编辑的冲突
  4. 版本管理与冲突解决:建立完善的版本管理机制,提供友好的冲突解决界面

实战经验总结

  • 大文档必须使用分段加载和虚拟滚动,避免内存溢出
  • 弱网环境需要动态调整同步策略,保证用户体验
  • 富文本格式需要标准化处理,避免跨设备格式丢失
  • 离线保护机制必不可少,防止数据丢失

HarmonyOS 6适配要点

  • 新的API命名空间(@kit.ArkData、@kit.DistributedService)
  • 增强的设备发现和信任管理
  • 更灵活的同步配置和流转选项
  • 默认数据加密和安全等级设置

分布式办公场景的实现,让"设备无界、数据有界"成为现实,真正实现了跨设备无缝办公体验。这不仅是技术的进步,更是工作方式的革新——让办公不再受设备限制,随时随地都能高效工作。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐