HarmonyOS开发:企业OA应用——办公自动化

举报
Jack20 发表于 2026/06/26 16:43:49 2026/06/26
【摘要】 HarmonyOS开发:企业OA应用——办公自动化📌 核心要点:企业OA不是简单地把网页搬到手机上——鸿蒙端OA要解决的是架构分层、数据隔离、安全合规和跨端协同,一套代码跑手机跑平板跑PC,这才是鸿蒙OA的正确打开方式。 背景与动机你接到一个需求:给公司做一个OA App。你心想,不就是登录、审批、公告、考勤那点事吗?一周搞定。结果刚开干就傻眼了——老板说:手机上能用,平板上也要能用,最...

HarmonyOS开发:企业OA应用——办公自动化

📌 核心要点:企业OA不是简单地把网页搬到手机上——鸿蒙端OA要解决的是架构分层、数据隔离、安全合规和跨端协同,一套代码跑手机跑平板跑PC,这才是鸿蒙OA的正确打开方式。

背景与动机

你接到一个需求:给公司做一个OA App。你心想,不就是登录、审批、公告、考勤那点事吗?一周搞定。

结果刚开干就傻眼了——

老板说:手机上能用,平板上也要能用,最好PC端也能用。你一个App要适配三种屏幕。

安全说:企业数据不能明文存储,离线数据要加密,截屏要防,分享要管。你一个App要过安全审计。

运维说:不同部门看到的菜单不一样,不同职级能操作的功能不一样,领导要看全局报表,员工只看自己的。你一个App要管十几种角色。

IT说:接口要对接现有的ERP、HR、财务系统,数据格式各不相同,同步策略要考虑断网情况。你一个App要跟五六个系统打交道。

一个"简单"的OA App,背后是企业级架构设计、安全合规、数据隔离、多端适配、系统集成五大难题。哪个处理不好,上线就是事故。

鸿蒙做OA有个天然优势——一次开发多端部署。手机、平板、PC、甚至智慧屏,一套ArkTS代码搞定。但这个优势要发挥出来,架构设计必须从一开始就做对。

核心原理

企业OA的架构核心:分层解耦 + 数据隔离 + 权限驱动 + 跨端适配

flowchart TB
    subgraph 展示层
        A[手机端<br/>紧凑布局]
        B[平板端<br/>分栏布局]
        C[PC<br/>多栏布局]
    end

    subgraph 业务层
        D[审批模块]
        E[文档模块]
        F[考勤模块]
        G[通讯录模块]
        H[会议模块]
    end

    subgraph 能力层
        I[权限引擎]
        J[数据同步]
        K[安全加密]
        L[消息推送]
    end

    subgraph 数据层
        M[本地RDB]
        N[分布式数据]
        O[云端API]
        P[文件缓存]
    end

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

    D --> I & J & K & L
    E --> I & J & K & L
    F --> I & J & K & L
    G --> I & J & K & L
    H --> I & J & K & L

    I --> M & N & O & P
    J --> M & N & O & P
    K --> M & N & O & P
    L --> M & N & O & P

    classDef ui fill:#1565C0,color:#fff,stroke:#0D47A1
    classDef biz fill:#2E7D32,color:#fff,stroke:#1B5E20
    classDef cap fill:#E65100,color:#fff,stroke:#BF360C
    classDef data fill:#6A1B9A,color:#fff,stroke:#4A148C

    class A,B,C ui
    class D,E,F,G,H biz
    class I,J,K,L cap
    class M,N,O,P data

四层架构详解

展示层:根据设备类型自动切换布局。手机用Stack导航,平板用Split导航,PC用侧边栏+多面板。同一个页面组件,不同设备渲染不同的布局。

业务层:每个业务模块独立,模块之间通过事件总线通信,不直接依赖。审批模块挂了不影响考勤模块,文档模块升级不影响通讯录。

能力层:公共能力下沉。权限判断、数据同步、加密解密、消息推送,所有业务模块共用。改一次,全局生效。

数据层:本地RDB存离线数据,分布式数据跨设备同步,云端API对接后端,文件缓存管理附件。

数据隔离策略

企业OA最敏感的问题:数据隔离。销售看不到研发的文档,普通员工看不到薪资审批,实习生只能看公告不能看报表。

