import { useEffect, useMemo, useState } from 'react'; import { api } from './api'; type FormVersion = { form_key: string; version: number; status: string }; type ProcessDefinition = { id: string; key: string; name?: string; version: number; suspended: boolean }; type Binding = { id: string; business_type: string; form_key: string; form_version: number; process_definition_key: string; leave_type?: string; min_duration_minutes?: number; max_duration_minutes?: number; priority: number; status: 'ACTIVE' | 'INACTIVE'; }; const leaveTypes = [{ value: '', label: '全部请假类型' }, { value: 'PERSONAL', label: '事假' }, { value: 'SICK', label: '病假' }, { value: 'ANNUAL', label: '年假' }]; const minutesToDays = (value?: number) => value == null ? '不限' : `${Number((value / 480).toFixed(1))} 天`; export function ProcessBindings() { const [forms, setForms] = useState([]); const [definitions, setDefinitions] = useState([]); const [bindings, setBindings] = useState([]); const [message, setMessage] = useState(''); const [formKey, setFormKey] = useState('leave-request'); const published = forms.filter(form => form.status === 'PUBLISHED'); const selectedVersions = published.filter(form => form.form_key === formKey); const [formVersion, setFormVersion] = useState(1); const [processKey, setProcessKey] = useState('leaveApproval'); const [leaveType, setLeaveType] = useState(''); const [minDays, setMinDays] = useState(''); const [maxDays, setMaxDays] = useState(''); const [priority, setPriority] = useState(100); async function load() { try { const [formResult, definitionResult, bindingResult] = await Promise.all([ api('/admin/process-configuration/forms'), api('/admin/workflows/definitions'), api('/admin/process-configuration/bindings'), ]); setForms(formResult); setDefinitions(definitionResult); setBindings(bindingResult); const firstPublished = formResult.find(form => form.status === 'PUBLISHED'); if (firstPublished) { setFormKey(firstPublished.form_key); setFormVersion(firstPublished.version); } const firstDefinition = definitionResult.find(definition => !definition.suspended); if (firstDefinition) setProcessKey(firstDefinition.key); } catch (error) { setMessage(error instanceof Error ? error.message : '配置加载失败'); } } useEffect(() => { void load(); }, []); useEffect(() => { if (selectedVersions.length && !selectedVersions.some(form => form.version === formVersion)) setFormVersion(selectedVersions[0].version); }, [formKey, forms]); const ruleSummary = useMemo(() => { const type = leaveTypes.find(item => item.value === leaveType)?.label ?? leaveType; return `${type} · ${minDays || '0'}–${maxDays || '∞'} 天 · 优先级 ${priority}`; }, [leaveType, minDays, maxDays, priority]); async function createBinding() { try { await api('/admin/process-configuration/bindings', { method: 'POST', body: JSON.stringify({ businessType: 'LEAVE_REQUEST', formKey, formVersion, processDefinitionKey: processKey, leaveType: leaveType || null, minDurationMinutes: minDays === '' ? null : Math.round(Number(minDays) * 480), maxDurationMinutes: maxDays === '' ? null : Math.round(Number(maxDays) * 480), priority, }) }); setMessage('流程绑定已启用'); await load(); } catch (error) { setMessage(error instanceof Error ? error.message : '绑定创建失败'); } } async function toggle(binding: Binding) { try { const status = binding.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'; await api(`/admin/process-configuration/bindings/${binding.id}/status`, { method: 'PUT', body: JSON.stringify({ status }) }); setMessage(status === 'ACTIVE' ? '绑定已启用' : '绑定已停用'); await load(); } catch (error) { setMessage(error instanceof Error ? error.message : '状态修改失败'); } } return
{message &&
setMessage('')}>{message}
}

流程选择与绑定

客户端只提交业务数据,服务端按类型、时长和优先级选择 Flowable 流程。

服务端权威路由

新建路由规则

匹配规则{ruleSummary}

运行时选择

业务提交LEAVE_REQUEST
规则匹配类型 · 时长 · 优先级
启动流程{processKey || '请选择流程'}

✓ 仅引用已发布表单

✓ 优先级高的精确规则先匹配

✓ 无匹配规则时拒绝启动流程

✓ 已运行实例不会随绑定变更

现有绑定

{bindings.length === 0 ?

暂无绑定

: bindings.map(binding =>
{binding.form_key} · v{binding.form_version}{binding.business_type} → {binding.process_definition_key}
{binding.leave_type || '全部类型'}{minutesToDays(binding.min_duration_minutes)} – {minutesToDays(binding.max_duration_minutes)}P{binding.priority}
)}
; }