Files
AIOA/admin-web/src/ProcessBindings.tsx
T
2026-07-18 22:45:06 +08:00

91 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<FormVersion[]>([]);
const [definitions, setDefinitions] = useState<ProcessDefinition[]>([]);
const [bindings, setBindings] = useState<Binding[]>([]);
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<FormVersion[]>('/admin/process-configuration/forms'),
api<ProcessDefinition[]>('/admin/workflows/definitions'),
api<Binding[]>('/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 <section className="bindingPage">
{message && <div className="toast" onClick={() => setMessage('')}>{message}</div>}
<div className="bindingIntro"><div><h1></h1><p> Flowable </p></div><span className="safeBadge"></span></div>
<div className="bindingGrid">
<div className="configCard"><h2></h2>
<label><input value="请假申请" disabled /></label>
<div className="twoColumns"><label><select value={formKey} onChange={e => setFormKey(e.target.value)}>{[...new Set(published.map(form => form.form_key))].map(key => <option key={key}>{key}</option>)}</select></label><label><select value={formVersion} onChange={e => setFormVersion(Number(e.target.value))}>{selectedVersions.map(form => <option key={form.version} value={form.version}>v{form.version} · </option>)}</select></label></div>
<label>Flowable <select value={processKey} onChange={e => setProcessKey(e.target.value)}>{definitions.filter(definition => !definition.suspended).map(definition => <option key={definition.id} value={definition.key}>{definition.name || definition.key} · v{definition.version}</option>)}</select></label>
<label><select value={leaveType} onChange={e => setLeaveType(e.target.value)}>{leaveTypes.map(type => <option key={type.value} value={type.value}>{type.label}</option>)}</select></label>
<div className="twoColumns"><label><input type="number" min="0" step="0.5" value={minDays} onChange={e => setMinDays(e.target.value)} placeholder="不限" /></label><label><input type="number" min="0" step="0.5" value={maxDays} onChange={e => setMaxDays(e.target.value)} placeholder="不限" /></label></div>
<label><input type="number" value={priority} onChange={e => setPriority(Number(e.target.value))} /></label>
<div className="rulePreview"><strong></strong><span>{ruleSummary}</span></div>
<button className="primaryWide" onClick={createBinding} disabled={!formKey || !processKey}></button>
</div>
<div className="flowCard"><h2></h2><div className="flowPreview"><div className="flowNode source"><span>LEAVE_REQUEST</span></div><i></i><div className="flowNode decision"><span> · · </span></div><i></i><div className="flowNode target"><span>{processKey || '请选择流程'}</span></div></div><div className="guardrails"><p> </p><p> </p><p> </p><p> </p></div></div>
</div>
<div className="bindingList"><h2></h2>{bindings.length === 0 ? <p className="empty"></p> : bindings.map(binding => <article key={binding.id} className={binding.status === 'ACTIVE' ? '' : 'inactive'}><div><strong>{binding.form_key} · v{binding.form_version}</strong><span>{binding.business_type} {binding.process_definition_key}</span></div><div className="conditions"><span>{binding.leave_type || '全部类型'}</span><span>{minutesToDays(binding.min_duration_minutes)} {minutesToDays(binding.max_duration_minutes)}</span><span>P{binding.priority}</span></div><button className={binding.status === 'ACTIVE' ? 'dangerGhost' : ''} onClick={() => toggle(binding)}>{binding.status === 'ACTIVE' ? '停用' : '启用'}</button></article>)}</div>
</section>;
}