数据隔离分三层:

  1. 租户隔离:不同公司(多租户场景)的数据物理隔离,数据库都不在同一个
  2. 部门隔离:同一公司不同部门的数据逻辑隔离,通过权限过滤
  3. 角色隔离:同一部门不同角色的数据权限隔离,通过菜单+数据双重控制

技术选型

技术点 推荐方案 理由
UI框架 ArkUI声明式 一套代码多端部署
本地存储 RDB关系数据库 结构化数据,支持复杂查询
跨设备同步 分布式数据对象 实时同步,自动冲突解决
网络请求 @kit.NetworkKit HTTP/HTTPS/WebSocket全支持
安全加密 @kit.CryptoArchitectureKit 国密算法+AES,过等保
消息推送 Push Kit 离线消息不丢失
文件管理 @kit.CoreFileKit 沙箱隔离,安全合规

代码实战

基础用法:OA应用架构搭建

先把骨架搭起来——分层架构、路由管理、权限控制。

// OaApp.ets - OA应用入口与架构管理
import { UIAbility, AbilityConstant, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { relationalStore } from '@kit.ArkData';
import { OaRouter } from '../router/OaRouter';
import { PermissionEngine } from '../security/PermissionEngine';
import { DataSyncManager } from '../sync/DataSyncManager';

export default class OaApp extends UIAbility {
  // 全局数据库实例
  private rdbStore: relationalStore.RdbStore | null = null;

  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 1. 初始化数据库
    this.initDatabase();
    // 2. 初始化权限引擎
    PermissionEngine.getInstance().init(this.context);
    // 3. 初始化数据同步
    DataSyncManager.getInstance().init(this.context);
    // 4. 初始化路由
    OaRouter.getInstance().init();
  }

  // 初始化本地数据库
  private async initDatabase(): Promise<void> {
    const config: relationalStore.StoreConfig = {
      name: 'oa_database.db',
      securityLevel: relationalStore.SecurityLevel.S3, // 企业级安全等级
      encrypt: true, // 开启加密
    };

    try {
      this.rdbStore = await relationalStore.getRdbStore(this.context, config);
      // 创建核心表
      await this.createTables();
      console.info('[OaApp] 数据库初始化完成');
    } catch (error) {
      console.error(`[OaApp] 数据库初始化失败: ${JSON.stringify(error)}`);
    }
  }

  // 创建核心数据表
  private async createTables(): Promise<void> {
    if (!this.rdbStore) return;

    // 用户信息表
    const createUserTable = `
      CREATE TABLE IF NOT EXISTS user_info (
        user_id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        dept_id TEXT NOT NULL,
        role TEXT NOT NULL,
        avatar_url TEXT,
        phone TEXT,
        email TEXT,
        last_login INTEGER,
        status INTEGER DEFAULT 1
      )`;

    // 审批记录表
    const createApprovalTable = `
      CREATE TABLE IF NOT EXISTS approval_record (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        approval_id TEXT UNIQUE NOT NULL,
        title TEXT NOT NULL,
        type TEXT NOT NULL,
        applicant_id TEXT NOT NULL,
        current_node TEXT,
        status TEXT DEFAULT 'pending',
        create_time INTEGER,
        update_time INTEGER
      )`;

    // 公告表
    const createNoticeTable = `
      CREATE TABLE IF NOT EXISTS notice (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        notice_id TEXT UNIQUE NOT NULL,
        title TEXT NOT NULL,
        content TEXT,
        publisher_id TEXT NOT NULL,
        publish_time INTEGER,
        is_read INTEGER DEFAULT 0,
        priority TEXT DEFAULT 'normal'
      )`;

    try {
      await this.rdbStore.executeSql(createUserTable);
      await this.rdbStore.executeSql(createApprovalTable);
      await this.rdbStore.executeSql(createNoticeTable);
      console.info('[OaApp] 数据表创建完成');
    } catch (error) {
      console.error(`[OaApp] 建表失败: ${JSON.stringify(error)}`);
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error(`[OaApp] 加载首页失败: ${JSON.stringify(err)}`);
        return;
      }
      console.info('[OaApp] 首页加载完成');
    });
  }
}

进阶用法:权限引擎与数据隔离

权限是OA的命脉。谁能看什么、能做什么,全靠权限引擎控制。

