HarmonyOS开发:企业应用实战——完整OA系统

举报
Jack20 发表于 2026/06/26 16:54:38 2026/06/26
【摘要】 HarmonyOS开发:企业应用实战——完整OA系统📌 核心要点:完整OA系统不是功能堆叠——架构分层是骨架、模块解耦是肌肉、数据流转是血液、安全合规是皮肤,四者合一才是活的企业应用。 背景与动机前面9篇文章,我们分别讲了OA系统的各个模块——审批、文档、会议、邮箱、日程、通讯录、考勤、门户。每个模块单独看都能跑,但拼到一起呢?审批模块要调通讯录找人,文档模块要调权限引擎判断谁能看,会议...

HarmonyOS开发:企业应用实战——完整OA系统

📌 核心要点:完整OA系统不是功能堆叠——架构分层是骨架、模块解耦是肌肉、数据流转是血液、安全合规是皮肤,四者合一才是活的企业应用。

背景与动机

前面9篇文章,我们分别讲了OA系统的各个模块——审批、文档、会议、邮箱、日程、通讯录、考勤、门户。每个模块单独看都能跑,但拼到一起呢?

审批模块要调通讯录找人,文档模块要调权限引擎判断谁能看,会议模块要调日程检测冲突,考勤模块要调定位和人脸识别……模块之间千丝万缕的依赖,怎么管理?

更关键的是:10个模块,每个模块3个页面,30个页面之间的导航怎么组织?数据怎么流转?状态怎么同步?错误怎么处理?

完整OA系统的核心问题不是"每个功能怎么做",而是"所有功能怎么拼"。

核心原理

完整OA系统的架构核心:模块化 + 事件总线 + 统一状态 + 全局拦截

flowchart TB
    subgraph 应用层
        A[门户模块]
        B[审批模块]
        C[文档模块]
        D[会议模块]
        E[邮箱模块]
        F[日程模块]
        G[通讯录模块]
        H[考勤模块]
    end

    subgraph 框架层
        I[模块注册中心<br/>动态加载]
        J[事件总线<br/>模块间通信]
        K[统一状态管理<br/>全局状态树]
        L[路由导航<br/>统一跳转]
    end

    subgraph 基础层
        M[权限引擎]
        N[数据同步]
        O[安全加密]
        P[消息推送]
        Q[日志采集]
    end

    subgraph 数据层
        R[本地RDB]
        S[分布式数据]
        T[云端API]
        U[文件缓存]
    end

    A --> I
    B --> I
    C --> I
    D --> I
    E --> I
    F --> I
    G --> I
    H --> I

    I --> J
    I --> K
    I --> L

    J --> M
    K --> N
    L --> O
    J --> P
    K --> Q

    M --> R
    N --> S
    O --> T
    P --> U

    classDef app fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef framework fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef base fill:#E65100,color:#fff,stroke:#BF360C
    classDef data fill:#6A1B9A,color:#fff,stroke:#4A148C

    class A,B,C,D,E,F,G,H app
    class I,J,K,L framework
    class M,N,O,P,Q base
    class R,S,T,U data

模块化架构

每个业务模块是一个独立的"微模块",有自己的页面、数据、逻辑。模块之间不直接依赖,通过事件总线通信。

模块定义:

// 每个模块必须实现的标准接口
interface OaModule {
  moduleId: string;           // 模块ID
  moduleName: string;         // 模块名称
  version: string;            // 版本号
  dependencies: string[];     // 依赖的其他模块
  routes: RouteConfig[];      // 模块的路由配置
  onInit(): void;             // 模块初始化
  onDestroy(): void;          // 模块销毁
}

事件总线

模块间通信的核心。审批模块不直接调用通讯录模块的方法,而是发送事件"需要选择审批人",通讯录模块监听这个事件并响应。

// 事件总线——解耦模块间通信
class EventBus {
  private handlers: Map<string, Function[]> = new Map();

  // 订阅事件
  on(event: string, handler: Function): void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, []);
    }
    this.handlers.get(event)?.push(handler);
  }

  // 发布事件
  emit(event: string, data?: Object): void {
    this.handlers.get(event)?.forEach(handler => handler(data));
  }
}

统一状态管理

全局状态集中管理,各模块通过状态树共享数据。

