第58篇:审批事件通知系统

概述

审批事件通知系统是连接审批操作和用户感知的桥梁。当审批状态发生变化时(如提交、同意、驳回、撤回),系统需要及时通知相关用户。星办OA 在 ApprovalStore.ets 中实现了完整的通知系统,包括待办转审批结果机制(resolvePendingMessages)、操作消息自动生成(addActionMessage)以及消息通知与审批操作的联动。本文将深入分析通知系统的实现细节。

一、通知系统架构

1.1 消息数据模型

export class ApprovalMessage {
  id: string = ''           // 消息唯一标识
  category: string = '系统通知'  // 消息分类(待办提醒/审批结果/系统通知)
  title: string = ''        // 消息标题
  content: string = ''      // 消息内容
  approvalId: string = ''   // 关联审批单号
  createdAt: string = ''    // 消息创建时间
  isRead: boolean = false   // 已读/未读状态
}

消息分类体系:

分类 标识 用途 示例

待办提醒 '待办提醒' 提醒用户有新的审批待处理 "你有一笔报销待审批"
审批结果 '审批结果' 通知用户审批操作结果 "采购申请被驳回"
系统通知 '系统通知' 系统级公告和提醒 "移动办公安全提醒"

1.2 通知系统的完整流程

审批操作 → Store 方法 → 数据更新 → 消息通知 → 用户感知
    │          │           │           │
    │          │      approvals 更新  │
    │          │      messages 更新   │
    │          │           │           │
 approve()  Store.approve()  resolvePendingMessages() + addActionMessage() → Toast + 消息列表更新

二、待办转审批结果机制

2.1 核心函数实现

export function resolvePendingMessages(messages: ApprovalMessage[], approvalId: string,
  action: string): ApprovalMessage[] {
  let changed: boolean = false
  let result: ApprovalMessage[] = []

  messages.forEach((item: ApprovalMessage) => {
    if (item.approvalId === approvalId && item.category === '待办提醒') {
      // 找到匹配的待办提醒,转换为审批结果
      let resolved: ApprovalMessage = cloneMessage(item)
      resolved.category = '审批结果'
      resolved.title = `待办已${action}`
      resolved.content = `你已完成该申请的${action}操作。`
      resolved.createdAt = '刚刚'
      resolved.isRead = true
      result.push(resolved)
      changed = true
    } else {
      result.push(item)
    }
  })

  return changed ? result : messages
}

2.2 待办转结果的业务逻辑

触发条件: 当审批人对一条待审批申请执行同意或驳回操作后,系统会查找与该审批关联的待办提醒消息。

转换规则:

字段 转换前 转换后

category "待办提醒" "审批结果"
title "你有一笔报销待审批" "待办已同意" / "待办已驳回"
content "周婷提交了..." "你已完成该申请的同意操作。"
createdAt 原始时间 "刚刚"
isRead false true

示例演示:

转换前(待办提醒):

消息ID: M001
分类: 待办提醒
标题: 你有一笔报销待审批
内容: 周婷提交了"客户拜访交通费报销"。
审批ID: A20260824001
已读: false

执行同意操作后,转换为此消息(审批结果):

消息ID: M001
分类: 审批结果
标题: 待办已同意
内容: 你已完成该申请的同意操作。
审批ID: A20260824001
已读: true

2.3 消息克隆函数

function cloneMessage(source: ApprovalMessage): ApprovalMessage {
  return newMessage(source.id, source.category, source.title, source.content,
    source.approvalId, source.createdAt, source.isRead)
}

消息克隆保证了原消息的不可变性,转换后的消息是新创建的对象,原消息不受影响。

三、操作消息自动生成

3.1 addActionMessage 实现