// PermissionEngine.ets - 权限引擎
import { common } from '@kit.AbilityKit';
import { relationalStore } from '@kit.ArkData';
import { emitter } from '@kit.BasicServicesKit';

// 权限类型
export enum PermissionType {
  MENU = 'menu',         // 菜单权限
  DATA = 'data',         // 数据权限
  OPERATION = 'operation' // 操作权限
}

// 权限结果
export interface PermissionResult {
  allowed: boolean;
  reason?: string;
}

// 角色定义
export interface Role {
  roleId: string;
  roleName: string;
  menuIds: string[];      // 可访问菜单
  dataScope: string[];    // 数据范围(部门ID列表)
  operations: string[];   // 可执行操作
}

export class PermissionEngine {
  private static instance: PermissionEngine;
  private context: common.UIAbilityContext | null = null;
  private currentRole: Role | null = null;
  private currentUserId: string = '';
  private currentDeptId: string = '';

  // 权限缓存,避免频繁查库
  private permissionCache: Map<string, PermissionResult> = new Map();

  private constructor() {}

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

  init(context: common.UIAbilityContext): void {
    this.context = context;
  }

  // 设置当前用户信息
  setCurrentUser(userId: string, deptId: string, role: Role): void {
    this.currentUserId = userId;
    this.currentDeptId = deptId;
    this.currentRole = role;
    this.permissionCache.clear(); // 切换用户时清空缓存
    console.info(`[Permission] 用户切换: ${userId}, 角色: ${role.roleName}`);
  }

  // 检查菜单权限
  checkMenuPermission(menuId: string): PermissionResult {
    const cacheKey = `menu_${menuId}`;
    const cached = this.permissionCache.get(cacheKey);
    if (cached) return cached;

    if (!this.currentRole) {
      return { allowed: false, reason: '未登录' };
    }

    const allowed = this.currentRole.menuIds.includes(menuId);
    const result: PermissionResult = {
      allowed,
      reason: allowed ? undefined : `角色[${this.currentRole.roleName}]无权访问菜单[${menuId}]`
    };

    this.permissionCache.set(cacheKey, result);
    return result;
  }

  // 检查数据权限——某个部门的数据当前用户能不能看
  checkDataPermission(targetDeptId: string): PermissionResult {
    if (!this.currentRole) {
      return { allowed: false, reason: '未登录' };
    }

    // 自己部门的数据一定能看
    if (targetDeptId === this.currentDeptId) {
      return { allowed: true };
    }

    // 检查数据范围
    const allowed = this.currentRole.dataScope.includes(targetDeptId);
    return {
      allowed,
      reason: allowed ? undefined : `角色[${this.currentRole.roleName}]无权查看部门[${targetDeptId}]的数据`
    };
  }

  // 检查操作权限
  checkOperationPermission(operation: string): PermissionResult {
    if (!this.currentRole) {
      return { allowed: false, reason: '未登录' };
    }

    const allowed = this.currentRole.operations.includes(operation);
    return {
      allowed,
      reason: allowed ? undefined : `角色[${this.currentRole.roleName}]无权执行操作[${operation}]`
    };
  }

  // 过滤菜单列表——根据权限返回用户可见菜单
  filterMenus(allMenus: MenuConfig[]): MenuConfig[] {
    return allMenus.filter(menu => {
      const result = this.checkMenuPermission(menu.id);
      if (!result.allowed) {
        console.info(`[Permission] 菜单已过滤: ${menu.id} - ${result.reason}`);
      }
      return result.allowed;
    });
  }

  // 获取当前用户的数据可见部门列表
  getVisibleDeptIds(): string[] {
    if (!this.currentRole) return [];
    return this.currentRole.dataScope;
  }
}

// 菜单配置
export interface MenuConfig {
  id: string;
  title: string;
  icon: Resource;
  page: string;
  children?: MenuConfig[];
}

完整示例:多端适配的OA首页

把架构、权限、多端适配串起来,做一个完整的OA首页。

// Index.ets - OA首页(多端适配)
import { PermissionEngine, MenuConfig } from '../security/PermissionEngine';
import { BreakpointType } from '@kit.ArkUI';

