HarmonyOS开发:企业应用实战——完整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系统的增强:
-
模块动态加载:支持HSP(Harmony Shared Package)动态加载模块,不用的模块不加载,减少启动时间和内存占用。
-
跨设备状态同步:全局状态可以在设备间实时同步。手机上审批了一个请求,平板上状态立即更新。
-
AI辅助工作流:AI自动识别用户操作意图,推荐下一步操作。审批完请假申请后,自动推荐"查看团队考勤"。
-
企业安全沙箱:OA应用运行在独立的企业安全沙箱中,个人应用无法访问企业数据,企业应用也无法访问个人数据。
-
智能通知聚合:多个模块的通知自动聚合,避免频繁打扰。5分钟内的多条通知合并为一条摘要。
总结
完整OA系统的核心不是功能堆叠,而是架构设计。模块化让每个功能独立开发,事件总线让模块间松耦合,统一状态让数据一致,全局拦截让安全和权限贯穿始终。
核心记住三点:
- 模块间用事件通信,不要直接调用,直接调用就是硬耦合,改一个模块牵连一片
- 初始化顺序按依赖关系排,拓扑排序是标准解法
- 全局状态要节制更新,每次更新都触发UI刷新,更新太频繁就卡了
| 评估维度 | 说明 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐⭐ 架构设计需要全局视野,模块化、事件总线、状态管理都是进阶概念 |
| 使用频率 | ⭐⭐⭐⭐⭐ OA系统是日常办公的核心,每天都在用 |
| 重要程度 | ⭐⭐⭐⭐⭐ 架构不对,代码三个月就腐化;架构对了,十年还能维护 |
做OA系统,先画架构图,再写代码。架构图没画清楚就动手,写到最后你会发现——你不是在做OA,你是在给自己挖一个填不完的坑。
- 点赞
- 收藏
- 关注作者
评论(0)