private addActionMessage(result: ApprovalMutation, title: string, content: string): void {
  let item: ApprovalMessage = new ApprovalMessage()
  item.id = `M${Date.now()}`          // 基于时间戳生成唯一 ID
  item.category = '审批结果'           // 分类为审批结果
  item.title = title                   // 消息标题
  item.content = content               // 消息内容
  item.approvalId = result.approvalId  // 关联审批单号
  item.createdAt = '刚刚'              // 创建时间
  item.isRead = false                  // 未读
  this.messages = [item, ...this.messages]  // 插入到消息列表头部
}

3.2 各操作的消息生成

提交操作:

// 在 submit 方法中
if (result.success) {
  this.approvals = result.approvals
  this.addActionMessage(result, '申请已提交', `${type}申请已进入审批流程。`)
}
// 生成消息:{ title: "申请已提交", content: "报销申请已进入审批流程。" }

同意操作:

// 在 approve 方法中
if (result.success) {
  this.approvals = result.approvals
  this.messages = resolvePendingMessages(this.messages, id, result.action)
  this.addActionMessage(result, '审批操作已完成', result.message)
}
// 生成消息:{ title: "审批操作已完成", content: "已同意,流程已转交下一审批人" }
// 或:{ title: "审批操作已完成", content: "审批已通过" }

驳回操作:

// 在 reject 方法中
if (result.success) {
  this.approvals = result.approvals
  this.messages = resolvePendingMessages(this.messages, id, result.action)
  this.addActionMessage(result, '申请已驳回', comment.trim())
}
// 生成消息:{ title: "申请已驳回", content: "费用说明不完整" }

撤回操作:

// 在 withdraw 方法中
if (result.success) {
  this.approvals = result.approvals
  this.addActionMessage(result, '申请已撤回', '你提交的申请已成功撤回。')
}
// 生成消息:{ title: "申请已撤回", content: "你提交的申请已成功撤回。" }

四、消息通知与审批操作的联动

4.1 同步联动机制

ApprovalStore 中,每个审批操作都会同步触发消息通知:

approve(id: string, comment: string): ApprovalMutation {
  let result: ApprovalMutation = approveApproval(this.approvals, id, comment, '刚刚')
  if (result.success) {
    this.approvals = result.approvals       // 1. 更新审批数据
    this.messages = resolvePendingMessages(  // 2. 转换待办消息
      this.messages, id, result.action)
    this.addActionMessage(result,            // 3. 生成操作消息
      '审批操作已完成', result.message)
  }
  return result
}

三步联动机制:

  • 更新审批数据this.approvals = result.approvals — 替换为最新的审批列表
  • 转换待办消息resolvePendingMessages — 将关联的待办提醒转换为审批结果
  • 生成操作消息addActionMessage — 在消息列表头部插入新的操作通知

4.2 消息 ID 的生成策略

item.id = `M${Date.now()}`

使用 Date.now() 时间戳作为消息 ID 的后缀,确保每次生成的消息 ID 都是唯一的。这种简单的 ID 生成策略在演示模式下足够使用,生产环境中可以使用更复杂的 UUID 生成策略。

4.3 消息列表的插入策略

this.messages = [item, ...this.messages]

新消息插入到消息列表的头部(...this.messages 展开语法),确保最新的消息始终显示在最前面。这是消息列表常见的"时间倒序"展示模式。

五、消息状态管理

5.1 已读/未读管理

markMessageRead(id: string): void {
  let changed: boolean = false
  this.messages.forEach((item: ApprovalMessage) => {
    if (item.id === id && !item.isRead) {
      item.isRead = true
      changed = true
    }
  })
  if (changed) {
    this.messages = [...this.messages]  // 触发视图更新
  }
}

markAllMessagesRead(): void {
  this.messages.forEach((item: ApprovalMessage) => {
    item.isRead = true
  })
  this.messages = [...this.messages]  // 触发视图更新
}

已读状态管理的设计要点:

  • 变更检测:通过 changed 标志位记录是否有消息被标记为已读,只有在有变更时才触发 this.messages = [...this.messages] 的组件更新。
  • 数组展开[...this.messages] 创建新的数组引用,触发 @ObservedV2/@Trace 的变更检测机制。
  • 批量操作markAllMessagesRead 支持一键标记所有消息为已读。