// 全部菜单配置——根据角色动态过滤
const ALL_MENUS: MenuConfig[] = [
  { id: 'approval', title: '审批中心', icon: $r('app.media.ic_approval'), page: 'pages/ApprovalPage' },
  { id: 'document', title: '文档管理', icon: $r('app.media.ic_document'), page: 'pages/DocumentPage' },
  { id: 'attendance', title: '移动考勤', icon: $r('app.media.ic_attendance'), page: 'pages/AttendancePage' },
  { id: 'contacts', title: '企业通讯录', icon: $r('app.media.ic_contacts'), page: 'pages/ContactsPage' },
  { id: 'meeting', title: '视频会议', icon: $r('app.media.ic_meeting'), page: 'pages/MeetingPage' },
  { id: 'email', title: '企业邮箱', icon: $r('app.media.ic_email'), page: 'pages/EmailPage' },
  { id: 'schedule', title: '日程管理', icon: $r('app.media.ic_schedule'), page: 'pages/SchedulePage' },
  { id: 'portal', title: '企业门户', icon: $r('app.media.ic_portal'), page: 'pages/PortalPage' },
  { id: 'report', title: '数据报表', icon: $r('app.media.ic_report'), page: 'pages/ReportPage' },
  { id: 'admin', title: '系统管理', icon: $r('app.media.ic_admin'), page: 'pages/AdminPage' },
];

@Entry
@Component
struct Index {
  @State currentBreakpoint: string = 'sm';
  @State visibleMenus: MenuConfig[] = [];
  @State selectedIndex: number = 0;
  @State userInfo: UserInfo = {
    userId: '', name: '', deptName: '', avatarUrl: '', role: ''
  };

  // 侧边栏控制器(平板和PC端用)
  private sideBarCtrl: SideBarContainerController = new SideBarContainerController();

  aboutToAppear(): void {
    // 根据权限过滤菜单
    this.visibleMenus = PermissionEngine.getInstance().filterMenus(ALL_MENUS);
    // 加载用户信息
    this.loadUserInfo();
  }

  // 加载当前用户信息
  private loadUserInfo(): void {
    // 从本地数据库或缓存加载
    this.userInfo = {
      userId: 'u_001',
      name: '张三',
      deptName: '研发部',
      avatarUrl: '',
      role: '部门经理',
    };
  }

  build() {
    // 根据断点选择不同布局
    GridRow({ columns: { sm: 4, md: 8, lg: 12 } }) {
      GridCol({ span: { sm: 4, md: 8, lg: 12 } }) {
        // 使用SideBarContainer实现多端适配
        SideBarContainer(this.sideBarCtrl, SideBarContainerType.Embed) {
          // 主内容区
          this.MainContent()
        }
        .sideBarWidth(280)
        .controlButton(this.currentBreakpoint === 'sm') // 手机端显示控制按钮
        .showSideBar(this.currentBreakpoint !== 'sm')   // 手机端默认隐藏侧边栏
        .onChange((isShow: boolean) => {
          console.info(`[OaApp] 侧边栏: ${isShow ? '展开' : '收起'}`);
        })
        // 侧边栏内容
        .SideBarContent() {
          this.SideBarPanel()
        }
      }
    }
    .onBreakpointChange((breakpoint: string) => {
      this.currentBreakpoint = breakpoint;
      console.info(`[OaApp] 断点变化: ${breakpoint}`);
    })
  }

