From 7b5745d1fcdff41ade09f5b4b700ee68f38c0cba Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Wed, 25 Feb 2026 08:32:29 +0800 Subject: [PATCH 1/4] add FlowNodeHistory --- frontend/apps/app-pc/src/pages/todo.tsx | 1 + .../components/flow-node-history.tsx | 42 +++++++++++ .../components/form-view-component.tsx | 64 ++++++++++++++++ .../components/flow-approval/layout/body.tsx | 75 ++----------------- 4 files changed, 113 insertions(+), 69 deletions(-) create mode 100644 frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx create mode 100644 frontend/packages/flow-design/src/components/flow-approval/components/form-view-component.tsx diff --git a/frontend/apps/app-pc/src/pages/todo.tsx b/frontend/apps/app-pc/src/pages/todo.tsx index 9188829d..c27611c0 100644 --- a/frontend/apps/app-pc/src/pages/todo.tsx +++ b/frontend/apps/app-pc/src/pages/todo.tsx @@ -180,6 +180,7 @@ const TodoPage: React.FC = () => { key={"create"} type={'primary'} onClick={() => { + setCurrentRecordId(''); setSelectVisible(true); }}>发起流程 ) diff --git a/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx b/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx new file mode 100644 index 00000000..367ccf68 --- /dev/null +++ b/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval-context"; +import {ProcessNode} from "@/components/flow-approval/typings"; + +export interface FlowNodeHistoryAction{ + refresh:()=>void; +} + +interface FlowNodeHistoryProps{ + actionRef?:React.Ref; +} + +export const FlowNodeHistory: React.FC = (props) => { + + const {context} = useApprovalContext(); + + const [processNodes, setProcessNodes] = React.useState([]); + + const triggerProcessNodes = () => { + context.getPresenter().processNodes().then(nodes => { + setProcessNodes(nodes); + }); + } + + React.useEffect(()=>{ + triggerProcessNodes(); + },[]); + + React.useImperativeHandle(props.actionRef,()=>{ + return { + refresh:()=>{ + triggerProcessNodes(); + } + } + },[]); + + return ( +
+ 流转历史 {processNodes.length} +
+ ) +} \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/components/form-view-component.tsx b/frontend/packages/flow-design/src/components/flow-approval/components/form-view-component.tsx new file mode 100644 index 00000000..0379b1f1 --- /dev/null +++ b/frontend/packages/flow-design/src/components/flow-approval/components/form-view-component.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval-context"; +import {ViewPlugin} from "@/plugins/view"; +import { Form } from "antd"; + +interface FormViewComponentProps{ + onValuesChange?:(values:any)=>void; +} + +export const FormViewComponent: React.FC = (props) => { + const {state, context} = useApprovalContext(); + const ViewComponent = ViewPlugin.getInstance().get(state.flow?.view || 'default'); + // 是否可合并审批 + const mergeable = state.flow?.mergeable || false; + const todos = state.flow?.todos || []; + const viewForms = todos.length>0?todos.map(item => { + return { + instance: Form.useForm()[0], + data: item.data, + } + }):[ + { + instance: Form.useForm()[0], + data: undefined, + } + ] + + React.useEffect(() => { + viewForms.forEach(item => { + const formInstance = item.instance; + const data = item.data; + context.getPresenter().getFormActionContext().addAction({ + save(): any { + return formInstance.getFieldsValue(); + }, + key(): string { + return 'view-form' + } + }); + formInstance.setFieldsValue(data); + }); + }, []); + + if (ViewComponent) { + if (mergeable) { + return ( +
+

合并审批

+
+ ) + } + return ( + <> + {viewForms.map((item, index) => ( + + ))} + + ) + } +} diff --git a/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx b/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx index a346673e..e0934b57 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx @@ -1,76 +1,13 @@ import React from "react"; -import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval-context"; -import {Col, Form, Row} from "antd"; -import {ViewPlugin} from "@/plugins/view"; - -interface FormViewComponentProps{ - onValuesChange?:(values:any)=>void; -} - -const FormViewComponent: React.FC = (props) => { - const {state, context} = useApprovalContext(); - const ViewComponent = ViewPlugin.getInstance().get(state.flow?.view || 'default'); - // 是否可合并审批 - const mergeable = state.flow?.mergeable || false; - const todos = state.flow?.todos || []; - const viewForms = todos.length>0?todos.map(item => { - return { - instance: Form.useForm()[0], - data: item.data, - } - }):[ - { - instance: Form.useForm()[0], - data: undefined, - } - ] - - React.useEffect(() => { - viewForms.forEach(item => { - const formInstance = item.instance; - const data = item.data; - context.getPresenter().getFormActionContext().addAction({ - save(): any { - return formInstance.getFieldsValue(); - }, - key(): string { - return 'view-form' - } - }); - formInstance.setFieldsValue(data); - }); - }, []); - - if (ViewComponent) { - if (mergeable) { - return ( -
-

合并审批