// 全局状态树
interface OaState {
  user: UserInfo;             // 当前用户
  permissions: string[];      // 权限列表
  networkStatus: 'online' | 'offline'; // 网络状态
  unreadCounts: {             // 未读数
    approval: number;
    email: number;
    notice: number;
  };
  currentModule: string;      // 当前模块
}

全局拦截

全局拦截器处理跨模块的通用逻辑:权限检查、登录验证、网络异常、数据加密。

代码实战

基础用法:OA系统框架搭建

先搭建框架——模块注册、事件总线、路由管理、全局状态。

// OaFramework.ets - OA系统框架
import { emitter } from '@kit.BasicServicesKit';
import { relationalStore } from '@kit.ArkData';

// ========== 模块定义 ==========

// 路由配置
export interface RouteConfig {
  path: string;               // 路由路径
  page: string;               // 页面路径
  title: string;              // 页面标题
  requireAuth: boolean;       // 是否需要登录
  requiredPermissions: string[]; // 需要的权限
}

// 模块接口
export interface OaModule {
  moduleId: string;
  moduleName: string;
  version: string;
  dependencies: string[];
  routes: RouteConfig[];
  onInit(): void;
  onDestroy(): void;
}

// ========== 事件总线 ==========

// 事件类型
export enum OaEvent {
  USER_LOGIN = 'user_login',
  USER_LOGOUT = 'user_logout',
  PERMISSION_CHANGED = 'permission_changed',
  NETWORK_STATUS_CHANGED = 'network_status_changed',
  APPROVAL_UPDATED = 'approval_updated',
  EMAIL_RECEIVED = 'email_received',
  SCHEDULE_CHANGED = 'schedule_changed',
  NOTICE_PUBLISHED = 'notice_published',
  ATTENDANCE_CLOCKED = 'attendance_clocked',
  CONTACT_UPDATED = 'contact_updated',
  MODULE_NAVIGATE = 'module_navigate',
}

// 事件数据
export interface OaEventData {
  event: OaEvent;
  data?: Record<string, Object>;
  timestamp: number;
  source: string;             // 事件来源模块
}

export class EventBus {
  private static instance: EventBus;
  private handlers: Map<string, Array<(data: OaEventData) => void>> = new Map();
  private eventHistory: OaEventData[] = []; // 事件历史(用于模块延迟初始化时回放)

  private constructor() {}

  static getInstance(): EventBus {
    if (!EventBus.instance) {
      EventBus.instance = new EventBus();
    }
    return EventBus.instance;
  }

  // 订阅事件
  on(event: OaEvent, handler: (data: OaEventData) => void): void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, []);
    }
    this.handlers.get(event)?.push(handler);
  }

  // 取消订阅
  off(event: OaEvent, handler: (data: OaEventData) => void): void {
    const handlers = this.handlers.get(event);
    if (handlers) {
      const index = handlers.indexOf(handler);
      if (index > -1) handlers.splice(index, 1);
    }
  }

  // 发布事件
  emit(event: OaEvent, data?: Record<string, Object>, source: string = ''): void {
    const eventData: OaEventData = {
      event,
      data,
      timestamp: Date.now(),
      source,
    };

    // 记录事件历史
    this.eventHistory.push(eventData);
    if (this.eventHistory.length > 100) {
      this.eventHistory.shift(); // 最多保留100条
    }

    // 通知所有订阅者
    this.handlers.get(event)?.forEach(handler => {
      try {
        handler(eventData);
      } catch (error) {
        console.error(`[EventBus] 事件处理异常: ${event}, ${error}`);
      }
    });

    console.info(`[EventBus] 事件已发布: ${event}`);
  }

  // 获取事件历史(用于模块延迟初始化时回放)
  getEventHistory(event?: OaEvent): OaEventData[] {
    if (event) {
      return this.eventHistory.filter(e => e.event === event);
    }
    return this.eventHistory;
  }
}

// ========== 模块注册中心 ==========

export class ModuleRegistry {
  private static instance: ModuleRegistry;
  private modules: Map<string, OaModule> = new Map();
  private initializedModules: Set<string> = new Set();

  private constructor() {}

  static getInstance(): ModuleRegistry {
    if (!ModuleRegistry.instance) {
      ModuleRegistry.instance = new ModuleRegistry();
    }
    return ModuleRegistry.instance;
  }

