For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 在审批「2 表单设计」增加「套件」菜单与请假套件:拖入后得到套件容器(工时属性)+ 四个基础子字段;发起时按双段工时自动算时长(可手改);提交时后端权威重算/校验。
Architecture: FormCreate 扩展 leaveKit 布局容器(input: false,可拖入子组件,默认带 4 子字段)+ menu: 'kit'。工时在容器 props。纯函数 calcLeaveHours 前后端各一份。发起页对 schema 中 leaveKit 绑定 watch;start_instance 调用 apply_leave_kit_hours。
Tech Stack: Vue 3 · @form-create/designer · Element Plus · FastAPI · pytest · Node node:test(前端算法单测)
Spec: docs/superpowers/specs/2026-07-30-oa-leave-kit-design.md
Demo: docs/mockups/oa-form-kit-tab-demo.html
| Path | Responsibility |
|---|---|
Create: web-admin/src/utils/oa-leave-hours.ts |
重叠工时计算纯函数 |
Create: web-admin/src/utils/oa-leave-hours.test.ts |
算法单测 |
Create: web-admin/src/components/oa-approval/OaLeaveKit.vue |
套件容器 UI(设计器/运行时) |
Create: web-admin/src/utils/oa-leave-kit-rule.ts |
DragRule:menu=kit,默认 4 子字段 + 工时 props |
Create: web-admin/src/utils/oa-form-create-leave-kit.ts |
addMenu('kit') + setupOaLeaveKit |
Create: web-admin/src/utils/oa-leave-kit-runtime.ts |
从 schema 解析 leaveKit、应用自动时长、手改标记 |
Modify: web-admin/src/main.ts |
注册 leaveKit |
Modify: web-admin/src/oa-fc-host/main.ts |
同上 |
Modify: web-admin/src/views/oa-approval/start/index.vue |
发起页联动时长 |
Create: backend/app/services/oa_leave_hours.py |
同算法 + apply_leave_kit_hours |
Create: backend/tests/test_oa_leave_hours.py |
pytest |
Modify: backend/app/services/oa_publish.py |
LAYOUT_TYPES 加 leaveKit |
Modify: backend/app/services/oa_engine.py |
start_instance 调用 apply |
Modify: docs/superpowers/specs/2026-07-30-oa-leave-kit-design.md |
状态 → 实现中/已实现 |
Files:
web-admin/src/utils/oa-leave-hours.tsCreate: web-admin/src/utils/oa-leave-hours.test.ts
[ ] Step 1: Write failing tests
// web-admin/src/utils/oa-leave-hours.test.ts
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { calcLeaveHours, DEFAULT_LEAVE_WORK_HOURS } from './oa-leave-hours'
describe('calcLeaveHours', () => {
const wh = DEFAULT_LEAVE_WORK_HOURS // 08:30-12:00, 13:00-17:30
it('same day morning+afternoon sample 09:00-15:00 => 5.0', () => {
assert.equal(calcLeaveHours('2026-07-30 09:00', '2026-07-30 15:00', wh), 5.0)
})
it('only morning', () => {
assert.equal(calcLeaveHours('2026-07-30 08:30', '2026-07-30 12:00', wh), 3.5)
})
it('gap across lunch not counted', () => {
assert.equal(calcLeaveHours('2026-07-30 11:00', '2026-07-30 14:00', wh), 2.0)
})
it('end before start => null', () => {
assert.equal(calcLeaveHours('2026-07-30 15:00', '2026-07-30 09:00', wh), null)
})
it('invalid => null', () => {
assert.equal(calcLeaveHours('', '2026-07-30 09:00', wh), null)
})
})
cd web-admin && pnpm exec tsx --test src/utils/oa-leave-hours.test.ts
oa-leave-hours.tsExport:
DEFAULT_LEAVE_WORK_HOURS = { morningStart, morningEnd, afternoonStart, afternoonEnd }calcLeaveHours(start, end, workHours): number | null — 1 decimal, null if invalid/end≤startOverlap minutes with morning+afternoon segments per calendar day
[ ] Step 4: Run tests — expect PASS
[ ] Step 5: Commit(仅当用户要求 commit 时执行;本会话默认可暂缓)
Files:
backend/app/services/oa_leave_hours.pybackend/tests/test_oa_leave_hours.pybackend/app/services/oa_publish.py — add leaveKit to LAYOUT_TYPESModify: backend/app/services/oa_engine.py — after formulas, call apply_leave_kit_hours
[ ] Step 1: pytest failing for calc_leave_hours 09:00–15:00 → 5.0
[ ] Step 2: Implement calc_leave_hours + apply_leave_kit_hours(schema, data):
type == leaveKitstartField/endField/durationField if set, else by title/order (select, datePicker, datePicker, inputNumber)durationManual truthy: validate ≥0 one decimal only[ ] Step 3: Wire start_instance
[ ] Step 4: pytest PASS
Files:
OaLeaveKit.vue — 虚线框 + 标题「请假套件」+ <slot />oa-leave-kit-rule.ts — DragRuleoa-form-create-leave-kit.ts — addMenu + component + addDragRulemain.ts, oa-fc-host/main.tsDragRule essentials:
{
menu: 'kit',
icon: 'icon-date',
label: '请假',
name: 'leaveKit',
input: false,
drag: true,
mask: false,
inside: true,
rule() {
return {
type: 'leaveKit',
props: { ...DEFAULT_LEAVE_WORK_HOURS },
children: [ /* select, 2x datePicker datetime, inputNumber duration */ ]
}
},
props() {
return [ /* 4x time inputs for morning/afternoon */ ]
}
}
setupOaLeaveKit:
FcDesigner.addMenu({ name: 'kit', title: '套件', list: [] })
FcDesigner.component('leaveKit', OaLeaveKit)
formCreate.component('leaveKit', OaLeaveKit)
FcDesigner.addDragRule(leaveKitDragRule)
子字段 field 用 uniqueId;时长 props.precision=1, props.durationManual=false;日期 props.type='datetime', format/valueFormat=YYYY-MM-DD HH:mm。
Files:
oa-leave-kit-runtime.ts — listLeaveKits(schema), applyLeaveKitDurations(schema, formData, { respectManual })start/index.vue — on form-create change / watch formData,未手改时写回时长;用户改时长字段时设 durationManual(若无法写回 schema,用并行隐藏字段或 formData 旁路 __leaveDurationManual_<field>;优先改 rule props 若 api 允许)手改标记落地(择一,实现时选可工作的):
props.durationManual(设计器可读)+ 提交时由前端把 manual 状态塞进 formData 特殊 key,后端读取;或__manual_<durationField>: true,后端识别后不覆盖。采用 2(更简单跑通):发起人手改时长 → 设 formData['__manual_' + durationField] = true;后端 apply_leave_kit_hours 检查该 key。
「恢复自动计算」:首期发起页可不做按钮,手改后仅断开;设计器 Demo 已有示意。Spec 中的恢复按钮可作为二期(或 start 页时长旁小链,有则加)。
./scripts/update-graph.sh frontend(若改了 web-admin)| Spec 项 | Task |
|---|---|
| 套件 Tab 双列卡片 | T3 |
| 4 基础字段 + 套件工时 | T3 |
| 双段重叠算法 1 位小数 | T1/T2 |
| 手改后不联动 | T4 + T2 |
| 发布白名单 leaveKit | T2 |
| 提交权威 | T2 |
| 多套 field 唯一 | T3 uniqueId |
| 恢复自动计算 | T4 可选/二期 |
用户已要求继续开发:本会话采用 Inline Execution(executing-plans)。