131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
/** 与后端 oa_engine.INITIATOR_CONDITION_FIELD 对齐:不依赖 formData */
|
|
const INITIATOR_CONDITION_FIELD = '__initiator__'
|
|
const FORMULA_FIELD_RE = /\{([^{}]+)\}/g
|
|
|
|
function nodeType(node: any): string {
|
|
return String((node && (node.type || node.nodeType)) || '').trim()
|
|
}
|
|
|
|
function addField(name: unknown, fields: string[], seen: Set<string>) {
|
|
const key = String(name || '').trim()
|
|
if (!key || seen.has(key)) return
|
|
if (key === INITIATOR_CONDITION_FIELD) return
|
|
seen.add(key)
|
|
fields.push(key)
|
|
}
|
|
|
|
function addFormulaFields(text: unknown, fields: string[], seen: Set<string>) {
|
|
if (!text) return
|
|
FORMULA_FIELD_RE.lastIndex = 0
|
|
let m: RegExpExecArray | null
|
|
while ((m = FORMULA_FIELD_RE.exec(String(text)))) {
|
|
addField(m[1], fields, seen)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 收集流程树中非默认条件分支引用的表单字段(去重保序)。
|
|
* 语义对齐 backend `oa_engine._collect_condition_fields`。
|
|
*/
|
|
export function collectConditionFields(node: unknown): string[] {
|
|
const fields: string[] = []
|
|
const seen = new Set<string>()
|
|
|
|
function walk(n: any) {
|
|
if (!n || typeof n !== 'object') return
|
|
if (nodeType(n) === 'condition') {
|
|
for (const g of n.conditionGroups || []) {
|
|
if (!g || typeof g !== 'object') continue
|
|
const rules = g.rules
|
|
const when = g.when
|
|
if (g.isDefault || (!rules && !when)) {
|
|
if (g.child && typeof g.child === 'object') walk(g.child)
|
|
continue
|
|
}
|
|
if (Array.isArray(rules) && rules.length) {
|
|
for (const r of rules) {
|
|
if (r && typeof r === 'object' && r.field != null) {
|
|
addField(r.field, fields, seen)
|
|
}
|
|
}
|
|
} else if (typeof rules === 'string' && rules.trim()) {
|
|
addFormulaFields(rules, fields, seen)
|
|
} else if (when) {
|
|
addFormulaFields(when, fields, seen)
|
|
}
|
|
if (g.child && typeof g.child === 'object') walk(g.child)
|
|
}
|
|
if (n.child && typeof n.child === 'object') walk(n.child)
|
|
return
|
|
}
|
|
if (n.child && typeof n.child === 'object') walk(n.child)
|
|
for (const g of n.conditionGroups || []) {
|
|
if (g && typeof g === 'object' && g.child && typeof g.child === 'object') {
|
|
walk(g.child)
|
|
}
|
|
}
|
|
}
|
|
|
|
walk(node)
|
|
return fields
|
|
}
|
|
|
|
function sortKeysDeep(value: unknown): unknown {
|
|
if (Array.isArray(value)) return value.map(sortKeysDeep)
|
|
if (value && typeof value === 'object') {
|
|
const out: Record<string, unknown> = {}
|
|
for (const k of Object.keys(value as Record<string, unknown>).sort()) {
|
|
out[k] = sortKeysDeep((value as Record<string, unknown>)[k])
|
|
}
|
|
return out
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function pickRelevantFormData(
|
|
formData: Record<string, unknown> | null | undefined,
|
|
conditionFields: string[] | null | undefined
|
|
): Record<string, unknown> {
|
|
const data = formData && typeof formData === 'object' ? formData : {}
|
|
const fields = Array.isArray(conditionFields) ? conditionFields : []
|
|
const out: Record<string, unknown> = {}
|
|
for (const f of fields) {
|
|
out[f] = data[f]
|
|
}
|
|
return out
|
|
}
|
|
|
|
export function buildFlowPreviewRequestKey(opts?: {
|
|
formData?: Record<string, unknown> | null
|
|
initiatorSelectByNode?: Record<string, string[]> | null
|
|
conditionFields?: string[] | null
|
|
}): string {
|
|
const payload = {
|
|
formData: pickRelevantFormData(opts?.formData, opts?.conditionFields),
|
|
initiatorSelectByNode:
|
|
opts?.initiatorSelectByNode && typeof opts.initiatorSelectByNode === 'object'
|
|
? opts.initiatorSelectByNode
|
|
: {}
|
|
}
|
|
return JSON.stringify(sortKeysDeep(payload))
|
|
}
|
|
|
|
export function shouldSkipFlowPreviewRequest(
|
|
prevKey: string | null | undefined,
|
|
nextKey: string,
|
|
opts: { inFlightKey?: string | null } = {}
|
|
): boolean {
|
|
const inFlightKey = opts.inFlightKey
|
|
if (inFlightKey != null) {
|
|
return inFlightKey === nextKey
|
|
}
|
|
return prevKey != null && prevKey === nextKey
|
|
}
|
|
|
|
/** 已有预览结果时静默刷新,避免整区 loading 闪烁 */
|
|
export function shouldShowFlowPreviewSpinner(opts?: {
|
|
hasCompletedPreview?: boolean
|
|
}): boolean {
|
|
return !opts?.hasCompletedPreview
|
|
}
|