2026-07-30-oa-leave-kit-design.md 11 KB

OA 表单设计 · 请假套件(Leave Kit)设计文档

日期: 2026-07-30
状态: 已实现(计划见 docs/superpowers/plans/2026-07-30-oa-leave-kit.md)
范围: web-admin 审批设计器「2 表单设计」套件 Tab + 发起/详情运行时;backend 时长重算与发布/提交校验
界面模式: 对齐 FormCreate 左侧控件库;视觉 Fresh Mint
Demo: docs/mockups/oa-form-kit-tab-demo.html
前置: docs/superpowers/specs/2026-07-14-oa-approval-dingtalk-design.md §4.5 / §4.6 / §5.4


1. 背景与目标

客户希望在「2 表单设计」左侧「组件」旁增加 套件 Tab:将常用业务字段组合为一键插入的预制包。首个套件为 请假套件。

1.1 目标

  • 左侧增加 套件 Tab(位于「组件」之后),展示模式与「组件」一致(双列小卡片)。
  • 请假套件 = 4 个基础组件拼装 + 套件级属性(上午/下午上班时段)。
  • 时长按工时时段重叠自动计算(小时,1 位小数);支持手改后断开联动。
  • Schema / form_data 仍符合双端契约;子字段可参与条件分支。

1.2 非目标(首期)

  • 午休单独配置、节假日/工作日历、按工作日扣减。
  • 租户全局考勤设置(工时挂在套件实例属性)。
  • 出差 / 加班 / 报销等其它套件(可灰显占位)。
  • 一体复合控件(单 type 包四字段对象值)——已否决,采用方案 1。

2. 产品决策(已确认)

项 决策
左侧 UI Tab:组件 → 套件 → 大纲;套件独立 Tab(不出现在「组件」里);双列小卡片
组成 4 个基础组件拼装;套件容器上配置工时属性
字段 请假类型、开始时间、结束时间、时长(h)
类型选项 内置:事假、调休、病假、年假、产假、陪产假、婚假、例假、丧假、哺乳假;设计器可维护
计量单位 全部按小时
时间精度 开始/结束:年月日时分(YYYY-MM-DD HH:mm)
工时模型 上午时段 + 下午时段;间隙(午休)自然不计
默认时段 上午 08:30–12:00,下午 13:00–17:30
配置位置 套件实例属性(每个审批应用各自配置)
时长手改 默认可改;手改后不再随起止/工时自动重算;提供「恢复自动计算」
小数 保留 1 位小数
实现方案 方案 1:套件容器 + 展开/内嵌基础子字段(非一体 leaveKit 值对象)

3. 信息模型

3.1 心智模型

┌─ 请假套件(组装体 / 容器)─────────────────────┐
│  套件属性:上午时段、下午时段                    │
│                                                 │
│  · 基础·下拉      → 请假类型                     │
│  · 基础·日期时间  → 开始时间                     │
│  · 基础·日期时间  → 结束时间                     │
│  · 基础·数字      → 时长(h)                    │
└─────────────────────────────────────────────────┘
  • 选中套件容器:配置工时时段等套件属性(设计器右侧);运行时/发起页不额外渲染套件边框或标题,子字段与单独拖入的标准组件样式一致。
  • 选中子字段:配置标题、必填、选项等(与普通组件相同)。

3.2 Schema 结构(示意)

容器类型建议登记为 leaveKit(布局类,不计可填字段数),或复用 group 并带 _fc_kit: 'leave' 标记。推荐独立 leaveKit 便于发布校验与选中态属性面板。

{
  "type": "leaveKit",
  "props": {
    "morningStart": "08:30",
    "morningEnd": "12:00",
    "afternoonStart": "13:00",
    "afternoonEnd": "17:30"
  },
  "children": [
    {
      "type": "select",
      "field": "leaveType",
      "title": "请假类型",
      "options": [
        { "label": "事假", "value": "事假" },
        { "label": "调休", "value": "调休" },
        { "label": "病假", "value": "病假" },
        { "label": "年假", "value": "年假" },
        { "label": "产假", "value": "产假" },
        { "label": "陪产假", "value": "陪产假" },
        { "label": "婚假", "value": "婚假" },
        { "label": "例假", "value": "例假" },
        { "label": "丧假", "value": "丧假" },
        { "label": "哺乳假", "value": "哺乳假" }
      ],
      "$required": true
    },
    {
      "type": "datePicker",
      "field": "leaveStart",
      "title": "开始时间",
      "props": { "type": "datetime", "format": "YYYY-MM-DD HH:mm", "valueFormat": "YYYY-MM-DD HH:mm" },
      "$required": true
    },
    {
      "type": "datePicker",
      "field": "leaveEnd",
      "title": "结束时间",
      "props": { "type": "datetime", "format": "YYYY-MM-DD HH:mm", "valueFormat": "YYYY-MM-DD HH:mm" },
      "$required": true
    },
    {
      "type": "inputNumber",
      "field": "leaveDuration",
      "title": "时长(h)",
      "props": {
        "precision": 1,
        "min": 0,
        "durationManual": false
      },
      "$required": true
    }
  ]
}