5.2 未读统计

getDashboardStats(approvals, messages) {
  stats.unread = 0
  messages.forEach((item: ApprovalMessage) => {
    if (!item.isRead) {
      stats.unread++
    }
  })
  return stats
}

未读消息数通过 getDashboardStats 计算,在仪表盘和消息角标中展示,提醒用户有未读的通知。

六、通知系统的演示数据

6.1 初始消息数据

export function createDemoMessages(): ApprovalMessage[] {
  return [
    newMessage('M001', '待办提醒', '你有一笔报销待审批',
      '周婷提交了"客户拜访交通费报销"。', 'A20260824001', '09:20', false),
    newMessage('M002', '待办提醒', '你有一条请假申请待审批',
      '陈昊申请 8 月 26 日至 27 日年假。', 'A20260823007', '昨天 16:40', false),
    newMessage('M003', '审批结果', '采购申请被驳回',
      '产品部显示器采购需要补充询价记录。', 'A20260821005', '8月21日', false),
    newMessage('M004', '审批结果', '报销申请已通过',
      '团队午餐费用已完成审批。', 'A20260818009', '8月19日', true),
    newMessage('M005', '系统通知', '移动办公安全提醒',
      '请勿将审批截图和企业数据转发到外部平台。', '', '8月18日', true),
  ]
}

6.2 消息与审批的关联

消息ID 分类 关联审批 审批状态 是否可转换

M001 待办提醒 A20260824001(报销) 审批中 是(同意/驳回后)
M002 待办提醒 A20260823007(请假) 审批中 是(同意/驳回后)
M003 审批结果 A20260821005(采购) 已驳回
M004 审批结果 A20260818009(团队午餐) 已通过
M005 系统通知 -

七、实际应用场景

7.1 场景一:审批人处理待办

  • 审批人打开应用,看到消息列表中有 2 条未读的"待办提醒"
  • 点击消息跳转到对应审批详情页
  • 执行同意操作
  • 系统自动将"待办提醒"消息转换为"审批结果"消息
  • 同时生成一条新的"审批操作已完成"消息
  • 审批人收到 Toast 提示"已同意,流程已转交下一审批人"

7.2 场景二:申请人收到通知

  • 申请人提交了一个新的审批申请
  • 系统自动生成一条"审批结果"类的消息
  • 消息内容为"请假申请已进入审批流程。"
  • 申请人在消息列表中看到这条新消息

八、总结

星办OA 的审批事件通知系统通过 ApprovalStore.ets 中的 resolvePendingMessagesaddActionMessage 两个核心机制,实现了审批操作与消息通知的自动联动。

通知系统的核心能力:

  • 待办转结果:审批人处理待办后,原待办提醒自动转换为审批结果,保持消息列表的整洁和准确。
  • 操作自动通知:每次审批操作(提交、同意、驳回、撤回)都自动生成对应的通知消息。
  • 已读状态管理:支持单条标记已读和批量标记已读,状态变更通过数组引用变更触发视图更新。
  • 消息分类体系:三类消息(待办提醒、审批结果、系统通知)覆盖了审批系统的所有通知场景。

设计模式总结:

  • 不可变更新resolvePendingMessages 通过克隆创建新消息对象,不修改原消息。
  • 变更检测优化:通过 changed 标志位避免不必要的数组更新。
  • 纯函数领域逻辑resolvePendingMessages 是纯函数,易于测试和调试。
  • Store 层协调ApprovalStore 负责协调审批操作和消息通知的联动逻辑。

通过这套通知系统,星办OA 实现了审批操作与消息通知的闭环——每次操作都有对应的通知,每个通知都关联到具体的审批单,用户可以通过消息列表快速感知和响应审批状态的变化。

源文件路径: commons/common/src/main/ets/model/ApprovalStore.ets

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