  // 侧边栏面板
  @Builder
  SideBarPanel() {
    Column() {
      // 用户信息区
      Row() {
        Image(this.userInfo.avatarUrl || $r('app.media.ic_avatar_default'))
          .width(48)
          .height(48)
          .borderRadius(24)
          .backgroundColor('#E0E0E0')

        Column() {
          Text(this.userInfo.name)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
          Text(`${this.userInfo.deptName} · ${this.userInfo.role}`)
            .fontSize(12)
            .fontColor('#999999')
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ left: 12 })
      }
      .width('100%')
      .padding(16)

      Divider().color('#F0F0F0')

      // 菜单列表
      List() {
        ForEach(this.visibleMenus, (menu: MenuConfig, index: number) => {
          ListItem() {
            Row() {
              Image(menu.icon)
                .width(24)
                .height(24)
                .fillColor(this.selectedIndex === index ? '#1565C0' : '#666666')

              Text(menu.title)
                .fontSize(16)
                .fontColor(this.selectedIndex === index ? '#1565C0' : '#333333')
                .margin({ left: 16 })
            }
            .width('100%')
            .height(56)
            .padding({ left: 16, right: 16 })
            .borderRadius(8)
            .backgroundColor(this.selectedIndex === index ? '#E3F2FD' : Color.Transparent)
            .onClick(() => {
              this.selectedIndex = index as number;
              // 手机端点击后自动收起侧边栏
              if (this.currentBreakpoint === 'sm') {
                this.sideBarCtrl.changeSideBarStatus(false);
              }
            })
          }
        })
      }
      .layoutWeight(1)
      .divider({ strokeWidth: 0.5, color: '#F0F0F0', startMargin: 56 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FAFAFA')
  }

  // 主内容区
  @Builder
  MainContent() {
    Column() {
      // 顶部导航栏
      Row() {
        if (this.currentBreakpoint === 'sm') {
          // 手机端显示菜单按钮
          Image($r('app.media.ic_menu'))
            .width(24)
            .height(24)
            .fillColor('#333333')
            .onClick(() => {
              this.sideBarCtrl.changeSideBarStatus(true);
            })
            .margin({ right: 16 })
        }

        Text(this.visibleMenus[this.selectedIndex]?.title || '办公自动化')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

        Blank()

        // 消息通知
        Badge({ count: 5, position: BadgePosition.RightTop }) {
          Image($r('app.media.ic_notification'))
            .width(24)
            .height(24)
            .fillColor('#666666')
        }
        .margin({ right: 16 })

        // 搜索
        Image($r('app.media.ic_search'))
          .width(24)
          .height(24)
          .fillColor('#666666')
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })

      // 内容区域——根据选中菜单加载对应页面
      // 这里简化为显示快捷入口网格
      this.QuickEntryGrid()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }

  // 快捷入口网格
  @Builder
  QuickEntryGrid() {
    Scroll() {
      Column() {
        // 待办事项概览
        Row() {
          this.TodoCard('待审批', 12, '#1565C0')
          this.TodoCard('待阅读', 8, '#2E7D32')
          this.TodoCard('待签到', 1, '#E65100')
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceAround)
        .margin({ top: 16, bottom: 24 })

        // 公告区域
        Text('最新公告')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .padding({ left: 16, bottom: 8 })

        List() {
          ForEach([1, 2, 3], (_item: number, index: number) => {
            ListItem() {
              Row() {
                Column() {
                  Text(`关于${index === 0 ? '端午节放假' : index === 1 ? '系统升级维护' : '新员工培训'}的通知`)
                    .fontSize(15)
                    .fontWeight(FontWeight.Medium)
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                  Text('2026-06-25')
                    .fontSize(12)
                    .fontColor('#999999')
                    .margin({ top: 4 })
                }
                .layoutWeight(1)
                .alignItems(HorizontalAlign.Start)

                Text('未读')
                  .fontSize(12)
                  .fontColor('#1565C0')
                  .backgroundColor('#E3F2FD')
                  .padding({ left: 8, right: 8, top: 2, bottom: 2 })
                  .borderRadius(4)
              }
              .width('100%')
              .padding(16)
            }
          })
        }
        .divider({ strokeWidth: 0.5, color: '#F0F0F0' })
      }
      .padding({ left: 16, right: 16 })
    }
    .layoutWeight(1)
  }

  // 待办卡片
  @Builder
  TodoCard(title: string, count: number, color: string) {
    Column() {
      Text(count.toString())
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .fontColor(color)
      Text(title)
        .fontSize(13)
        .fontColor('#666666')
        .margin({ top: 4 })
    }
    .width(100)
    .height(80)
    .justifyContent(FlexAlign.Center)
    .borderRadius(12)
    .backgroundColor(Color.White)
    .shadow({ radius: 4, color: '#1A000000', offsetY: 2 })
  }
}

// 用户信息
interface UserInfo {
  userId: string;
  name: string;
  deptName: string;
  avatarUrl: string;
  role: string;
}

踩坑与注意事项

坑1:数据库加密别忘了密钥管理

RDB的encrypt: true只是开启了加密,密钥从哪来?硬编码在代码里?反编译一下就暴露了。密钥必须从服务端动态获取,或者用设备唯一标识派生。

更靠谱的方案:用@kit.CryptoArchitectureKit生成密钥,存在HUKS(通用密钥库)里,应用卸载后密钥自动销毁。

坑2:多端布局不是简单的if-else

很多人做多端适配就是if (isPhone) { ... } else { ... },这种写法维护成本极高。正确做法是用GridRow/GridCol的断点系统,配合@Builder拆分组件。布局逻辑交给框架,业务逻辑交给组件。

坑3:权限缓存要及时失效

权限引擎加了缓存是好事,但角色变更时缓存必须清掉。用户从"普通员工"升为"部门经理",如果缓存没清,他看到的还是普通员工的菜单——直到他杀掉App重进。

解决方案:服务端推送角色变更事件,客户端收到后清空缓存并刷新页面。

坑4:离线数据同步的冲突问题

OA应用断网时数据存在本地,连网后要同步到服务端。如果同一条数据在两端都改了,以谁的为准?

常见的冲突策略:

  • 服务端优先:简单粗暴,客户端改的被服务端覆盖
  • 时间戳优先:谁最后改的谁生效,但时钟不同步时不可靠
  • 字段级合并:只合并修改的字段,最精确但实现最复杂

建议用"服务端优先 + 手动解决":自动以服务端为准,但冲突时弹窗让用户确认。

坑5:侧边栏在折叠屏上的适配

折叠屏展开时是平板布局,折叠时是手机布局。如果用固定的断点判断,折叠过程中布局会突变,体验很差。

解决方案:监听onBreakpointChange事件,加一个300ms的过渡动画,让布局变化不那么突兀。

坑6:安全合规不能只靠客户端

客户端做的所有安全措施(加密、防截屏、数据隔离)都是"防君子不防小人"。真正要过等保,服务端必须有对应的安全措施——接口鉴权、数据脱敏、操作审计、日志留存。客户端安全只是第一道防线,不是全部。

HarmonyOS 6适配说明

HarmonyOS 6对企业级应用做了多项增强:

  1. 企业设备管理(MDM)增强:新增设备策略引擎,可以远程配置应用黑名单、限制截屏、强制加密等策略。之前这些需要自研,现在系统原生支持。
// HarmonyOS 6 企业设备策略
import { enterpriseDeviceManagement } from '@kit.MDMKit';

// 检查截屏是否被策略禁止
async function checkScreenshotPolicy(): Promise<boolean> {
  const admin = enterpriseDeviceManagement.getDeviceAdmin();
  const policies = await admin.getPolicies();
  return policies.disableScreenshot; // 返回true表示禁止截屏
}
  1. 安全沙箱增强:应用数据沙箱新增"企业容器"概念,企业数据和个人数据物理隔离。用户在"个人空间"和"工作空间"之间切换,数据互不可见。

  2. 分布式能力增强:OA应用可以在多个设备间无缝流转。你在手机上审批到一半,走到办公桌前,直接流转到PC上继续审批,状态完全保留。

  3. 窗口管理增强:PC端支持多窗口、自由缩放、分屏操作。OA应用需要适配WindowStage的多窗口模式,不能只按单窗口设计。

  4. 网络代理支持:企业内网通常有代理服务器,HarmonyOS 6新增了全局代理配置能力,应用不需要自己处理代理逻辑。

总结

企业OA不是"做个App"那么简单。架构分层做不好,代码三个月就腐化;数据隔离做不好,安全事故随时来;权限引擎做不好,越权操作防不住;多端适配做不好,平板和PC就是摆设。

核心记住三点:

  • 架构必须分层,展示层、业务层、能力层、数据层各司其职,互不干扰
  • 权限必须贯穿始终,菜单权限、数据权限、操作权限三位一体,缺一不可
  • 多端适配用框架能力,GridRow/GridCol断点系统比if-else靠谱一百倍
评估维度 说明
学习难度 ⭐⭐⭐⭐ 架构设计需要经验,权限引擎需要业务理解
使用频率 ⭐⭐⭐⭐⭐ 企业级应用的标配,几乎每个B端项目都需要
重要程度 ⭐⭐⭐⭐⭐ 架构不对,后面全是补丁;架构对了,后面事半功倍

做OA,先想清楚架构,再动手写代码。否则写到最后你会发现——你不是在做OA,你是在给自己挖坑。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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