说明:

  • 子字段 field 插入时生成唯一 ID(同表单可插入多套请假套件,自动去重后缀)。
  • 子字段通过 props.kitRef 或父 leaveKit 关联,用于运行时读取工时配置。
  • durationManual 存在时长字段 props;发起人手改后置 true。

3.3 form_data

{
  "leaveType": "事假",
  "leaveStart": "2026-07-30 09:00",
  "leaveEnd": "2026-07-30 15:00",
  "leaveDuration": 5.5
}
字段 JSON 值形态
请假类型 string(选项 value)
开始/结束 string,YYYY-MM-DD HH:mm
时长 number,一位小数

工时时段与 durationManual 不作为业务展示主字段写入 form_data 的独立 key(配置在 schema);若运行时需回传手改状态,可在时长字段旁用约定 props 或并行隐藏字段,实现计划中二选一并保持双端一致。


4. 时长算法

4.1 规则

对闭开或闭闭区间,实现统一为:将 [start, end] 与每一天的上午段、下午段求交集,累加分钟数,再 / 60,四舍五入到 1 位小数。

  • 若 end <= start:时长视为无效(前端提示;后端拒绝或置 0 并报错)。
  • 与任一时段无重叠:结果为 0.0。
  • 跨天:按自然日切分后分别与两段求交再求和。

示例(默认工时):

  • 开始 09:00,结束 15:00(同日)
    → 上午 09:00–12:00(3h)+ 下午 13:00–15:00(2h)= 5.0h
    (12:00–13:00 不计)

4.2 手改

状态 行为
durationManual = false 起止或套件工时变化时自动重算并写回时长
durationManual = true 不再自动重算;保留用户输入
「恢复自动计算」 清 durationManual,按当前起止与工时重算

4.3 前后端一致性

  • 前端发起页:即时预览(未手改时)。
  • 后端提交:
    • 未手改:按同一算法重算并覆盖提交时长后落库。
    • 已手改:信任提交时长,校验 number ≥ 0 且一位小数。
  • 禁止任意 eval;算法放在独立纯函数模块(可与 oa_formula 并列,如 oa_leave_hours.py + 前端 oa-leave-hours.ts)。

5. 设计器与运行时交互

5.1 套件 Tab

  • FcDesigner.addMenu({ name: 'kit', title: '套件' })(或等价配置),保证顺序在「组件」相关菜单之后。
  • addDragRule 注册请假套件;视觉与基础组件卡片一致。
  • 其它套件可 coming 占位,不注册拖拽或禁用。

5.2 拖入与编辑

操作 行为
拖入/点击请假 插入 leaveKit + 4 子字段
选中容器 右侧:上午/下午四个时间;校验段内 start < end
选中子字段 常规属性;类型字段可维护 options
删除子字段 允许;套件不完整时发布 警告可发(不强制阻断)
再拖一套 允许;field 自动唯一

5.3 发起 / 详情 / 打印

  • 四个子字段按基础组件渲染与打印。
  • 套件容器不输出独立 form 值。
  • 详情只读展示已保存时长;不再因工时变更重算历史单。

5.4 条件分支

子字段与普通字段相同:勾选「参与流程条件分支」后可在流程设计中引用(类型、时长等)。


6. 校验清单

6.1 设计器 / 发布

  • leaveKit(或约定 group 标记)加入布局类白名单。
  • 子 type 须为已允许的 select / datePicker / inputNumber(或项目现用等价 type)。
  • 工时四段格式 HH:mm;上午/下午各自 start < end;下午起点 < 上午终点时仅 提示(不强制)。

6.2 提交

  • 结束 ≥ 开始。
  • 类型 ∈ 当前 options。
  • 时长规则见 §4.3。
  • 必填子字段非空。

7. 实现落点(指引,非任务拆解)

层 落点
设计器宿主 web-admin/src/oa-fc-host/:菜单「套件」、拖拽规则、容器组件与属性
主应用注册 与金额/人员同类:setupOaLeaveKit + main.ts / oa-fc-host/main.ts
算法前端 web-admin/src/utils/oa-leave-hours.ts
算法后端 backend/app/services/oa_leave_hours.py;发起提交路径调用
发布白名单 backend/app/services/oa_publish.py → LAYOUT_TYPES 增加 leaveKit
Demo docs/mockups/oa-form-kit-tab-demo.html

8. 验收标准

  1. 「2 表单设计」左侧出现 套件 Tab,样式与组件双列卡片一致;可插入请假套件。
  2. 画布出现类型 / 开始 / 结束 / 时长四个基础字段;选中套件可配默认双段工时。
  3. 类型含约定 10 项且可在设计器维护选项。
  4. 未手改时,改起止后时长按重叠工时更新为 1 位小数;示例 09:00–15:00 → 5.0。
  5. 手改时长后改起止不再变;「恢复自动计算」后恢复联动。
  6. 提交后后端对未手改单据重算覆盖;已手改保留用户值(合法前提下)。
  7. 手机预览可用;打印/详情展示四个字段值。
  8. 子字段可勾选参与条件分支。

9. 开放问题(实现时可定)

  • 手改状态是仅存 schema props,还是提交时附带隐藏字段:实现计划择一并写测试。
  • Element Plus datePicker datetime 的 valueFormat 与后端解析严格对齐。
  • 同一套件内删除「开始」后,时长字段是否自动禁用编辑提示:建议弱提示即可。