  // 注册模块
  register(module: OaModule): void {
    if (this.modules.has(module.moduleId)) {
      console.warn(`[ModuleRegistry] 模块已注册: ${module.moduleId}`);
      return;
    }

    this.modules.set(module.moduleId, module);
    console.info(`[ModuleRegistry] 模块已注册: ${module.moduleName} v${module.version}`);
  }

  // 初始化所有模块
  async initAll(): Promise<void> {
    // 按依赖顺序初始化
    const sorted = this.topologicalSort();
    for (const moduleId of sorted) {
      const module = this.modules.get(moduleId);
      if (module && !this.initializedModules.has(moduleId)) {
        try {
          module.onInit();
          this.initializedModules.add(moduleId);
          console.info(`[ModuleRegistry] 模块已初始化: ${module.moduleName}`);
        } catch (error) {
          console.error(`[ModuleRegistry] 模块初始化失败: ${module.moduleName}, ${error}`);
        }
      }
    }
  }

  // 获取模块
  getModule(moduleId: string): OaModule | undefined {
    return this.modules.get(moduleId);
  }

  // 获取所有路由
  getAllRoutes(): RouteConfig[] {
    const routes: RouteConfig[] = [];
    for (const module of this.modules.values()) {
      routes.push(...module.routes);
    }
    return routes;
  }

  // 拓扑排序(按依赖关系排序)
  private topologicalSort(): string[] {
    const result: string[] = [];
    const visited = new Set<string>();
    const visiting = new Set<string>();

    const visit = (moduleId: string) => {
      if (visited.has(moduleId)) return;
      if (visiting.has(moduleId)) {
        console.error(`[ModuleRegistry] 检测到循环依赖: ${moduleId}`);
        return;
      }

      visiting.add(moduleId);
      const module = this.modules.get(moduleId);
      if (module) {
        for (const dep of module.dependencies) {
          visit(dep);
        }
      }
      visiting.delete(moduleId);
      visited.add(moduleId);
      result.push(moduleId);
    };

    for (const moduleId of this.modules.keys()) {
      visit(moduleId);
    }

    return result;
  }
}

// ========== 全局状态管理 ==========

export interface OaGlobalState {
  isLoggedIn: boolean;
  userId: string;
  userName: string;
  roles: string[];
  deptId: string;
  permissions: string[];
  networkStatus: 'online' | 'offline';
  unreadCounts: {
    approval: number;
    email: number;
    notice: number;
  };
}

type StateChangeListener = (state: OaGlobalState) => void;

export class StateManager {
  private static instance: StateManager;
  private state: OaGlobalState = {
    isLoggedIn: false,
    userId: '',
    userName: '',
    roles: [],
    deptId: '',
    permissions: [],
    networkStatus: 'online',
    unreadCounts: { approval: 0, email: 0, notice: 0 },
  };
  private listeners: StateChangeListener[] = [];

  private constructor() {}

  static getInstance(): StateManager {
    if (!StateManager.instance) {
      StateManager.instance = new StateManager();
    }
    return StateManager.instance;
  }

  // 获取当前状态
  getState(): OaGlobalState {
    return { ...this.state };
  }

  // 更新状态
  updateState(partial: Partial<OaGlobalState>): void {
    this.state = { ...this.state, ...partial };
    this.notifyListeners();
  }

  // 更新未读数
  updateUnreadCount(key: keyof OaGlobalState['unreadCounts'], count: number): void {
    this.state.unreadCounts[key] = count;
    this.notifyListeners();
  }

  // 监听状态变化
  subscribe(listener: StateChangeListener): void {
    this.listeners.push(listener);
  }

  // 取消监听
  unsubscribe(listener: StateChangeListener): void {
    const index = this.listeners.indexOf(listener);
    if (index > -1) this.listeners.splice(index, 1);
  }

  // 通知监听者
  private notifyListeners(): void {
    const state = this.getState();
    for (const listener of this.listeners) {
      try {
        listener(state);
      } catch (error) {
        console.error(`[StateManager] 状态监听异常: ${error}`);
      }
    }
  }
}

// ========== 全局拦截器 ==========

export class GlobalInterceptor {
  private static instance: GlobalInterceptor;
  private eventBus: EventBus = EventBus.getInstance();
  private stateManager: StateManager = StateManager.getInstance();

  private constructor() {}

