import { useEffect, useMemo, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { archiveWorkflowDefinition, createWorkflowDefinition, fetchWorkflowDefinitions, publishWorkflowDefinition, updateWorkflowDefinition, type WorkflowDefinitionEditorModel, } from "../api/workflowDefinitions"; import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { ActionConfirmDialog } from "../components/common/ActionConfirmDialog"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; import { toast } from "sonner"; const ENTITY_TYPES: WorkflowDefinitionEditorModel["entityType"][] = ["reimbursement", "leave", "purchase_request", "travel_request"]; type DraftStep = WorkflowDefinitionEditorModel["steps"][number]; function emptyStep(): DraftStep { return { id: `step_${Date.now()}`, name: "审批节点", assigneeRule: { type: "department_manager" } }; } export function WorkflowDefinitionsPage() { const queryClient = useQueryClient(); const [entityType, setEntityType] = useState("leave"); const [selectedId, setSelectedId] = useState(""); const [name, setName] = useState(""); const [notes, setNotes] = useState(""); const [steps, setSteps] = useState([emptyStep()]); const [confirmDefinitionAction, setConfirmDefinitionAction] = useState<{ action: "publish" | "archive"; id: string; name: string; } | null>(null); const listQuery = useQuery({ queryKey: ["workflow-definitions"], queryFn: () => fetchWorkflowDefinitions() }); const items = listQuery.data?.items ?? []; const scopedItems = useMemo(() => items.filter((item) => item.entityType === entityType), [items, entityType]); const selected = useMemo(() => scopedItems.find((item) => item.id === selectedId) ?? scopedItems[0] ?? null, [scopedItems, selectedId]); const workflowStats = { total: items.length, scoped: scopedItems.length, draft: items.filter((item) => item.status === "draft").length, published: items.filter((item) => item.status === "published").length, archived: items.filter((item) => item.status === "archived").length, }; useEffect(() => { if (!selected) return; setSelectedId(selected.id); setName(selected.name); setNotes(selected.notes ?? ""); setSteps(selected.steps.length ? selected.steps : [emptyStep()]); }, [selected?.id]); const refresh = () => queryClient.invalidateQueries({ queryKey: ["workflow-definitions"] }); const createMutation = useMutation({ mutationFn: () => createWorkflowDefinition({ entityType, name: name.trim() || `${entityType}-workflow`, notes, steps }), onSuccess: async ({ item }) => { setSelectedId(item.id); await refresh(); toast.success("流程定义草稿已创建"); }, }); const saveMutation = useMutation({ mutationFn: (id: string) => updateWorkflowDefinition(id, { name, notes, steps }), onSuccess: async () => { await refresh(); toast.success("流程定义已保存"); }, }); const publishMutation = useMutation({ mutationFn: publishWorkflowDefinition, onSuccess: async () => { await refresh(); toast.success("流程定义已发布"); }, }); const archiveMutation = useMutation({ mutationFn: archiveWorkflowDefinition, onSuccess: async () => { await refresh(); toast.success("流程定义已归档"); }, }); return (
定义总数
{workflowStats.total}
当前业务域
{workflowStats.scoped}
草稿
{workflowStats.draft}
已发布
{workflowStats.published}
已归档
{workflowStats.archived}
{entityType !== "reimbursement" ? : null}

版本列表

{scopedItems.map((item) => ( ))} {!scopedItems.length ?
当前实体暂无流程定义
: null}
{selected ? ( <>
{selected.source}
{selected.name}
{selected.status !== "derived_readonly" ? (
) : null}
setName(event.target.value)} disabled={selected.status !== "draft"} />
setNotes(event.target.value)} disabled={selected.status !== "draft"} />

步骤

{selected.status === "draft" ? : null}
{steps.map((step, index) => (
setSteps((prev) => prev.map((item, itemIndex) => itemIndex === index ? { ...item, id: event.target.value } : item))} /> setSteps((prev) => prev.map((item, itemIndex) => itemIndex === index ? { ...item, name: event.target.value } : item))} />
{step.assigneeRule.type !== "department_manager" ? (
setSteps((prev) => prev.map((item, itemIndex) => itemIndex === index ? { ...item, assigneeRule: { ...item.assigneeRule, value: event.target.value } as DraftStep["assigneeRule"] } : item))} placeholder={step.assigneeRule.type === "role" ? "角色编码" : "用户 ID"} />
) : null}
))}
) : (
暂无可编辑流程定义
)}
{ if (!open) setConfirmDefinitionAction(null); }} kind={confirmDefinitionAction?.action === "archive" ? "archive" : "submit"} targetName={confirmDefinitionAction?.name} title={confirmDefinitionAction?.action === "archive" ? "确认归档流程定义" : "确认发布流程定义"} description={ confirmDefinitionAction?.action === "archive" ? "归档后该流程定义不再用于新流程,历史流程不回写。" : "发布后该流程定义会参与后续新提交业务的审批解析,历史流程不回写。" } onConfirm={async () => { if (!confirmDefinitionAction) return; if (confirmDefinitionAction.action === "publish") publishMutation.mutate(confirmDefinitionAction.id); if (confirmDefinitionAction.action === "archive") archiveMutation.mutate(confirmDefinitionAction.id); }} />
); }