-
- ) - } - return ( - <> - {viewForms.map((item, index) => ( - - ))} - - ) - } -} +import {Col, Row} from "antd"; +import {FormViewComponent} from "@/components/flow-approval/components/form-view-component"; +import {FlowNodeHistory, FlowNodeHistoryAction} from "@/components/flow-approval/components/flow-node-history"; export const Body = () => { - - const {state, context} = useApprovalContext(); + const flowNodeHistoryAction = React.useRef(null); const handleValuesChange = (values:any) => { - context.getPresenter().processNodes().then(nodes => { - console.log('流程节点:', nodes); - }); + flowNodeHistoryAction.current?.refresh(); } return ( @@ -82,7 +19,7 @@ export const Body = () => { /> - 流转历史 + ) From 6b490a9b651f163666cc4657f7ff789ad2e487d0 Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Wed, 25 Feb 2026 09:03:13 +0800 Subject: [PATCH 2/4] add FlowNodeHistory --- .../packages/flow-design/src/components/flow-approval/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/flow-design/src/components/flow-approval/model.ts b/frontend/packages/flow-design/src/components/flow-approval/model.ts index a921c5ff..c61f004e 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/model.ts +++ b/frontend/packages/flow-design/src/components/flow-approval/model.ts @@ -17,7 +17,7 @@ export class FlowApprovalApiImpl implements FlowApprovalApi { processNodes =async (body: Record)=> { const response = await postProcessNodes(body); if(response.success){ - return response.data; + return response.data.list; } } From 715ad9e5fe9c94e1aa9c9ba501a1cbe2df0029f0 Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Wed, 25 Feb 2026 15:23:25 +0800 Subject: [PATCH 3/4] feat: enhance flow-approval UI with Ant Design components - Implement FlowNodeHistory Timeline component with status icons and tags - Refactor ApprovalLayout body with Ant Design Layout + Card + collapsible Sider - Enhance ApprovalLayout header with title and improved button styling - Add layout constants (ApprovalLayoutHeight, ApprovalSidebarWidth, etc.) - Add approval design file (approval.pen) - Add git-push command and CLAUDE.md documentation Co-Authored-By: Claude Opus 4.6 --- designs/approval.pen | 973 ++++++++++++++++++ frontend/.claude/commands/git-push.md | 3 + frontend/CLAUDE.md | 466 +++++++++ .../components/flow-node-history.tsx | 100 +- .../components/flow-approval/layout/body.tsx | 102 +- .../flow-approval/layout/header.tsx | 53 +- .../components/flow-approval/layout/index.tsx | 16 +- .../components/flow-approval/typings/index.ts | 8 + 8 files changed, 1681 insertions(+), 40 deletions(-) create mode 100644 designs/approval.pen create mode 100644 frontend/.claude/commands/git-push.md create mode 100644 frontend/CLAUDE.md diff --git a/designs/approval.pen b/designs/approval.pen new file mode 100644 index 00000000..c199d832 --- /dev/null +++ b/designs/approval.pen @@ -0,0 +1,973 @@ +{ + "version": "2.8", + "children": [ + { + "type": "frame", + "id": "H0Aq7", + "x": 0, + "y": 0, + "name": "Design System", + "width": 400, + "layout": "vertical", + "gap": 40, + "padding": 28, + "children": [ + { + "type": "frame", + "id": "r5fJ4", + "name": "Button/Primary", + "reusable": true, + "height": 40, + "fill": "$--color-accent", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-accent" + }, + "gap": 8, + "padding": [ + 10, + 20 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "geiia", + "name": "ButtonText", + "fill": "#FFFFFF", + "content": "通过", + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Space Grotesk", + "fontSize": 13, + "fontWeight": "500" + } + ] + }, + { + "id": "fyr3p", + "type": "ref", + "ref": "r5fJ4", + "name": "Button/Secondary", + "y": 108, + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-accent" + } + }, + { + "id": "d3jNf", + "type": "ref", + "ref": "r5fJ4", + "name": "Button/Outline", + "y": 188, + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + } + }, + { + "id": "2rwqv", + "type": "ref", + "ref": "r5fJ4", + "name": "Button/Ghost", + "y": 268, + "fill": "none", + "stroke": { + "thickness": 0, + "fill": "transparent" + }, + "padding": [ + 10, + 12 + ] + }, + { + "type": "frame", + "id": "Y0xmz", + "name": "StatusIcon/Completed", + "reusable": true, + "width": 24, + "height": 24, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "yOY6V", + "name": "Icon", + "fill": "$--color-success", + "content": "✓", + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 16 + } + ] + }, + { + "type": "frame", + "id": "v7PGP", + "name": "StatusIcon/Current", + "reusable": true, + "width": 24, + "height": 24, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "ellipse", + "id": "7jOsJ", + "name": "Dot", + "fill": "$--color-accent", + "width": 16, + "height": 16 + } + ] + }, + { + "type": "frame", + "id": "MWEbp", + "name": "StatusIcon/Pending", + "reusable": true, + "width": 24, + "height": 24, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "ellipse", + "id": "jNZdZ", + "name": "Circle", + "fill": "none", + "width": 16, + "height": 16, + "stroke": { + "align": "center", + "thickness": 2, + "fill": "$--color-text-muted" + } + } + ] + }, + { + "type": "frame", + "id": "Q7nd7", + "name": "FormField/ReadOnly", + "reusable": true, + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "L7fix", + "name": "Label", + "fill": "$--color-text-secondary", + "content": "字段名称", + "fontFamily": "Inter", + "fontSize": 13 + }, + { + "type": "frame", + "id": "GoHrJ", + "name": "ValueContainer", + "width": "fill_container", + "height": 40, + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + }, + "padding": [ + 10, + 16 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "HP9Uk", + "name": "Value", + "fill": "$--color-text-primary", + "content": "字段值", + "fontFamily": "Inter", + "fontSize": 14 + } + ] + } + ] + }, + { + "type": "frame", + "id": "jALdQ", + "name": "ApprovalNodeItem", + "reusable": true, + "width": "fill_container", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "UGfOf", + "name": "IconContainer", + "width": 24, + "height": "fit_content(0)", + "layout": "vertical", + "alignItems": "center" + }, + { + "type": "frame", + "id": "OT9MM", + "name": "Content", + "width": "fill_container", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "W0CwU", + "name": "Header", + "width": "fill_container", + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "152m4", + "name": "NodeName", + "fill": "$--color-text-primary", + "content": "节点名称", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "frame", + "id": "UKK6F", + "name": "StatusBadge", + "reusable": true, + "width": 60, + "height": 24, + "fill": "$--color-success", + "padding": [ + 4, + 10 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "Z8Mks", + "name": "StatusText", + "fill": "#FFFFFF", + "content": "通过", + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "500" + } + ] + } + ] + }, + { + "type": "frame", + "id": "BVFyN", + "name": "Info", + "width": "fill_container", + "layout": "vertical", + "gap": 6, + "children": [ + { + "type": "text", + "id": "QQCRM", + "name": "Approver", + "fill": "$--color-text-secondary", + "content": "审批人: 张三", + "fontFamily": "Inter", + "fontSize": 13 + }, + { + "type": "text", + "id": "YbMfY", + "name": "Time", + "fill": "$--color-text-muted", + "content": "2024-02-25 10:30", + "fontFamily": "Inter", + "fontSize": 12 + } + ] + }, + { + "type": "frame", + "id": "ZLJZr", + "name": "CommentArea", + "width": "fill_container", + "layout": "vertical", + "gap": 6, + "children": [ + { + "type": "text", + "id": "4dp39", + "name": "CommentLabel", + "fill": "$--color-text-secondary", + "content": "审批意见", + "fontFamily": "Inter", + "fontSize": 13 + }, + { + "type": "frame", + "id": "hJVqy", + "name": "CommentText", + "width": "fill_container", + "fill": "$--color-surface-tint", + "padding": 12, + "children": [ + { + "type": "text", + "id": "jOsZn", + "name": "Content", + "fill": "$--color-text-primary", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "同意采购,请财务部门审核。", + "fontFamily": "Inter", + "fontSize": 13 + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "MTclw", + "name": "CollapseButton", + "reusable": true, + "width": 32, + "height": 32, + "fill": "none", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "V6fOi", + "name": "Icon", + "fill": "$--color-text-secondary", + "content": "◀", + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14 + } + ] + }, + { + "type": "frame", + "id": "vo8wW", + "name": "ExpandButton", + "reusable": true, + "width": 32, + "height": 32, + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "5QBgk", + "name": "Icon", + "fill": "$--color-text-secondary", + "content": "▶", + "textAlign": "center", + "textAlignVertical": "middle", + "fontFamily": "Inter", + "fontSize": 14 + } + ] + }, + { + "type": "frame", + "id": "taMBt", + "name": "CollapsedStrip", + "reusable": true, + "width": 48, + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + }, + "layout": "vertical", + "gap": 16, + "padding": [ + 12, + 8 + ], + "children": [ + { + "id": "5IDWO", + "type": "ref", + "ref": "vo8wW", + "name": "StripBtn" + }, + { + "type": "text", + "id": "eTqHS", + "name": "StripLabel", + "fill": "$--color-text-primary", + "content": "审批\n历史", + "lineHeight": 1.4, + "textAlign": "center", + "fontFamily": "Space Grotesk", + "fontSize": 14, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "Eiyti", + "x": 500, + "y": 0, + "name": "Approval Screen", + "width": 1440, + "fill": "#ffffffff", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "lb1ge", + "name": "Header", + "width": "fill_container", + "height": 64, + "stroke": { + "thickness": 1, + "fill": "$--color-border" + }, + "padding": [ + 20, + 32 + ], + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "HO6K1", + "name": "HeaderLeft", + "height": "fill_container", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "jSWs6", + "name": "Title", + "fill": "$--color-text-primary", + "content": "审批详情", + "textAlignVertical": "middle", + "fontFamily": "Space Grotesk", + "fontSize": 18, + "fontWeight": "600" + } + ] + }, + { + "type": "frame", + "id": "hAhBA", + "name": "HeaderRight", + "height": "fill_container", + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "wMIrq", + "type": "ref", + "ref": "r5fJ4", + "name": "PassButton" + }, + { + "id": "FtQF8", + "type": "ref", + "ref": "r5fJ4", + "name": "RejectButton", + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-accent" + }, + "descendants": { + "geiia": { + "fill": "$--color-accent", + "content": "驳回" + } + } + }, + { + "id": "pKiQj", + "type": "ref", + "ref": "r5fJ4", + "name": "RevokeButton", + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + }, + "descendants": { + "geiia": { + "fill": "$--color-text-primary", + "content": "撤回" + } + } + }, + { + "id": "tQJQj", + "type": "ref", + "ref": "r5fJ4", + "name": "CloseButton", + "fill": "none", + "stroke": { + "thickness": 0, + "fill": "transparent" + }, + "padding": [ + 10, + 12 + ], + "descendants": { + "geiia": { + "fill": "$--color-text-secondary", + "content": "关闭" + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "300ij", + "name": "Body", + "width": "fill_container", + "gap": 24, + "padding": [ + 40, + 48, + 48, + 48 + ], + "children": [ + { + "type": "frame", + "id": "SJri1", + "name": "LeftContent", + "width": 880, + "layout": "vertical", + "gap": 24, + "children": [ + { + "type": "frame", + "id": "GCd39", + "name": "FormCard", + "width": "fill_container", + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + }, + "layout": "vertical", + "padding": 28, + "children": [ + { + "type": "text", + "id": "rK77e", + "name": "Title", + "fill": "$--color-text-primary", + "content": "流程表单", + "fontFamily": "Space Grotesk", + "fontSize": 18, + "fontWeight": "600" + }, + { + "type": "frame", + "id": "7iOiP", + "name": "FormFields", + "width": "fill_container", + "layout": "vertical", + "gap": 16, + "children": [ + { + "id": "qluNt", + "type": "ref", + "ref": "Q7nd7", + "name": "Field1", + "width": "fill_container", + "descendants": { + "L7fix": { + "content": "申请标题" + }, + "HP9Uk": { + "content": "采购办公用品申请" + } + } + }, + { + "id": "88OuI", + "type": "ref", + "ref": "Q7nd7", + "name": "Field2", + "width": "fill_container", + "descendants": { + "L7fix": { + "content": "申请部门" + }, + "HP9Uk": { + "content": "技术部" + } + } + }, + { + "id": "JJzOr", + "type": "ref", + "ref": "Q7nd7", + "name": "Field3", + "width": "fill_container", + "descendants": { + "L7fix": { + "content": "申请金额" + }, + "HP9Uk": { + "content": "¥15,000.00" + } + } + }, + { + "id": "aOrE2", + "type": "ref", + "ref": "Q7nd7", + "name": "Field4", + "width": "fill_container", + "descendants": { + "L7fix": { + "content": "申请日期" + }, + "HP9Uk": { + "content": "2024-02-24" + } + } + }, + { + "id": "VfIko", + "type": "ref", + "ref": "Q7nd7", + "name": "Field5", + "width": "fill_container", + "descendants": { + "L7fix": { + "content": "申请事由" + }, + "HP9Uk": { + "content": "部门日常办公需要,采购电脑配件及文具用品" + } + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "AxmH9", + "name": "RightContent", + "width": 420, + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "jh1VJ", + "name": "HistoryCard", + "width": "fill_container", + "fill": "#FFFFFF", + "stroke": { + "align": "center", + "thickness": 1, + "fill": "$--color-border" + }, + "layout": "vertical", + "gap": 20, + "padding": 24, + "children": [ + { + "type": "frame", + "id": "BVaLL", + "name": "TitleBar", + "width": "fill_container", + "gap": 12, + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "P5RbQ", + "name": "Title", + "fill": "$--color-text-primary", + "content": "审批节点历史记录", + "fontFamily": "Space Grotesk", + "fontSize": 18, + "fontWeight": "600" + }, + { + "id": "0bCKW", + "type": "ref", + "ref": "MTclw", + "name": "CollapseBtn" + } + ] + }, + { + "type": "frame", + "id": "yrBm2", + "name": "HistoryList", + "width": "fill_container", + "layout": "vertical", + "gap": 16, + "children": [ + { + "id": "bCwzp", + "type": "ref", + "ref": "jALdQ", + "name": "HistoryItem1", + "width": "fill_container", + "descendants": { + "UGfOf": { + "children": [ + { + "id": "iMe5r", + "type": "ref", + "ref": "Y0xmz", + "name": "StatusIcon" + } + ] + }, + "152m4": { + "content": "部门审批" + }, + "QQCRM": { + "content": "审批人: 王经理" + }, + "YbMfY": { + "content": "2024-02-24 14:30" + } + } + }, + { + "id": "IzCjx", + "type": "ref", + "ref": "jALdQ", + "name": "HistoryItem2", + "width": "fill_container", + "descendants": { + "UGfOf": { + "children": [ + { + "id": "JRtZQ", + "type": "ref", + "ref": "v7PGP", + "name": "StatusIcon" + } + ] + }, + "152m4": { + "content": "财务审批" + }, + "UKK6F": { + "fill": "$--color-accent" + }, + "Z8Mks": { + "content": "待审批" + }, + "QQCRM": { + "content": "审批人: 李总监" + }, + "YbMfY": { + "content": "等待处理" + } + } + }, + { + "id": "b3lJU", + "type": "ref", + "ref": "jALdQ", + "name": "HistoryItem3", + "width": "fill_container", + "descendants": { + "UGfOf": { + "children": [ + { + "id": "YPYal", + "type": "ref", + "ref": "MWEbp", + "name": "StatusIcon" + } + ] + }, + "152m4": { + "content": "总经理审批" + }, + "UKK6F": { + "fill": "$--color-text-secondary" + }, + "Z8Mks": { + "content": "待处理" + }, + "QQCRM": { + "content": "审批人: 张总" + }, + "YbMfY": { + "content": "待审批人处理" + } + } + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "hX1PR", + "x": 2000, + "y": 0, + "name": "Demo Area", + "width": 500, + "gap": 40, + "padding": 48, + "children": [ + { + "type": "text", + "id": "FWWsO", + "name": "Label", + "fill": "$--color-text-muted", + "content": "展开状态", + "fontFamily": "Space Grotesk", + "fontSize": 14, + "fontWeight": "600" + }, + { + "id": "L9mDo", + "type": "ref", + "ref": "taMBt", + "name": "CollapsedDemo" + }, + { + "type": "text", + "id": "C8Mwq", + "name": "Label", + "fill": "$--color-text-muted", + "content": "收缩状态", + "fontFamily": "Space Grotesk", + "fontSize": 14, + "fontWeight": "600" + } + ] + } + ], + "variables": { + "--color-accent": { + "type": "color", + "value": "#E42313" + }, + "--color-bg": { + "type": "color", + "value": "#FFFFFF" + }, + "--color-border": { + "type": "color", + "value": "#E8E8E8" + }, + "--color-success": { + "type": "color", + "value": "#22C55E" + }, + "--color-surface-tint": { + "type": "color", + "value": "#FAFAFA" + }, + "--color-text-muted": { + "type": "color", + "value": "#B0B0B0" + }, + "--color-text-primary": { + "type": "color", + "value": "#0D0D0D" + }, + "--color-text-secondary": { + "type": "color", + "value": "#7A7A7A" + }, + "--font-body": { + "type": "string", + "value": "Inter" + }, + "--font-heading": { + "type": "string", + "value": "Space Grotesk" + }, + "--spacing-card-padding": { + "type": "number", + "value": 28 + }, + "--spacing-content-padding-h": { + "type": "number", + "value": 48 + }, + "--spacing-content-padding-v": { + "type": "number", + "value": 40 + }, + "--spacing-header-height": { + "type": "number", + "value": 64 + }, + "--spacing-section-gap": { + "type": "number", + "value": 24 + } + } +} \ No newline at end of file diff --git a/frontend/.claude/commands/git-push.md b/frontend/.claude/commands/git-push.md new file mode 100644 index 00000000..40f55f37 --- /dev/null +++ b/frontend/.claude/commands/git-push.md @@ -0,0 +1,3 @@ +# git push command + +根据当前调整内容,并创建git的提交指令,并添加对应的提交信息,然后执行git push命令将本地的提交推送到远程仓库。 \ No newline at end of file diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md new file mode 100644 index 00000000..51811900 --- /dev/null +++ b/frontend/CLAUDE.md @@ -0,0 +1,466 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Flow Engine frontend is a React/TypeScript monorepo built with Rsbuild/Rspack. It provides a visual workflow designer (flow-design) and runtime applications (app-pc, app-mobile) that integrate with the Java backend workflow engine. + +## Common Commands + +### Package Manager + +This project uses **pnpm workspaces**. Always use `pnpm` for package management. + +```bash +# Install all dependencies +pnpm install + +# Add a dependency to a specific package +pnpm -F @flow-engine/flow-design add +``` + +### Building + +```bash +# Build all packages in order +pnpm run build + +# Build individual packages +pnpm run build:flow-types # Build types first (no dependencies) +pnpm run build:flow-core # Build core API library +pnpm run build:flow-engine # Build flow-design (depends on flow-types, flow-core) +pnpm run build:app-pc # Build PC application + +# Watch mode for development +pnpm run watch:flow-design # Rebuild flow-design on changes +``` + +### Development + +```bash +pnpm run dev:app-pc # Run PC app on localhost:3000 +pnpm run dev:app-mobile # Run mobile app +``` + +### Testing + +```bash +# Run tests (uses @rstest/core) +cd packages/flow-design +pnpm run test + +# Currently: Only one basic test exists in flow-design/tests/demo.test.ts +``` + +## Monorepo Structure + +``` +frontend/ +├── apps/ # Runtime applications +│ ├── app-pc/ # PC client (React Router, proxies to localhost:8090) +│ └── app-mobile/ # Mobile client (in development) +│ +├── packages/ # Shared libraries +│ ├── flow-types/ # TypeScript definitions (no dependencies) +│ ├── flow-core/ # API client + service interfaces +│ └── flow-design/ # Workflow designer component library +│ +└── pnpm-workspace.yaml # Workspace configuration +``` + +### Package Dependencies + +``` +flow-types (no dependencies) + ↓ +flow-core (depends on: flow-types) + ↓ +flow-design (depends on: flow-types, flow-core) + ↓ ↓ +app-pc app-mobile +``` + +When making changes: +1. **flow-types**: Rebuild everything that depends on it +2. **flow-core**: Rebuild flow-design and apps +3. **flow-design**: Rebuild apps + +## Architecture + +### @flowgram.ai Integration + +The flow-design package is built on top of `@flowgram.ai/fixed-layout-editor`: + +- **fixed-layout-editor**: Core editor that handles the visual canvas, node rendering, and drag-drop +- **fixed-semi-materials**: Semi Design UI components for node configuration +- **panel-manager-plugin**: Manages side panels (left/right panels) +- **minimap-plugin**: Adds minimap overview +- **export-plugin**: Workflow export functionality + +**Critical**: The editor uses a **fixed layout system**, not absolute positioning like traditional flowchart libraries. Nodes are positioned using a coordinate system managed by the editor. + +### Redux Architecture (flow-design) + +State management follows a **presenter pattern** with Redux Toolkit: + +``` +Context (useDesignContext) + └── state: { workflow, view } + └── presenter: DesignPanelPresenter + ├── getWorkflowPresenter() + ├── getNodePresenter() + ├── getFlowActionPresenter() + └── getFormPresenter() +``` + +- **Context**: Created via `createDesignContext(props)`, provides state and presenter +- **Presenters**: Handle business logic and API calls +- **Store**: Uses Redux Toolkit slices (`workflowSlice`, `viewSlice`) +- **Selectors**: Access state via `useDesignContext()` hook + +### Design Panel Structure + +The `DesignPanelLayout` follows a Header-Body-Footer pattern using Ant Design components: + +``` +DesignPanelLayout +├── Header - Tabs (base/form/flow/setting) + action buttons +├── Body - Tab content components +└── Footer - Empty (reserved) +``` + +**Tabs**: +- `base` - Basic workflow info (name, code) +- `form` - Form design with field configuration +- `flow` - Visual workflow design with @flowgram.ai editor +- `setting` - Advanced workflow parameters + +### Approval Layout Structure + +The `ApprovalLayout` uses Ant Design Layout with collapsible sidebar: + +``` +ApprovalLayout +├── Header - Typography.Title "审批详情" + Button group +└── Body (Layout) + ├── Content - Card with FormViewComponent + └── Sider - Collapsible sidebar with FlowNodeHistory (Timeline) +``` + +**Key Ant Design Components Used**: +- `Layout`, `Content`, `Sider` - Main layout structure +- `Flex` - Flexible box layout for headers and button groups +- `Card` - Form container with title +- `Timeline` - Approval history display +- `Tag` - Status badges (color prop for different states) +- `Typography` - Text styling with `type="secondary"` for muted text +- `Button` - Action buttons with `type="primary"`, `danger`, `ghost` +- `Space` - Button grouping with spacing +- `Empty` - Empty state display + +## Key Components + +### flow-design Package + +**Directory Structure**: +``` +packages/flow-design/src/components/ +├── design-panel/ # Main design panel +│ ├── layout/ # Layout components (Header, Body, Footer) +│ ├── tabs/ # Tab content components +│ ├── context/ # DesignPanelContext +│ ├── hooks/ # use-design-context +│ ├── store/ # Redux store and slices +│ └── types.ts # TypeScript interfaces +│ +├── design-editor/ # @flowgram.ai editor wrapper +│ └── index.tsx # Editor initialization +│ +└── flow-approval/ # Approval workflow components + ├── layout/ # ApprovalLayout (Header, Body) + ├── components/ # FormViewComponent, FlowNodeHistory + ├── context/ # ApprovalContext + ├── hooks/ # use-approval-context + └── typings/ # TypeScript interfaces +``` + +### Type Definitions (flow-types) + +**Critical Types** in `packages/flow-types/src/pages/design-panel/types.ts`: + +```typescript +interface Workflow { + id: string; + title: string; + code: string; + form: FlowForm; + strategies?: any[]; + nodes?: FlowNode[]; +} + +interface FlowNode { + id: string; + name: string; + type: NodeType; + blocks?: FlowNode[]; // HIERARCHICAL: child nodes + strategies?: any[]; + actions?: FlowAction[]; +} + +interface FlowForm { + name: string; + code: string; + fields: FormField[]; + subForms: FlowForm[]; +} +``` + +**Important**: `FlowNode.blocks` is the hierarchical structure - NOT edge-based connections. + +## State Management Patterns + +### DesignPanel Context + +```typescript +const {state, context} = useDesignContext(); + +// Access state +state.workflow.title +state.view.tabPanel + +// Use presenter to trigger actions +context.getPresenter().updateTitle("New Title"); +context.getPresenter().getFlowActionPresenter().action(actionId); +``` + +### Approval Context + +```typescript +const {state, context} = useApprovalContext(); + +// Process nodes (approval history) +context.getPresenter().processNodes().then(nodes => {...}); + +// Action execution +context.getPresenter().getFlowActionPresenter().action(actionId); +``` + +## UI Component Conventions + +### Using Ant Design Components + +This project uses **Ant Design 6.x** as the primary UI library. Follow these patterns: + +**Layout Components**: +```typescript +import { Layout, Flex, Space } from 'antd'; + + + + {/* Left content */} + {/* Right content */} + + +``` + +**Buttons**: +```typescript +import { Button } from 'antd'; + +// Primary action (first button) + + +// Secondary/Destructive action + + +// Tertiary/Ghost action + + +// Button group with spacing + + + + +``` + +**Card with Title**: +```typescript +import { Card } from 'antd'; + + + {/* Content */} + +``` + +**Collapsible Sidebar**: +```typescript +import { Layout, Sider } from 'antd'; + + + {/* Main content */} + + {/* Sidebar content */} + + +``` + +**Typography**: +```typescript +import { Typography } from 'antd'; + +const { Title, Text } = Typography; + +审批详情 +辅助文本 +``` + +**Timeline (Approval History)**: +```typescript +import { Timeline, Tag } from 'antd'; +import { + CheckCircleFilled, + ClockCircleOutlined, + LoadingOutlined +} from '@ant-design/icons'; + + + } + > +
节点名称
+ 通过 +
+
+``` + +**Tag Colors for Status**: +- `color="success"` - Completed/通过 +- `color="error"` - Current/待审批 +- `color="default"` - Pending/待处理 + +**Spacing**: +- Use Ant Design's standard spacing: 8, 16, 24, 32, 48 +- Use `Space` component for consistent gaps +- Use `Flex` with `gap` prop for layout spacing + +### Component Patterns + +**Header Pattern**: +```typescript + + 页面标题 + + + + + +``` + +**Sidebar Pattern**: +```typescript + + {!collapsed ? ( +
+ 侧边栏标题 + {/* Content */} +
+ ) : ( +
+ {/* Collapsed content */} +
+ )} +
+``` + +## Path Aliases + +All packages use `@/` as an alias for `src/`: + +```typescript +import {Header} from "@/components/design-panel/layout/header"; +import {DesignPanelTypes} from "@/components/design-panel/types"; +``` + +## API Integration + +### Backend Proxy + +During development, API requests are proxied to the backend: + +```javascript +// apps/app-pc/rsbuild.config.ts +proxy: { + '/api': 'http://localhost:8090', + '/open': 'http://localhost:8090', + '/user': 'http://localhost:8090', +} +``` + +### flow-core API Client + +The `flow-core` package provides the HTTP client and service interfaces: + +```typescript +// API client setup in flow-core +const client = axios.create({ + baseURL: '/api', + timeout: 30000, +}); +``` + +## Build System (Rsbuild/Rspack) + +- **Rsbuild**: High-level build configuration (like Vite) +- **Rspack**: Rust-powered bundler (5-10x faster than webpack) +- **Rslib**: Library mode for building packages + +**Configuration Files**: +- Apps: `rsbuild.config.ts` +- Libraries: `rslib.config.ts` + +**Key Features**: +- TypeScript support with strict mode +- Sass/Less support for styling +- ES module output +- Tree-shaking enabled +- React Fast Refresh in dev mode + +## Extension Points + +### Adding New Node Types + +1. Update `NodeType` in `flow-types/src/pages/design-panel/types.ts` +2. Create node configuration component in `flow-design/src/components/design-editor/` +3. Register in `@flowgram.ai` editor materials + +### Adding New Layout Components + +Follow Ant Design patterns: +1. Use Ant Design Layout components (Layout, Content, Sider) +2. Use Flex for responsive layouts +3. Use Space for consistent spacing +4. Use Card for content containers +5. Use Typography for text styling +6. Follow existing component patterns in flow-approval/layout/ + +## Documentation References + +- **flow-design/README.md** - Component library documentation +- **apps/app-pc/AGENTS.md** - App development guidelines +- **Ant Design**: https://ant.design/components/overview-cn/ +- **Rsbuild**: https://rsbuild.rs/llms.txt +- **Rspack**: https://rspack.rs/llms.txt +- **Parent CLAUDE.md** (../CLAUDE.md) - Backend architecture and Java patterns diff --git a/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx b/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx index 367ccf68..ac22659f 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx @@ -1,6 +1,10 @@ import React from "react"; import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval-context"; import {ProcessNode} from "@/components/flow-approval/typings"; +import {Timeline, Tag, Badge, Empty, Typography, Space} from "antd"; +import {CheckCircleFilled, ClockCircleOutlined, LoadingOutlined} from "@ant-design/icons"; + +const {Text, Paragraph} = Typography; export interface FlowNodeHistoryAction{ refresh:()=>void; @@ -10,10 +14,88 @@ interface FlowNodeHistoryProps{ actionRef?:React.Ref; } -export const FlowNodeHistory: React.FC = (props) => { +// 获取节点状态 +const getNodeStatus = (node: ProcessNode): 'completed' | 'current' | 'pending' => { + if (node.operators && node.operators.length > 0) { + return 'completed'; + } + return 'pending'; +}; - const {context} = useApprovalContext(); +// 获取状态配置 +const getStatusConfig = (status: 'completed' | 'current' | 'pending') => { + switch (status) { + case 'completed': + return { + color: 'success', + label: '通过', + icon: + }; + case 'current': + return { + color: 'error', + label: '待审批', + icon: + }; + case 'pending': + return { + color: 'default', + label: '待处理', + icon: + }; + } +}; + +// 单个审批节点项组件 +const ApprovalNodeItem: React.FC<{node: ProcessNode}> = ({node}) => { + const status = getNodeStatus(node); + const firstOperator = node.operators?.[0]; + const statusConfig = getStatusConfig(status); + return ( + + + + {node.nodeName} + {statusConfig.label} + + + {firstOperator && ( + 审批人: {firstOperator.flowOperator.name} + )} + + {firstOperator?.approveTime && ( + + {new Date(firstOperator.approveTime).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + })} + + )} + + {firstOperator?.advice && ( +
+ 审批意见 +
+ {firstOperator.advice} +
+
+ )} +
+
+ ); +}; + +export const FlowNodeHistory: React.FC = (props) => { + const {context} = useApprovalContext(); const [processNodes, setProcessNodes] = React.useState([]); const triggerProcessNodes = () => { @@ -35,8 +117,16 @@ export const FlowNodeHistory: React.FC = (props) => { },[]); return ( -
- 流转历史 {processNodes.length} -
+ <> + {processNodes.length > 0 ? ( + + {processNodes.map(node => ( + + ))} + + ) : ( + + )} + ) } \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx b/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx index e0934b57..7b728f92 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx @@ -1,9 +1,20 @@ -import React from "react"; -import {Col, Row} from "antd"; +import React, {useState} from "react"; import {FormViewComponent} from "@/components/flow-approval/components/form-view-component"; import {FlowNodeHistory, FlowNodeHistoryAction} from "@/components/flow-approval/components/flow-node-history"; +import { + ApprovalContentPaddingV, + ApprovalContentPaddingH, + ApprovalSidebarWidth, + ApprovalSidebarCollapsedWidth, + ApprovalLayoutHeight, +} from "@/components/flow-approval/typings"; +import {Layout, Card, Button, Typography, Flex} from "antd"; + +const {Sider, Content} = Layout; +const {Title} = Typography; export const Body = () => { + const [collapsed, setCollapsed] = useState(false); const flowNodeHistoryAction = React.useRef(null); const handleValuesChange = (values:any) => { @@ -11,16 +22,81 @@ export const Body = () => { } return ( - - - 表单详情 - - - - - - + + + 流程表单} + bordered={true} + style={{height: '100%'}} + > + + + + + + {!collapsed && ( + + + 审批节点历史记录 + - ) - })} - + > + {item.title} + + ))} + + ) } \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/layout/index.tsx b/frontend/packages/flow-design/src/components/flow-approval/layout/index.tsx index 6de74580..0e3049a8 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/layout/index.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/layout/index.tsx @@ -7,23 +7,29 @@ import {createApprovalContext} from "@/components/flow-approval/hooks/use-approv import {Header} from "@/components/flow-approval/layout/header"; import {Body} from "@/components/flow-approval/layout/body"; - const ApprovalLayoutScope: React.FC = (props) => { const {context} = createApprovalContext(props); return ( -
- +
+
+ +
) } export const ApprovalLayout: React.FC = (props) => { - return ( ) -}; +} diff --git a/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts b/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts index b8bc758d..e8cc1e7e 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts +++ b/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts @@ -92,6 +92,14 @@ export interface ApprovalLayoutProps { onClose?:() => void; } +// Layout constants +export const ApprovalLayoutHeight = 64; +export const ApprovalContentPaddingV = 24; +export const ApprovalContentPaddingH = 24; +export const ApprovalColumnGap = 16; +export const ApprovalSidebarWidth = 400; +export const ApprovalSidebarCollapsedWidth = 48; + export type State = { flow?:FlowContent; From e4cfb0caf862edaf1005ecee2e53a3a5d5409f07 Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Thu, 26 Feb 2026 10:51:08 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E8=AE=B0=E5=BD=95=E5=B1=95=E7=A4=BA=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复Ant Design Timeline组件API兼容性问题,使用items属性替代子组件 - 利用history字段正确区分历史节点和未执行节点状态 - 优化UI样式:添加边框、修复关闭按钮显示、使用Ant Design图标 - 侧边栏标题改为"审批流程记录" Co-Authored-By: Claude Opus 4.6 --- .../flow/pojo/response/ProcessNode.java | 29 ++++- .../service/impl/FlowProcessNodeService.java | 3 + .../components/flow-node-history.tsx | 123 +++++++++--------- .../src/components/flow-approval/index.tsx | 24 ++-- .../components/flow-approval/layout/body.tsx | 39 ++++-- .../flow-approval/layout/header.tsx | 64 ++++----- .../components/flow-approval/typings/index.ts | 5 +- 7 files changed, 168 insertions(+), 119 deletions(-) diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/ProcessNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/ProcessNode.java index 2dd5028b..5da9e391 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/ProcessNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/ProcessNode.java @@ -16,6 +16,11 @@ @Data @NoArgsConstructor public class ProcessNode { + + public final static int STATE_HISTORY = -1; + public final static int STATE_CURRENT = 0; + public final static int STATE_NEXT = 1; + /** * 节点名称 */ @@ -30,9 +35,12 @@ public class ProcessNode { private String nodeType; /** - * 是否历史记录 + * 记录状态 + * -1 为历史状态 + * 0 为当前状态 + * 1 为后续状态 */ - private boolean history; + private int state; /** * 节点审批人 @@ -40,13 +48,17 @@ public class ProcessNode { private List operators; + public boolean isHistory(){ + return this.state == STATE_HISTORY; + } + public ProcessNode(FlowRecord flowRecord, Workflow workflow) { this.nodeId = flowRecord.getNodeId(); IFlowNode flowNode = workflow.getFlowNode(this.nodeId); this.nodeName = flowNode.getName(); this.nodeType = flowNode.getType(); this.operators = new ArrayList<>(); - this.history = true; + this.state = STATE_HISTORY; this.operators.add(new FlowOperatorBody(flowRecord)); } @@ -56,7 +68,16 @@ public ProcessNode(IFlowNode flowNode, List operators) { this.nodeName = flowNode.getName(); this.nodeType = flowNode.getType(); this.operators = operators.stream().map(FlowOperatorBody::new).toList(); - this.history = false; + this.state = STATE_NEXT; + } + + + public boolean isFlowNode(IFlowNode currentNode) { + return this.nodeId.equals(currentNode.getId()); + } + + public void setCurrentState() { + this.state = STATE_CURRENT; } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java index 61dd930a..c3da7dd6 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java @@ -122,6 +122,9 @@ private void fetchNextNode(FlowSession flowSession, List nexNodes) { operators = operatorManager.getOperators(); } ProcessNode processNode = new ProcessNode(flowNode,operators); + if(processNode.isFlowNode(this.currentNode)){ + processNode.setCurrentState(); + } this.nodeList.add(processNode); List nextNodes = workflow.nextNodes(flowNode); this.fetchNextNode(flowSession.updateSession(flowNode), nextNodes); diff --git a/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx b/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx index ac22659f..040adb0a 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/components/flow-node-history.tsx @@ -1,10 +1,10 @@ import React from "react"; import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval-context"; import {ProcessNode} from "@/components/flow-approval/typings"; -import {Timeline, Tag, Badge, Empty, Typography, Space} from "antd"; -import {CheckCircleFilled, ClockCircleOutlined, LoadingOutlined} from "@ant-design/icons"; +import {Timeline, Tag, Empty, Typography} from "antd"; +import {CheckCircleFilled, ClockCircleOutlined, LoadingOutlined, SyncOutlined} from "@ant-design/icons"; -const {Text, Paragraph} = Typography; +const {Text} = Typography; export interface FlowNodeHistoryAction{ refresh:()=>void; @@ -16,9 +16,13 @@ interface FlowNodeHistoryProps{ // 获取节点状态 const getNodeStatus = (node: ProcessNode): 'completed' | 'current' | 'pending' => { - if (node.operators && node.operators.length > 0) { + if (node.state===-1) { return 'completed'; } + // 非历史节点,检查是否有审批人 + if (node.state === 0) { + return 'current'; + } return 'pending'; }; @@ -28,72 +32,24 @@ const getStatusConfig = (status: 'completed' | 'current' | 'pending') => { case 'completed': return { color: 'success', - label: '通过', + label: '已通过', icon: }; case 'current': return { - color: 'error', + color: 'processing', label: '待审批', - icon: + icon: }; case 'pending': return { color: 'default', - label: '待处理', + label: '未执行', icon: }; } }; -// 单个审批节点项组件 -const ApprovalNodeItem: React.FC<{node: ProcessNode}> = ({node}) => { - const status = getNodeStatus(node); - const firstOperator = node.operators?.[0]; - const statusConfig = getStatusConfig(status); - - return ( - - - - {node.nodeName} - {statusConfig.label} - - - {firstOperator && ( - 审批人: {firstOperator.flowOperator.name} - )} - - {firstOperator?.approveTime && ( - - {new Date(firstOperator.approveTime).toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - })} - - )} - - {firstOperator?.advice && ( -
- 审批意见 -
- {firstOperator.advice} -
-
- )} -
-
- ); -}; - export const FlowNodeHistory: React.FC = (props) => { const {context} = useApprovalContext(); const [processNodes, setProcessNodes] = React.useState([]); @@ -119,13 +75,56 @@ export const FlowNodeHistory: React.FC = (props) => { return ( <> {processNodes.length > 0 ? ( - - {processNodes.map(node => ( - - ))} - + ({ + icon: getStatusConfig(getNodeStatus(node)).icon, + content: ( +
+
+ {node.nodeName} + + {getStatusConfig(getNodeStatus(node)).label} + +
+ {node.state===-1 && node.operators?.[0] && ( + <> + + 审批人: {node.operators[0].flowOperator.name} + + {node.operators[0].approveTime > 0 && ( + + {new Date(node.operators[0].approveTime).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + })} + + )} + {node.operators[0].advice && ( +
+ + {node.operators[0].advice} + +
+ )} + + )} + {node.state!==-1 && node.operators?.[0] && ( + + 待审批人: {node.operators[0].flowOperator.name} + + )} +
+ ) + }))} /> ) : ( - + )} ) diff --git a/frontend/packages/flow-design/src/components/flow-approval/index.tsx b/frontend/packages/flow-design/src/components/flow-approval/index.tsx index 0b709524..6cc743b2 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/index.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/index.tsx @@ -6,27 +6,27 @@ import {ApprovalLayout} from "@/components/flow-approval/layout"; interface ApprovalPanelProps { workflowCode?: string; - recordId?:string; + recordId?: string; onClose?: () => void; } export const ApprovalPanel: React.FC = (props) => { - const [content,dispatch] = React.useState(undefined); + const [content, dispatch] = React.useState(undefined); - React.useEffect(()=>{ - const id = props.recordId || props.workflowCode || ''; - detail(id).then(res=>{ - if(res.success){ + React.useEffect(() => { + const id = props.recordId || props.workflowCode || ''; + detail(id).then(res => { + if (res.success) { dispatch(res.data); } }); - },[]); + }, []); return ( - <> +
{content && } - +
) } @@ -41,6 +41,12 @@ export const ApprovalPanelDrawer: React.FC = (props) = { return ( - + 流程表单} - bordered={true} - style={{height: '100%'}} + style={{height: '100%', borderRadius: 8}} + styles={{body: {padding: 24}}} > { onCollapse={setCollapsed} trigger={null} style={{ + marginLeft: "8px", backgroundColor: '#fff', - borderLeft: '1px solid #f0f0f0', + borderRadius: 8, + overflow: 'hidden', }} > {!collapsed && ( @@ -56,14 +64,19 @@ export const Body = () => { - 审批节点历史记录 + 流程记录 + ))} - ))} - - - + + + ) } \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts b/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts index e8cc1e7e..12685a5e 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts +++ b/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts @@ -64,7 +64,7 @@ export interface ProcessNode{ nodeId:string; nodeName:string; nodeType:string; - history:boolean; + state:number; operators:FlowOperatorBody[] } @@ -96,8 +96,7 @@ export interface ApprovalLayoutProps { export const ApprovalLayoutHeight = 64; export const ApprovalContentPaddingV = 24; export const ApprovalContentPaddingH = 24; -export const ApprovalColumnGap = 16; -export const ApprovalSidebarWidth = 400; +export const ApprovalSidebarWidth = 250; export const ApprovalSidebarCollapsedWidth = 48;