  static getInstance(): GlobalInterceptor {
    if (!GlobalInterceptor.instance) {
      GlobalInterceptor.instance = new GlobalInterceptor();
    }
    return GlobalInterceptor.instance;
  }

  // 初始化全局拦截
  init(): void {
    // 监听网络状态变化
    this.eventBus.on(OaEvent.NETWORK_STATUS_CHANGED, (data: OaEventData) => {
      const status = data.data?.['status'] as 'online' | 'offline';
      this.stateManager.updateState({ networkStatus: status });

      if (status === 'offline') {
        console.warn('[Interceptor] 网络已断开,切换到离线模式');
      } else {
        console.info('[Interceptor] 网络已恢复,开始同步数据');
      }
    });

    // 监听权限变化
    this.eventBus.on(OaEvent.PERMISSION_CHANGED, (data: OaEventData) => {
      const permissions = data.data?.['permissions'] as string[];
      this.stateManager.updateState({ permissions });
    });

    console.info('[Interceptor] 全局拦截器已初始化');
  }

  // 检查权限
  checkPermission(required: string[]): boolean {
    const state = this.stateManager.getState();
    if (required.length === 0) return true;
    return required.every(p => state.permissions.includes(p));
  }

  // 检查登录状态
  checkLogin(): boolean {
    return this.stateManager.getState().isLoggedIn;
  }
}

进阶用法:各模块注册与数据流转

把审批、文档、会议等模块注册到框架中,通过事件总线实现数据流转。

// OaModules.ets - OA系统各模块注册

import {
  OaModule, RouteConfig, EventBus, OaEvent,
  ModuleRegistry, StateManager
} from './OaFramework';

// ========== 审批模块 ==========
class ApprovalModule implements OaModule {
  moduleId = 'approval';
  moduleName = '审批中心';
  version = '1.0.0';
  dependencies = ['contacts']; // 依赖通讯录模块(选择审批人)

  routes: RouteConfig[] = [
    { path: '/approval/list', page: 'pages/ApprovalPage', title: '审批列表', requireAuth: true, requiredPermissions: [] },
    { path: '/approval/detail', page: 'pages/ApprovalDetailPage', title: '审批详情', requireAuth: true, requiredPermissions: [] },
    { path: '/approval/create', page: 'pages/ApprovalCreatePage', title: '发起审批', requireAuth: true, requiredPermissions: ['approval:create'] },
  ];

  private eventBus = EventBus.getInstance();

  onInit(): void {
    // 监听审批相关事件
    this.eventBus.on(OaEvent.APPROVAL_UPDATED, (data) => {
      console.info('[Approval] 审批状态更新');
      // 更新未读数
      StateManager.getInstance().updateUnreadCount('approval', data.data?.['unreadCount'] as number || 0);
    });

    // 监听日程变更——会议审批可能影响日程
    this.eventBus.on(OaEvent.SCHEDULE_CHANGED, (data) => {
      console.info('[Approval] 日程变更,检查是否有待审批的会议申请');
    });
  }

  onDestroy(): void {
    console.info('[Approval] 模块销毁');
  }
}

// ========== 文档模块 ==========
class DocumentModule implements OaModule {
  moduleId = 'document';
  moduleName = '文档管理';
  version = '1.0.0';
  dependencies = [];

  routes: RouteConfig[] = [
    { path: '/document/list', page: 'pages/DocumentPage', title: '文档列表', requireAuth: true, requiredPermissions: [] },
    { path: '/document/detail', page: 'pages/DocumentDetailPage', title: '文档详情', requireAuth: true, requiredPermissions: [] },
  ];

  onInit(): void {
    console.info('[Document] 模块初始化');
  }

  onDestroy(): void {
    console.info('[Document] 模块销毁');
  }
}

// ========== 会议模块 ==========
class MeetingModule implements OaModule {
  moduleId = 'meeting';
  moduleName = '视频会议';
  version = '1.0.0';
  dependencies = ['contacts', 'schedule']; // 依赖通讯录和日程

  routes: RouteConfig[] = [
    { path: '/meeting/list', page: 'pages/MeetingListPage', title: '会议列表', requireAuth: true, requiredPermissions: [] },
    { path: '/meeting/room', page: 'pages/MeetingPage', title: '会议室', requireAuth: true, requiredPermissions: [] },
  ];

