feat(oa): add overtime kit form components and designer registration
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
684f152683
commit
b3d267e9ca
5 files changed
+299
No files matched your search
@@ -0,0 +1,34 @@
|
||||
# OA 表单设计 · 加班套件(Overtime Kit)设计文档
|
||||
|
||||
**日期**: 2026-07-30
|
||||
**状态**: 已实现
|
||||
**范围**: `web-admin` 审批设计器「套件」Tab + 发起/详情运行时;发布白名单
|
||||
**前置**: 请假套件 `docs/superpowers/specs/2026-07-30-oa-leave-kit-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标
|
||||
|
||||
套件 Tab 增加 **加班套件**:一键插入 4 个字段。
|
||||
|
||||
| 字段 | 控件 | 说明 |
|
||||
|------|------|------|
|
||||
| 加班人 | `person`(多选,与人员组件一致) | 必填 |
|
||||
| 开始时间 | `datePicker` datetime `YYYY-MM-DD HH:mm` | 必填 |
|
||||
| 结束时间 | `datePicker` datetime `YYYY-MM-DD HH:mm` | 必填 |
|
||||
| 时长(h) | `inputNumber` 1 位小数 | **仅手动填写**,无自动计算 |
|
||||
|
||||
## 2. 实现要点
|
||||
|
||||
- 容器类型:`overtimeKit`(布局类,不计可填字段数)
|
||||
- 运行时不渲染套件边框/标题(同请假)
|
||||
- 无套件级工时属性、无前后端时长重算
|
||||
- 打印 / 发布白名单纳入 `overtimeKit`
|
||||
|
||||
## 3. 文件
|
||||
|
||||
- `OaOvertimeKit.vue`
|
||||
- `oa-overtime-kit-rule.ts` / `oa-form-create-overtime-kit.ts` / `oa-overtime-kit-runtime.ts`
|
||||
- `main.ts` / `oa-fc-host/main.ts` 注册
|
||||
- `oa_publish.py` `LAYOUT_TYPES`
|
||||
- `oa-approval-print.ts` 布局 unwrap
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- 加班套件:仅组合容器;时长手动填写,无自动计算 -->
|
||||
<template>
|
||||
<div class="oa-overtime-kit" v-bind="$attrs">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'OaOvertimeKit', inheritAttrs: false })
|
||||
|
||||
defineProps<{
|
||||
personField?: string
|
||||
startField?: string
|
||||
endField?: string
|
||||
durationField?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.oa-overtime-kit {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
import FcDesigner from '@form-create/designer'
|
||||
import OaOvertimeKit from '@/components/oa-approval/OaOvertimeKit.vue'
|
||||
import { overtimeKitDragRule } from '@/utils/oa-overtime-kit-rule'
|
||||
|
||||
const FLAG = '__oaOvertimeKitConfigured'
|
||||
|
||||
export function setupOaOvertimeKit(formCreate?: { component?: (name: string, c: unknown) => void }) {
|
||||
if ((FcDesigner as any)[FLAG]) return
|
||||
|
||||
if (typeof FcDesigner.addMenu === 'function') {
|
||||
try {
|
||||
FcDesigner.addMenu({ name: 'kit', title: '套件', list: [] })
|
||||
} catch {
|
||||
/* kit menu may already exist */
|
||||
}
|
||||
}
|
||||
|
||||
FcDesigner.component('overtimeKit', OaOvertimeKit)
|
||||
if (typeof formCreate?.component === 'function') {
|
||||
formCreate.component('overtimeKit', OaOvertimeKit)
|
||||
}
|
||||
if (typeof FcDesigner.addDragRule === 'function') {
|
||||
FcDesigner.addDragRule(overtimeKitDragRule)
|
||||
}
|
||||
;(FcDesigner as any)[FLAG] = true
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/** FormCreate Designer DragRule:加班套件 */
|
||||
|
||||
let _uidSeq = 0
|
||||
function uniqueId() {
|
||||
const num = 370 + ++_uidSeq
|
||||
return (
|
||||
'F' +
|
||||
Math.random().toString(36).slice(3, 6) +
|
||||
Number(`${Date.now()}`).toString(36) +
|
||||
num.toString(36) +
|
||||
'o'
|
||||
)
|
||||
}
|
||||
|
||||
function buildOvertimeKitChildren() {
|
||||
const personField = uniqueId()
|
||||
const startField = uniqueId()
|
||||
const endField = uniqueId()
|
||||
const durationField = uniqueId()
|
||||
return {
|
||||
personField,
|
||||
startField,
|
||||
endField,
|
||||
durationField,
|
||||
children: [
|
||||
{
|
||||
type: 'person',
|
||||
field: personField,
|
||||
title: '加班人',
|
||||
info: '',
|
||||
$required: true,
|
||||
props: {
|
||||
_fc_kit_field: 'overtimePerson',
|
||||
multiple: true
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'datePicker',
|
||||
field: startField,
|
||||
title: '开始时间',
|
||||
info: '',
|
||||
$required: true,
|
||||
props: {
|
||||
_fc_kit_field: 'overtimeStart',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm',
|
||||
placeholder: '请选择开始时间'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'datePicker',
|
||||
field: endField,
|
||||
title: '结束时间',
|
||||
info: '',
|
||||
$required: true,
|
||||
props: {
|
||||
_fc_kit_field: 'overtimeEnd',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm',
|
||||
placeholder: '请选择结束时间'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'inputNumber',
|
||||
field: durationField,
|
||||
title: '时长(h)',
|
||||
info: '请手动填写加班时长',
|
||||
$required: true,
|
||||
props: {
|
||||
_fc_kit_field: 'overtimeDuration',
|
||||
precision: 1,
|
||||
min: 0,
|
||||
controlsPosition: 'right',
|
||||
align: 'left'
|
||||
},
|
||||
class: 'oa-overtime-duration'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** FormCreate Designer DragRule:加班套件 */
|
||||
export const overtimeKitDragRule = {
|
||||
menu: 'kit' as const,
|
||||
icon: 'icon-date',
|
||||
label: '加班',
|
||||
name: 'overtimeKit',
|
||||
input: false,
|
||||
drag: true,
|
||||
dragBtn: true,
|
||||
mask: false,
|
||||
inside: true,
|
||||
rule() {
|
||||
const built = buildOvertimeKitChildren()
|
||||
return {
|
||||
type: 'overtimeKit',
|
||||
props: {
|
||||
personField: built.personField,
|
||||
startField: built.startField,
|
||||
endField: built.endField,
|
||||
durationField: built.durationField
|
||||
},
|
||||
children: built.children
|
||||
}
|
||||
},
|
||||
props() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/** 加班套件运行时:时长仅手动填写,补齐数字框左对齐 */
|
||||
|
||||
function asRules(schema: unknown): Record<string, unknown>[] {
|
||||
if (Array.isArray(schema)) return schema.filter((r) => r && typeof r === 'object') as any
|
||||
if (schema && typeof schema === 'object') {
|
||||
const s = schema as any
|
||||
const rules = s.rule || s.rules || s.fields
|
||||
if (Array.isArray(rules)) return rules.filter((r: any) => r && typeof r === 'object')
|
||||
if (s.type || s.field) return [s]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function resolveDurationField(kit: Record<string, unknown>): string | undefined {
|
||||
const props = (kit.props && typeof kit.props === 'object' ? kit.props : {}) as Record<
|
||||
string,
|
||||
any
|
||||
>
|
||||
if (props.durationField) return String(props.durationField)
|
||||
const children = (
|
||||
Array.isArray(kit.children)
|
||||
? kit.children
|
||||
: Array.isArray(props.rule)
|
||||
? props.rule
|
||||
: []
|
||||
) as Record<string, any>[]
|
||||
for (const c of children) {
|
||||
if (!c?.field) continue
|
||||
if (String(c.props?._fc_kit_field || '') === 'overtimeDuration') return String(c.field)
|
||||
const t = String(c.type || '')
|
||||
if ((t === 'inputNumber' || t === 'number') && String(c.title || '').includes('时长')) {
|
||||
return String(c.field)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时补齐加班套件子规则:时长数字框默认左对齐。
|
||||
*/
|
||||
export function ensureOvertimeKitRuleProps(rules: unknown): any[] {
|
||||
if (!Array.isArray(rules)) return []
|
||||
return rules.map((raw) => {
|
||||
if (!raw || typeof raw !== 'object') return raw
|
||||
const rule: any = { ...(raw as object) }
|
||||
if (String(rule.type || '') === 'overtimeKit') {
|
||||
const durationField = resolveDurationField(rule)
|
||||
const patchChildren = (list: any[]): any[] =>
|
||||
list.map((c) => {
|
||||
if (!c || typeof c !== 'object') return c
|
||||
const child: any = { ...c }
|
||||
const t = String(child.type || '')
|
||||
const isDuration =
|
||||
(durationField && child.field === durationField) ||
|
||||
((t === 'inputNumber' || t === 'number') &&
|
||||
String(child.title || '').includes('时长'))
|
||||
if (isDuration) {
|
||||
child.props = { ...(child.props || {}), align: 'left' }
|
||||
const cls = String(child.class || child.className || '')
|
||||
if (!cls.includes('oa-overtime-duration')) {
|
||||
child.class = (cls ? cls + ' ' : '') + 'oa-overtime-duration'
|
||||
}
|
||||
}
|
||||
if (Array.isArray(child.children)) {
|
||||
child.children = patchChildren(child.children)
|
||||
}
|
||||
return child
|
||||
})
|
||||
if (Array.isArray(rule.children)) {
|
||||
rule.children = patchChildren(rule.children)
|
||||
}
|
||||
if (Array.isArray(rule.props?.rule)) {
|
||||
rule.props = {
|
||||
...rule.props,
|
||||
rule: patchChildren(rule.props.rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(rule.children)) {
|
||||
rule.children = ensureOvertimeKitRuleProps(rule.children)
|
||||
}
|
||||
if (Array.isArray(rule.props?.rule)) {
|
||||
rule.props = {
|
||||
...rule.props,
|
||||
rule: ensureOvertimeKitRuleProps(rule.props.rule)
|
||||
}
|
||||
}
|
||||
return rule
|
||||
})
|
||||
}
|
||||
|
||||
/** 供类型检查 / 后续扩展:当前 schema 是否含加班套件 */
|
||||
export function hasOvertimeKit(schema: unknown): boolean {
|
||||
const flat: Record<string, unknown>[] = []
|
||||
const walk = (rules: Record<string, unknown>[]) => {
|
||||
for (const r of rules) {
|
||||
flat.push(r)
|
||||
if (Array.isArray(r.children)) walk(r.children as any)
|
||||
const props = r.props as any
|
||||
if (props && Array.isArray(props.rule)) walk(props.rule)
|
||||
}
|
||||
}
|
||||
walk(asRules(schema))
|
||||
return flat.some((r) => String(r.type || '') === 'overtimeKit')
|
||||
}
|
||||
Reference in new issue
Block a user