  private eventBus = EventBus.getInstance();

  onInit(): void {
    // 会议创建后通知日程模块
    // 日程模块会自动在日历上创建对应的日程
    this.eventBus.on(OaEvent.SCHEDULE_CHANGED, (data) => {
      console.info('[Meeting] 日程变更,检查会议时间冲突');
    });
  }

  onDestroy(): void {
    console.info('[Meeting] 模块销毁');
  }
}

// ========== 邮箱模块 ==========
class EmailModule implements OaModule {
  moduleId = 'email';
  moduleName = '企业邮箱';
  version = '1.0.0';
  dependencies = ['contacts'];

  routes: RouteConfig[] = [
    { path: '/email/list', page: 'pages/EmailPage', title: '收件箱', requireAuth: true, requiredPermissions: [] },
    { path: '/email/compose', page: 'pages/EmailComposePage', title: '写邮件', requireAuth: true, requiredPermissions: [] },
  ];

  private eventBus = EventBus.getInstance();

  onInit(): void {
    this.eventBus.on(OaEvent.EMAIL_RECEIVED, (data) => {
      console.info('[Email] 新邮件到达');
      StateManager.getInstance().updateUnreadCount('email', data.data?.['unreadCount'] as number || 0);
    });
  }

  onDestroy(): void {
    console.info('[Email] 模块销毁');
  }
}

// ========== 日程模块 ==========
class ScheduleModule implements OaModule {
  moduleId = 'schedule';
  moduleName = '日程管理';
  version = '1.0.0';
  dependencies = [];

  routes: RouteConfig[] = [
    { path: '/schedule/view', page: 'pages/SchedulePage', title: '日程', requireAuth: true, requiredPermissions: [] },
  ];

  private eventBus = EventBus.getInstance();

  onInit(): void {
    // 监听会议创建事件——自动创建日程
    this.eventBus.on(OaEvent.MODULE_NAVIGATE, (data) => {
      if (data.data?.['target'] === 'meeting_created') {
        console.info('[Schedule] 会议已创建,同步到日程');
      }
    });
  }

  onDestroy(): void {
    console.info('[Schedule] 模块销毁');
  }
}

// ========== 通讯录模块 ==========
class ContactsModule implements OaModule {
  moduleId = 'contacts';
  moduleName = '企业通讯录';
  version = '1.0.0';
  dependencies = [];

  routes: RouteConfig[] = [
    { path: '/contacts/list', page: 'pages/ContactsPage', title: '通讯录', requireAuth: true, requiredPermissions: [] },
  ];

  private eventBus = EventBus.getInstance();

  onInit(): void {
    this.eventBus.on(OaEvent.CONTACT_UPDATED, (data) => {
      console.info('[Contacts] 通讯录数据更新');
    });
  }

  onDestroy(): void {
    console.info('[Contacts] 模块销毁');
  }
}

// ========== 考勤模块 ==========
class AttendanceModule implements OaModule {
  moduleId = 'attendance';
  moduleName = '移动考勤';
  version = '1.0.0';
  dependencies = [];

  routes: RouteConfig[] = [
    { path: '/attendance/clock', page: 'pages/AttendancePage', title: '考勤打卡', requireAuth: true, requiredPermissions: [] },
  ];

  private eventBus = EventBus.getInstance();

  onInit(): void {
    this.eventBus.on(OaEvent.ATTENDANCE_CLOCKED, (data) => {
      console.info('[Attendance] 打卡成功');
    });
  }

  onDestroy(): void {
    console.info('[Attendance] 模块销毁');
  }
}

// ========== 门户模块 ==========
class PortalModule implements OaModule {
  moduleId = 'portal';
  moduleName = '企业门户';
  version = '1.0.0';
  dependencies = []; // 门户是入口,不依赖其他模块

  routes: RouteConfig[] = [
    { path: '/portal/home', page: 'pages/PortalPage', title: '首页', requireAuth: true, requiredPermissions: [] },
  ];

  onInit(): void {
    console.info('[Portal] 门户模块初始化');
  }

  onDestroy(): void {
    console.info('[Portal] 门户模块销毁');
  }
}

// ========== 模块注册 ==========

export function registerAllModules(): void {
  const registry = ModuleRegistry.getInstance();

  // 按依赖顺序注册(先注册被依赖的模块)
  registry.register(new PortalModule());
  registry.register(new ContactsModule());
  registry.register(new ScheduleModule());
  registry.register(new DocumentModule());
  registry.register(new ApprovalModule());
  registry.register(new MeetingModule());
  registry.register(new EmailModule());
  registry.register(new AttendanceModule());

  console.info('[OA] 所有模块已注册');
}

完整示例:OA系统入口与初始化

把框架、模块、拦截器串起来,完成OA系统的启动流程。

// OaEntry.ets - OA系统入口
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { ModuleRegistry, EventBus, OaEvent, StateManager, GlobalInterceptor } from '../framework/OaFramework';
import { registerAllModules } from '../modules/OaModules';
import { PermissionEngine } from '../security/PermissionEngine';
import { DataSyncManager } from '../sync/DataSyncManager';
import { CrashCollector } from '../crash/CrashCollector';

export default class OaEntry extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 1. 最先初始化崩溃采集
    CrashCollector.getInstance().init(this.context);

    // 2. 初始化全局状态
    StateManager.getInstance().updateState({
      isLoggedIn: false,
      networkStatus: 'online',
    });

    // 3. 初始化权限引擎
    PermissionEngine.getInstance().init(this.context);

    // 4. 注册所有业务模块
    registerAllModules();

    // 5. 初始化全局拦截器
    GlobalInterceptor.getInstance().init();

    // 6. 初始化数据同步
    DataSyncManager.getInstance().init(this.context);

    console.info('[OA] 系统初始化完成');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 加载首页
    windowStage.loadContent('pages/OaMainPage', (err) => {
      if (err.code) {
        console.error(`[OA] 加载首页失败: ${JSON.stringify(err)}`);
        return;
      }
      console.info('[OA] 首页加载完成');
    });
  }
}
// OaMainPage.ets - OA主页面(底部导航+模块容器)
import { ModuleRegistry, EventBus, OaEvent, StateManager, OaGlobalState } from '../framework/OaFramework';

@Entry
@Component
struct OaMainPage {
  @State selectedIndex: number = 0;
  @State unreadApproval: number = 0;
  @State unreadEmail: number = 0;
  @State unreadNotice: number = 0;
  @State isLoggedIn: boolean = false;
  @State networkStatus: string = 'online';

  private eventBus: EventBus = EventBus.getInstance();
  private stateManager: StateManager = StateManager.getInstance();

  // 底部导航项
  private tabs: TabItem[] = [
    { title: '首页', icon: $r('app.media.ic_home'), activeIcon: $r('app.media.ic_home_selected') },
    { title: '审批', icon: $r('app.media.ic_approval'), activeIcon: $r('app.media.ic_approval_selected') },
    { title: '消息', icon: $r('app.media.ic_message'), activeIcon: $r('app.media.ic_message_selected') },
    { title: '日程', icon: $r('app.media.ic_schedule'), activeIcon: $r('app.media.ic_schedule_selected') },
    { title: '我的', icon: $r('app.media.ic_profile'), activeIcon: $r('app.media.ic_profile_selected') },
  ];

  aboutToAppear(): void {
    // 监听全局状态变化
    this.stateManager.subscribe((state: OaGlobalState) => {
      this.unreadApproval = state.unreadCounts.approval;
      this.unreadEmail = state.unreadCounts.email;
      this.unreadNotice = state.unreadCounts.notice;
      this.isLoggedIn = state.isLoggedIn;
      this.networkStatus = state.networkStatus;
    });

    // 模拟登录
    this.handleLogin();
  }

  build() {
    Column() {
      // 网络状态提示
      if (this.networkStatus === 'offline') {
        Row() {
          Text('网络已断开,部分功能不可用')
            .fontSize(12)
            .fontColor('#FFFFFF')
        }
        .width('100%')
        .height(24)
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#FF9800')
      }

      // 内容区域
      Tabs({ index: this.selectedIndex }) {
        TabContent() { this.PortalContent() }.tabBar(this.TabBar(0))
        TabContent() { this.ApprovalContent() }.tabBar(this.TabBar(1))
        TabContent() { this.MessageContent() }.tabBar(this.TabBar(2))
        TabContent() { this.ScheduleContent() }.tabBar(this.TabBar(3))
        TabContent() { this.ProfileContent() }.tabBar(this.TabBar(4))
      }
      .layoutWeight(1)
      .onChange((index: number) => {
        this.selectedIndex = index;
      })
      .barPosition(BarPosition.End)
      .scrollable(false)
    }
    .width('100%')
    .height('100%')
  }

  // 底部Tab栏
  @Builder
  TabBar(index: number) {
    Column() {
      Badge({
        count: index === 1 ? this.unreadApproval : index === 2 ? this.unreadEmail + this.unreadNotice : 0,
        position: BadgePosition.RightTop,
        maxCount: 99,
      }) {
        Image(index === this.selectedIndex ? this.tabs[index].activeIcon : this.tabs[index].icon)
          .width(24).height(24)
      }

      Text(this.tabs[index].title)
        .fontSize(10)
        .fontColor(index === this.selectedIndex ? '#1565C0' : '#999999')
        .margin({ top: 2 })
    }
    .width('100%')
    .padding({ top: 4, bottom: 4 })
  }

  // ========== 各Tab内容 ==========

  @Builder
  PortalContent() {
    Column() {
      Text('企业门户')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      Text('公告、新闻、快捷入口')
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F5F5F5')
  }

  @Builder
  ApprovalContent() {
    Column() {
      Text('审批中心')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      Text(`待审批: ${this.unreadApproval}`)
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F5F5F5')
  }

  @Builder
  MessageContent() {
    Column() {
      Text('消息中心')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      Text(`未读: ${this.unreadEmail + this.unreadNotice}`)
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F5F5F5')
  }

  @Builder
  ScheduleContent() {
    Column() {
      Text('日程管理')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      Text('日历、日程、提醒')
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor('#F5F5F5')
  }

  @Builder
  ProfileContent() {
    Column() {
      // 用户信息
      Column() {
        Text('张三')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Text('研发部 · 部门经理')
          .fontSize(14)
          .fontColor('#999999')
          .margin({ top: 4 })
      }
      .padding(24)

      // 功能列表
      List() {
        ListItem() {
          Row() {
            Text('企业通讯录')
              .fontSize(15)
              .layoutWeight(1)
            Image($r('app.media.ic_chevron_right')).width(16).height(16).fillColor('#CCCCCC')
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 14, bottom: 14 })
        }

        ListItem() {
          Row() {
            Text('文档管理')
              .fontSize(15)
              .layoutWeight(1)
            Image($r('app.media.ic_chevron_right')).width(16).height(16).fillColor('#CCCCCC')
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 14, bottom: 14 })
        }

        ListItem() {
          Row() {
            Text('考勤打卡')
              .fontSize(15)
              .layoutWeight(1)
            Image($r('app.media.ic_chevron_right')).width(16).height(16).fillColor('#CCCCCC')
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 14, bottom: 14 })
        }

        ListItem() {
          Row() {
            Text('企业邮箱')
              .fontSize(15)
              .layoutWeight(1)
            Image($r('app.media.ic_chevron_right')).width(16).height(16).fillColor('#CCCCCC')
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 14, bottom: 14 })
        }

        ListItem() {
          Row() {
            Text('视频会议')
              .fontSize(15)
              .layoutWeight(1)
            Image($r('app.media.ic_chevron_right')).width(16).height(16).fillColor('#CCCCCC')
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 14, bottom: 14 })
        }

        ListItem() {
          Row() {
            Text('设置')
              .fontSize(15)
              .layoutWeight(1)
            Image($r('app.media.ic_chevron_right')).width(16).height(16).fillColor('#CCCCCC')
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 14, bottom: 14 })
        }

        ListItem() {
          Row() {
            Text('退出登录')
              .fontSize(15)
              .fontColor('#C62828')
              .layoutWeight(1)
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 14, bottom: 14 })
          .onClick(() => this.handleLogout())
        }
      }
      .backgroundColor(Color.White)
      .borderRadius(12)
      .margin({ top: 16, left: 16, right: 16 })
      .divider({ strokeWidth: 0.5, color: '#F0F0F0', startMargin: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  // ========== 业务方法 ==========

  private handleLogin(): void {
    // 模拟登录
    this.stateManager.updateState({
      isLoggedIn: true,
      userId: 'user_001',
      userName: '张三',
      roles: ['经理', '研发'],
      deptId: 'dept_rd',
      permissions: ['approval:create', 'approval:approve', 'document:edit'],
      unreadCounts: { approval: 5, email: 12, notice: 3 },
    });

    // 初始化所有模块
    ModuleRegistry.getInstance().initAll();

    // 触发登录事件
    this.eventBus.emit(OaEvent.USER_LOGIN, {
      userId: 'user_001',
    }, 'system');
  }

  private handleLogout(): void {
    this.stateManager.updateState({
      isLoggedIn: false,
      userId: '',
      userName: '',
      roles: [],
      permissions: [],
    });

    this.eventBus.emit(OaEvent.USER_LOGOUT, {}, 'system');
  }
}

// Tab项
interface TabItem {
  title: string;
  icon: Resource;
  activeIcon: Resource;
}

踩坑与注意事项

坑1:模块初始化顺序

模块有依赖关系——审批模块依赖通讯录模块。如果审批模块先初始化,调用通讯录接口时通讯录还没准备好,就会报错。

解决方案:用拓扑排序确保按依赖顺序初始化。上面的ModuleRegistry已经实现了这个逻辑。

坑2:事件总线的内存泄漏

模块销毁时没有取消事件订阅,导致已销毁模块的事件处理器还在执行,访问已释放的数据,直接崩溃。

解决方案:每个模块在onDestroy中必须取消所有事件订阅。或者用弱引用(WeakRef)存储事件处理器。

坑3:全局状态的性能

每次状态更新都通知所有监听者,如果监听者太多或更新太频繁,UI会卡顿。

解决方案:状态更新做节流(throttle),100ms内只通知一次。监听者做差异化更新,只关心自己需要的字段。

坑4:底部导航的页面保持

5个Tab页面,切换Tab时页面重新创建?还是保持状态?

建议:保持状态。用户在审批页面填了一半表单,切到消息Tab看一眼,再切回来表单还在。用Tabs组件的cachedCount属性控制缓存数量。

坑5:多端适配的导航结构

手机用底部Tab导航,平板用侧边栏导航,PC用顶部导航+侧边栏。导航结构完全不同,怎么统一?

建议:导航结构由布局层决定,业务页面不关心导航方式。用GridRow断点系统自动切换布局,页面组件只负责内容。

坑6:离线模式的数据一致性

断网时用户在审批页面同意了一个审批,但数据没同步到服务端。另一台设备上看这个审批还是"待审批"状态。

解决方案:离线操作标记为"pending"状态,联网后自动同步。同步时做冲突检测,如果服务端状态已变更,弹窗让用户确认。

HarmonyOS 6适配说明

HarmonyOS 6对完整OA系统的增强:

  1. 模块动态加载:支持HSP(Harmony Shared Package)动态加载模块,不用的模块不加载,减少启动时间和内存占用。

  2. 跨设备状态同步:全局状态可以在设备间实时同步。手机上审批了一个请求,平板上状态立即更新。

  3. AI辅助工作流:AI自动识别用户操作意图,推荐下一步操作。审批完请假申请后,自动推荐"查看团队考勤"。

  4. 企业安全沙箱:OA应用运行在独立的企业安全沙箱中,个人应用无法访问企业数据,企业应用也无法访问个人数据。

  5. 智能通知聚合:多个模块的通知自动聚合,避免频繁打扰。5分钟内的多条通知合并为一条摘要。

总结

完整OA系统的核心不是功能堆叠,而是架构设计。模块化让每个功能独立开发,事件总线让模块间松耦合,统一状态让数据一致,全局拦截让安全和权限贯穿始终。

核心记住三点:

  • 模块间用事件通信,不要直接调用,直接调用就是硬耦合,改一个模块牵连一片
  • 初始化顺序按依赖关系排,拓扑排序是标准解法
  • 全局状态要节制更新,每次更新都触发UI刷新,更新太频繁就卡了
评估维度 说明
学习难度 ⭐⭐⭐⭐⭐ 架构设计需要全局视野,模块化、事件总线、状态管理都是进阶概念
使用频率 ⭐⭐⭐⭐⭐ OA系统是日常办公的核心,每天都在用
重要程度 ⭐⭐⭐⭐⭐ 架构不对,代码三个月就腐化;架构对了,十年还能维护

做OA系统,先画架构图,再写代码。架构图没画清楚就动手,写到最后你会发现——你不是在做OA,你是在给自己挖一个填不完的坑。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。