From 0eafb4d3fd0cd7dae8f77878ae44afe4df13eca7 Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Mon, 14 Apr 2025 20:03:17 +0800 Subject: [PATCH 01/20] update flow form --- .../flow/nodes/panel/ButtonPanel.tsx | 200 +++++++++--------- .../components/flow/nodes/panel/EdgePanel.tsx | 3 +- .../components/flow/nodes/panel/NodePanel.tsx | 9 +- .../flow/nodes/panel/ScriptModal.tsx | 58 ++--- .../components/flow/nodes/panel/circulate.tsx | 8 +- .../src/components/flow/nodes/panel/node.tsx | 8 +- .../src/components/flow/nodes/panel/over.tsx | 8 +- .../src/components/flow/nodes/panel/start.tsx | 8 +- admin-pro-ui/src/components/flow/types.ts | 4 +- 9 files changed, 155 insertions(+), 151 deletions(-) diff --git a/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx b/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx index d027b993..dfac4cc5 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx @@ -1,6 +1,6 @@ import React from "react"; -import {ActionType, ModalForm, ProColumns, ProForm, ProFormText, ProTable} from "@ant-design/pro-components"; -import {Button, ColorPicker, Popconfirm, Space} from "antd"; +import {ActionType, ProColumns, ProTable} from "@ant-design/pro-components"; +import {Button, ColorPicker, Modal, Popconfirm, Space} from "antd"; import ScriptModal from "@/components/flow/nodes/panel/ScriptModal"; import {EyeOutlined} from "@ant-design/icons"; import FlowContext from "@/components/flow/domain/FlowContext"; @@ -8,6 +8,7 @@ import FormInput from "@/components/form/input"; import ValidateUtils from "@/components/form/utils"; import FormSelect from "@/components/form/select"; import FormColor from "@/components/form/color"; +import Form from "@/components/form"; interface ButtonPanelProps { id: string; @@ -17,9 +18,8 @@ const ButtonPanel: React.FC = (props) => { const actionRef = React.useRef(); - const [form] = ProForm.useForm(); - const [groovyForm] = ProForm.useForm(); - + const form = Form.useForm(); + const groovyForm = Form.useForm(); const [visible, setVisible] = React.useState(false); @@ -70,7 +70,7 @@ const ButtonPanel: React.FC = (props) => { { - groovyForm.resetFields(); + groovyForm.reset(); form.setFieldsValue(record); setType(record.type); setVisible(true); @@ -113,7 +113,7 @@ const ButtonPanel: React.FC = (props) => { @@ -121,111 +121,111 @@ const ButtonPanel: React.FC = (props) => { }} /> - { - flowContext.getFlowPanelContext()?.updateButton(props.id, values as any); + onCancel={()=>{ setVisible(false); - actionRef.current?.reload(); }} - modalProps={{ - onCancel: () => { - setVisible(false); - }, - onClose:()=>{ - setVisible(false); - }, - destroyOnClose:true + onClose={()=>{ + setVisible(false); }} + onOk={async ()=>{ + await form.submit(); + }} + destroyOnClose={true} > -
-
-
- - {markdown} - + { + props.onFinish(values); + props.setVisible(false); + }} + > +
+
+
+ + {markdown} + +
-
-
+
-
- + + ) } diff --git a/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx b/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx index 5e79774d..ff135049 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx @@ -3,11 +3,11 @@ import {Button, Drawer, Space, Tabs} from "antd"; import NodePanel from "@/components/flow/nodes/panel/NodePanel"; import EdgePanel from "@/components/flow/nodes/panel/EdgePanel"; import {SettingPanelProps} from "@/components/flow/types"; -import {ProForm} from "@ant-design/pro-components"; +import Form from "@/components/form"; const CirculateSettingPanel: React.FC = (props) => { - const [form] = ProForm.useForm(); + const form = Form.useForm(); return ( = (props) => { diff --git a/admin-pro-ui/src/components/flow/nodes/panel/node.tsx b/admin-pro-ui/src/components/flow/nodes/panel/node.tsx index 27705a91..06358d54 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/node.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/node.tsx @@ -4,12 +4,12 @@ import NodePanel from "@/components/flow/nodes/panel/NodePanel"; import EdgePanel from "@/components/flow/nodes/panel/EdgePanel"; import ButtonPanel from "@/components/flow/nodes/panel/ButtonPanel"; import {SettingPanelProps} from "@/components/flow/types"; -import {ProForm} from "@ant-design/pro-components"; +import Form from "@/components/form"; const NodeSettingPanel: React.FC = (props) => { - const [form] = ProForm.useForm(); + const form = Form.useForm(); return ( = (props) => { diff --git a/admin-pro-ui/src/components/flow/nodes/panel/over.tsx b/admin-pro-ui/src/components/flow/nodes/panel/over.tsx index 7b06d57d..4eda3772 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/over.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/over.tsx @@ -2,11 +2,11 @@ import React from "react"; import {Button, Drawer, Space} from "antd"; import NodePanel from "@/components/flow/nodes/panel/NodePanel"; import {SettingPanelProps} from "@/components/flow/types"; -import {ProForm} from "@ant-design/pro-components"; +import Form from "@/components/form"; const OverSettingPanel: React.FC = (props) => { - const [form] = ProForm.useForm(); + const form = Form.useForm(); return ( = (props) => { diff --git a/admin-pro-ui/src/components/flow/nodes/panel/start.tsx b/admin-pro-ui/src/components/flow/nodes/panel/start.tsx index ddc76c8d..2fa1a79c 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/start.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/start.tsx @@ -2,13 +2,13 @@ import React from "react"; import {Button, Drawer, Space, Tabs} from "antd"; import EdgePanel from "@/components/flow/nodes/panel/EdgePanel"; import NodePanel from "@/components/flow/nodes/panel/NodePanel"; -import {ProForm} from "@ant-design/pro-components"; import ButtonPanel from "@/components/flow/nodes/panel/ButtonPanel"; import {SettingPanelProps} from "@/components/flow/types"; +import Form from "@/components/form"; const StartSettingPanel: React.FC = (props) => { - const [form] = ProForm.useForm(); + const form = Form.useForm(); return ( = (props) => { diff --git a/admin-pro-ui/src/components/flow/types.ts b/admin-pro-ui/src/components/flow/types.ts index 18f34b25..d91b90c6 100644 --- a/admin-pro-ui/src/components/flow/types.ts +++ b/admin-pro-ui/src/components/flow/types.ts @@ -1,5 +1,5 @@ // 节点状态 -import {FormAction} from "@/components/form"; +import FormInstance from "@/components/form/domain/FormInstance"; export type NodeState = "done" | "wait" | "undone" | "current"; @@ -122,7 +122,7 @@ export interface FlowFormViewProps { // 表单数据 data: FlowFormParams; // 表单控制对象 - formAction: React.RefObject; + formInstance: FormInstance; // 数据版本 dataVersion?: number; } From 06050c21624ea34b5bb0913c6d6b491f3f036895 Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Mon, 14 Apr 2025 20:18:20 +0800 Subject: [PATCH 02/20] update flow form --- .../src/pages/flow/leave/LeaveForm.tsx | 47 +++---- admin-pro-ui/src/pages/flow/user/index.tsx | 122 ++++++++-------- admin-pro-ui/src/pages/flow/work/index.tsx | 130 ++++++++---------- .../app/cmd/domain/router/UserRouter.java | 4 +- .../domain/user/service/UserService.java | 6 +- .../api/domain/UserDomainCmdController.java | 2 +- 6 files changed, 141 insertions(+), 170 deletions(-) diff --git a/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx b/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx index d4af68af..1602ea71 100644 --- a/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx +++ b/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx @@ -1,52 +1,47 @@ -import React from "react"; -import {ProForm, ProFormDigit, ProFormText, ProFormTextArea} from "@ant-design/pro-components"; +import React, {useEffect} from "react"; import {FlowFormViewProps} from "@/components/flow/types"; - +import Form from "@/components/form"; +import FormInput from "@/components/form/input"; +import ValidateUtils from "@/components/form/utils"; +import FormTextArea from "@/components/form/textarea"; const LeaveForm: React.FC = (props) => { + useEffect(() => { + props.formInstance.setFieldsValue(props.data) + }, [props.dataVersion]); + return ( - - + ) } diff --git a/admin-pro-ui/src/pages/flow/user/index.tsx b/admin-pro-ui/src/pages/flow/user/index.tsx index f1c99fb3..885c102d 100644 --- a/admin-pro-ui/src/pages/flow/user/index.tsx +++ b/admin-pro-ui/src/pages/flow/user/index.tsx @@ -1,18 +1,13 @@ import React, {useRef} from "react"; -import { - ActionType, - ModalForm, - PageContainer, - ProForm, - ProFormSwitch, - ProFormText, - ProTable -} from "@ant-design/pro-components"; +import {ActionType, PageContainer, ProTable} from "@ant-design/pro-components"; import {changeManager, entrust, list, remove, removeEntrust, save} from "@/api/user"; -import {Button, message, Popconfirm, Space} from "antd"; +import {Button, message, Modal, Popconfirm, Space} from "antd"; import {DeleteOutlined, SettingOutlined} from "@ant-design/icons"; import UserSelect from "@/pages/flow/user/select"; - +import Form from "@/components/form"; +import FormInput from "@/components/form/input"; +import ValidateUtils from "@/components/form/utils"; +import FormSwitch from "@/components/form/switch"; const UserPage = () => { @@ -22,7 +17,7 @@ const UserPage = () => { const [user, setUser] = React.useState({}); const actionRef = useRef(); - const [form] = ProForm.useForm(); + const form = Form.useForm(); const handleSave = (values: any) => { save(values).then(res => { @@ -186,67 +181,58 @@ const UserPage = () => { }} /> - { - setVisible(false); - }, - onClose: () => { - setVisible(false); - }, - destroyOnClose: true + onClose={()=>{ + setVisible(false); + }} + onCancel={()=>{ + setVisible(false); }} - onFinish={async (values) => { - handleSave(values); + onOk={async ()=>{ + await form.submit(); }} > - +
{ + handleSave(values); + }} + > +
- {buttons.map((item) => { - const style = item.style && JSON.parse(item.style) || {}; - return ( - - ) - })} - - - + return ( +
+
+ {currentNode && currentNode.name}
- ) - }else { - return ( -
- +
+
- ) - } +
+ ) } export default FlowHeader; diff --git a/admin-pro-ui/src/components/flow/components/FlowPage.tsx b/admin-pro-ui/src/components/flow/components/FlowPage.tsx index 79f87037..26645cc5 100644 --- a/admin-pro-ui/src/components/flow/components/FlowPage.tsx +++ b/admin-pro-ui/src/components/flow/components/FlowPage.tsx @@ -7,7 +7,7 @@ import { UserSelectFormProps, UserSelectFormViewKey } from "@/components/flow/types"; -import FlowHeader from "@/components/flow/components/FlowHeader"; +import FlowButton from "@/components/flow/components/FlowButton"; import {FlowReduxState, updateState} from "@/components/flow/store/FlowSlice"; import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; @@ -21,6 +21,7 @@ import {FlowViewReactContext} from "../view"; import FlowResult from "@/components/flow/components/FlowResult"; import FlowContent from "@/components/flow/components/FlowContent"; import FlowForm404 from "@/components/flow/components/FlowForm404"; +import FlowHeader from "@/components/flow/components/FlowHeader"; interface FlowPageProps extends FlowViewProps { @@ -75,6 +76,7 @@ const FlowPage:React.FC = (props)=>{ }}>
+ {currentState.result && ( )} diff --git a/admin-pro-ui/src/components/flow/domain/FlowRecordContext.ts b/admin-pro-ui/src/components/flow/domain/FlowRecordContext.ts index cc2a9354..69fa1339 100644 --- a/admin-pro-ui/src/components/flow/domain/FlowRecordContext.ts +++ b/admin-pro-ui/src/components/flow/domain/FlowRecordContext.ts @@ -42,6 +42,12 @@ export class FlowRecordContext { return null; } + // 获取当前的节点信息 + getCurrentNode=()=>{ + const currentNodeCode = this.getNodeCode(); + return this.getNode(currentNodeCode); + } + // 获取当前节点的表单数据 (内部使用) private getNodeState = (code: string) => { const historyRecords = this.data.historyRecords || []; diff --git a/admin-pro-ui/src/components/flow/view/index.scss b/admin-pro-ui/src/components/flow/view/index.scss index 7adc0c7c..d27afb4e 100644 --- a/admin-pro-ui/src/components/flow/view/index.scss +++ b/admin-pro-ui/src/components/flow/view/index.scss @@ -16,19 +16,30 @@ $flow-footer-height: 80px; width: 100%; } - -.flow-view-header { - height: $flow-footer-height; +.flow-header{ display: flex; + justify-content: space-between; align-items: center; + + .flow-header-left{ + font-size: 16pt; + } + + .flow-header-right{ + + } + +} + +.flow-buttons-content { + display: flex; justify-content: center; - .flow-view-header-button { - margin: 10px; + .flow-buttons-item { font-size: 14px; - padding: 8px 8px; - width: 100%; - border-color: $theme-primary-color; + margin: 5px 5px; + padding-left: 20px; + padding-right: 20px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/mobile-ui/src/components/flow/domain/FlowRecordContext.ts b/mobile-ui/src/components/flow/domain/FlowRecordContext.ts index cc2a9354..69fa1339 100644 --- a/mobile-ui/src/components/flow/domain/FlowRecordContext.ts +++ b/mobile-ui/src/components/flow/domain/FlowRecordContext.ts @@ -42,6 +42,12 @@ export class FlowRecordContext { return null; } + // 获取当前的节点信息 + getCurrentNode=()=>{ + const currentNodeCode = this.getNodeCode(); + return this.getNode(currentNodeCode); + } + // 获取当前节点的表单数据 (内部使用) private getNodeState = (code: string) => { const historyRecords = this.data.historyRecords || []; From 2b3a56c317f6f953cdcff35c0c325e1d8c5e0ebc Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Tue, 15 Apr 2025 17:28:16 +0800 Subject: [PATCH 06/20] add flow detail --- .../src/components/descriptions/index.scss | 34 ++++ .../src/components/descriptions/index.tsx | 99 +++++++++++ .../components/flow/components/FlowChart.tsx | 95 ++++++++++ .../flow/components/FlowContent.tsx | 47 ++++- .../flow/components/FlowFormOpinion.tsx | 45 +++++ .../flow/components/FlowHistory.tsx | 167 ++++++++++++++++++ .../flow/components/FlowHistoryLine.tsx | 55 ++++++ .../flow/components/FlowOpinion.tsx | 54 ++++++ admin-pro-ui/src/components/flow/types.ts | 2 +- .../src/components/flow/view/index.scss | 20 ++- admin-pro-ui/src/config/variables.scss | 5 + .../src/pages/flow/leave/LeaveForm.tsx | 8 +- 12 files changed, 625 insertions(+), 6 deletions(-) create mode 100644 admin-pro-ui/src/components/descriptions/index.scss create mode 100644 admin-pro-ui/src/components/descriptions/index.tsx create mode 100644 admin-pro-ui/src/components/flow/components/FlowChart.tsx create mode 100644 admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx create mode 100644 admin-pro-ui/src/components/flow/components/FlowHistory.tsx create mode 100644 admin-pro-ui/src/components/flow/components/FlowHistoryLine.tsx create mode 100644 admin-pro-ui/src/components/flow/components/FlowOpinion.tsx diff --git a/admin-pro-ui/src/components/descriptions/index.scss b/admin-pro-ui/src/components/descriptions/index.scss new file mode 100644 index 00000000..bd0d0289 --- /dev/null +++ b/admin-pro-ui/src/components/descriptions/index.scss @@ -0,0 +1,34 @@ +@use "@/config/variables" as *; + +.descriptions-list { + padding: 3px; + background-color: white; + + .descriptions-list-item { + background-color: white; + padding: 15px; + font-size: $content-font-size; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid $body-background-color; + + .descriptions-list-item-label { + font-weight: bold; + } + + .descriptions-list-item-value { + } + } + + .form-header-title { + width: 100%; + height: 20px; + border-left: 4px solid $theme-primary-color; + padding-left: 4px; + font-size: 14px; + margin-bottom: 10px; + } +} + + diff --git a/admin-pro-ui/src/components/descriptions/index.tsx b/admin-pro-ui/src/components/descriptions/index.tsx new file mode 100644 index 00000000..f32d86ce --- /dev/null +++ b/admin-pro-ui/src/components/descriptions/index.tsx @@ -0,0 +1,99 @@ +import React, {useEffect, useImperativeHandle} from "react"; +import {FormField} from "@/components/form/types"; +import "./index.scss"; + +export interface DescriptionsAction { + // 重新刷新数据 + reload: () => void; +} + +// 详情展示属性 +interface DescriptionsProps { + // 展示的字段 + columns?: FormField[]; + // 请求数据 + request?: () => Promise; + // 操作对象 + actionRef?: React.Ref; + // 页脚 + footer?: React.ReactNode; + // 页头 + header?: React.ReactNode; + // 数据转换 + dataConvert?: (field: FormField, data: any) => Promise; +} + +// 详情展示 +const Descriptions: React.FC = (props) => { + + const [data, setData] = React.useState(null); + + const reload = () => { + if (props.request) { + props.request().then((data) => { + if(props.dataConvert) { + const promise = [] as Promise[]; + props.columns?.map(item => { + promise.push(new Promise((resolve, reject) => { + props.dataConvert?.(item, data).then(value => { + data[item.props.name] = value; + resolve(value); + }).catch(reject); + })); + }); + Promise.all(promise).then(() => { + setData(data); + }); + }else { + setData(data); + } + }) + } + } + + useImperativeHandle(props.actionRef, () => ({ + reload: () => { + reload() + } + }), [props.actionRef]); + + useEffect(() => { + reload(); + + return () => { + setData(null); + } + }, []); + + return ( +
+ {props.header} + {data && props.columns && props.columns + .filter(item => !item.props.hidden) + .map((item) => { + const key = item.props.name; + const label = item.props.label as string || ""; + const value = data[key]; + const valueType = typeof value === 'object' ? 'object' : 'string'; + return ( +
+
+ {valueType === 'string' && ( +
+ )} + {valueType === 'object' && ( +
+ {value} +
+ )} +
+ ) + })} + {props.footer} +
+ ) +} + +export default Descriptions; diff --git a/admin-pro-ui/src/components/flow/components/FlowChart.tsx b/admin-pro-ui/src/components/flow/components/FlowChart.tsx new file mode 100644 index 00000000..325d53f4 --- /dev/null +++ b/admin-pro-ui/src/components/flow/components/FlowChart.tsx @@ -0,0 +1,95 @@ +import React, {useContext, useEffect} from "react"; +import '@logicflow/core/es/index.css'; +import '@logicflow/extension/lib/style/index.css'; +import {LogicFlow, Options} from "@logicflow/core"; +import {DndPanel, Menu, MiniMap, Snapshot} from "@logicflow/extension"; +import {FlowViewReactContext} from "@/components/flow/view"; +import Start from "@/components/flow/nodes/Start"; +import Over from "@/components/flow/nodes/Over"; +import Circulate from "@/components/flow/nodes/Circulate"; +import Node from "@/components/flow/nodes/Node"; +import "@/components/flow/index.scss"; + +import EdgeType = Options.EdgeType; + +interface FlowChartProps { + edgeType?: EdgeType; +} + +const FlowChart: React.FC = (props) => { + + const flowViewReactContext = useContext(FlowViewReactContext); + const flowRecordContext = flowViewReactContext?.flowRecordContext; + const flowSchema = flowRecordContext?.getFlowSchema(); + + const [url, setUrl] = React.useState(''); + + const edgeType = props.edgeType || 'polyline'; + const container = React.useRef(null); + const lfRef = React.useRef(null); + + useEffect(() => { + const SilentConfig = { + isSilentMode: true, + stopScrollGraph: false, + stopMoveGraph: false, + stopZoomGraph: false, + edgeTextEdit: false, + }; + + //@ts-ignore + lfRef.current = new LogicFlow({ + //@ts-ignore + container: container.current, + ...SilentConfig, + background: { + backgroundColor: '#f3f5f8' + }, + width: 0, + height: 0, + plugins: [Menu, DndPanel, MiniMap, Snapshot], + grid: false, + edgeType: edgeType, + }); + + lfRef.current.setTheme({ + bezier: { + stroke: '#8f94e3', + strokeWidth: 1, + }, + polyline: { + stroke: '#8f94e3', + strokeWidth: 1, + }, + line: { + stroke: '#8f94e3', + strokeWidth: 1, + }, + }); + lfRef.current.register(Start); + lfRef.current.register(Node); + lfRef.current.register(Over); + lfRef.current.register(Circulate); + lfRef.current.render(flowSchema); + + setTimeout(() => { + lfRef.current?.getSnapshotBlob().then((blob: any) => { + setUrl(URL.createObjectURL(blob.data)); + }); + }, 100) + + }, [flowViewReactContext]); + + return ( +
+
+
+ {url && ( + + )} +
+
+ ) +} + +export default FlowChart; diff --git a/admin-pro-ui/src/components/flow/components/FlowContent.tsx b/admin-pro-ui/src/components/flow/components/FlowContent.tsx index 866cbf68..f7a857d8 100644 --- a/admin-pro-ui/src/components/flow/components/FlowContent.tsx +++ b/admin-pro-ui/src/components/flow/components/FlowContent.tsx @@ -3,7 +3,12 @@ import {FlowFormViewProps} from "@/components/flow/types"; import {FlowViewReactContext} from "@/components/flow/view"; import {useSelector} from "react-redux"; import {FlowReduxState} from "@/components/flow/store/FlowSlice"; -import {Tabs, TabsProps} from "antd"; +import {Divider, Tabs, TabsProps} from "antd"; +import FlowFormOpinion from "@/components/flow/components/FlowFormOpinion"; +import FlowHistory from "@/components/flow/components/FlowHistory"; +import FlowOpinion from "@/components/flow/components/FlowOpinion"; +import FlowChart from "@/components/flow/components/FlowChart"; +import FlowHistoryLine from "@/components/flow/components/FlowHistoryLine"; const FlowContent = () => { const flowViewReactContext = useContext(FlowViewReactContext); @@ -18,6 +23,7 @@ const FlowContent = () => { const opinionVisible = useSelector((state: FlowReduxState) => state.flow.opinionVisible); const dataVersion = useSelector((state: FlowReduxState) => state.flow.dataVersion); const contentHiddenVisible = useSelector((state: FlowReduxState) => state.flow.contentHiddenVisible); + const [currentTab, setCurrentTab] = React.useState('detail'); useEffect(() => { if (!flowRecordContext?.isEditable()) { @@ -46,7 +52,44 @@ const FlowContent = () => { return (
- + { + setCurrentTab(value) + }}/> + + {currentTab ==='detail' && ( + <> + {formInstance && ( + + )} + + {opinionVisible && ( + + )} + + )} + + {currentTab ==='record' && ( + <> + + 审批记录 + + + )} + + {currentTab ==='chart' && ( + <> + + 流转历史 + + + )}
) } diff --git a/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx b/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx new file mode 100644 index 00000000..6d966f2c --- /dev/null +++ b/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx @@ -0,0 +1,45 @@ +import React, {useContext, useEffect} from "react"; +import Form from "@/components/form"; +import {FlowViewReactContext} from "@/components/flow/view"; + +const FlowFormOpinion = ()=>{ + + const flowViewReactContext = useContext(FlowViewReactContext); + const opinionInstance = flowViewReactContext?.opinionInstance; + const flowRecordContext = flowViewReactContext?.flowRecordContext; + + useEffect(() => { + opinionInstance?.setFieldValue("advice", flowRecordContext?.getOpinionAdvice()); + }, []); + + return ( + <> +
{ + return [ + { + type:'textarea', + props:{ + name:"advice", + label:"审批意见", + textAreaRows:2, + required:true, + validateFunction:async (content)=>{ + const value = content.value; + if(value){ + return []; + } + return ["请输入审批意见"]; + } + } + } + ] + }} + > +
+ + ) +} + +export default FlowFormOpinion; diff --git a/admin-pro-ui/src/components/flow/components/FlowHistory.tsx b/admin-pro-ui/src/components/flow/components/FlowHistory.tsx new file mode 100644 index 00000000..c81b8533 --- /dev/null +++ b/admin-pro-ui/src/components/flow/components/FlowHistory.tsx @@ -0,0 +1,167 @@ +import React, {useContext} from "react"; +import {FlowViewReactContext} from "@/components/flow/view"; +import Descriptions from "@/components/descriptions"; +import {FormField} from "@/components/form/types"; +import moment from "moment"; +import {Tag} from "antd"; + +const fields = [ + { + type: 'input', + props: { + name: 'title', + label: '', + } + }, + { + type: 'input', + props: { + name: 'createOperatorName', + label: '发起人', + } + }, + { + type: 'input', + props: { + name: 'createOperatorDate', + label: '发起时间', + } + }, + { + type: 'input', + props: { + name: 'flowStatus', + label: '状态', + } + }, + { + type: 'input', + props: { + name: 'recodeType', + label: '流程状态', + } + }, + { + type: 'input', + props: { + name: 'postponedCount', + label: '是否延期', + } + }, + { + type: 'input', + props: { + name: 'interfere', + label: '是否干预', + } + }, + { + type: 'input', + props: { + name: 'read', + label: '是否已读', + } + }, + { + type: 'input', + props: { + name: 'timeoutTime', + label: '超时时间', + } + }, + { + type: 'input', + props: { + name: 'nodeName', + label: '节点名称', + } + } +] as FormField[]; + +const flowStatusConvert = (data: any) => { + if (data.flowStatus === 'RUNNING') { + return '进行中'; + } + if (data.flowStatus === 'FINISH') { + return '已结束'; + } + return ''; +} + +const recodeTypeConvert = (data: any) => { + if (data.flowType === 'TODO') { + return 待办; + } + if (data.flowType === 'DONE') { + return 已办; + } + if (data.flowType === 'TRANSFER') { + return 已转办; + } + return ''; +} + +const postponedCountConvert = (data: any) => { + if (data.postponedCount > 0) { + return '延期'; + } + return '未延期'; +} + +const interfereConvert = (data: any) => { + if (data.interfere) { + return '干预'; + } + return '未干预'; +} + +const readConvert = (data: any) => { + if (data.read) { + return '已读'; + } + return '未读'; +} + +const timeoutTimeConvert = (data: any) => { + if (data.timeoutTime == 0) { + return '未设置'; + } + return moment(data.timeoutTime).format("YYYY-MM-DD HH:mm:ss"); +} + + +const FlowHistory = () => { + const flowViewReactContext = useContext(FlowViewReactContext); + const flowRecordContext = flowViewReactContext?.flowRecordContext; + + const currentFlowRecord = flowRecordContext?.getCurrentFlowRecord(); + + if(currentFlowRecord) { + return ( +
+
+ { + return { + ...currentFlowRecord, + createOperatorName: currentFlowRecord.createOperator?.name, + createOperatorDate: moment(currentFlowRecord.createTime).format('YYYY-MM-DD HH:mm:ss'), + flowStatus: flowStatusConvert(currentFlowRecord), + recodeType: recodeTypeConvert(currentFlowRecord), + postponedCount: postponedCountConvert(currentFlowRecord), + interfere: interfereConvert(currentFlowRecord), + read: readConvert(currentFlowRecord), + timeoutTime: timeoutTimeConvert(currentFlowRecord), + nodeName: flowRecordContext?.getNode(currentFlowRecord.nodeCode)?.name, + }; + }} + /> +
+
+ ) + } + return <> +} + +export default FlowHistory; diff --git a/admin-pro-ui/src/components/flow/components/FlowHistoryLine.tsx b/admin-pro-ui/src/components/flow/components/FlowHistoryLine.tsx new file mode 100644 index 00000000..6f3fd5bf --- /dev/null +++ b/admin-pro-ui/src/components/flow/components/FlowHistoryLine.tsx @@ -0,0 +1,55 @@ +import React, {useContext} from "react"; +import {FlowViewReactContext} from "@/components/flow/view"; +import moment from "moment"; +import {Tag} from "antd"; + +const recodeTypeConvert = (data: any) => { + if (data.flowType === 'TODO') { + return 待办; + } + if (data.flowType === 'DONE') { + return 已办; + } + if (data.flowType === 'TRANSFER') { + return 已转办; + } + return ''; +} + + +const FlowHistoryLine = () => { + const flowViewReactContext = useContext(FlowViewReactContext); + const flowRecordContext = flowViewReactContext?.flowRecordContext; + + const historyRecords = flowRecordContext?.getHistoryRecords(); + + return ( +
+
+ {historyRecords && historyRecords.map((historyRecord: any, index: number) => { + const nodeName = flowRecordContext?.getNode(historyRecord.nodeCode)?.name; + const recodeType = recodeTypeConvert(historyRecord); + const createName = historyRecord.createOperator?.name; + const createTime = moment(historyRecord.createTime).format('YYYY-MM-DD HH:mm:ss'); + const flowAdvice = historyRecord.opinion?.advice || '暂无意见'; + return ( +
+
+ {historyRecords.length > (index + 1) &&
} +
+
+
{nodeName}
+
状态:{recodeType}
+
创建人:{createName}
+
创建时间:{createTime}
+
审批意见:{flowAdvice}
+
+
+ ) + })} +
+
+ ) +} + +export default FlowHistoryLine; diff --git a/admin-pro-ui/src/components/flow/components/FlowOpinion.tsx b/admin-pro-ui/src/components/flow/components/FlowOpinion.tsx new file mode 100644 index 00000000..4ff19b83 --- /dev/null +++ b/admin-pro-ui/src/components/flow/components/FlowOpinion.tsx @@ -0,0 +1,54 @@ +import React, {useContext} from "react"; +import {FlowViewReactContext} from "@/components/flow/view"; +import moment from "moment"; + + +const optionStateConvert = (data: any) => { + if (data.opinion.result === 1) { + return '转办' + } + if (data.opinion.result === 2) { + return '通过' + } + if (data.opinion.result === 3) { + return '驳回' + } + return '暂存' +} + +const FlowOpinion = () => { + + const flowViewReactContext = useContext(FlowViewReactContext); + const flowRecordContext = flowViewReactContext?.flowRecordContext; + + const historyOpinions = flowRecordContext?.getHistoryOpinions(); + + return ( + <> + {historyOpinions && historyOpinions.map((item: any) => { + const flowOperatorName = item.operator.name; + const optionState = optionStateConvert(item); + const createTime = moment(item.createTime).format("YYYY-MM-DD HH:mm:ss"); + const advice = item.opinion.advice; + return ( +
+
+ 审批人: + {flowOperatorName} {optionState} +
+
+ 审批时间: + {createTime} +
+
+ 审批意见: + {advice} +
+
+ ) + })} + + ) +} + +export default FlowOpinion; diff --git a/admin-pro-ui/src/components/flow/types.ts b/admin-pro-ui/src/components/flow/types.ts index 70ec6a3d..0b0521c8 100644 --- a/admin-pro-ui/src/components/flow/types.ts +++ b/admin-pro-ui/src/components/flow/types.ts @@ -150,7 +150,7 @@ export interface FlowFormViewProps { // 表单数据 data: FlowFormParams; // 表单控制对象 - formInstance: FormInstance; + form: FormInstance; // 数据版本 dataVersion?: number; } diff --git a/admin-pro-ui/src/components/flow/view/index.scss b/admin-pro-ui/src/components/flow/view/index.scss index d27afb4e..201c720e 100644 --- a/admin-pro-ui/src/components/flow/view/index.scss +++ b/admin-pro-ui/src/components/flow/view/index.scss @@ -1,6 +1,7 @@ @use "@/config/variables" as *; $flow-footer-height: 80px; + .flow-skeleton-header { width: 100%; height: 40px; @@ -16,13 +17,30 @@ $flow-footer-height: 80px; width: 100%; } + +.flow-chart { + transition: 0.3s; + border-radius: 12px; + text-align: center; + + .flow-chart-content { + width: 0; + height: 0; + } + + .flow-img { + + } +} + + .flow-header{ display: flex; justify-content: space-between; align-items: center; .flow-header-left{ - font-size: 16pt; + font-size: $title-font-size; } .flow-header-right{ diff --git a/admin-pro-ui/src/config/variables.scss b/admin-pro-ui/src/config/variables.scss index da639f27..db056393 100644 --- a/admin-pro-ui/src/config/variables.scss +++ b/admin-pro-ui/src/config/variables.scss @@ -2,3 +2,8 @@ $theme-primary-color: #4a79d8; // 背景颜色 $body-background-color: #e6e7ea; + +// 标题字体大小 +$title-font-size: 16px; +// 内容字体大小 +$content-font-size:14px; diff --git a/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx b/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx index 1602ea71..efc526c4 100644 --- a/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx +++ b/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx @@ -8,12 +8,16 @@ import FormTextArea from "@/components/form/textarea"; const LeaveForm: React.FC = (props) => { useEffect(() => { - props.formInstance.setFieldsValue(props.data) + if (props.dataVersion && props.data) { + props.form?.setFieldsValue({ + ...props.data + }); + } }, [props.dataVersion]); return (
Date: Tue, 15 Apr 2025 17:32:16 +0800 Subject: [PATCH 07/20] add flow detail --- .../src/components/form/domain/FormInstance.tsx | 11 +---------- mobile-ui/src/components/form/domain/FormInstance.tsx | 11 +---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/admin-pro-ui/src/components/form/domain/FormInstance.tsx b/admin-pro-ui/src/components/form/domain/FormInstance.tsx index e40d722b..b1d0e433 100644 --- a/admin-pro-ui/src/components/form/domain/FormInstance.tsx +++ b/admin-pro-ui/src/components/form/domain/FormInstance.tsx @@ -2,7 +2,7 @@ import {FormValidateContext} from "@/components/form/validate"; import {FormFieldOptionListenerContext, FormFieldReloadListenerContext} from "@/components/form/listener"; import {NamePath} from "rc-field-form/es/interface"; import {FormField} from "@/components/form/types"; -import {Form as AntdForm, message} from "antd"; +import {Form as AntdForm} from "antd"; import {FiledData, FormAction} from "@/components/form"; import {FormInstance as AntdFormInstance} from "antd/es/form/hooks/useForm"; import React from "react"; @@ -60,7 +60,6 @@ class FormInstance { public hidden = (name: NamePath) => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -81,7 +80,6 @@ class FormInstance { public required = (name: NamePath, required: boolean) => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -101,7 +99,6 @@ class FormInstance { public show = (name: NamePath) => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -121,7 +118,6 @@ class FormInstance { public disable = (name: NamePath) => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -141,7 +137,6 @@ class FormInstance { public disableAll = () => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -158,7 +153,6 @@ class FormInstance { public enable = (name: NamePath) => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -178,7 +172,6 @@ class FormInstance { public enableAll = () => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -195,7 +188,6 @@ class FormInstance { public remove = (name: NamePath) => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => prevFields.filter((field) => !this.namePathEqual(field.props.name, name))); @@ -204,7 +196,6 @@ class FormInstance { public create = (field: FormField, index?: number) => { if (this.fields.length == 0) { - message.error('表单项未加载').then(); return; } this.updateFields(prevFields => { diff --git a/mobile-ui/src/components/form/domain/FormInstance.tsx b/mobile-ui/src/components/form/domain/FormInstance.tsx index e3e42d3b..7502c8f0 100644 --- a/mobile-ui/src/components/form/domain/FormInstance.tsx +++ b/mobile-ui/src/components/form/domain/FormInstance.tsx @@ -3,7 +3,7 @@ import {FormFieldOptionListenerContext, FormFieldReloadListenerContext} from "@/ import {FormInstance as MobileFormInstance} from "rc-field-form/es/interface"; import {FormField} from "@/components/form/types"; import {NamePath} from "antd-mobile/es/components/form"; -import {Form as MobileForm, Toast} from "antd-mobile"; +import {Form as MobileForm} from "antd-mobile"; import {FiledData, FormAction} from "@/components/form"; import React from "react"; @@ -60,7 +60,6 @@ class FormInstance { public hidden = (name: NamePath) => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -81,7 +80,6 @@ class FormInstance { public required = (name: NamePath, required: boolean) => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -101,7 +99,6 @@ class FormInstance { public show = (name: NamePath) => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -121,7 +118,6 @@ class FormInstance { public disable = (name: NamePath) => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -141,7 +137,6 @@ class FormInstance { public disableAll = () => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -158,7 +153,6 @@ class FormInstance { public enable = (name: NamePath) => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -178,7 +172,6 @@ class FormInstance { public enableAll = () => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.map((field) => { @@ -195,7 +188,6 @@ class FormInstance { public remove = (name: NamePath) => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => prevFields.filter((field) => !this.namePathEqual(field.props.name,name))); @@ -204,7 +196,6 @@ class FormInstance { public create = (field: FormField, index?: number) => { if (this.fields.length == 0) { - Toast.show('表单项未加载'); return; } this.updateFields(prevFields => { From a68cd4f36059b3d74699ccea576d5a16dc7571cd Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Tue, 15 Apr 2025 21:30:59 +0800 Subject: [PATCH 08/20] add flow --- .../src/components/flow/components/FlowFormOpinion.tsx | 1 + admin-pro-ui/src/components/flow/domain/FlowEventContext.ts | 2 +- admin-pro-ui/src/components/flow/view/index.scss | 2 +- admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx | 2 ++ admin-pro-ui/src/pages/flow/leave/index.tsx | 2 +- mobile-ui/src/components/flow/domain/FlowEventContext.ts | 4 +--- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx b/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx index 6d966f2c..08e25d8c 100644 --- a/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx +++ b/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx @@ -16,6 +16,7 @@ const FlowFormOpinion = ()=>{ <> { return [ { diff --git a/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts b/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts index 3901ed9d..715e5412 100644 --- a/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts +++ b/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts @@ -31,7 +31,7 @@ export class FlowEventContext { } private getRequestBody = () => { - const formData = this.opinionInstance.getFieldsValue(); + const formData = this.flowInstance.getFieldsValue(); const flowData = this.flowRecordContext.getFlowFormParams(); const workCode = this.flowRecordContext.getWorkCode(); const recordId = this.flowStateContext.getRecordId(); diff --git a/admin-pro-ui/src/components/flow/view/index.scss b/admin-pro-ui/src/components/flow/view/index.scss index 201c720e..84d70004 100644 --- a/admin-pro-ui/src/components/flow/view/index.scss +++ b/admin-pro-ui/src/components/flow/view/index.scss @@ -29,7 +29,7 @@ $flow-footer-height: 80px; } .flow-img { - + width: 30vw; } } diff --git a/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx b/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx index efc526c4..ad317b31 100644 --- a/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx +++ b/admin-pro-ui/src/pages/flow/leave/LeaveForm.tsx @@ -9,6 +9,7 @@ const LeaveForm: React.FC = (props) => { useEffect(() => { if (props.dataVersion && props.data) { + console.log('data',props.data); props.form?.setFieldsValue({ ...props.data }); @@ -18,6 +19,7 @@ const LeaveForm: React.FC = (props) => { return ( { view={LeaveForm} workCode={"leave"} formParams={{ - clazzName: 'com.codingapi.example.domain.Leave', + clazzName: 'com.codingapi.example.infra.flow.form.LeaveForm', username: username }} /> diff --git a/mobile-ui/src/components/flow/domain/FlowEventContext.ts b/mobile-ui/src/components/flow/domain/FlowEventContext.ts index 2abeda57..b05a3d80 100644 --- a/mobile-ui/src/components/flow/domain/FlowEventContext.ts +++ b/mobile-ui/src/components/flow/domain/FlowEventContext.ts @@ -1,5 +1,3 @@ -import React from "react"; -import {FormAction} from "@/components/form"; import {FlowRecordContext} from "@/components/flow/domain/FlowRecordContext"; import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; import * as flowApi from "@/api/flow"; @@ -33,7 +31,7 @@ export class FlowEventContext { } private getRequestBody = () => { - const formData = this.opinionInstance.getFieldsValue(); + const formData = this.flowInstance.getFieldsValue(); const flowData = this.flowRecordContext.getFlowFormParams(); const workCode = this.flowRecordContext.getWorkCode(); const recordId = this.flowStateContext.getRecordId(); From ca3d90fd4eb8ebd88118ffcddb0d199777181620 Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Fri, 25 Apr 2025 09:25:20 +0800 Subject: [PATCH 09/20] add mirco component bus --- .../components/flow/components/FlowPage.tsx | 7 +- .../components/flow/nodes/panel/NodePanel.tsx | 4 +- .../flow/register/flow.register.tsx | 6 +- admin-pro-ui/src/config/menus.tsx | 6 + .../src/framework/ComponentBus/index.tsx | 80 ++++++++++-- admin-pro-ui/src/gateway/default/Header.tsx | 17 +++ admin-pro-ui/src/gateway/index.tsx | 6 + admin-pro-ui/src/pages/mirco/index.tsx | 114 ++++++++++++++++++ admin-pro-ui/src/pages/welcome/index.tsx | 1 - 9 files changed, 223 insertions(+), 18 deletions(-) create mode 100644 admin-pro-ui/src/gateway/default/Header.tsx create mode 100644 admin-pro-ui/src/gateway/index.tsx create mode 100644 admin-pro-ui/src/pages/mirco/index.tsx diff --git a/admin-pro-ui/src/components/flow/components/FlowPage.tsx b/admin-pro-ui/src/components/flow/components/FlowPage.tsx index 26645cc5..fb0bff4c 100644 --- a/admin-pro-ui/src/components/flow/components/FlowPage.tsx +++ b/admin-pro-ui/src/components/flow/components/FlowPage.tsx @@ -7,7 +7,6 @@ import { UserSelectFormProps, UserSelectFormViewKey } from "@/components/flow/types"; -import FlowButton from "@/components/flow/components/FlowButton"; import {FlowReduxState, updateState} from "@/components/flow/store/FlowSlice"; import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; @@ -16,12 +15,12 @@ import {FlowRecordContext} from "@/components/flow/domain/FlowRecordContext"; import {useDispatch, useSelector} from "react-redux"; import {FlowEventContext} from "@/components/flow/domain/FlowEventContext"; import {FlowButtonClickContext} from "@/components/flow/domain/FlowButtonClickContext"; -import {getComponent} from "@/framework/ComponentBus"; import {FlowViewReactContext} from "../view"; import FlowResult from "@/components/flow/components/FlowResult"; import FlowContent from "@/components/flow/components/FlowContent"; import FlowForm404 from "@/components/flow/components/FlowForm404"; import FlowHeader from "@/components/flow/components/FlowHeader"; +import ComponentBus from "@/framework/ComponentBus"; interface FlowPageProps extends FlowViewProps { @@ -49,9 +48,9 @@ const FlowPage:React.FC = (props)=>{ const FlowFormView = flowRecordContext.getFlowFormView() as React.ComponentType; // 延期表单视图 - const PostponedFormView = getComponent(PostponedFormViewKey) as React.ComponentType; + const PostponedFormView = ComponentBus.getInstance().getComponent(PostponedFormViewKey); // 选人表单视图 - const UserSelectFormView = getComponent(UserSelectFormViewKey) as React.ComponentType; + const UserSelectFormView = ComponentBus.getInstance().getComponent(UserSelectFormViewKey); const version = useSelector((state: FlowReduxState) => state.flow.version); diff --git a/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx b/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx index 0a846722..2da789af 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx @@ -3,7 +3,7 @@ import {Button, Divider, Space} from "antd"; import {EyeOutlined, SettingOutlined} from "@ant-design/icons"; import GroovyScript from "@/components/flow/utils/script"; import ScriptModal from "@/components/flow/nodes/panel/ScriptModal"; -import {getComponent} from "@/framework/ComponentBus"; +import ComponentBus from "@/framework/ComponentBus"; import ValidateUtils from "@/components/form/utils"; import FormSelect from "@/components/form/select"; import FormSwitch from "@/components/form/switch"; @@ -31,7 +31,7 @@ const NodePanel: React.FC = (props) => { const [operatorMatcherType, setOperatorMatcherType] = React.useState(props.data?.operatorMatcherType); // 用户选人视图 - const UserSelectView = getComponent(UserSelectFormViewKey) as React.ComponentType; + const UserSelectView = ComponentBus.getInstance().getComponent(UserSelectFormViewKey); return ( <> diff --git a/admin-pro-ui/src/components/flow/register/flow.register.tsx b/admin-pro-ui/src/components/flow/register/flow.register.tsx index e954c10f..13ea4a43 100644 --- a/admin-pro-ui/src/components/flow/register/flow.register.tsx +++ b/admin-pro-ui/src/components/flow/register/flow.register.tsx @@ -1,4 +1,4 @@ -import {registerComponent} from "@/framework/ComponentBus"; +import ComponentBus from "@/framework/ComponentBus"; import PostponedFormView from "@/components/flow/components/PostponedFormView"; import { PostponedFormViewKey, @@ -6,5 +6,5 @@ import { } from "@/components/flow/types"; import UserSelectView from "@/components/flow/components/UserSelectView"; -registerComponent(PostponedFormViewKey, PostponedFormView); -registerComponent(UserSelectFormViewKey, UserSelectView); +ComponentBus.getInstance().registerComponent(PostponedFormViewKey, PostponedFormView); +ComponentBus.getInstance().registerComponent(UserSelectFormViewKey, UserSelectView); diff --git a/admin-pro-ui/src/config/menus.tsx b/admin-pro-ui/src/config/menus.tsx index f654d47f..a5dfd64c 100644 --- a/admin-pro-ui/src/config/menus.tsx +++ b/admin-pro-ui/src/config/menus.tsx @@ -21,6 +21,12 @@ export const menus = [ icon: "FormOutlined", page: 'form', }, + { + path: '/mirco', + name: '微前端', + icon: "FormOutlined", + page: 'mirco', + }, { path: '/flow', name: '流程', diff --git a/admin-pro-ui/src/framework/ComponentBus/index.tsx b/admin-pro-ui/src/framework/ComponentBus/index.tsx index 6d7fbaf7..22528f84 100644 --- a/admin-pro-ui/src/framework/ComponentBus/index.tsx +++ b/admin-pro-ui/src/framework/ComponentBus/index.tsx @@ -1,14 +1,78 @@ import React from "react"; +import {loadRemoteComponent, loadRemoteScript} from "@/utils/dynamicLoader"; -const componentBus = new Map>(); +class ComponentBus { + private readonly componentBus = new Map>(); -// 注册表操作方法 -export const registerComponent = (name: string, component: React.ComponentType) => { - componentBus.set(name, component); -}; + /** + * 注册组件 + * @param key 组件key + * @param component 组件类型 + */ + public registerComponent = (key: string, component: React.ComponentType) => { + this.componentBus.set(key, component); + } -// 获取组件 -export const getComponent = (name: string): React.ComponentType | undefined => { - return componentBus.get(name); + /** + * 删除组件 + * @param key 组件key + */ + public removeComponent = (key: string) => { + this.componentBus.delete(key); + } + + /** + * 注册远程组件 + * @param key 组件key + * @param remoteUrl 远程组件地址 + * @param scope 远程组件作用域 + * @param module 远程组件名称 + */ + public registerRemoteComponent = (key: string, remoteUrl: string, scope: string, module: string): Promise => { + return new Promise((resolve, reject) => { + loadRemoteScript(remoteUrl).then(() => { + loadRemoteComponent(scope, module) + .then((ComponentModule: any) => { + const Component = ComponentModule.default || ComponentModule; + this.registerComponent(key, Component); + resolve(true); + }).catch(error => { + reject(error); + }); + }).catch(error => { + reject(error); + }); + }) + } + + /** + * 获取组件 + * @param key 组件key + * @param defaultComponent 默认组件 + */ + public getComponent = (key: string, defaultComponent?: React.ComponentType): React.ComponentType | undefined => { + const component = this.componentBus.get(key) as React.ComponentType | undefined; + if (component) { + return component; + } + if (defaultComponent) { + return defaultComponent; + } + return undefined; + }; + + private ComponentBus() { + // 私有构造函数,防止外部实例化 + } + + private static instance: ComponentBus = new ComponentBus(); + + // 单例模式 + public static getInstance(): ComponentBus { + return ComponentBus.instance; + } } + +export default ComponentBus; + diff --git a/admin-pro-ui/src/gateway/default/Header.tsx b/admin-pro-ui/src/gateway/default/Header.tsx new file mode 100644 index 00000000..1d0356ad --- /dev/null +++ b/admin-pro-ui/src/gateway/default/Header.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import {HeaderProps} from "@/gateway"; + + +const HeaderDefault: React.FC = (props) => { + return ( +
+

{props.title}

+ +
+ PS:local default component +
+
+ ) +} + +export default HeaderDefault; diff --git a/admin-pro-ui/src/gateway/index.tsx b/admin-pro-ui/src/gateway/index.tsx new file mode 100644 index 00000000..a8fb44d1 --- /dev/null +++ b/admin-pro-ui/src/gateway/index.tsx @@ -0,0 +1,6 @@ + +export interface HeaderProps{ + title: string; + onClick: () => void; +} + diff --git a/admin-pro-ui/src/pages/mirco/index.tsx b/admin-pro-ui/src/pages/mirco/index.tsx new file mode 100644 index 00000000..38c3b09b --- /dev/null +++ b/admin-pro-ui/src/pages/mirco/index.tsx @@ -0,0 +1,114 @@ +import React, {useState} from "react"; +import {Button, Space} from "antd"; +import ComponentBus from "@/framework/ComponentBus"; +import {HeaderProps} from "@/gateway"; +import HeaderDefault from "@/gateway/default/Header"; +import {ModalForm, ProForm, ProFormText} from "@ant-design/pro-components"; + + +const MircoPage = () => { + + // 用于重新渲染界面 + const [pageVersion, setPageVersion] = useState(0); + + const HeaderKey = "Header"; + const Header = ComponentBus.getInstance().getComponent(HeaderKey, HeaderDefault); + + const [visible, setVisible] = useState(false); + + const [form] = ProForm.useForm(); + + const handlerLoadComponent = async (values: any) => { + const {remoteUrl, scope, module} = values; + ComponentBus.getInstance() + .registerRemoteComponent(HeaderKey, remoteUrl, scope, module) + .finally(() => { + setVisible(false); + }); + } + + return ( + <> +
+ + + + + +
+ + { + setVisible(false); + }, + destroyOnClose: true + }} + onFinish={handlerLoadComponent} + > + + + + + + + + + + + {Header && ( +
{ + alert('click'); + }} + /> + )} + + + ) +} + +export default MircoPage; diff --git a/admin-pro-ui/src/pages/welcome/index.tsx b/admin-pro-ui/src/pages/welcome/index.tsx index 35cc0da6..5bf4fca0 100644 --- a/admin-pro-ui/src/pages/welcome/index.tsx +++ b/admin-pro-ui/src/pages/welcome/index.tsx @@ -14,7 +14,6 @@ const WelcomePage = () => {
    3. 管理权限
    4. 动态菜单
    5. 动态加载组件
- ); } From 41b4dc001d59db31661f117e5ce592ed09ee355f Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Fri, 25 Apr 2025 09:31:02 +0800 Subject: [PATCH 10/20] add mirco component bus --- mobile-ui/package.json | 1 + .../components/flow/components/FlowPage.tsx | 6 +- .../src/components/flow/register/index.tsx | 6 +- .../src/framework/ComponentBus/index.tsx | 80 +++++++++++-- mobile-ui/src/utils/base64.ts | 27 +++++ mobile-ui/src/utils/dynamicLoader.ts | 108 ++++++++++++++++++ 6 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 mobile-ui/src/utils/base64.ts create mode 100644 mobile-ui/src/utils/dynamicLoader.ts diff --git a/mobile-ui/package.json b/mobile-ui/package.json index 44acb0bd..eea4365b 100644 --- a/mobile-ui/package.json +++ b/mobile-ui/package.json @@ -17,6 +17,7 @@ "base64-js": "^1.5.1", "cross-env": "^7.0.3", "dayjs": "^1.11.13", + "jszip": "^3.10.1", "lodash": "^4.17.21", "moment": "^2.30.1", "monaco-editor": "^0.51.0", diff --git a/mobile-ui/src/components/flow/components/FlowPage.tsx b/mobile-ui/src/components/flow/components/FlowPage.tsx index acf4c9c5..36a8edc6 100644 --- a/mobile-ui/src/components/flow/components/FlowPage.tsx +++ b/mobile-ui/src/components/flow/components/FlowPage.tsx @@ -18,7 +18,7 @@ import FlowContent from "@/components/flow/components/FlowContent"; import FlowFooter from "@/components/flow/components/FlowFooter"; import {FlowViewReactContext} from "@/components/flow/view"; import FlowForm404 from "@/components/flow/components/FlowForm404"; -import {getComponent} from "@/framework/ComponentBus"; +import ComponentBus from "@/framework/ComponentBus"; import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; import {FlowButtonClickContext} from "@/components/flow/domain/FlowButtonClickContext"; @@ -48,9 +48,9 @@ const FlowPage: React.FC = (props) => { const FlowFormView = flowRecordContext.getFlowFormView() as React.ComponentType; // 延期表单视图 - const PostponedFormView = getComponent(PostponedFormViewKey) as React.ComponentType; + const PostponedFormView = ComponentBus.getInstance().getComponent(PostponedFormViewKey); // 选人表单视图 - const UserSelectFormView = getComponent(UserSelectFormViewKey) as React.ComponentType; + const UserSelectFormView = ComponentBus.getInstance().getComponent(UserSelectFormViewKey); const version = useSelector((state: FlowReduxState) => state.flow.version); diff --git a/mobile-ui/src/components/flow/register/index.tsx b/mobile-ui/src/components/flow/register/index.tsx index 35686332..c629f6bd 100644 --- a/mobile-ui/src/components/flow/register/index.tsx +++ b/mobile-ui/src/components/flow/register/index.tsx @@ -1,8 +1,8 @@ -import {registerComponent} from "@/framework/ComponentBus"; +import ComponentBus from "@/framework/ComponentBus"; import {PostponedFormViewKey, UserSelectFormViewKey} from "@/components/flow/types"; import PostponedFormView from "@/components/flow/plugins/PostponedFormView"; import UserSelectFormView from "@/components/flow/plugins/UserSelectFormView"; -registerComponent(PostponedFormViewKey, PostponedFormView); -registerComponent(UserSelectFormViewKey, UserSelectFormView); +ComponentBus.getInstance().registerComponent(PostponedFormViewKey, PostponedFormView); +ComponentBus.getInstance().registerComponent(UserSelectFormViewKey, UserSelectFormView); diff --git a/mobile-ui/src/framework/ComponentBus/index.tsx b/mobile-ui/src/framework/ComponentBus/index.tsx index 6d7fbaf7..22528f84 100644 --- a/mobile-ui/src/framework/ComponentBus/index.tsx +++ b/mobile-ui/src/framework/ComponentBus/index.tsx @@ -1,14 +1,78 @@ import React from "react"; +import {loadRemoteComponent, loadRemoteScript} from "@/utils/dynamicLoader"; -const componentBus = new Map>(); +class ComponentBus { + private readonly componentBus = new Map>(); -// 注册表操作方法 -export const registerComponent = (name: string, component: React.ComponentType) => { - componentBus.set(name, component); -}; + /** + * 注册组件 + * @param key 组件key + * @param component 组件类型 + */ + public registerComponent = (key: string, component: React.ComponentType) => { + this.componentBus.set(key, component); + } -// 获取组件 -export const getComponent = (name: string): React.ComponentType | undefined => { - return componentBus.get(name); + /** + * 删除组件 + * @param key 组件key + */ + public removeComponent = (key: string) => { + this.componentBus.delete(key); + } + + /** + * 注册远程组件 + * @param key 组件key + * @param remoteUrl 远程组件地址 + * @param scope 远程组件作用域 + * @param module 远程组件名称 + */ + public registerRemoteComponent = (key: string, remoteUrl: string, scope: string, module: string): Promise => { + return new Promise((resolve, reject) => { + loadRemoteScript(remoteUrl).then(() => { + loadRemoteComponent(scope, module) + .then((ComponentModule: any) => { + const Component = ComponentModule.default || ComponentModule; + this.registerComponent(key, Component); + resolve(true); + }).catch(error => { + reject(error); + }); + }).catch(error => { + reject(error); + }); + }) + } + + /** + * 获取组件 + * @param key 组件key + * @param defaultComponent 默认组件 + */ + public getComponent = (key: string, defaultComponent?: React.ComponentType): React.ComponentType | undefined => { + const component = this.componentBus.get(key) as React.ComponentType | undefined; + if (component) { + return component; + } + if (defaultComponent) { + return defaultComponent; + } + return undefined; + }; + + private ComponentBus() { + // 私有构造函数,防止外部实例化 + } + + private static instance: ComponentBus = new ComponentBus(); + + // 单例模式 + public static getInstance(): ComponentBus { + return ComponentBus.instance; + } } + +export default ComponentBus; + diff --git a/mobile-ui/src/utils/base64.ts b/mobile-ui/src/utils/base64.ts new file mode 100644 index 00000000..bb10d6fa --- /dev/null +++ b/mobile-ui/src/utils/base64.ts @@ -0,0 +1,27 @@ + +export const rcFileToBase64 = (file: any): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result as string); + reader.onerror = error => reject(error); + }); +} + + +export const base64ToBlob = (base64: string, type: string) => { + const binStr = atob(base64.split(',')[1]); + const len = binStr.length; + const arr = new Uint8Array(len); + for (let i = 0; i < len; i++) { + arr[i] = binStr.charCodeAt(i); + } + return new Blob([arr], {type: type}); +} + +export const base64ToString = (base64: string) => { + if (base64.indexOf('base64') !== -1) { + base64 = base64.split(',')[1]; + } + return atob(base64); +} diff --git a/mobile-ui/src/utils/dynamicLoader.ts b/mobile-ui/src/utils/dynamicLoader.ts new file mode 100644 index 00000000..f26908e2 --- /dev/null +++ b/mobile-ui/src/utils/dynamicLoader.ts @@ -0,0 +1,108 @@ +import JSZip from "jszip"; +import {base64ToBlob} from "@/utils/base64"; + +export const loadRemoteComponent = (scope: string, module: string) => { + return new Promise(async (resolve, reject) => { + try { + // Initialize the sharing scope (shared modules like react, etc.) + //@ts-ignore + await __webpack_init_sharing__('default'); + //@ts-ignore + const container = window[scope]; // Get the container loaded on the window object + if (!container) { + reject(new Error(`Remote scope ${scope} not found on window.`)); + return; + } + + //@ts-ignore + await container.init(__webpack_share_scopes__.default); // Initialize the container + const factory = await container.get(module); // Get the module factory + resolve(factory()); // Get the actual module + } catch (e) { + reject(e); + } + }); +}; + +export const loadRemoteScript = (url: string): Promise => { + return new Promise((resolve, reject) => { + try { + const script = document.createElement('script'); + script.src = url; + script.onload = (e) => { + resolve(); + }; + script.onerror = reject; + document.head.appendChild(script); + } catch (e) { + reject(e); + } + }); +}; + + +export const loadFileScript = (content: string): Promise => { + return new Promise((resolve, reject) => { + try { + try { + eval(content); + } catch (e) { + resolve(); + return; + } + const encoder = new TextEncoder(); + const encodedContent = encoder.encode(content); + const blob = new Blob([encodedContent], {type: 'application/javascript'}); + const url = URL.createObjectURL(blob); + const script = document.createElement('script'); + script.src = url; + script.onload = () => { + resolve(); + }; + script.onerror = (e) => { + reject(e); + }; + document.head.appendChild(script); + } catch (e) { + reject(e); + } + }); +}; + + +export const loadZipJsFileScript = async (base64: string): Promise => { + return new Promise((resolve, reject) => { + const file = base64ToBlob(base64, 'application/zip'); + if (file) { + const zip = new JSZip(); + const content = file.arrayBuffer(); + zip.loadAsync(content).then((unzipped:any) => { + const jsFiles: { relativePath: string, content: string }[] = []; + + const filePromises: Promise[] = []; + unzipped.forEach((relativePath:any, file:any) => { + if (relativePath.endsWith(".js")) { + const filePromise = file.async('text').then((text:any) => { + jsFiles.push({relativePath, content: text}); + }); + filePromises.push(filePromise); + } + }); + + Promise.all(filePromises).then(() => { + jsFiles.reduce((prevPromise: any, jsFile) => { + return prevPromise.then(() => { + return loadFileScript(jsFile.content).then(() => { + console.log('Load success file:', jsFile.relativePath); + }); + }); + }, Promise.resolve()).then(() => { + resolve(); + }).catch(reject); + }).catch(reject); + }).catch(reject); + } else { + reject(new Error('Failed to convert base64 to Blob.')); + } + }); +}; From b57afb169948e493848c2c7b169f4fdbe0d8d790 Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Fri, 25 Apr 2025 09:37:27 +0800 Subject: [PATCH 11/20] add mirco component bus --- admin-pro-ui/src/pages/mirco/index.tsx | 10 +++++++--- admin-pro-ui/webpack.config.dev.js | 2 +- admin-pro-ui/webpack.config.mock.js | 2 +- admin-pro-ui/webpack.config.prod.js | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/admin-pro-ui/src/pages/mirco/index.tsx b/admin-pro-ui/src/pages/mirco/index.tsx index 38c3b09b..b62af029 100644 --- a/admin-pro-ui/src/pages/mirco/index.tsx +++ b/admin-pro-ui/src/pages/mirco/index.tsx @@ -1,5 +1,5 @@ import React, {useState} from "react"; -import {Button, Space} from "antd"; +import {Button, message, Space} from "antd"; import ComponentBus from "@/framework/ComponentBus"; import {HeaderProps} from "@/gateway"; import HeaderDefault from "@/gateway/default/Header"; @@ -22,6 +22,10 @@ const MircoPage = () => { const {remoteUrl, scope, module} = values; ComponentBus.getInstance() .registerRemoteComponent(HeaderKey, remoteUrl, scope, module) + .catch(error=>{ + console.error("load remote component error", error); + message.error('load remote component error'); + }) .finally(() => { setVisible(false); }); @@ -35,8 +39,8 @@ const MircoPage = () => {
From f2bc1646591a903810402242d9a6deeb9654515a Mon Sep 17 00:00:00 2001 From: lorne <1991wangliang@gmail.com> Date: Sun, 27 Apr 2025 20:41:42 +0800 Subject: [PATCH 13/20] remove form --- admin-pro-ui/package.json | 3 +- .../src/components/CodeEditor/index.tsx | 145 -------- .../src/components/descriptions/index.tsx | 6 +- .../flow/components/FlowFormOpinion.tsx | 2 +- .../flow/components/FlowHistory.tsx | 2 +- .../components/flow/components/FlowPage.tsx | 4 +- .../flow/domain/FlowEventContext.ts | 2 +- .../flow/nodes/panel/ButtonPanel.tsx | 7 +- .../components/flow/nodes/panel/EdgePanel.tsx | 4 +- .../components/flow/nodes/panel/NodePanel.tsx | 9 +- .../flow/nodes/panel/ScriptModal.tsx | 6 +- .../components/flow/nodes/panel/circulate.tsx | 2 +- .../src/components/flow/nodes/panel/node.tsx | 2 +- .../src/components/flow/nodes/panel/over.tsx | 2 +- .../src/components/flow/nodes/panel/start.tsx | 2 +- admin-pro-ui/src/components/flow/types.ts | 3 +- .../src/components/flow/view/index.tsx | 2 +- admin-pro-ui/src/components/form/captcha.tsx | 75 ----- admin-pro-ui/src/components/form/cascader.tsx | 100 ------ admin-pro-ui/src/components/form/checkbox.tsx | 81 ----- admin-pro-ui/src/components/form/code.tsx | 40 --- admin-pro-ui/src/components/form/color.tsx | 66 ---- admin-pro-ui/src/components/form/common.tsx | 41 --- admin-pro-ui/src/components/form/date.tsx | 136 -------- .../components/form/domain/FormInstance.tsx | 314 ------------------ admin-pro-ui/src/components/form/factory.tsx | 183 ---------- admin-pro-ui/src/components/form/form.scss | 16 - admin-pro-ui/src/components/form/index.tsx | 133 -------- admin-pro-ui/src/components/form/input.tsx | 39 --- admin-pro-ui/src/components/form/listener.ts | 64 ---- admin-pro-ui/src/components/form/password.tsx | 38 --- admin-pro-ui/src/components/form/radio.tsx | 60 ---- admin-pro-ui/src/components/form/rate.tsx | 33 -- admin-pro-ui/src/components/form/select.tsx | 104 ------ admin-pro-ui/src/components/form/selector.tsx | 12 - admin-pro-ui/src/components/form/slider.tsx | 46 --- admin-pro-ui/src/components/form/stepper.tsx | 42 --- admin-pro-ui/src/components/form/switch.tsx | 44 --- admin-pro-ui/src/components/form/textarea.tsx | 37 --- admin-pro-ui/src/components/form/types.tsx | 190 ----------- admin-pro-ui/src/components/form/uploder.tsx | 171 ---------- admin-pro-ui/src/components/form/utils.ts | 30 -- admin-pro-ui/src/components/form/validate.ts | 92 ----- .../src/framework/ComponentBus/index.tsx | 78 ----- admin-pro-ui/src/framework/EventBus/index.ts | 58 ---- .../src/pages/flow/leave/LeaveForm.tsx | 6 +- admin-pro-ui/src/pages/flow/user/index.tsx | 6 +- admin-pro-ui/src/pages/flow/work/index.tsx | 7 +- admin-pro-ui/src/pages/form/index.tsx | 47 +-- admin-pro-ui/src/pages/mirco/index.tsx | 12 +- 50 files changed, 61 insertions(+), 2543 deletions(-) delete mode 100644 admin-pro-ui/src/components/CodeEditor/index.tsx delete mode 100644 admin-pro-ui/src/components/form/captcha.tsx delete mode 100644 admin-pro-ui/src/components/form/cascader.tsx delete mode 100644 admin-pro-ui/src/components/form/checkbox.tsx delete mode 100644 admin-pro-ui/src/components/form/code.tsx delete mode 100644 admin-pro-ui/src/components/form/color.tsx delete mode 100644 admin-pro-ui/src/components/form/common.tsx delete mode 100644 admin-pro-ui/src/components/form/date.tsx delete mode 100644 admin-pro-ui/src/components/form/domain/FormInstance.tsx delete mode 100644 admin-pro-ui/src/components/form/factory.tsx delete mode 100644 admin-pro-ui/src/components/form/form.scss delete mode 100644 admin-pro-ui/src/components/form/index.tsx delete mode 100644 admin-pro-ui/src/components/form/input.tsx delete mode 100644 admin-pro-ui/src/components/form/listener.ts delete mode 100644 admin-pro-ui/src/components/form/password.tsx delete mode 100644 admin-pro-ui/src/components/form/radio.tsx delete mode 100644 admin-pro-ui/src/components/form/rate.tsx delete mode 100644 admin-pro-ui/src/components/form/select.tsx delete mode 100644 admin-pro-ui/src/components/form/selector.tsx delete mode 100644 admin-pro-ui/src/components/form/slider.tsx delete mode 100644 admin-pro-ui/src/components/form/stepper.tsx delete mode 100644 admin-pro-ui/src/components/form/switch.tsx delete mode 100644 admin-pro-ui/src/components/form/textarea.tsx delete mode 100644 admin-pro-ui/src/components/form/types.tsx delete mode 100644 admin-pro-ui/src/components/form/uploder.tsx delete mode 100644 admin-pro-ui/src/components/form/utils.ts delete mode 100644 admin-pro-ui/src/components/form/validate.ts delete mode 100644 admin-pro-ui/src/framework/ComponentBus/index.tsx delete mode 100644 admin-pro-ui/src/framework/EventBus/index.ts diff --git a/admin-pro-ui/package.json b/admin-pro-ui/package.json index e3ae9c08..b181b121 100644 --- a/admin-pro-ui/package.json +++ b/admin-pro-ui/package.json @@ -7,6 +7,8 @@ "@ant-design/icons": "^5.4.0", "@ant-design/pro-components": "^2.8.2", "@babel/standalone": "^7.25.6", + "@codingapi/form-pc": "^0.0.15", + "@codingapi/ui-framework": "^0.0.12", "@dnd-kit/core": "^6.2.0", "@dnd-kit/sortable": "^9.0.0", "@handsontable/react-wrapper": "^15.0.0", @@ -25,7 +27,6 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "moment": "^2.30.1", - "monaco-editor": "^0.51.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^10.0.0", diff --git a/admin-pro-ui/src/components/CodeEditor/index.tsx b/admin-pro-ui/src/components/CodeEditor/index.tsx deleted file mode 100644 index 6a146eac..00000000 --- a/admin-pro-ui/src/components/CodeEditor/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, {Ref, useEffect, useImperativeHandle} from 'react'; -import * as monaco from 'monaco-editor'; - -export interface CodeEditorAction { - // 重置编辑器的值 - resetValue: (value: string) => void; - // 获取选中的值 - getSelectedValue: () => string; - // 获取编辑器的值 - getValue: () => string; - // 获取编辑器实例 - getEditor:() => monaco.editor.IStandaloneCodeEditor | null; -} - - -interface CodeEditorProps { - language?: string, - value?: string, - onChange?: (value: string) => void, - onSelectedRun?: (value: string) => void; - style?: React.CSSProperties; - readonly?: boolean; - theme?: string; - fontSize?: number; - actionRef?: Ref; -} - - -const CodeEditor: React.FC = (props) => { - const language = props.language || 'javascript'; - const theme = props.theme || "vs-dark"; - const fontSize = props.fontSize || 14; - const [editorId, setEditorId] = React.useState(""); - - const container = React.useRef(null); - - const style = props.style || { - height: "80px", - } - - const codeEditorAction = { - getEditor: ()=>{ - const editors = monaco.editor.getEditors(); - if(editors.length > 0) { - return editors.find(editor => editor.getId() === editorId); - } - return null; - }, - - resetValue: (value: string) => { - const editor = codeEditorAction.getEditor(); - if(editor) { - const position = editor.getPosition(); - editor.setValue(value); - if (position) { - editor.setPosition({ - lineNumber: position.lineNumber, - column: position.column - }); - } - } - }, - getSelectedValue: () => { - const editor = codeEditorAction.getEditor(); - if(editor) { - const selection = editor.getSelection(); - //@ts-ignore - return editor.getModel().getValueInRange(selection); - } - return ""; - }, - getValue: () => { - const editor = codeEditorAction.getEditor(); - if(editor) { - return editor.getValue(); - } - return ""; - } - } as CodeEditorAction; - - useImperativeHandle(props.actionRef, () => (codeEditorAction), [props.actionRef]); - - useEffect(() => { - if(props.value) { - codeEditorAction.resetValue(props.value); - } - }, [props.value]); - - useEffect(() => { - const model = monaco.editor.createModel(props.value || "", language); - const editor = monaco.editor.create( - //@ts-ignore - container?.current, - { - automaticLayout: true, - model: model, - fontSize: fontSize, - theme: theme, - readOnly: props.readonly, - }, - ); - - setEditorId(editor.getId()); - - const subscription = editor.onDidChangeModelContent((event) => { - props.onChange && props.onChange(editor.getValue()); - }); - - let runSelectedCodeActionDispose: monaco.IDisposable | null = null; - if (props.onSelectedRun) { - const runSelectedCodeAction = { - id: "run-code", - label: "Run Selected Code", - contextMenuGroupId: "navigation", - keybindings: [ - monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, - monaco.KeyMod.WinCtrl | monaco.KeyCode.KeyR, - ], - run: () => { - const selection = editor.getSelection(); - //@ts-ignore - const selectedText = editor.getModel().getValueInRange(selection); - props.onSelectedRun && props.onSelectedRun(selectedText); - }, - } - runSelectedCodeActionDispose = monaco.editor.addEditorAction(runSelectedCodeAction); - } - - return () => { - editor.dispose(); - subscription.dispose(); - model.dispose(); - - if (runSelectedCodeActionDispose !== null) { - runSelectedCodeActionDispose.dispose(); - } - }; - }, [props.readonly]); - - return ( -
- ); -}; - -export default CodeEditor; diff --git a/admin-pro-ui/src/components/descriptions/index.tsx b/admin-pro-ui/src/components/descriptions/index.tsx index f32d86ce..8e2c7427 100644 --- a/admin-pro-ui/src/components/descriptions/index.tsx +++ b/admin-pro-ui/src/components/descriptions/index.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useImperativeHandle} from "react"; -import {FormField} from "@/components/form/types"; +import {FormField} from "@codingapi/ui-framework"; import "./index.scss"; export interface DescriptionsAction { @@ -36,7 +36,7 @@ const Descriptions: React.FC = (props) => { props.columns?.map(item => { promise.push(new Promise((resolve, reject) => { props.dataConvert?.(item, data).then(value => { - data[item.props.name] = value; + data[item.props.name as string] = value; resolve(value); }).catch(reject); })); @@ -73,7 +73,7 @@ const Descriptions: React.FC = (props) => { .map((item) => { const key = item.props.name; const label = item.props.label as string || ""; - const value = data[key]; + const value = data[key as string]; const valueType = typeof value === 'object' ? 'object' : 'string'; return (
diff --git a/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx b/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx index 08e25d8c..b9fcbba0 100644 --- a/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx +++ b/admin-pro-ui/src/components/flow/components/FlowFormOpinion.tsx @@ -1,5 +1,5 @@ import React, {useContext, useEffect} from "react"; -import Form from "@/components/form"; +import {Form} from "@codingapi/form-pc"; import {FlowViewReactContext} from "@/components/flow/view"; const FlowFormOpinion = ()=>{ diff --git a/admin-pro-ui/src/components/flow/components/FlowHistory.tsx b/admin-pro-ui/src/components/flow/components/FlowHistory.tsx index c81b8533..445efa37 100644 --- a/admin-pro-ui/src/components/flow/components/FlowHistory.tsx +++ b/admin-pro-ui/src/components/flow/components/FlowHistory.tsx @@ -1,7 +1,7 @@ import React, {useContext} from "react"; import {FlowViewReactContext} from "@/components/flow/view"; import Descriptions from "@/components/descriptions"; -import {FormField} from "@/components/form/types"; +import {FormField} from "@codingapi/ui-framework"; import moment from "moment"; import {Tag} from "antd"; diff --git a/admin-pro-ui/src/components/flow/components/FlowPage.tsx b/admin-pro-ui/src/components/flow/components/FlowPage.tsx index fb0bff4c..c53b249d 100644 --- a/admin-pro-ui/src/components/flow/components/FlowPage.tsx +++ b/admin-pro-ui/src/components/flow/components/FlowPage.tsx @@ -10,7 +10,7 @@ import { import {FlowReduxState, updateState} from "@/components/flow/store/FlowSlice"; import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; -import Form from "@/components/form"; +import {Form} from "@codingapi/form-pc"; import {FlowRecordContext} from "@/components/flow/domain/FlowRecordContext"; import {useDispatch, useSelector} from "react-redux"; import {FlowEventContext} from "@/components/flow/domain/FlowEventContext"; @@ -20,7 +20,7 @@ import FlowResult from "@/components/flow/components/FlowResult"; import FlowContent from "@/components/flow/components/FlowContent"; import FlowForm404 from "@/components/flow/components/FlowForm404"; import FlowHeader from "@/components/flow/components/FlowHeader"; -import ComponentBus from "@/framework/ComponentBus"; +import {ComponentBus} from "@codingapi/ui-framework"; interface FlowPageProps extends FlowViewProps { diff --git a/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts b/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts index 715e5412..73e5088f 100644 --- a/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts +++ b/admin-pro-ui/src/components/flow/domain/FlowEventContext.ts @@ -5,7 +5,7 @@ import {FlowUser} from "@/components/flow/types"; import {FlowSubmitResultParser} from "@/components/flow/domain/FlowResultParser"; import {UserSelectMode} from "@/components/flow/store/FlowSlice"; import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; -import FormInstance from "@/components/form/domain/FormInstance"; +import {FormInstance} from "@codingapi/ui-framework"; /** * 流程的事件控制上下文对象 diff --git a/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx b/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx index dfac4cc5..c8affa8f 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/ButtonPanel.tsx @@ -4,11 +4,8 @@ import {Button, ColorPicker, Modal, Popconfirm, Space} from "antd"; import ScriptModal from "@/components/flow/nodes/panel/ScriptModal"; import {EyeOutlined} from "@ant-design/icons"; import FlowContext from "@/components/flow/domain/FlowContext"; -import FormInput from "@/components/form/input"; -import ValidateUtils from "@/components/form/utils"; -import FormSelect from "@/components/form/select"; -import FormColor from "@/components/form/color"; -import Form from "@/components/form"; +import {Form,FormInput,FormSelect,FormColor} from "@codingapi/form-pc"; +import {ValidateUtils} from "@codingapi/ui-framework"; interface ButtonPanelProps { id: string; diff --git a/admin-pro-ui/src/components/flow/nodes/panel/EdgePanel.tsx b/admin-pro-ui/src/components/flow/nodes/panel/EdgePanel.tsx index 553f6342..14b0cf1f 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/EdgePanel.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/EdgePanel.tsx @@ -1,10 +1,10 @@ import React from "react"; -import {ActionType, ProForm, ProTable} from "@ant-design/pro-components"; +import {ActionType, ProTable} from "@ant-design/pro-components"; import {Input, InputNumber, Popconfirm, Space} from "antd"; import {CheckOutlined, EditOutlined, SettingOutlined} from "@ant-design/icons"; import ScriptModal from "@/components/flow/nodes/panel/ScriptModal"; import FlowContext from "@/components/flow/domain/FlowContext"; -import Form from "@/components/form"; +import {Form} from "@codingapi/form-pc"; interface EdgePanelProps { id?: string; diff --git a/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx b/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx index 2da789af..57e7f80c 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/NodePanel.tsx @@ -3,14 +3,9 @@ import {Button, Divider, Space} from "antd"; import {EyeOutlined, SettingOutlined} from "@ant-design/icons"; import GroovyScript from "@/components/flow/utils/script"; import ScriptModal from "@/components/flow/nodes/panel/ScriptModal"; -import ComponentBus from "@/framework/ComponentBus"; -import ValidateUtils from "@/components/form/utils"; -import FormSelect from "@/components/form/select"; -import FormSwitch from "@/components/form/switch"; -import FormInput from "@/components/form/input"; +import {ValidateUtils,ComponentBus,FormInstance} from "@codingapi/ui-framework"; +import {FormSelect,FormSwitch,FormInput,Form} from "@codingapi/form-pc"; import {UserSelectFormProps, UserSelectFormViewKey} from "@/components/flow/types"; -import FormInstance from "@/components/form/domain/FormInstance"; -import Form from "@/components/form"; interface NodePanelProps { id?: string, diff --git a/admin-pro-ui/src/components/flow/nodes/panel/ScriptModal.tsx b/admin-pro-ui/src/components/flow/nodes/panel/ScriptModal.tsx index 253b2e86..ae68961e 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/ScriptModal.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/ScriptModal.tsx @@ -2,12 +2,10 @@ import React from "react"; import Markdown from "react-markdown"; import {markdown} from "@/components/flow/nodes/panel/help"; import remarkGfm from 'remark-gfm' -import FormInput from "@/components/form/input"; -import FormCode from "@/components/form/code"; import "./ScriptModal.scss"; -import FormInstance from "@/components/form/domain/FormInstance"; +import {FormInstance} from "@codingapi/ui-framework"; import {Modal} from "antd"; -import Form from "@/components/form"; +import {Form,FormInput,FormCode} from "@codingapi/form-pc"; interface ScriptModalProps { form: FormInstance; diff --git a/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx b/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx index ff135049..8c3dedff 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/circulate.tsx @@ -3,7 +3,7 @@ import {Button, Drawer, Space, Tabs} from "antd"; import NodePanel from "@/components/flow/nodes/panel/NodePanel"; import EdgePanel from "@/components/flow/nodes/panel/EdgePanel"; import {SettingPanelProps} from "@/components/flow/types"; -import Form from "@/components/form"; +import {Form} from "@codingapi/form-pc"; const CirculateSettingPanel: React.FC = (props) => { diff --git a/admin-pro-ui/src/components/flow/nodes/panel/node.tsx b/admin-pro-ui/src/components/flow/nodes/panel/node.tsx index 06358d54..9a77173a 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/node.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/node.tsx @@ -4,7 +4,7 @@ import NodePanel from "@/components/flow/nodes/panel/NodePanel"; import EdgePanel from "@/components/flow/nodes/panel/EdgePanel"; import ButtonPanel from "@/components/flow/nodes/panel/ButtonPanel"; import {SettingPanelProps} from "@/components/flow/types"; -import Form from "@/components/form"; +import {Form} from "@codingapi/form-pc"; const NodeSettingPanel: React.FC = (props) => { diff --git a/admin-pro-ui/src/components/flow/nodes/panel/over.tsx b/admin-pro-ui/src/components/flow/nodes/panel/over.tsx index 4eda3772..2e3f2969 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/over.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/over.tsx @@ -2,7 +2,7 @@ import React from "react"; import {Button, Drawer, Space} from "antd"; import NodePanel from "@/components/flow/nodes/panel/NodePanel"; import {SettingPanelProps} from "@/components/flow/types"; -import Form from "@/components/form"; +import {Form} from "@codingapi/form-pc"; const OverSettingPanel: React.FC = (props) => { diff --git a/admin-pro-ui/src/components/flow/nodes/panel/start.tsx b/admin-pro-ui/src/components/flow/nodes/panel/start.tsx index 2fa1a79c..b2adec36 100644 --- a/admin-pro-ui/src/components/flow/nodes/panel/start.tsx +++ b/admin-pro-ui/src/components/flow/nodes/panel/start.tsx @@ -4,7 +4,7 @@ import EdgePanel from "@/components/flow/nodes/panel/EdgePanel"; import NodePanel from "@/components/flow/nodes/panel/NodePanel"; import ButtonPanel from "@/components/flow/nodes/panel/ButtonPanel"; import {SettingPanelProps} from "@/components/flow/types"; -import Form from "@/components/form"; +import {Form} from "@codingapi/form-pc"; const StartSettingPanel: React.FC = (props) => { diff --git a/admin-pro-ui/src/components/flow/types.ts b/admin-pro-ui/src/components/flow/types.ts index 0b0521c8..3c1bc2d6 100644 --- a/admin-pro-ui/src/components/flow/types.ts +++ b/admin-pro-ui/src/components/flow/types.ts @@ -1,5 +1,4 @@ -// 节点状态 -import FormInstance from "@/components/form/domain/FormInstance"; +import {FormInstance} from "@codingapi/ui-framework"; export type NodeState = "done" | "wait" | "undone" | "current"; diff --git a/admin-pro-ui/src/components/flow/view/index.tsx b/admin-pro-ui/src/components/flow/view/index.tsx index 01eec8f1..9539ba0b 100644 --- a/admin-pro-ui/src/components/flow/view/index.tsx +++ b/admin-pro-ui/src/components/flow/view/index.tsx @@ -1,6 +1,6 @@ import React, {createContext, useEffect} from "react"; import {FlowViewProps} from "@/components/flow/types"; -import FormInstance from "@/components/form/domain/FormInstance"; +import {FormInstance} from "@codingapi/ui-framework"; import {FlowButtonClickContext} from "@/components/flow/domain/FlowButtonClickContext"; import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; diff --git a/admin-pro-ui/src/components/form/captcha.tsx b/admin-pro-ui/src/components/form/captcha.tsx deleted file mode 100644 index 85380115..00000000 --- a/admin-pro-ui/src/components/form/captcha.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, {useEffect, useState} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Input} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - - -const Captcha:React.FC = (props)=>{ - - const [captchaImg, setCaptchaImg] = useState(''); - const {formContext} = formFieldInit(props); - - const reloadCaptcha = () => { - props.onCaptchaRefresh && props.onCaptchaRefresh().then((res) => { - if(res) { - setCaptchaImg(res.url); - props.onCaptchaChange && props.onCaptchaChange(res.code); - } - }); - } - - useEffect(() => { - reloadCaptcha(); - }, []) - - return ( -
- { - const currentValue = value.target.value; - formContext?.setFieldValue(props.name, currentValue); - props.onChange && props.onChange(currentValue,formContext); - }} - /> - - { - reloadCaptcha(); - }} - src={captchaImg} - alt="点击重置" - /> -
- ) -} - - -const FormCaptcha: React.FC = (props) => { - - return ( - - ) -} - -export default FormCaptcha; diff --git a/admin-pro-ui/src/components/form/cascader.tsx b/admin-pro-ui/src/components/form/cascader.tsx deleted file mode 100644 index 027a3697..00000000 --- a/admin-pro-ui/src/components/form/cascader.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Cascader, Form, Space} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; -import FormInstance from "@/components/form/domain/FormInstance"; - -const valueToForm = (value: string) => { - if (value && value.length > 0) { - return value.split(","); - } - return value; -} - -const formToValue = (value: string[]) => { - if (value && value.length > 0) { - return value.join(",") - } - return value; -} - -interface $CascaderProps extends FormItemProps{ - formInstance?:FormInstance; -} - -const $Cascader:React.FC<$CascaderProps> = (props)=>{ - const formInstance = props.formInstance; - return ( - - {props.addonBefore} - { - formInstance?.setFieldValue(props.name, formToValue(value as string[])); - props.onChange && props.onChange(value, formInstance); - }} - /> - {props.addonAfter} - - ) -} - -const FormCascader: React.FC = (props) => { - - const [options, setOptions] = React.useState(props.options); - - const {formContext} = formFieldInit(props, () => { - reloadOptions(); - }); - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(res => { - setOptions(res); - }); - } - } - - useEffect(() => { - reloadOptions(); - }, []); - - - return ( - - ) -} - -export default FormCascader; diff --git a/admin-pro-ui/src/components/form/checkbox.tsx b/admin-pro-ui/src/components/form/checkbox.tsx deleted file mode 100644 index 7f361453..00000000 --- a/admin-pro-ui/src/components/form/checkbox.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Checkbox, Form, Space} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const valueToForm = (value: string) => { - if (value && value.length > 0) { - return value.split(","); - } - return value; -} - -const formToValue = (value: string[]) => { - if (value && value.length > 0) { - return value.join(",") - } - return value; -} - -const FormCheckbox: React.FC = (props) => { - const [options, setOptions] = React.useState(props.options); - - const {formContext} = formFieldInit(props, () => { - reloadOptions(); - }); - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(res => { - setOptions(res); - }); - } - } - - useEffect(() => { - reloadOptions(); - }, []); - - return ( - - ) -} - -export default FormCheckbox; diff --git a/admin-pro-ui/src/components/form/code.tsx b/admin-pro-ui/src/components/form/code.tsx deleted file mode 100644 index 7aad7307..00000000 --- a/admin-pro-ui/src/components/form/code.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; -import CodeEditor from "@/components/CodeEditor"; - - -const FormCode: React.FC = (props) => { - - const {formContext} = formFieldInit(props); - - return ( - - ) -} - -export default FormCode; diff --git a/admin-pro-ui/src/components/form/color.tsx b/admin-pro-ui/src/components/form/color.tsx deleted file mode 100644 index a70fad47..00000000 --- a/admin-pro-ui/src/components/form/color.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {ColorPicker, Form, Space} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; -import type {AggregationColor} from "antd/es/color-picker/color"; -import FormInstance from "@/components/form/domain/FormInstance"; - -const formToValue = (value: AggregationColor) => { - if (value) { - return value.toHexString(); - } - return value; -} - -interface $ColorPickerProps extends FormItemProps{ - formInstance?:FormInstance; -} - -const $ColorPicker:React.FC<$ColorPickerProps> = (props)=>{ - const formInstance = props.formInstance; - - return ( - - {props.addonBefore} - { - const currentValue = formToValue(value); - formInstance?.setFieldValue(props.name, currentValue); - props.onChange && props.onChange(currentValue, formInstance); - }} - /> - {props.addonAfter} - - ) -} - -const FormColor: React.FC = (props) => { - - const {formContext} = formFieldInit(props); - - return ( - - ) -} - -export default FormColor; diff --git a/admin-pro-ui/src/components/form/common.tsx b/admin-pro-ui/src/components/form/common.tsx deleted file mode 100644 index 14cf8d55..00000000 --- a/admin-pro-ui/src/components/form/common.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, {useEffect} from "react"; -import {FormContext} from "@/components/form"; -import {FormItemProps} from "@/components/form/types"; - -const formFieldInit = (props: FormItemProps,reloadOption?:()=>void) => { - const formContext = React.useContext(FormContext) || undefined; - const formAction = formContext?.getFormAction(); - const validateContext = formContext?.getFormValidateContext(); - const [random, setRandom] = React.useState(0); - - useEffect(() => { - if (props.validateFunction) { - if (validateContext) { - if (props.disabled || props.hidden) { - // do nothing - } else { - validateContext.addValidateFunction(props.name, props.validateFunction); - } - } - } - const reloadContext = formContext?.getFormFieldReloadListenerContext(); - if (reloadContext) { - reloadContext.addListener(props.name, () => { - setRandom(Math.random); - }); - } - - const optionContext = formContext?.getFormFieldOptionListenerContext(); - if (optionContext) { - optionContext.addListener(props.name, () => { - if(reloadOption){ - reloadOption(); - } - }); - } - }, [formContext]); - - return {formContext, validateContext}; -} - -export default formFieldInit; diff --git a/admin-pro-ui/src/components/form/date.tsx b/admin-pro-ui/src/components/form/date.tsx deleted file mode 100644 index e5d86a7a..00000000 --- a/admin-pro-ui/src/components/form/date.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {DatePicker, Form, Space} from "antd"; -import dayjs from "dayjs"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; -import FormInstance from "@/components/form/domain/FormInstance"; - -const datePrecisionConverter = (precision?: string) => { - if (precision === "day") { - return "date"; - } - if (precision === "year") { - return "year"; - } - if (precision === "month") { - return "month"; - } - if (precision === "week") { - return "week"; - } - if (precision === "quarter") { - return "quarter"; - } - if (precision === "week-day") { - return "week"; - } - - if (precision === "hour") { - return "date"; - } - - if (precision === "minute") { - return "date"; - } - - if (precision === "second") { - return "date"; - } - - return null; -} - -const showTime = (precision?: string) => { - if (precision === "hour") { - return { - format: 'HH' - } - } - - if (precision === "minute") { - return { - format: 'HH:mm' - } - } - - if (precision === "second") { - return { - format: 'HH:mm:ss' - } - } - - return null; -} - -interface $DatePicker extends FormItemProps{ - formInstance?:FormInstance; -} - -const $DatePicker:React.FC<$DatePicker> = (props)=>{ - - const formInstance = props.formInstance; - - const format = props.dateFormat || 'YYYY-MM-DD'; - const precision = datePrecisionConverter(props.datePrecision) || "date"; - const showTimeConfig = showTime(props.datePrecision); - - return ( - - {props.addonBefore} - { - const currentDate = dayjs(date).format(format); - formInstance?.setFieldValue(props.name, currentDate); - props.onChange && props.onChange(currentDate, formInstance); - }} - /> - {props.addonAfter} - - ) -} - -const FormDate: React.FC = (props) => { - - const {formContext} = formFieldInit(props); - - return ( - - ) -} - -export default FormDate; diff --git a/admin-pro-ui/src/components/form/domain/FormInstance.tsx b/admin-pro-ui/src/components/form/domain/FormInstance.tsx deleted file mode 100644 index b1d0e433..00000000 --- a/admin-pro-ui/src/components/form/domain/FormInstance.tsx +++ /dev/null @@ -1,314 +0,0 @@ -import {FormValidateContext} from "@/components/form/validate"; -import {FormFieldOptionListenerContext, FormFieldReloadListenerContext} from "@/components/form/listener"; -import {NamePath} from "rc-field-form/es/interface"; -import {FormField} from "@/components/form/types"; -import {Form as AntdForm} from "antd"; -import {FiledData, FormAction} from "@/components/form"; -import {FormInstance as AntdFormInstance} from "antd/es/form/hooks/useForm"; -import React from "react"; - -class FormInstance { - private readonly validateContext: FormValidateContext; - private readonly reloadContext: FormFieldReloadListenerContext; - private readonly optionContext: FormFieldOptionListenerContext; - private readonly formInstance: AntdFormInstance; - private readonly formAction: FormAction; - private fields: FormField[]; - - private fieldsUpdateDispatch: React.Dispatch> | undefined; - - public setFieldsUpdateDispatch = (fieldsUpdateDispatch: React.Dispatch>) => { - this.fieldsUpdateDispatch = fieldsUpdateDispatch; - } - - private updateFields = (resetFields: (prevState: FormField[]) => FormField[]) => { - this.fields = resetFields(this.fields); - if (this.fieldsUpdateDispatch) { - this.fieldsUpdateDispatch(resetFields); - } - } - - private namePathEqual = (name1: NamePath, name2: NamePath) => { - if (Array.isArray(name1) && Array.isArray(name2)) { - if (name1.length !== name2.length) { - return false; - } - for (let i = 0; i < name1.length; i++) { - if (name1[i] !== name2[i]) { - return false; - } - } - return true; - } - return name1 === name2; - } - - public submit = async () => { - const res = await this.validateContext.validate(this); - if (res) { - this.formInstance.submit(); - } - } - - public reset = (values?: any) => { - this.formInstance.resetFields(); - if (values) { - this.formInstance.setFieldsValue(values); - this.reloadContext.notifyAll(); - } - } - - public hidden = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name, name)) { - return { - ...field, - props: { - ...field.props, - hidden: true, - required: false - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public required = (name: NamePath, required: boolean) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name, name)) { - return { - ...field, - props: { - ...field.props, - required: required - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public show = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name, name)) { - return { - ...field, - props: { - ...field.props, - hidden: false - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public disable = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name, name)) { - return { - ...field, - props: { - ...field.props, - disabled: true - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public disableAll = () => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - return { - ...field, - props: { - ...field.props, - disabled: true - } - } - })); - this.validateContext.clear(); - } - - public enable = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name, name)) { - return { - ...field, - props: { - ...field.props, - disabled: false - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public enableAll = () => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - return { - ...field, - props: { - ...field.props, - disabled: false - } - } - })); - this.validateContext.clear(); - } - - public remove = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.filter((field) => !this.namePathEqual(field.props.name, name))); - this.validateContext.clear(); - } - - public create = (field: FormField, index?: number) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => { - const filteredFields = prevFields.filter((item) => item.props.name !== field.props.name); - if (index === undefined || index < 0) { - return [...filteredFields, field]; - } else { - const newFields = [...filteredFields]; - newFields.splice(index, 0, field); - return newFields; - } - }); - this.validateContext.clear(); - } - - public getFieldValue = (name: NamePath) => { - return this.formInstance.getFieldValue(name); - } - - public getFieldsValue = () => { - return this.formInstance.getFieldsValue(); - } - - public getFieldProps = (name: NamePath) => { - for (const field of this.fields) { - if (this.namePathEqual(field.props.name, name)) { - return field; - } - } - return null; - } - - public reloadOptions = (name: NamePath) => { - this.optionContext.notify(name); - } - - public reloadAllOptions = () => { - this.optionContext.notifyAll(); - } - - public setFieldValue = (name: NamePath, value: any) => { - this.formInstance.setFieldValue(name, value); - this.reloadContext.notify(name); - this.validateContext?.validateField(name, this); - } - - public setFieldsValue = (values: any) => { - this.formInstance.setFieldsValue(values); - this.reloadContext.notifyAll(); - } - - public setFields = (fields: FiledData[]) => { - this.formInstance.setFields(fields); - } - - public validate = () => { - return this.validateContext.validate(this); - } - - public resetFields = (fields: FormField[]) => { - this.fields = fields; - } - - constructor() { - this.validateContext = new FormValidateContext(); - this.reloadContext = new FormFieldReloadListenerContext(); - this.optionContext = new FormFieldOptionListenerContext(); - this.formInstance = AntdForm.useForm()[0]; - this.fields = []; - this.formAction = { - submit: this.submit, - reset: this.reset, - hidden: this.hidden, - show: this.show, - remove: this.remove, - create: this.create, - disable: this.disable, - disableAll: this.disableAll, - enable: this.enable, - enableAll: this.enableAll, - required: this.required, - getFieldValue: this.getFieldValue, - getFieldsValue: this.getFieldsValue, - getFieldProps: this.getFieldProps, - reloadOptions: this.reloadOptions, - reloadAllOptions: this.reloadAllOptions, - setFieldValue: this.setFieldValue, - setFieldsValue: this.setFieldsValue, - setFields: this.setFields, - validate: this.validate, - } - } - - public getFormAction = () => { - return this.formAction; - } - - public getFormValidateContext = () => { - return this.validateContext; - } - - public getFormFieldReloadListenerContext = () => { - return this.reloadContext; - } - - public getFormFieldOptionListenerContext = () => { - return this.optionContext; - } - - public getFormControlInstance = (): AntdFormInstance => { - return this.formInstance; - } - -} - -export default FormInstance; diff --git a/admin-pro-ui/src/components/form/factory.tsx b/admin-pro-ui/src/components/form/factory.tsx deleted file mode 100644 index 17abf4e3..00000000 --- a/admin-pro-ui/src/components/form/factory.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React from "react"; -import {FormField} from "@/components/form/types"; -import FormInput from "@/components/form/input"; -import FormPassword from "@/components/form/password"; -import FormCaptcha from "@/components/form/captcha"; -import FormCheckbox from "@/components/form/checkbox"; -import FormRadio from "@/components/form/radio"; -import FormRate from "@/components/form/rate"; -import FormSlider from "@/components/form/slider"; -import FormStepper from "@/components/form/stepper"; -import FormTextArea from "@/components/form/textarea"; -import FormSwitch from "@/components/form/switch"; -import FormDate from "@/components/form/date"; -import FormCascader from "@/components/form/cascader"; -import FormSelect from "@/components/form/select"; -import FormSelector from "@/components/form/selector"; -import FormUploader from "@/components/form/uploder"; -import FormColor from "@/components/form/color"; -import FormCode from "@/components/form/code"; - - -class FormFactory { - - static create = (field: FormField) => { - const type = field.type; - const props = field.props; - - if (type === 'input') { - return ( - - ) - } - - if (type === 'password') { - return ( - - ) - } - - if (type === 'captcha') { - return ( - - ) - } - - if (type === 'checkbox') { - return ( - - ) - } - - if (type === 'radio') { - return ( - - ) - } - - if (type === 'rate') { - return ( - - ) - } - - if (type === 'slider') { - return ( - - ) - } - if (type === 'stepper') { - return ( - - ) - } - - if (type === 'textarea') { - return ( - - ) - } - - if (type === 'switch') { - return ( - - ) - } - - if (type === 'date') { - return ( - - ) - } - - if (type === 'cascader') { - return ( - - ) - } - - if (type === 'selector') { - return ( - - ) - } - - if (type === 'select') { - return ( - - ) - } - - if (type === 'uploader') { - return ( - - ) - } - - if (type === 'color') { - return ( - - ) - } - - if (type === 'code') { - return ( - - ) - } - } - -} - -export default FormFactory; diff --git a/admin-pro-ui/src/components/form/form.scss b/admin-pro-ui/src/components/form/form.scss deleted file mode 100644 index 0b8c49ff..00000000 --- a/admin-pro-ui/src/components/form/form.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use "@/config/variables" as *; - -.form-captcha { - display: flex; - justify-content: space-between; - align-items: center; - - .form-captcha-input { - } - - .form-captcha-img { - margin-left: 10px; - cursor: pointer; - height: 30px; - } -} diff --git a/admin-pro-ui/src/components/form/index.tsx b/admin-pro-ui/src/components/form/index.tsx deleted file mode 100644 index ced2b758..00000000 --- a/admin-pro-ui/src/components/form/index.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, {useEffect} from "react"; -import {NamePath} from "rc-field-form/es/interface"; -import {FormField} from "@/components/form/types"; -import {Form as AntForm} from "antd"; -import FormFactory from "@/components/form/factory"; -import "./form.scss"; -import FormInstance from "@/components/form/domain/FormInstance"; - -export interface FiledData { - name: NamePath; - errors?: string[]; -} - -export interface FormAction { - // 提交表单,提取之前会先校验表单 - submit: () => void; - // 重置表单,恢复表单项最初的状态 - reset: (values?: any) => void; - // 隐藏表单项,隐藏后的表单项不会显示在表单中,但是值会被提交 - hidden: (name: NamePath) => void; - // 展示表单项,展示后的表单项会显示在表单中,值也会被提交 - show: (name: NamePath) => void; - // 删除表单项,删除后的表单项不会显示在表单中,值也不会被提交,在fields配置的情况下生效 - remove: (name: NamePath) => void; - // 添加表单项,添加后的表单项会显示在表单中,值也会被提交,在fields配置的情况下生效 - create: (field: FormField, index?: number) => void; - // 禁用表单项,禁用后的表单项还会被提交 - disable: (name: NamePath) => void; - // 全部禁用,禁用后的表单项还会被提交 - disableAll: () => void; - // 启用表单项,启用后的表单项还会被提交 - enable: (name: NamePath) => void; - // 全部启用,启用后的表单项还会被提交 - enableAll: () => void; - // 必填选项控制,true为必填false为非必填提示 - required: (name: NamePath, required: boolean) => void; - // 获取字段的值 - getFieldValue: (name: NamePath) => any; - // 重新加载选项 - reloadOptions: (name: NamePath) => any; - // 重新加载所有选项 - reloadAllOptions: () => any; - // 获取全部字段的值 - getFieldsValue: () => any; - // 设置字段的值 - setFieldValue: (name: NamePath, value: any) => void; - // 设置全部字段的值 - setFieldsValue: (values: any) => void; - // 设置Field字段 - setFields: (fields: FiledData[]) => void; - // 获取Field属性 - getFieldProps: (name: NamePath) => FormField | null; - // 校验表单 - validate: () => Promise; -} - -export interface FormProps { - // 表单字段 - loadFields?: () => Promise; - // 表单提交事件 - onFinish?: (values: any) => Promise; - // form布局,默认vertical - layout?: 'horizontal' | 'vertical'; - // children元素 - children?: React.ReactNode; - // footer元素 - footer?: React.ReactNode; - // 初始化值 - initialValues?: any; - // 表单实例 - form?: FormInstance; -} - -export const FormContext = React.createContext(null); - - -const FormComponent: React.FC = (props) => { - - const formInstance = props.form? props.form : new FormInstance(); - - const [fields, setFields] = React.useState([]); - formInstance.setFieldsUpdateDispatch(setFields); - - const formControl = formInstance.getFormControlInstance(); - - const reloadFields = () => { - if (props.loadFields) { - props.loadFields().then(fields => { - setFields(fields); - formInstance.resetFields(fields); - }) - } - } - - useEffect(() => { - reloadFields(); - }, [props.loadFields]); - - return ( - - { - props.onFinish && props.onFinish(values); - }} - initialValues={props.initialValues} - layout={props.layout} - > - {fields.length > 0 && fields.map((field) => { - return FormFactory.create(field) as React.ReactNode; - })} - - {props.children} - - {props.footer} - - - ) -} - -type FormType = typeof FormComponent; -type FormComponentType = FormType & { - useForm: ()=>FormInstance; -}; - -const Form = FormComponent as FormComponentType; -Form.useForm = ()=>{ - return new FormInstance(); -}; - -export default Form; diff --git a/admin-pro-ui/src/components/form/input.tsx b/admin-pro-ui/src/components/form/input.tsx deleted file mode 100644 index 7b1327ce..00000000 --- a/admin-pro-ui/src/components/form/input.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Input} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormInput: React.FC = (props) => { - - const inputType = props.inputType || "text"; - const {formContext} = formFieldInit(props); - - return ( - - ) -} - -export default FormInput; diff --git a/admin-pro-ui/src/components/form/listener.ts b/admin-pro-ui/src/components/form/listener.ts deleted file mode 100644 index 8b0277b8..00000000 --- a/admin-pro-ui/src/components/form/listener.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {NamePath} from "rc-field-form/es/interface"; - -// Form字段组件刷新控制监听上下文对象 -export class FormFieldReloadListenerContext { - - private readonly map:Mapvoid>; - - constructor() { - this.map = new Map(); - } - - public addListener(name:NamePath,listener:()=>void){ - const key = Array.isArray(name)?name.join("."):name; - this.map.set(key, listener); - } - - - public notify(name:NamePath){ - const key = Array.isArray(name)?name.join("."):name; - const listener = this.map.get(key); - if(listener){ - listener(); - } - } - - public notifyAll(){ - this.map.forEach(listener=>{ - listener(); - }) - } - -} - -// Form字段组件选项刷新控制监听上下文对象 -export class FormFieldOptionListenerContext { - - private readonly map:Mapvoid>; - - constructor() { - this.map = new Map(); - } - - public addListener(name:NamePath,listener:()=>void){ - const key = Array.isArray(name)?name.join("."):name; - this.map.set(key, listener); - } - - - public notify(name:NamePath){ - const key = Array.isArray(name)?name.join("."):name; - const listener = this.map.get(key); - if(listener){ - listener(); - } - } - - public notifyAll(){ - this.map.forEach(listener=>{ - listener(); - }) - } - -} - diff --git a/admin-pro-ui/src/components/form/password.tsx b/admin-pro-ui/src/components/form/password.tsx deleted file mode 100644 index 9aafa524..00000000 --- a/admin-pro-ui/src/components/form/password.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Input} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormPassword: React.FC = (props) => { - - const {formContext} = formFieldInit(props); - - return ( - - ) -} - -export default FormPassword; diff --git a/admin-pro-ui/src/components/form/radio.tsx b/admin-pro-ui/src/components/form/radio.tsx deleted file mode 100644 index af39e893..00000000 --- a/admin-pro-ui/src/components/form/radio.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Radio, Space} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormRadio: React.FC = (props) => { - const [options, setOptions] = React.useState(props.options); - - const {formContext} = formFieldInit(props, () => { - reloadOptions(); - }); - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(res => { - setOptions(res); - }); - } - } - - useEffect(() => { - reloadOptions(); - }, []); - - return ( - - ) -} - -export default FormRadio; diff --git a/admin-pro-ui/src/components/form/rate.tsx b/admin-pro-ui/src/components/form/rate.tsx deleted file mode 100644 index 446eb8f7..00000000 --- a/admin-pro-ui/src/components/form/rate.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Rate} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormRate: React.FC = (props) => { - const {formContext} = formFieldInit(props); - - return ( - - ) -} - -export default FormRate; diff --git a/admin-pro-ui/src/components/form/select.tsx b/admin-pro-ui/src/components/form/select.tsx deleted file mode 100644 index 9cfc271d..00000000 --- a/admin-pro-ui/src/components/form/select.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Select, Space} from "antd"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; -import FormInstance from "@/components/form/domain/FormInstance"; - -const valueToForm = (value: string) => { - if (value && value.length > 0) { - return value.split(","); - } - return value; -} - -const formToValue = (value: string[] |string) => { - if(value instanceof Array) { - if (value && value.length > 0) { - return value.join(",") - } - } - return value; -} - -interface $SelectProps extends FormItemProps{ - formInstance?:FormInstance; -} - -const $Select: React.FC<$SelectProps> = (props) => { - const formInstance = props.formInstance; - - return ( - - {props.addonBefore} - - -
- ); - }; - callback(Test); -`; - const [code, setCode] = useState(defaultCode); // 直接在代码中返回 Test 组件实例 - - const executeCode = (codeStr: string, mode: string) => { - loadComponent(codeStr, [ - Button, - Input, - Select - ]).then((Component) => { - - setRemoteTestComponent(() => Component); - - if (mode === 'menu') { - addMenu({ - path: '/test', - name: '测试页面', - icon: 'BugFilled', - routes: [ - { - path: '/test/test1', - element: ( - - - - ), - name: '测试页面1', - } - ] - }); - } - }).catch((error) => { - console.error(error); - }); - }; - - - return ( - - - - - - - - - )} - > - - - - - - - - {RemoteTestComponent && ( - }> - - - )} - - - - ); -}; - -export default Test1; diff --git a/admin-ui/src/pages/dynamic/test2.tsx b/admin-ui/src/pages/dynamic/test2.tsx deleted file mode 100644 index d7890bfb..00000000 --- a/admin-ui/src/pages/dynamic/test2.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import React, {Suspense, useEffect, useState} from "react"; -import {Button, Space, Spin} from "antd"; -import {ModalForm, PageContainer, ProForm, ProFormText} from "@ant-design/pro-components"; -import ProFormUploader from "@/components/Form/ProFormUploader"; -import {loadRemoteComponent, loadZipJsFileScript} from "@/utils/dynamicLoader"; -import {useRoutesContext} from "@/framework/Routes/RoutesProvider"; -import Role from "@/pages/role"; - - -const Test2 = () => { - const [RemoteTestComponent, setRemoteTestComponent] = useState | null>(null); - - const {addMenu} = useRoutesContext(); - - const [visible, setVisible] = useState(false); - const [mode, setMode] = useState('zip' as 'zip' | 'menu'); - - const [form] = ProForm.useForm(); - - useEffect(() => { - if(visible){ - form.setFieldsValue({ - scope: "MircoApp", - module: "./Header", - }) - } - }, [visible]); - - const loadComponent = (values: any) => { - return new Promise((resolve, reject) => { - const base64 = values.component; - const scope = values.scope; - const module = values.module; - loadZipJsFileScript(base64).then(() => { - loadRemoteComponent(scope, module).then((ComponentModule: any) => { - const Component = ComponentModule.default || ComponentModule; - resolve(Component); - }).catch(e => { - reject(e); - }); - }); - }); - } - - const handlerLoadComponent = async (values: any) => { - loadComponent(values).then((Component:any) => { - if(Component) { - if (mode === 'menu') { - addMenu({ - path: '/test', - name:'测试页面', - icon: 'BugFilled', - routes:[ - { - path: '/test/test1', - element: ( - - - - ), - name:'测试页面1', - } - ] - }); - }else{ - setRemoteTestComponent(() => Component); - } - } - setVisible(false); - }); - } - - return ( - -
-

动态加载组件

- - {RemoteTestComponent && ( - }> - { - alert('click'); - }} - /> - - )} - - - - - - - - - - - { - setVisible(false); - }, - destroyOnClose: true - }} - onFinish={handlerLoadComponent} - > - -
-
- ) -} - -export default Test2; diff --git a/admin-ui/src/pages/flow/leave/LeaveForm.tsx b/admin-ui/src/pages/flow/leave/LeaveForm.tsx index 718b28fe..8f1cfbc4 100644 --- a/admin-ui/src/pages/flow/leave/LeaveForm.tsx +++ b/admin-ui/src/pages/flow/leave/LeaveForm.tsx @@ -1,132 +1,51 @@ import React, {useEffect} from "react"; -import {ProForm, ProFormDigit, ProFormText, ProFormTextArea} from "@ant-design/pro-components"; -import {EVENT_CLOSE_RESULT_VIEW, EVENT_RELOAD_DATA, FlowFormViewProps} from "@/components/Flow/flow/types"; -import {Button} from "antd"; -import {useSelector} from "react-redux"; -import {FlowReduxState} from "@/components/Flow/store/FlowSlice"; - +import {FlowFormViewProps} from "@codingapi/ui-framework"; +import {Form,FormInput,FormTextArea} from "@codingapi/form-pc"; +import {ValidateUtils} from "@codingapi/ui-framework"; const LeaveForm: React.FC = (props) => { - // 审批意见输入框展示状态 - const opinionEditorVisible = useSelector((state: FlowReduxState) => state.flow.opinionEditorVisible); - - useEffect(() => { - if (props.visible) { - console.log('init props.visible '); - props.form.resetFields(); - props.form.setFieldsValue(props.data); - - // 关闭意见输入框,仅当在开始节点关闭 - if (props.flowData?.getNodeCode() === 'start') { - props.opinionEditorVisible && props.opinionEditorVisible(false); - } else { - props.opinionEditorVisible && props.opinionEditorVisible(true); - } - } - }, [props.visible]); - - const eventKey = props.eventKey; - - const [visible, setVisible] = React.useState(false); - useEffect(() => { - if (eventKey === 'test') { - console.log("点击了自定义事件", eventKey); - setVisible(true); - } - - // 当流程审批反馈结果关闭时,重新加载数据 - if (eventKey == EVENT_CLOSE_RESULT_VIEW && props.flowData?.getNodeCode() === 'start') { - // 重新加载数据 - console.log("重新加载数据"); - props.handlerClick && props.handlerClick({type: "RELOAD"}); + if (props.dataVersion && props.data) { + console.log('data',props.data); + props.form?.setFieldsValue({ + ...props.data + }); } - - if (eventKey == EVENT_RELOAD_DATA) { - props.form.resetFields(); - props.form.setFieldsValue(props.data); - } - }, [eventKey]); - - - useEffect(() => { - setTimeout(() => { - console.log('flowData recordId', props.flowData?.getRecordId()); - }, 100) - }, [props.flowData]); - + }, [props.dataVersion]); return ( - - + ) } diff --git a/admin-ui/src/pages/flow/leave/index.tsx b/admin-ui/src/pages/flow/leave/index.tsx index bfc61a8d..bd1470fb 100644 --- a/admin-ui/src/pages/flow/leave/index.tsx +++ b/admin-ui/src/pages/flow/leave/index.tsx @@ -1,11 +1,9 @@ import React from "react"; -import Page from "@/components/Layout/Page"; import {ActionType, PageContainer, ProTable} from "@ant-design/pro-components"; import {list} from "@/api/leave"; import {Button} from "antd"; -import FlowView from "@/components/Flow/flow"; import LeaveForm from "@/pages/flow/leave/LeaveForm"; - +import {FlowModelView} from "@codingapi/flow-pc"; const LeavePage = () => { @@ -37,37 +35,35 @@ const LeavePage = () => { return ( - - [ - - ]} - columns={columns} - search={false} - rowKey={"id"} - request={async (params, sort, filter) => { - return list(params, sort, filter, []); - }} - /> + [ + + ]} + columns={columns} + search={false} + rowKey={"id"} + request={async (params, sort, filter) => { + return list(params, sort, filter, []); + }} + /> - + - ) } diff --git a/admin-ui/src/pages/flow/record/index.tsx b/admin-ui/src/pages/flow/record/index.tsx index ac873cbe..fada0816 100644 --- a/admin-ui/src/pages/flow/record/index.tsx +++ b/admin-ui/src/pages/flow/record/index.tsx @@ -12,13 +12,13 @@ import { import moment from "moment"; import {message, Tabs} from "antd"; import "./index.scss"; -import FlowView from "@/components/Flow/flow"; import LeaveForm from "@/pages/flow/leave/LeaveForm"; +import {FlowModelView} from "@codingapi/flow-pc"; const FlowRecordPage = () => { const [flowViewVisible, setFlowViewVisible] = React.useState(false); - const [currentId, setCurrentId] = React.useState(0); + const [currentId, setCurrentId] = React.useState(''); const [reviewVisible, setReviewVisible] = React.useState(false); @@ -31,7 +31,7 @@ const FlowRecordPage = () => { const allTodoActionRef = React.useRef(); - const handlerUrgeFlow = (recordId:any) => { + const handlerUrgeFlow = (recordId: any) => { const body = { recordId, } @@ -61,9 +61,9 @@ const FlowRecordPage = () => { { title: '标题', dataIndex: 'title', - render:(value:any,record:any)=>{ + render: (value: any, record: any) => { return ( -
+
); } }, @@ -171,7 +171,7 @@ const FlowRecordPage = () => { ] as any[]; - const reloadTable = ()=>{ + const reloadTable = () => { if (key === 'todo') { todoActionRef.current?.reload(); } @@ -213,7 +213,7 @@ const FlowRecordPage = () => { search={false} columns={columns} rowClassName={(record) => { - return record.read?"record-read":"record-unread"; + return record.read ? "record-read" : "record-unread"; }} request={async (params, sort, filter) => { return findTodoByOperatorId(params, sort, filter, []); @@ -230,7 +230,7 @@ const FlowRecordPage = () => { search={false} columns={columns} rowClassName={(record) => { - return record.read?"record-read":"record-unread"; + return record.read ? "record-read" : "record-unread"; }} request={async (params, sort, filter) => { return findDoneByOperatorId(params, sort, filter, []); @@ -247,7 +247,7 @@ const FlowRecordPage = () => { search={false} columns={columns} rowClassName={(record) => { - return record.read?"record-read":"record-unread"; + return record.read ? "record-read" : "record-unread"; }} request={async (params, sort, filter) => { return findInitiatedByOperatorId(params, sort, filter, []); @@ -264,7 +264,7 @@ const FlowRecordPage = () => { search={false} columns={columns} rowClassName={(record) => { - return record.read?"record-read":"record-unread"; + return record.read ? "record-read" : "record-unread"; }} request={async (params, sort, filter) => { return findTimeoutTodoByOperatorId(params, sort, filter, []); @@ -282,7 +282,7 @@ const FlowRecordPage = () => { search={false} columns={columns} rowClassName={(record) => { - return record.read?"record-read":"record-unread"; + return record.read ? "record-read" : "record-unread"; }} request={async (params, sort, filter) => { return findPostponedTodoByOperatorId(params, sort, filter, []); @@ -299,7 +299,7 @@ const FlowRecordPage = () => { search={false} columns={columns} rowClassName={(record) => { - return record.read?"record-read":"record-unread"; + return record.read ? "record-read" : "record-unread"; }} request={async (params, sort, filter) => { return findAllByOperatorId(params, sort, filter, []); @@ -310,17 +310,16 @@ const FlowRecordPage = () => { ]} /> - - ) } diff --git a/admin-ui/src/pages/flow/user/index.tsx b/admin-ui/src/pages/flow/user/index.tsx index a25b0ab9..d52717ec 100644 --- a/admin-ui/src/pages/flow/user/index.tsx +++ b/admin-ui/src/pages/flow/user/index.tsx @@ -1,19 +1,11 @@ import React, {useRef} from "react"; -import Page from "@/components/Layout/Page"; -import { - ActionType, - ModalForm, - PageContainer, - ProForm, - ProFormSwitch, - ProFormText, - ProTable -} from "@ant-design/pro-components"; +import {ActionType, PageContainer, ProTable} from "@ant-design/pro-components"; import {changeManager, entrust, list, remove, removeEntrust, save} from "@/api/user"; -import {Button, message, Popconfirm, Space} from "antd"; +import {Button, message, Modal, Popconfirm, Space} from "antd"; import {DeleteOutlined, SettingOutlined} from "@ant-design/icons"; import UserSelect from "@/pages/flow/user/select"; - +import {Form,FormInput,FormSwitch} from "@codingapi/form-pc"; +import {ValidateUtils} from "@codingapi/ui-framework"; const UserPage = () => { @@ -23,7 +15,7 @@ const UserPage = () => { const [user, setUser] = React.useState({}); const actionRef = useRef(); - const [form] = ProForm.useForm(); + const form = Form.useForm(); const handleSave = (values: any) => { save(values).then(res => { @@ -168,7 +160,6 @@ const UserPage = () => { return ( - [ @@ -188,67 +179,58 @@ const UserPage = () => { }} /> - { - setVisible(false); - }, - onClose: () => { - setVisible(false); - }, - destroyOnClose: true + onClose={()=>{ + setVisible(false); + }} + onCancel={()=>{ + setVisible(false); }} - onFinish={async (values) => { - handleSave(values); + onOk={async ()=>{ + await form.submit(); }} > - +
{ + handleSave(values); + }} + > +
) } diff --git a/admin-ui/src/pages/flow/work/__test__/index.test.tsx b/admin-ui/src/pages/flow/work/__test__/index.test.tsx deleted file mode 100644 index 30686478..00000000 --- a/admin-ui/src/pages/flow/work/__test__/index.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import {act, fireEvent, render, screen, waitFor} from "@testing-library/react"; -import FlowPage from "@/pages/flow/work"; - -jest.mock('axios'); - -// test description for the component -describe('Welcome Component', () => { - - test('renders with default initial value', async () => { - - // render the component - render( - - ); - - const flowTable = screen.getByTestId("flow-table"); - expect(flowTable).toBeInTheDocument(); - - const flowAddBtn = screen.getByTestId("flow-add-btn"); - expect(flowAddBtn).toBeInTheDocument(); - - - await act(async () => { - fireEvent.click(flowAddBtn); - }); - - await waitFor(() => { - const flowEditor = screen.getByTestId("flow-editor"); - expect(flowEditor).toBeInTheDocument(); - }); - - const inputTitle = screen.getByLabelText('标题'); - const inputCode = screen.getByLabelText('编码'); - const inputDescription = screen.getByLabelText('描述'); - const inputPostponedMax = screen.getByLabelText('最大延期次数'); - const inputSkipIfSameApprover = screen.getByLabelText('是否跳过相同审批人'); - const submitButton = screen.getByTestId('flow-editor-submit'); - expect(submitButton).toBeInTheDocument(); - - - await act(async () => { - fireEvent.change(inputTitle, {target: {value: 'test'}}); - expect(inputTitle).toHaveValue('test'); - fireEvent.change(inputCode, {target: {value: 'test'}}); - expect(inputCode).toHaveValue('test'); - fireEvent.change(inputDescription, {target: {value: 'test'}}); - expect(inputDescription).toHaveValue('test'); - fireEvent.change(inputPostponedMax, {target: {value: '1'}}); - expect(inputPostponedMax).toHaveValue('1'); - fireEvent.change(inputSkipIfSameApprover, {target: {value: 'true'}}); - expect(inputSkipIfSameApprover).toHaveValue('true'); - fireEvent.click(submitButton); - }); - - await waitFor(() => { - // todo 不敢展示,却必须得展示才能正常 - const submitButton = screen.getByTestId('flow-editor-submit'); - expect(submitButton).toBeInTheDocument(); - }); - - }); -}); diff --git a/admin-ui/src/pages/flow/work/index.tsx b/admin-ui/src/pages/flow/work/index.tsx index 69d45222..4c0719b2 100644 --- a/admin-ui/src/pages/flow/work/index.tsx +++ b/admin-ui/src/pages/flow/work/index.tsx @@ -1,23 +1,17 @@ import React from "react"; -import Flow, {FlowActionType} from "@/components/Flow"; -import { - ActionType, - ModalForm, - PageContainer, - ProForm, - ProFormDigit, ProFormSwitch, - ProFormText, ProFormTextArea, - ProTable -} from "@ant-design/pro-components"; +import {Flow,FlowActionType} from "@codingapi/flow-pc"; +import {ActionType, PageContainer, ProTable} from "@ant-design/pro-components"; import {changeState, copy, list, remove, save, schema} from "@/api/flow"; -import {Button, Drawer, message, Popconfirm, Space} from "antd"; +import {Button, Drawer, message, Modal, Popconfirm, Space} from "antd"; +import {Form,FormInput,FormTextArea,FormSwitch} from "@codingapi/form-pc"; +import {ValidateUtils} from "@codingapi/ui-framework"; const FlowPage = () => { const [visible, setVisible] = React.useState(false); const [editorVisible, setEditorVisible] = React.useState(false); const flowActionType = React.useRef(null); - const [form] = ProForm.useForm(); + const form = Form.useForm(); const actionRef = React.useRef(); const [current, setCurrent] = React.useState(null); @@ -181,7 +175,7 @@ const FlowPage = () => { data-testid={"flow-add-btn"} type={"primary"} onClick={() => { - form.resetFields(); + form.reset(); setEditorVisible(true); }} >新增 @@ -197,77 +191,68 @@ const FlowPage = () => { }} /> - setEditorVisible(false), - onCancel: () => setEditorVisible(false), + destroyOnClose={true} + onClose={()=>{ + setEditorVisible(false) + }} + onCancel={()=>{ + setEditorVisible(false) }} - submitter={{ - submitButtonProps:{ - "data-testid":"flow-editor-submit", - }, + onOk={async ()=>{ + await form.submit(); }} - onFinish={handlerSave} > - + { - const [form] = Form.useForm(); +const FooterButtons: React.FC<{ formInstance: FormInstance }> = ({formInstance}) => { + const data = { + user: { + name: '张三', + age: 18, + password: '123456', + code: '123', + checkbox: '1,2', + radio: '1', + rate: 3, + slider: 50, + switch: true, + textarea: '这是一段文本', + date: '2021-08-01', + cascader: '1,1-1,1-1-1', + select: '1,2', + avatar: 'c84fb304c180f61bb7db40efef7f85b7', + color: '#000000', + ideCode: 'console.log("hello world")' + } + } return ( - -
-
{ - console.log(values); - }} - > - { - // 自定义格式化值的方式 - return value.join(','); - }} - getValueProps={(value)=>{ - // 自定义设置值的方式,这里是将字符串转为数组 - if(value) { - const arr = value.split(','); - return { - value: arr - }; +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ ) +} + +const FormPage = () => { + const leftFormInstance = Form.useForm(); + const rightFormInstance = Form.useForm(); + + const fields = [ + { + type: 'input', + props: { + required: true, + name: ['user', 'name'], + label: '姓名', + placeholder: '请输入姓名', + validateFunction: async (content) => { + const value = content.value; + if (value) { + return [] + } + return ['姓名不能为空'] + } + } + }, + { + type: 'stepper', + props: { + required: true, + name: ['user', 'age'], + label: '年龄', + placeholder: '请输入年龄', + } + }, + { + type: 'password', + props: { + required: true, + name: ['user', 'password'], + label: '银行卡密码', + placeholder: '请输入银行卡密码', + validateFunction: async (content) => { + const value = content.value; + if (value) { + return [] + } + return ['银行卡密码不能为空'] + } + } + }, + { + type: 'captcha', + props: { + required: true, + name: ['user', 'code'], + label: '银行卡验证码', + placeholder: '请输入银行卡验证码', + onCaptchaRefresh: async () => { + console.log('refresh captcha') + return { + url: '/captcha.jpeg', + code: '123' + } + } + } + }, + { + type: 'checkbox', + props: { + required: true, + name: ['user', 'checkbox'], + label: '复选框', + options: [ + {label: '选项1', value: '1'}, + {label: '选项2', value: '2'}, + {label: '选项3', value: '3'}, + ] + } + }, + { + type: 'radio', + props: { + required: true, + name: ['user', 'radio'], + label: '单选框', + options: [ + {label: '选项1', value: '1'}, + {label: '选项2', value: '2'}, + {label: '选项3', value: '3'}, + ] + } + }, + { + type: 'rate', + props: { + required: true, + name: ['user', 'rate'], + label: '评分', + } + }, + { + type: 'slider', + props: { + required: true, + name: ['user', 'slider'], + label: '滑块', + sliderPopover: true + } + }, + { + type: 'switch', + props: { + required: true, + name: ['user', 'switch'], + label: '开关', + } + }, + { + type: 'textarea', + props: { + required: true, + name: ['user', 'textarea'], + label: '文本域', + } + }, + { + type: 'date', + props: { + required: true, + name: ['user', 'date'], + label: '日期', + } + }, + { + type: 'cascader', + props: { + required: true, + name: ['user', 'cascader'], + label: '级联选择', + options: [ + { + label: '选项1', + value: '1', + children: [ + { + label: '选项1-1', + value: '1-1', + children: [ + { + label: '选项1-1-1', + value: '1-1-1', + }, + { + label: '选项1-1-2', + value: '1-1-2', + }, + ] + }, + { + label: '选项1-2', + value: '1-2', + }, + ] + }, + { + label: '选项2', + value: '2', + children: [ + { + label: '选项2-1', + value: '2-1', + }, + { + label: '选项2-2', + value: '2-2', + }, + ] + }, + ] + } + }, + { + type: 'select', + props: { + required: true, + name: ['user', 'select'], + label: '选择器', + selectMultiple: true, + options: [ + {label: '选项1', value: '1'}, + {label: '选项2', value: '2'}, + {label: '选项3', value: '3'}, + ] + } + }, + { + type: 'uploader', + props: { + required: true, + name: ['user', 'avatar'], + label: '头像', + } + }, + { + type: 'color', + props: { + required: true, + name: ['user', 'color'], + label: '颜色', + } + }, + { + type: 'code', + props: { + required: true, + name: ['user', 'ideCode'], + label: '代码', + } + }, + ] as FormField[]; + + return ( + + + + + )} > - {/* - 对于下属组件,需要提供以下属性: - value: 1 - onChange: (value: any) => void - 通过这两个属性就可以实现双向绑定 - */} - - // } - }, - { - title: '科目1', - dataIndex: 'kemu1', - valueType: 'digit', - editable: true, - // render: (text, record) => { - // return { - // const data = { - // ...record, - // kemu1: parseFloat(e.target.value) - // } - // table2UpdateService.onChange(data); - // }} - // /> - // } - }, - { - title: '科目2', - dataIndex: 'kemu2', - valueType: 'digit', - editable: true, - // render: (text, record) => { - // return { - // const data = { - // ...record, - // kemu2: parseFloat(e.target.value ) - // } - // console.log(data) - // table2UpdateService.onChange(data); - // }} - // /> - // } - }, - { - title: '小计', - dataIndex: 'sum', - valueType: 'text', - editable: true, - // render: (text, record) => { - // return - // } - } - ] as ProColumns[]; - - - for(let i=0;i<30;i++){ - columns.push( - { - title: '科目21'+i, - dataIndex: 'kemu2'+i, - //@ts-ignore - valueType: 'digit'+i, - // render: (text, record) => { - // return { - // const data = { - // ...record, - // kemu2: parseFloat(e.target.value ) - // } - // console.log(data) - // table2UpdateService.onChange(data); - // }} - // /> - // } - }, - ) - } - - return ( - <> - {/* {*/} - {/* console.log('reload table2')*/} - {/* return {*/} - {/* data: table2,*/} - {/* success: true*/} - {/* }*/} - {/* }}*/} - {/*/>*/} - - - ) -} - -export default SalaryTable2; diff --git a/admin-ui/src/pages/salary/data/table1.ts b/admin-ui/src/pages/salary/data/table1.ts deleted file mode 100644 index cbfc2ee7..00000000 --- a/admin-ui/src/pages/salary/data/table1.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {Table1, Table2, User} from "@/pages/salary/types"; -import {SalaryStore} from "@/pages/salary/store/salary"; - -export class Table1UpdateService { - - private readonly data: Table1[]; - private readonly update: (data: Table1[]) => void; - private readonly reload: () => void; - - - constructor(store: SalaryStore, update: (data: Table1[]) => void, reload: () => void) { - this.data = store.table1; - this.update = update; - this.reload = reload; - - } - - - public initData(users: User[]) { - const t1 = new Date().getTime(); - const data = users.map((user) => { - return { - id: user.id, - name: user.name, - jxgz: 0, - sum: 0 - } - }); - this.updateData(data); - const t2 = new Date().getTime(); - console.log("initData", t2 - t1); - } - - - public reloadData(table2: Table2[]) { - const list = this.data.map((item) => { - const row = table2.find((row) => row.id === item.id); - if (row) { - if (item.id === row.id) { - return { - ...item, - jxgz: row.sum, - sum: row.sum - } - } - } - return item; - }); - this.updateData(list); - } - - - private updateData(data: Table1[]) { - this.update(data); - this.reload(); - } - -} diff --git a/admin-ui/src/pages/salary/data/table2.ts b/admin-ui/src/pages/salary/data/table2.ts deleted file mode 100644 index 2bf91c12..00000000 --- a/admin-ui/src/pages/salary/data/table2.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {Table2, User} from "@/pages/salary/types"; - -export class Table2UpdateService { - - private data: Table2[]; - private readonly update: (data: Table2[]) => void; - private readonly reload: () => void; - private readonly updateVersion: (version: number) => void; - - constructor(table2: Table2[], update: (data: Table2[]) => void, reload: () => void, updateVersion: (version: number) => void) { - this.data = table2; - this.update = update; - this.reload = reload; - this.updateVersion = updateVersion; - } - - - public initData(users: User[]) { - const data = users.map((user) => { - return { - id: user.id, - name: user.name, - kemu1: 0, - kemu2: 0, - sum: 0 - } - }); - this.updateData(data); - } - - - public onChange(row: Table2) { - const t1 = new Date().getMilliseconds(); - const sum = row.kemu1 + row.kemu2; - const list = this.data.map((item) => { - if (item.id === row.id) { - return { - ...item, - ...row, - sum - } - } - return item; - }); - console.log(sum); - this.updateData(list); - const t2 = new Date().getMilliseconds(); - console.log("onChange", t2 - t1); - } - - - private updateData(data: Table2[]) { - this.update(data); - this.reload(); - this.updateVersion(Math.random()); - } -} diff --git a/admin-ui/src/pages/salary/index.tsx b/admin-ui/src/pages/salary/index.tsx deleted file mode 100644 index b17754d2..00000000 --- a/admin-ui/src/pages/salary/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, {useEffect, useState} from "react"; -import {Provider, useDispatch} from "react-redux"; -import {salaryStore, updateUsers} from "@/pages/salary/store/salary"; -import {PageContainer} from "@ant-design/pro-components"; -import {Tabs} from "antd"; -import SalaryTable1 from "@/pages/salary/compoments/SalaryTable1"; -import SalaryTable2 from "@/pages/salary/compoments/SalaryTable2"; -import {users} from "@/api/salary"; - - -const $Salary = () => { - - const items = [ - { - key: "1", - label: "总科目", - children: - }, - ]; - - for (let i = 2; i < 500; i++) { - items.push( - { - key: i + '', - label: "绩效工资", - children: ( - - ) - }) - } - - const [activeKey, setActiveKey] = useState("1"); - - const dispatch = useDispatch(); - - useEffect(() => { - users().then(res => { - dispatch(updateUsers(res)); - }) - }, []); - - return ( - - { - setActiveKey(value); - }} - > - {items.map(item => { - return ( - - ) - })} - - - {items.map(item => { - if (item.key === activeKey) { - return item.children; - } - })} - - - - ) -} - -const Salary = () => { - return ( - <> - - <$Salary/> - - - ) -} - - -export default Salary; diff --git a/admin-ui/src/pages/salary/store/salary.ts b/admin-ui/src/pages/salary/store/salary.ts deleted file mode 100644 index ddd54a23..00000000 --- a/admin-ui/src/pages/salary/store/salary.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {configureStore, createSlice, PayloadAction} from '@reduxjs/toolkit'; -import {Table1, Table2, User} from "@/pages/salary/types"; - -export interface SalaryStore { - users: User[]; - table1: Table1[]; - table2: Table2[]; - table2Version:number; -} - -export type SalaryStoreAction = { - - updateUsers: (state: SalaryStore, action: PayloadAction) => void; - - updateTable1: (state: SalaryStore, action: PayloadAction) => void; - - updateTable2: (state: SalaryStore, action: PayloadAction) => void; - - updateTable2Version: (state: SalaryStore, action: PayloadAction) => void; -} - -export const SalarySlice = createSlice({ - name: 'salary', - initialState: { - users: [], - table1: [], - table2: [], - table2Version:0 - }, - reducers: { - - updateUsers: (state, action) => { - state.users = action.payload; - }, - - updateTable1: (state, action) => { - state.table1 = action.payload; - }, - - updateTable2: (state, action) => { - state.table2 = action.payload; - }, - - updateTable2Version: (state, action) => { - state.table2Version = action.payload; - } - - }, -}); - -export const { - updateUsers, - updateTable1, - updateTable2, - updateTable2Version -} = SalarySlice.actions; - -export const salaryStore = configureStore({ - reducer: { - salary: SalarySlice.reducer - }, -}); - -export type SalaryState = ReturnType; diff --git a/admin-ui/src/pages/salary/types.ts b/admin-ui/src/pages/salary/types.ts deleted file mode 100644 index d4fd1ad4..00000000 --- a/admin-ui/src/pages/salary/types.ts +++ /dev/null @@ -1,22 +0,0 @@ - -export interface User{ - id:number, - name:string -} - - -export interface Table1{ - id:number, - name:string, - jxgz:number, - sum:number -} - - -export interface Table2{ - id:number, - name:string, - kemu1:number, - kemu2:number, - sum:number -} diff --git a/admin-ui/src/pages/user/index.tsx b/admin-ui/src/pages/user/index.tsx deleted file mode 100644 index a7533db5..00000000 --- a/admin-ui/src/pages/user/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import {list} from '@/api/user'; -import type {ActionType} from '@ant-design/pro-components'; -import {PageContainer, ProTable} from '@ant-design/pro-components'; -import React, {useRef} from 'react'; - -const UserPage: React.FC = () => { - - const actionRef = useRef(null); - - const columns = [ - { - title: "编号", - dataIndex: 'id', - key: 'id', - sorter: true, - search: false, - }, - { - title: "名称", - dataIndex: 'name', - key: 'name', - }, - ]; - - return ( - - { - return await list(params, sort, filter, [ - { - key: 'name', - type: 'LIKE' - } - ]); - }} - /> - - ); -}; - -export default UserPage; diff --git a/admin-ui/src/pages/welcome/__test__/index.test.tsx b/admin-ui/src/pages/welcome/__test__/index.test.tsx deleted file mode 100644 index 4fbc8f3d..00000000 --- a/admin-ui/src/pages/welcome/__test__/index.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import Welcome from '../index'; -import {render} from "@testing-library/react"; -import store from "@/store/Redux"; -import {Provider} from "react-redux"; -import {Calculate} from "../data"; - -// test description for the component -describe('Welcome Component', () => { - // test case for the component - test('renders with default initial value', () => { - // render the component - render( - - - - ); - // get the element that will be tested - const countElement = document.querySelector('.App-header p'); - // assert the element is exist - expect(countElement).toBeInTheDocument(); - // assert the element text content - expect(countElement).toHaveTextContent('hi , Redux counter: 0, Roles:'); - // log the element text content - console.log(countElement?.textContent); - }); - - - test('method add test', () => { - const sum = Calculate.add(1,1); - expect(sum).toBe(2); - }); -}); diff --git a/admin-ui/src/pages/welcome/data.ts b/admin-ui/src/pages/welcome/data.ts deleted file mode 100644 index 8128121b..00000000 --- a/admin-ui/src/pages/welcome/data.ts +++ /dev/null @@ -1,18 +0,0 @@ -export class Calculate{ - - static add = (a: number, b: number) => { - return a + b; - } - - static sub = (a: number, b: number) => { - return a - b; - } - - static mul = (a: number, b: number) => { - return a * b; - } - - static div = (a: number, b: number) => { - return a / b; - } -} diff --git a/admin-ui/src/pages/welcome/index.tsx b/admin-ui/src/pages/welcome/index.tsx index 80579a34..5bf4fca0 100644 --- a/admin-ui/src/pages/welcome/index.tsx +++ b/admin-ui/src/pages/welcome/index.tsx @@ -1,61 +1,21 @@ import React from 'react'; +import {PageContainer} from "@ant-design/pro-components"; import './index.scss'; -import Page from "@/components/Layout/Page"; -import {Button, Form, Input} from "antd"; -import {FlowFormApiContext, FlowFormCustomValidateContext} from "@/api/validate"; -import * as api from "@/api" -FlowFormApiContext.getInstance().setApi({ - get: (url: string, params?: any) => { - return api.get(url, params); - }, - post: (url: string, data: any) => { - return api.post(url, data); - } -}); -const Index = () => { - - const [form] = Form.useForm(); - - const context = new FlowFormCustomValidateContext(); - - const validateFuncCode = ` - if (content.value) { - return []; - } else { - return ["姓名不存在"]; - } - `; - context.addCustomFunctionCodeValidate(["user", "name"], validateFuncCode); +const WelcomePage = () => { return ( - - - - - - - - - - - - - - + + +
    Admin-UI 支持的功能有
+
    1. 自定义流程
+
    2. 表单渲染
+
    3. 管理权限
+
    4. 动态菜单
+
    5. 动态加载组件
+
); } -export default Index; +export default WelcomePage; diff --git a/admin-ui/src/portal/index.tsx b/admin-ui/src/portal/index.tsx deleted file mode 100644 index 4e063986..00000000 --- a/admin-ui/src/portal/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; - -const Portal = ()=>{ - return ( - <> - - - ) -} -export default Portal; diff --git a/admin-pro-ui/src/utils/captcha.ts b/admin-ui/src/utils/captcha.ts similarity index 100% rename from admin-pro-ui/src/utils/captcha.ts rename to admin-ui/src/utils/captcha.ts diff --git a/admin-pro-ui/src/utils/oss.ts b/admin-ui/src/utils/oss.ts similarity index 100% rename from admin-pro-ui/src/utils/oss.ts rename to admin-ui/src/utils/oss.ts diff --git a/admin-pro-ui/todo.md b/admin-ui/todo.md similarity index 100% rename from admin-pro-ui/todo.md rename to admin-ui/todo.md diff --git a/admin-ui/webpack.config.dev.js b/admin-ui/webpack.config.dev.js index 60b77fdb..2ac3919e 100644 --- a/admin-ui/webpack.config.dev.js +++ b/admin-ui/webpack.config.dev.js @@ -4,7 +4,7 @@ const common = require('./webpack.common.js'); module.exports = merge(common, { mode: 'development', devServer: { - port: 3000, + port: 8000, proxy: [ { diff --git a/admin-ui/webpack.config.mock.js b/admin-ui/webpack.config.mock.js index d53c66ae..1998dc09 100644 --- a/admin-ui/webpack.config.mock.js +++ b/admin-ui/webpack.config.mock.js @@ -7,7 +7,7 @@ const express = require('express'); module.exports = merge(common, { mode: 'development', devServer: { - port: 3000, + port: 8000, setupMiddlewares: (middlewares, devServer) => { if (!devServer) { diff --git a/admin-ui/webpack.config.prod.js b/admin-ui/webpack.config.prod.js index 90868278..3e2d47c5 100644 --- a/admin-ui/webpack.config.prod.js +++ b/admin-ui/webpack.config.prod.js @@ -4,6 +4,6 @@ const common = require('./webpack.common.js'); module.exports = merge(common,{ mode: 'production', devServer: { - port: 13000, + port: 8000, }, }); diff --git a/mobile-ui/package.json b/mobile-ui/package.json index eea4365b..f9f0b19b 100644 --- a/mobile-ui/package.json +++ b/mobile-ui/package.json @@ -4,6 +4,9 @@ "private": true, "dependencies": { "@babel/standalone": "^7.25.6", + "@codingapi/flow-mobile": "^0.0.2", + "@codingapi/form-mobile": "^0.0.2", + "@codingapi/ui-framework": "^0.0.12", "@logicflow/core": "^2.0.10", "@logicflow/extension": "^2.0.14", "@reduxjs/toolkit": "^2.2.7", diff --git a/mobile-ui/src/components/descriptions/index.scss b/mobile-ui/src/components/descriptions/index.scss deleted file mode 100644 index bd0d0289..00000000 --- a/mobile-ui/src/components/descriptions/index.scss +++ /dev/null @@ -1,34 +0,0 @@ -@use "@/config/variables" as *; - -.descriptions-list { - padding: 3px; - background-color: white; - - .descriptions-list-item { - background-color: white; - padding: 15px; - font-size: $content-font-size; - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid $body-background-color; - - .descriptions-list-item-label { - font-weight: bold; - } - - .descriptions-list-item-value { - } - } - - .form-header-title { - width: 100%; - height: 20px; - border-left: 4px solid $theme-primary-color; - padding-left: 4px; - font-size: 14px; - margin-bottom: 10px; - } -} - - diff --git a/mobile-ui/src/components/descriptions/index.tsx b/mobile-ui/src/components/descriptions/index.tsx deleted file mode 100644 index cdb95701..00000000 --- a/mobile-ui/src/components/descriptions/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, {useEffect, useImperativeHandle} from "react"; -import {FormField} from "@/components/form/types"; -import "./index.scss"; - -export interface DescriptionsAction { - // 重新刷新数据 - reload: () => void; -} - -// 详情展示属性 -interface DescriptionsProps { - // 展示的字段 - columns?: FormField[]; - // 请求数据 - request?: () => Promise; - // 操作对象 - actionRef?: React.Ref; - // 页脚 - footer?: React.ReactNode; - // 页头 - header?: React.ReactNode; - // 数据转换 - dataConvert?: (field: FormField, data: any) => Promise; -} - -// 详情展示 -const Descriptions: React.FC = (props) => { - - const [data, setData] = React.useState(null); - - const reload = () => { - if (props.request) { - props.request().then((data) => { - if(props.dataConvert) { - const promise = [] as Promise[]; - props.columns?.map(item => { - promise.push(new Promise((resolve, reject) => { - props.dataConvert?.(item, data).then(value => { - data[item.props.name] = value; - resolve(value); - }).catch(reject); - })); - }); - Promise.all(promise).then(() => { - setData(data); - }); - }else { - setData(data); - } - }) - } - } - - useImperativeHandle(props.actionRef, () => ({ - reload: () => { - reload() - } - }), [props.actionRef]); - - useEffect(() => { - reload(); - - return () => { - setData(null); - } - }, []); - - return ( -
- {props.header} - {data && props.columns && props.columns - .filter(item => !item.props.hidden) - .map((item) => { - const key = item.props.name; - const value = data[key]; - const valueType = typeof value === 'object' ? 'object' : 'string'; - return ( -
-
- {valueType === 'string' && ( -
- )} - {valueType === 'object' && ( -
- {value} -
- )} -
- ) - })} - {props.footer} -
- ) -} - -export default Descriptions; diff --git a/mobile-ui/src/components/flow/plugins/PostponedFormView.tsx b/mobile-ui/src/components/flow/PostponedFormView.tsx similarity index 90% rename from mobile-ui/src/components/flow/plugins/PostponedFormView.tsx rename to mobile-ui/src/components/flow/PostponedFormView.tsx index f3707631..7c903cf2 100644 --- a/mobile-ui/src/components/flow/plugins/PostponedFormView.tsx +++ b/mobile-ui/src/components/flow/PostponedFormView.tsx @@ -1,9 +1,9 @@ import React from "react"; -import {PostponedFormProps} from "@/components/flow/types"; +import {PostponedFormProps} from "@codingapi/ui-framework"; import Popup from "@/components/popup"; import {DatePickerView} from "antd-mobile"; import dayjs from "dayjs"; -import {dateLabelRenderer} from "@/components/form/date"; +import {dateLabelRenderer} from "@codingapi/form-mobile"; const PostponedFormView: React.FC = (props) => { diff --git a/mobile-ui/src/components/flow/plugins/UserSelectFormView.tsx b/mobile-ui/src/components/flow/UserSelectFormView.tsx similarity index 90% rename from mobile-ui/src/components/flow/plugins/UserSelectFormView.tsx rename to mobile-ui/src/components/flow/UserSelectFormView.tsx index 9c6dfdd1..919e42fa 100644 --- a/mobile-ui/src/components/flow/plugins/UserSelectFormView.tsx +++ b/mobile-ui/src/components/flow/UserSelectFormView.tsx @@ -1,8 +1,7 @@ import React, {useEffect} from "react"; -import {UserSelectFormProps} from "@/components/flow/types"; +import {UserSelectFormProps} from "@codingapi/ui-framework"; import Popup from "@/components/popup"; -import Form from "@/components/form"; -import FormInput from "@/components/form/input"; +import {Form,FormInput} from "@codingapi/form-mobile"; const UserSelectFormView: React.FC = (props) => { diff --git a/mobile-ui/src/components/flow/components/FlowChart.tsx b/mobile-ui/src/components/flow/components/FlowChart.tsx deleted file mode 100644 index 8b5e2b6a..00000000 --- a/mobile-ui/src/components/flow/components/FlowChart.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, {useContext, useEffect} from "react"; -import '@logicflow/core/es/index.css'; -import '@logicflow/extension/lib/style/index.css'; -import {LogicFlow, Options} from "@logicflow/core"; -import {DndPanel, Menu, MiniMap, Snapshot} from "@logicflow/extension"; -import {FlowViewReactContext} from "@/components/flow/view"; -import Start from "@/components/flow/nodes/Start"; -import Over from "@/components/flow/nodes/Over"; -import Circulate from "@/components/flow/nodes/Circulate"; -import Node from "@/components/flow/nodes/Node"; -import EdgeType = Options.EdgeType; - -interface FlowChartProps { - edgeType?: EdgeType; -} - -const FlowChart: React.FC = (props) => { - - const flowViewReactContext = useContext(FlowViewReactContext); - const flowRecordContext = flowViewReactContext?.flowRecordContext; - const flowSchema = flowRecordContext?.getFlowSchema(); - - const [url, setUrl] = React.useState(''); - - const edgeType = props.edgeType || 'polyline'; - const container = React.useRef(null); - const lfRef = React.useRef(null); - - useEffect(() => { - const SilentConfig = { - isSilentMode: true, - stopScrollGraph: false, - stopMoveGraph: false, - stopZoomGraph: false, - edgeTextEdit: false, - }; - - //@ts-ignore - lfRef.current = new LogicFlow({ - //@ts-ignore - container: container.current, - ...SilentConfig, - background: { - backgroundColor: '#f3f5f8' - }, - width: 0, - height: 0, - plugins: [Menu, DndPanel, MiniMap, Snapshot], - grid: false, - edgeType: edgeType, - }); - - lfRef.current.setTheme({ - bezier: { - stroke: '#8f94e3', - strokeWidth: 1, - }, - polyline: { - stroke: '#8f94e3', - strokeWidth: 1, - }, - line: { - stroke: '#8f94e3', - strokeWidth: 1, - }, - }); - lfRef.current.register(Start); - lfRef.current.register(Node); - lfRef.current.register(Over); - lfRef.current.register(Circulate); - lfRef.current.render(flowSchema); - - setTimeout(() => { - lfRef.current?.getSnapshotBlob().then((blob: any) => { - setUrl(URL.createObjectURL(blob.data)); - }); - }, 100) - - }, [flowViewReactContext]); - - return ( -
-
-
- {url && ( - - )} -
-
- ) -} - -export default FlowChart; diff --git a/mobile-ui/src/components/flow/components/FlowContent.tsx b/mobile-ui/src/components/flow/components/FlowContent.tsx deleted file mode 100644 index 0949d166..00000000 --- a/mobile-ui/src/components/flow/components/FlowContent.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, {useContext, useEffect} from "react"; -import {Divider, Tabs} from "antd-mobile"; -import {FlowFormViewProps} from "@/components/flow/types"; -import {FlowViewReactContext} from "@/components/flow/view"; -import FlowHistory from "@/components/flow/components/FlowHistory"; -import FlowFormOpinion from "@/components/flow/components/FlowFormOpinion"; -import {useSelector} from "react-redux"; -import {FlowReduxState} from "@/components/flow/store/FlowSlice"; -import FlowChart from "@/components/flow/components/FlowChart"; -import FlowHistoryLine from "@/components/flow/components/FlowHistoryLine"; -import FlowOpinion from "@/components/flow/components/FlowOpinion"; - -const FlowContent= () => { - const flowViewReactContext = useContext(FlowViewReactContext); - - const flowRecordContext = flowViewReactContext?.flowRecordContext; - const formInstance = flowViewReactContext?.formInstance; - - const FlowFormView = flowRecordContext?.getFlowFormView() as React.ComponentType; - - const formParams = flowRecordContext?.getFlowFormParams(); - - const opinionVisible = useSelector((state: FlowReduxState) => state.flow.opinionVisible); - const dataVersion = useSelector((state: FlowReduxState) => state.flow.dataVersion); - const contentHiddenVisible = useSelector((state: FlowReduxState) => state.flow.contentHiddenVisible); - - useEffect(() => { - if(!flowRecordContext?.isEditable()){ - setTimeout(()=>{ - formInstance?.disableAll(); - },100); - } - }, []); - - const style = contentHiddenVisible ? {"display":"none"} : {}; - return ( -
- - - {formInstance && ( - - )} - - {opinionVisible && ( - - )} - - - - 审批记录 - - - - - 流转历史 - - - -
- ) -} - -export default FlowContent; diff --git a/mobile-ui/src/components/flow/components/FlowFooter.tsx b/mobile-ui/src/components/flow/components/FlowFooter.tsx deleted file mode 100644 index eea062e5..00000000 --- a/mobile-ui/src/components/flow/components/FlowFooter.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import React, {useContext} from "react"; -import {ActionSheet, Button} from "antd-mobile"; -import {FlowViewReactContext} from "@/components/flow/view"; -import {useSelector} from "react-redux"; -import {FlowReduxState} from "@/components/flow/store/FlowSlice"; -import {useNavigate} from "react-router"; - -interface FlowFooterProps { - maxButtonCount?: number; -} - -const FlowFooter: React.FC = (props) => { - const flowViewReactContext = useContext(FlowViewReactContext); - - const flowRecordContext = flowViewReactContext?.flowRecordContext; - - const flowButtonClickContext = flowViewReactContext?.flowButtonClickContext; - - const buttons = flowRecordContext?.getFlowButtons()||[]; - const maxButtonCount = props.maxButtonCount || 4; - const requestLoading = useSelector((state: FlowReduxState) => state.flow.requestLoading); - const contentHiddenVisible = useSelector((state: FlowReduxState) => state.flow.contentHiddenVisible); - const [visible, setVisible] = React.useState(false); - const navigate = useNavigate(); - - const style = contentHiddenVisible ? {"display":"none"} : {}; - - if(flowRecordContext?.isEditable()){ - return ( -
- {buttons && buttons.length <= maxButtonCount && buttons.map((item) => { - const style = item.style && JSON.parse(item.style) || {}; - return ( - - ) - })} - {buttons && buttons.length > maxButtonCount && ( - <> - {buttons && buttons.slice(0, maxButtonCount - 1).map(item => { - const style = item.style && JSON.parse(item.style) || {}; - return ( - - ) - })} - - - - { - return { - text: item.name, - key: item.id, - onClick: () => { - flowButtonClickContext?.handlerClick(item); - setVisible(false); - } - } - })} - onClose={() => setVisible(false)} - /> - - )} - - -
- ) - }else { - return ( -
- -
- ) - } -} - -export default FlowFooter; diff --git a/mobile-ui/src/components/flow/components/FlowForm404.tsx b/mobile-ui/src/components/flow/components/FlowForm404.tsx deleted file mode 100644 index 31257336..00000000 --- a/mobile-ui/src/components/flow/components/FlowForm404.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import {Button, Result} from "antd-mobile"; -import {useNavigate} from "react-router"; - - -const FlowForm404 = ()=>{ - - const navigate = useNavigate(); - - return ( - - -
- )} - /> - ) -} - -export default FlowForm404; diff --git a/mobile-ui/src/components/flow/components/FlowFormOpinion.tsx b/mobile-ui/src/components/flow/components/FlowFormOpinion.tsx deleted file mode 100644 index fe300015..00000000 --- a/mobile-ui/src/components/flow/components/FlowFormOpinion.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, {useContext, useEffect} from "react"; -import Form from "@/components/form"; -import {FlowViewReactContext} from "@/components/flow/view"; - - -const FlowFormOpinion = ()=>{ - - const flowViewReactContext = useContext(FlowViewReactContext); - const opinionInstance = flowViewReactContext?.opinionInstance; - const flowRecordContext = flowViewReactContext?.flowRecordContext; - - useEffect(() => { - opinionInstance?.setFieldValue("advice", flowRecordContext?.getOpinionAdvice()); - }, []); - - return ( - <> -
{ - return [ - { - type:'textarea', - props:{ - name:"advice", - label:"审批意见", - textAreaRows:2, - required:true, - validateFunction:async (content)=>{ - const value = content.value; - if(value){ - return []; - } - return ["请输入审批意见"]; - } - } - } - ] - }} - > -
- - ) -} - -export default FlowFormOpinion; diff --git a/mobile-ui/src/components/flow/components/FlowHistory.tsx b/mobile-ui/src/components/flow/components/FlowHistory.tsx deleted file mode 100644 index 25faa305..00000000 --- a/mobile-ui/src/components/flow/components/FlowHistory.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, {useContext} from "react"; -import {FlowViewReactContext} from "@/components/flow/view"; -import Descriptions from "@/components/descriptions"; -import {FormField} from "@/components/form/types"; -import moment from "moment"; -import {Tag} from "antd-mobile"; - -const fields = [ - { - type: 'input', - props: { - name: 'title', - label: '', - } - }, - { - type: 'input', - props: { - name: 'createOperatorName', - label: '发起人', - } - }, - { - type: 'input', - props: { - name: 'createOperatorDate', - label: '发起时间', - } - }, - { - type: 'input', - props: { - name: 'flowStatus', - label: '状态', - } - }, - { - type: 'input', - props: { - name: 'recodeType', - label: '流程状态', - } - }, - { - type: 'input', - props: { - name: 'postponedCount', - label: '是否延期', - } - }, - { - type: 'input', - props: { - name: 'interfere', - label: '是否干预', - } - }, - { - type: 'input', - props: { - name: 'read', - label: '是否已读', - } - }, - { - type: 'input', - props: { - name: 'timeoutTime', - label: '超时时间', - } - }, - { - type: 'input', - props: { - name: 'nodeName', - label: '节点名称', - } - } -] as FormField[]; - -const flowStatusConvert = (data: any) => { - if (data.flowStatus === 'RUNNING') { - return '进行中'; - } - if (data.flowStatus === 'FINISH') { - return '已结束'; - } - return ''; -} - -const recodeTypeConvert = (data: any) => { - if (data.flowType === 'TODO') { - return 待办; - } - if (data.flowType === 'DONE') { - return 已办; - } - if (data.flowType === 'TRANSFER') { - return 已转办; - } - return ''; -} - -const postponedCountConvert = (data: any) => { - if (data.postponedCount > 0) { - return '延期'; - } - return '未延期'; -} - -const interfereConvert = (data: any) => { - if (data.interfere) { - return '干预'; - } - return '未干预'; -} - -const readConvert = (data: any) => { - if (data.read) { - return '已读'; - } - return '未读'; -} - -const timeoutTimeConvert = (data: any) => { - if (data.timeoutTime == 0) { - return '未设置'; - } - return moment(data.timeoutTime).format("YYYY-MM-DD HH:mm:ss"); -} - - -const FlowHistory = () => { - const flowViewReactContext = useContext(FlowViewReactContext); - const flowRecordContext = flowViewReactContext?.flowRecordContext; - - const currentFlowRecord = flowRecordContext?.getCurrentFlowRecord(); - - if(currentFlowRecord) { - return ( -
-
- { - return { - ...currentFlowRecord, - createOperatorName: currentFlowRecord.createOperator?.name, - createOperatorDate: moment(currentFlowRecord.createTime).format('YYYY-MM-DD HH:mm:ss'), - flowStatus: flowStatusConvert(currentFlowRecord), - recodeType: recodeTypeConvert(currentFlowRecord), - postponedCount: postponedCountConvert(currentFlowRecord), - interfere: interfereConvert(currentFlowRecord), - read: readConvert(currentFlowRecord), - timeoutTime: timeoutTimeConvert(currentFlowRecord), - nodeName: flowRecordContext?.getNode(currentFlowRecord.nodeCode)?.name, - }; - }} - /> -
-
- ) - } - return <> -} - -export default FlowHistory; diff --git a/mobile-ui/src/components/flow/components/FlowHistoryLine.tsx b/mobile-ui/src/components/flow/components/FlowHistoryLine.tsx deleted file mode 100644 index 6ffeb24c..00000000 --- a/mobile-ui/src/components/flow/components/FlowHistoryLine.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, {useContext} from "react"; -import {FlowViewReactContext} from "@/components/flow/view"; -import moment from "moment"; -import {Tag} from "antd-mobile"; - -const recodeTypeConvert = (data: any) => { - if (data.flowType === 'TODO') { - return 待办; - } - if (data.flowType === 'DONE') { - return 已办; - } - if (data.flowType === 'TRANSFER') { - return 已转办; - } - return ''; -} - - -const FlowHistoryLine = () => { - const flowViewReactContext = useContext(FlowViewReactContext); - const flowRecordContext = flowViewReactContext?.flowRecordContext; - - const historyRecords = flowRecordContext?.getHistoryRecords(); - - return ( -
-
- {historyRecords && historyRecords.map((historyRecord: any, index: number) => { - const nodeName = flowRecordContext?.getNode(historyRecord.nodeCode)?.name; - const recodeType = recodeTypeConvert(historyRecord); - const createName = historyRecord.createOperator?.name; - const createTime = moment(historyRecord.createTime).format('YYYY-MM-DD HH:mm:ss'); - const flowAdvice = historyRecord.opinion?.advice || '暂无意见'; - return ( -
-
- {historyRecords.length > (index + 1) &&
} -
-
-
{nodeName}
-
状态:{recodeType}
-
创建人:{createName}
-
创建时间:{createTime}
-
审批意见:{flowAdvice}
-
-
- ) - })} -
-
- ) -} - -export default FlowHistoryLine; diff --git a/mobile-ui/src/components/flow/components/FlowOpinion.tsx b/mobile-ui/src/components/flow/components/FlowOpinion.tsx deleted file mode 100644 index 4ff19b83..00000000 --- a/mobile-ui/src/components/flow/components/FlowOpinion.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, {useContext} from "react"; -import {FlowViewReactContext} from "@/components/flow/view"; -import moment from "moment"; - - -const optionStateConvert = (data: any) => { - if (data.opinion.result === 1) { - return '转办' - } - if (data.opinion.result === 2) { - return '通过' - } - if (data.opinion.result === 3) { - return '驳回' - } - return '暂存' -} - -const FlowOpinion = () => { - - const flowViewReactContext = useContext(FlowViewReactContext); - const flowRecordContext = flowViewReactContext?.flowRecordContext; - - const historyOpinions = flowRecordContext?.getHistoryOpinions(); - - return ( - <> - {historyOpinions && historyOpinions.map((item: any) => { - const flowOperatorName = item.operator.name; - const optionState = optionStateConvert(item); - const createTime = moment(item.createTime).format("YYYY-MM-DD HH:mm:ss"); - const advice = item.opinion.advice; - return ( -
-
- 审批人: - {flowOperatorName} {optionState} -
-
- 审批时间: - {createTime} -
-
- 审批意见: - {advice} -
-
- ) - })} - - ) -} - -export default FlowOpinion; diff --git a/mobile-ui/src/components/flow/components/FlowPage.tsx b/mobile-ui/src/components/flow/components/FlowPage.tsx deleted file mode 100644 index 36a8edc6..00000000 --- a/mobile-ui/src/components/flow/components/FlowPage.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, {useEffect} from "react"; -import { - FlowFormViewProps, - FlowViewProps, - PostponedFormProps, - PostponedFormViewKey, - UserSelectFormProps, - UserSelectFormViewKey -} from "@/components/flow/types"; -import {useDispatch, useSelector} from "react-redux"; -import {FlowReduxState, updateState} from "@/components/flow/store/FlowSlice"; -import {FlowRecordContext} from "@/components/flow/domain/FlowRecordContext"; -import Form from "@/components/form"; -import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; -import {FlowEventContext} from "@/components/flow/domain/FlowEventContext"; -import FlowResult from "@/components/flow/components/FlowResult"; -import FlowContent from "@/components/flow/components/FlowContent"; -import FlowFooter from "@/components/flow/components/FlowFooter"; -import {FlowViewReactContext} from "@/components/flow/view"; -import FlowForm404 from "@/components/flow/components/FlowForm404"; -import ComponentBus from "@/framework/ComponentBus"; -import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; -import {FlowButtonClickContext} from "@/components/flow/domain/FlowButtonClickContext"; - - -interface FlowPageProps extends FlowViewProps { - // 流程详情数据 - flowData: any; -} - -const FlowPage: React.FC = (props) => { - - const dispatch = useDispatch(); - - const currentState = useSelector((state: FlowReduxState) => state.flow); - const flowRecordContext = new FlowRecordContext(props, props.flowData); - const formInstance = Form.useForm(); - const opinionInstance = Form.useForm(); - - const flowStateContext = new FlowStateContext(currentState, (state: any) => { - dispatch(updateState({ - ...state - })); - }); - const flowTriggerContext = new FlowTriggerContext(); - const flowEvenContext = new FlowEventContext(flowRecordContext, flowTriggerContext, formInstance, opinionInstance, flowStateContext); - const flowButtonClickContext = new FlowButtonClickContext(flowEvenContext, flowStateContext); - const FlowFormView = flowRecordContext.getFlowFormView() as React.ComponentType; - - // 延期表单视图 - const PostponedFormView = ComponentBus.getInstance().getComponent(PostponedFormViewKey); - // 选人表单视图 - const UserSelectFormView = ComponentBus.getInstance().getComponent(UserSelectFormViewKey); - - const version = useSelector((state: FlowReduxState) => state.flow.version); - - // 设置流程编号 - useEffect(() => { - if (props.id) { - flowStateContext.setRecordId(props.id); - } - }, [version]); - - if (FlowFormView) { - return ( - -
- {currentState.result && ( - - )} - - -
- - {PostponedFormView && ( - { - flowStateContext.setPostponedVisible(visible); - }} - onFinish={(timeOut) => { - flowEvenContext.postponedFlow(timeOut, (res) => { - flowStateContext.setResult({ - title: '延期成功', - state: 'success', - closeable: true, - items: [ - { - label: '延期时间', - value: `${timeOut}小时` - } - ] - }) - }); - }} - /> - )} - - {UserSelectFormView && currentState.userSelectMode && ( - { - flowStateContext.setUserSelectVisible(visible); - }} - onFinish={(users) => { - // 选择的人 - flowEvenContext.userSelectCallback(users, currentState.userSelectMode); - }} - multiple={currentState.userSelectMode.multiple} - specifyUserIds={currentState.userSelectMode.specifyUserIds} - currentUserIds={currentState.userSelectMode.currentUserIds} - userSelectType={currentState.userSelectMode.userSelectType} - /> - )} - -
- ) - } else { - return ( - - ) - } -} - -export default FlowPage; diff --git a/mobile-ui/src/components/flow/components/FlowRecord.tsx b/mobile-ui/src/components/flow/components/FlowRecord.tsx deleted file mode 100644 index 4362e1c8..00000000 --- a/mobile-ui/src/components/flow/components/FlowRecord.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, {useContext} from "react"; -import {FlowViewReactContext} from "@/components/flow/view"; - - -const FlowRecord = () => { - const flowViewReactContext = useContext(FlowViewReactContext); - const flowRecordContext = flowViewReactContext?.flowRecordContext; - - const historyRecords = flowRecordContext?.getHistoryRecords(); - - return ( -
-
流程历史记录
- -
- {historyRecords && historyRecords.map((item: any) => { - return ( -
-
{item.title}
-
- ) - })} -
- -
- ) -} - -export default FlowRecord; diff --git a/mobile-ui/src/components/flow/components/FlowResult.tsx b/mobile-ui/src/components/flow/components/FlowResult.tsx deleted file mode 100644 index c0554e52..00000000 --- a/mobile-ui/src/components/flow/components/FlowResult.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, {useContext} from "react"; -import {Button, Result} from "antd-mobile"; -import {useSelector} from "react-redux"; -import {FlowReduxState} from "@/components/flow/store/FlowSlice"; -import {useNavigate} from "react-router"; -import {FlowViewReactContext} from "@/components/flow/view"; - - -const FlowResult = () => { - - const result = useSelector((state: FlowReduxState) => state.flow.result); - const navigate = useNavigate(); - - const flowViewReactContext = useContext(FlowViewReactContext); - - return ( - - {result && result.items && result.items.map((item) => { - return ( -
-
{item.label}:
-
{item.value}
-
- ) - })} - -
- -
-
- )} - /> - ) -} - -export default FlowResult; diff --git a/mobile-ui/src/components/flow/domain/FlowButtonClickContext.ts b/mobile-ui/src/components/flow/domain/FlowButtonClickContext.ts deleted file mode 100644 index 4a0f8a4d..00000000 --- a/mobile-ui/src/components/flow/domain/FlowButtonClickContext.ts +++ /dev/null @@ -1,316 +0,0 @@ -import {FlowButton} from "@/components/flow/types"; -import {Toast} from "antd-mobile"; -import {FlowSubmitResultParser, FlowTrySubmitResultParser} from "@/components/flow/domain/FlowResultParser"; -import {FlowEventContext} from "@/components/flow/domain/FlowEventContext"; -import {FlowStateContext} from "./FlowStateContext"; - -// 流程按钮事件点击触发器 -export class FlowButtonClickContext { - - private readonly flowEventContext: FlowEventContext; - private readonly flowStateContext: FlowStateContext; - - constructor(flowEventContext: FlowEventContext, - flowStateContext: FlowStateContext) { - this.flowEventContext = flowEventContext; - this.flowStateContext = flowStateContext; - } - - - /** - * 触发刷新动作 - */ - handlerReload(){ - this.flowEventContext?.reloadFlow(); - } - - /** - * 触发保存动作 - */ - handlerSave(){ - if (this.flowStateContext?.hasRecordId()) { - this.flowEventContext?.saveFlow(() => { - Toast.show('流程保存成功'); - }) - } else { - this.flowEventContext?.startFlow(() => { - this.flowEventContext?.saveFlow(() => { - Toast.show('流程保存成功'); - }) - }); - } - } - - /** - * 触发发起动作 - */ - handlerStart(){ - if (this.flowStateContext?.hasRecordId()) { - Toast.show('流程已发起,无需重复发起'); - } else { - this.flowEventContext?.startFlow((res) => { - Toast.show('流程发起成功.'); - }) - } - } - - /** - * 指定人提交流程 - * @param approvalState 审批状态 同意为true 驳回为false - * @param operatorIds 流程操作人员id - */ - handlerSpecifyOperatorSubmit(approvalState:boolean,operatorIds:number[]){ - this.flowEventContext?.submitFlow(true, (res) => { - const flowSubmitResultParser = new FlowSubmitResultParser(res.data); - this.flowStateContext.setResult(flowSubmitResultParser.parser()); - }, operatorIds); - } - - /** - * 触发指定人提交动作 - */ - handlerSpecifySubmit(){ - const trySpecifySubmitHandler = ()=>{ - this.flowEventContext?.trySubmitFlow((res) => { - const operators = res.data.operators; - const userIds = operators.map((item: any) => { - return item.userId; - }); - this.flowStateContext?.setUserSelectMode({ - userSelectType: 'nextNodeUser', - multiple: true, - specifyUserIds: userIds, - }); - }); - } - if (this.flowStateContext?.hasRecordId()) { - trySpecifySubmitHandler(); - }else { - this.flowEventContext?.startFlow(() => { - trySpecifySubmitHandler(); - }); - } - } - - /** - * 触发提交动作 - */ - handlerSubmit(){ - const submitHandler = ()=>{ - this.flowEventContext?.submitFlow(true, (res) => { - const flowSubmitResultParser = new FlowSubmitResultParser(res.data); - this.flowStateContext?.setResult(flowSubmitResultParser.parser()); - }) - } - if (this.flowStateContext?.hasRecordId()) { - submitHandler(); - } else { - this.flowEventContext?.startFlow(() => { - submitHandler(); - }); - } - } - - /** - * 触发驳回动作 - */ - handlerReject(){ - if (this.flowStateContext?.hasRecordId()) { - this.flowEventContext?.submitFlow(false, (res) => { - const flowSubmitResultParser = new FlowSubmitResultParser(res.data); - this.flowStateContext?.setResult(flowSubmitResultParser.parser()); - }) - } else { - Toast.show('流程尚未发起,无法操作'); - } - } - - /** - * 触发尝试提交动作 - */ - handlerTrySubmit(){ - const trySubmitHandler = ()=>{ - this.flowEventContext?.trySubmitFlow((res) => { - const flowTrySubmitResultParser = new FlowTrySubmitResultParser(res.data); - this.flowStateContext?.setResult(flowTrySubmitResultParser.parser()); - }); - } - if (this.flowStateContext?.hasRecordId()) { - trySubmitHandler(); - }else { - this.flowEventContext?.startFlow(() => { - trySubmitHandler(); - }); - } - } - - /** - * 触发撤回动作 - */ - handlerRecall(){ - this.flowEventContext?.recallFlow(() => { - this.flowStateContext?.setResult({ - state: 'success', - closeable: true, - title: '流程撤回成功', - }); - }); - } - - /** - * 触发删除动作 - */ - handlerRemove(){ - if (this.flowStateContext?.hasRecordId()) { - this.flowEventContext?.removeFlow(() => { - this.flowStateContext?.setResult({ - state: 'success', - closeable: true, - title: '流程删除成功', - }); - }); - } else { - Toast.show('流程尚未发起,无法删除'); - } - } - - /** - * 触发催办动作 - */ - handlerUrge(){ - this.flowEventContext?.urgeFlow(() => { - this.flowStateContext?.setResult({ - state: 'success', - closeable: true, - title: '催办提醒已发送', - }); - }); - } - - /** - * 触发延期动作 - */ - handlerPostponed(){ - if (this.flowStateContext?.hasRecordId()) { - this.flowStateContext?.setPostponedVisible(true); - }else { - Toast.show('流程尚未发起,无法操作'); - } - } - - /** - * 触发转办动作 - */ - handlerTransfer(){ - if (this.flowStateContext?.hasRecordId()) { - this.flowStateContext?.setUserSelectMode({ - userSelectType: 'transfer', - multiple: false, - }); - }else { - Toast.show('流程尚未发起,无法操作'); - } - } - - /** - * 触发自定义接口动作 - * @param buttonId 自定义按钮的id - */ - handlerCustom(buttonId:string){ - const customHandler = ()=>{ - this.flowEventContext?.customFlow(buttonId, (res) => { - const customMessage = res.data; - this.flowStateContext?.setResult({ - state: customMessage.resultState.toLowerCase(), - ...customMessage - }); - }); - } - if (this.flowStateContext?.hasRecordId()) { - customHandler(); - } else { - this.flowEventContext?.startFlow((res) => { - customHandler(); - }); - } - } - - /** - * 触发自定义前端动作 - * @param eventKey 自定义按钮的事件key - */ - handlerView(eventKey:string){ - const viewHandler = ()=>{ - this.flowEventContext?.triggerEvent(eventKey); - } - - if (this.flowStateContext?.hasRecordId()) { - viewHandler(); - }else { - this.flowEventContext?.startFlow((res) => { - viewHandler(); - }); - } - } - - /** - * 处理按钮点击事件 - * @param button - */ - handlerClick(button: FlowButton) { - if (button.type === "RELOAD") { - this.handlerReload(); - } - - if (button.type === 'SAVE') { - this.handlerSave(); - } - - if (button.type === "START") { - this.handlerStart(); - } - if (button.type === 'SPECIFY_SUBMIT') { - this.handlerSpecifySubmit(); - } - - if (button.type === 'SUBMIT') { - this.handlerSubmit(); - } - - if (button.type === 'REJECT') { - this.handlerReject(); - } - - if (button.type === 'TRY_SUBMIT') { - this.handlerTrySubmit(); - } - - if (button.type === 'RECALL') { - this.handlerRecall(); - } - - if (button.type === 'REMOVE') { - this.handlerRemove(); - } - - if (button.type === 'URGE') { - this.handlerUrge() - } - - if (button.type === 'POSTPONED') { - this.handlerPostponed(); - } - - if (button.type === 'TRANSFER') { - this.handlerTransfer(); - } - - if (button.type === "CUSTOM") { - this.handlerCustom(button.id); - } - - if (button.type === 'VIEW') { - this.handlerView(button.eventKey); - } - } -} diff --git a/mobile-ui/src/components/flow/domain/FlowEventContext.ts b/mobile-ui/src/components/flow/domain/FlowEventContext.ts deleted file mode 100644 index b05a3d80..00000000 --- a/mobile-ui/src/components/flow/domain/FlowEventContext.ts +++ /dev/null @@ -1,348 +0,0 @@ -import {FlowRecordContext} from "@/components/flow/domain/FlowRecordContext"; -import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; -import * as flowApi from "@/api/flow"; -import {FlowUser} from "@/components/flow/types"; -import {FlowSubmitResultParser} from "@/components/flow/domain/FlowResultParser"; -import {UserSelectMode} from "@/components/flow/store/FlowSlice"; -import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; -import FormInstance from "@/components/form/domain/FormInstance"; - -/** - * 流程的事件控制上下文对象 - */ -export class FlowEventContext { - - private readonly flowRecordContext: FlowRecordContext; - private readonly flowTriggerContext: FlowTriggerContext; - private readonly flowInstance: FormInstance; - private readonly opinionInstance: FormInstance; - private readonly flowStateContext: FlowStateContext; - - constructor(flowViewContext: FlowRecordContext, - flowTriggerContext:FlowTriggerContext, - flowInstance: FormInstance, - opinionInstance: FormInstance, - flowStateContext: FlowStateContext) { - this.flowRecordContext = flowViewContext; - this.flowTriggerContext = flowTriggerContext; - this.flowInstance = flowInstance; - this.opinionInstance = opinionInstance; - this.flowStateContext = flowStateContext; - } - - private getRequestBody = () => { - const formData = this.flowInstance.getFieldsValue(); - const flowData = this.flowRecordContext.getFlowFormParams(); - const workCode = this.flowRecordContext.getWorkCode(); - const recordId = this.flowStateContext.getRecordId(); - const advice = this.opinionInstance.getFieldsValue(); - - return { - recordId, - workCode, - ...advice, - formData: { - ...flowData, - ...formData, - } - } - } - - - private validateForm = async () => { - const formState = await this.flowInstance.validate(); - const opinionState = await this.opinionInstance.validate(); - return formState && opinionState; - } - - /** - * 发起流程 - * @param callback 回调函数 - */ - startFlow = (callback?: (res: any) => void) => { - const body = this.getRequestBody(); - this.flowStateContext.setRequestLoading(true); - flowApi.startFlow(body) - .then(res => { - if (res.success) { - const newRecordId = res.data.records[0].id; - this.flowStateContext.setRecordId(newRecordId); - - if (callback) { - callback(res); - } - } - }) - .finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - - - /** - * 提交流程 - * @param approvalState 是否审批通过 - * @param callback 回调函数 - * @param operatorIds 指定审批人 - */ - submitFlow = (approvalState: boolean, callback?: (res: any) => void, operatorIds?: number[]) => { - this.validateForm().then((validateState) => { - if (validateState) { - const body = { - ...this.getRequestBody(), - success: approvalState, - operatorIds: operatorIds, - } - this.flowStateContext.setRequestLoading(true); - flowApi.submitFlow(body) - .then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }) - .finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - }) - } - - /** - * 删除流程 - * @param callback 回调函数 - */ - removeFlow = (callback?: (res: any) => void) => { - this.flowStateContext.setRequestLoading(true); - const body = { - recordId: this.flowStateContext.getRecordId() - }; - flowApi.removeFlow(body).then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }).finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - - /** - * 保存流程 - * @param callback 回调函数 - */ - saveFlow = (callback?: (res: any) => void) => { - this.flowStateContext.setRequestLoading(true); - const body = this.getRequestBody(); - flowApi.saveFlow(body).then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }).finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - - /** - * 延期流程 - * @param timeOut 延期时间 - * @param callback 回调函数 - */ - postponedFlow(timeOut: number, callback?: (res: any) => void) { - this.flowStateContext.setRequestLoading(true); - const body = { - recordId: this.flowStateContext.getRecordId(), - timeOut - }; - flowApi.postponed(body).then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }).finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - - /** - * 自定义流程 - * @param buttonId 自定义按钮Id - * @param callback 回调函数 - */ - customFlow(buttonId: string, callback?: (res: any) => void) { - this.validateForm().then((validateState) => { - console.log('validateState', validateState); - if (validateState) { - const body = { - ...this.getRequestBody(), - buttonId: buttonId, - } - this.flowStateContext.setRequestLoading(true); - flowApi.custom(body) - .then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }) - .finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - }) - } - - /** - * 转办流程 - * @param user 转办用户 - * @param callback 回调函数 - */ - transferFlow(user: FlowUser, callback?: (res: any) => void) { - this.validateForm().then((validateState) => { - if (validateState) { - const body = { - ...this.getRequestBody(), - targetUserId: user.id - } - this.flowStateContext.setRequestLoading(true); - flowApi.transfer(body) - .then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }) - .finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - }) - } - - /** - * 催办流程 - * @param callback - */ - urgeFlow(callback?: (res: any) => void) { - this.flowStateContext.setRequestLoading(true); - const body = { - recordId: this.flowStateContext.getRecordId() - }; - flowApi.urge(body).then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }).finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - - /** - * 撤回流程 - */ - recallFlow(callback?: (res: any) => void) { - this.flowStateContext.setRequestLoading(true); - const body = { - recordId: this.flowStateContext.getRecordId() - }; - flowApi.recall(body).then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }).finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - - /** - * 预提交流程 - * @param callback - */ - trySubmitFlow(callback?: (res: any) => void) { - this.validateForm().then((validateState) => { - if (validateState) { - const body = { - ...this.getRequestBody(), - success: true, - } - this.flowStateContext.setRequestLoading(true); - flowApi.trySubmitFlow(body) - .then(res => { - if (res.success) { - if (callback) { - callback(res); - } - } - }) - .finally(() => { - this.flowStateContext.setRequestLoading(false); - }) - } - }) - } - - - /** - * 重新加载流程数据 - */ - reloadFlow() { - this.flowStateContext.randomVersion(); - } - - - /** - * 触发流程事件 - */ - triggerEvent(eventKey:string){ - this.flowTriggerContext.triggerEvent(eventKey); - } - - /** - * 用户选择回调 - * @param users 选择用户 - * @param userSelectMode 用户选择模式 - */ - userSelectCallback(users: FlowUser[], userSelectMode: UserSelectMode | null) { - if (users.length > 0) { - if (userSelectMode) { - if (userSelectMode.userSelectType === 'transfer') { - const targetUser = users[0]; - this.transferFlow(targetUser, (res) => { - const message = `已经成功转办给${targetUser.name}`; - this.flowStateContext.setResult({ - state: 'success', - closeable: true, - title: message, - }); - }); - } - if (userSelectMode.userSelectType === 'nextNodeUser') { - const userIds = users.map((item: any) => { - return item.id; - }); - this.submitFlow(true, (res) => { - const flowSubmitResultParser = new FlowSubmitResultParser(res.data); - this.flowStateContext.setResult(flowSubmitResultParser.parser()); - }, userIds); - } - } - } - - this.flowStateContext.setUserSelectVisible(false); - } - -} - - - diff --git a/mobile-ui/src/components/flow/domain/FlowRecordContext.ts b/mobile-ui/src/components/flow/domain/FlowRecordContext.ts deleted file mode 100644 index 69fa1339..00000000 --- a/mobile-ui/src/components/flow/domain/FlowRecordContext.ts +++ /dev/null @@ -1,211 +0,0 @@ -import {FlowButton, FlowFormView, FlowViewProps} from "@/components/flow/types"; - -/** - * 流程的详情控制上下文对象 - */ -export class FlowRecordContext { - - private readonly props: FlowViewProps; - private readonly data: any; - - constructor(props: FlowViewProps, data?: any) { - this.props = props; - this.data = data; - } - - // 获取流程的工作编码 - getWorkCode() { - return this.data.flowWork.code; - } - - // 获取流程的Form视图 - getFlowFormView() { - const view = this.props.view; - if (typeof view === 'object') { - const nodeView = this.data.flowNode.view; - return (view as FlowFormView)[nodeView]; - } - return view; - } - - - // 获取节点的数据 - getNode = (code: string) => { - if (this.data) { - const nodes = this.data.flowWork.nodes; - for (const node of nodes) { - if (node.code === code) { - return node; - } - } - } - return null; - } - - // 获取当前的节点信息 - getCurrentNode=()=>{ - const currentNodeCode = this.getNodeCode(); - return this.getNode(currentNodeCode); - } - - // 获取当前节点的表单数据 (内部使用) - private getNodeState = (code: string) => { - const historyRecords = this.data.historyRecords || []; - if (code==='over' && this.isFinished()) { - return "done"; - } - for (const record of historyRecords) { - if (record.nodeCode === code) { - if (record.flowType === 'TODO') { - return "current"; - } - return "done"; - } - } - if(this.isFinished()){ - return "undone"; - } - return "wait"; - } - - - // 获取历史记录 - getHistoryRecords = () => { - return this.data.historyRecords; - } - - // 获取当前的详情的记录数据 - getCurrentFlowRecord = () => { - return this.data.flowRecord; - } - - // 获取当前节点的流程图 - getFlowSchema = () => { - if (this.data.flowWork.schema) { - const schema = JSON.parse(this.data.flowWork.schema); - - for (const node of schema.nodes) { - node.properties.settingVisible = false; - node.properties.state = this.getNodeState(node.properties.code); - } - return schema; - } - return null; - } - - // 获取审批意见 - getOpinionAdvice = () => { - if(this.data.flowRecord){ - if(this.data.flowRecord.opinion){ - return this.data.flowRecord.opinion.advice; - } - } - return null; - } - - // 获取历史审批意见 - getHistoryOpinions() { - if(this.data.opinions){ - return this.data.opinions.filter((item:any)=>{ - if(!item.opinion){ - return false; - } - return item.opinion.result!==0; - }); - } - return []; - } - - //获取流程的form数据 - getFlowFormParams() { - return { - ...this.data.bindData, - ...this.props.formParams - } - } - - // 获取流程的操作按钮 - getFlowButtons() { - const buttons = this.data.flowNode.buttons as FlowButton[] || []; - return buttons.sort((item1: any, item2: any) => { - return item1.order - item2.order; - }) - } - - - // 是否可以审批 - isApproval = () => { - return this.data.canHandle; - } - - // 是否是开始节点 - isStartNode = ()=>{ - if (this.data) { - return this.data.flowNode.startNode; - } - return false; - } - - // 获取当前节点的code - getNodeCode = () => { - if (this.data) { - return this.data.flowNode.code; - } - return null; - } - - // 获取当前节点是否可编辑 - isEditable = () => { - if(this.isFinished()){ - return false; - } - if(this.isDone()){ - return false; - } - return this.getFlowNodeEditable() - } - - // 获取当前节点是否可编辑 - getFlowNodeEditable = () => { - if (this.data) { - const node = this.data.flowNode; - if (node) { - return node.editable; - } - } - return false - } - - // 是否已审批 - isDone() { - if (this.data.flowRecord) { - return this.data.flowRecord.flowStatus === 'FINISH' || this.data.flowRecord.flowType === 'DONE'; - } - return false; - } - - - // 是否完成 - isFinished() { - if (this.data.flowRecord) { - return this.data.flowRecord.flowStatus === 'FINISH'; - } - return false; - } - - - // 是否是退回状态 - isReject(){ - const historyRecords = this.data.historyRecords || []; - const currentRecord = this.data.flowRecord; - if(currentRecord && historyRecords.length>0){ - const preId = currentRecord.preId; - const preRecord = historyRecords.find((item:any)=>item.id===preId); - if(preRecord){ - return preRecord.flowSourceDirection === 'REJECT'; - } - } - return false; - } - -} diff --git a/mobile-ui/src/components/flow/domain/FlowResultParser.ts b/mobile-ui/src/components/flow/domain/FlowResultParser.ts deleted file mode 100644 index 686e7c67..00000000 --- a/mobile-ui/src/components/flow/domain/FlowResultParser.ts +++ /dev/null @@ -1,92 +0,0 @@ -import {FlowResultMessage} from "@/components/flow/types"; - -export class FlowWorkData { - - protected readonly data: any; - - constructor(data: any) { - this.data = data; - } - - getNode = (code: string) => { - if (this.data) { - const nodes = this.data.flowWork.nodes; - for (const node of nodes) { - if (node.code === code) { - return node; - } - } - } - return null; - } - - getWorkCode() { - return this.data.flowWork.code; - } -} - - -export class FlowSubmitResultParser extends FlowWorkData { - - constructor(data: any) { - super(data); - } - - parser() { - const records = this.data.records; - const resultItems = []; - for (const record of records) { - const node = this.getNode(record.nodeCode); - resultItems.push({ - label: '下级审批节点', - value: node.name - }); - - resultItems.push({ - label: '下级审批人', - value: record.currentOperator.name - }); - } - return { - title: '流程审批完成', - closeable: true, - state: 'success', - items: resultItems - } as FlowResultMessage; - } -} - - -export class FlowTrySubmitResultParser { - - private readonly data: any; - - constructor(data: any) { - this.data = data; - } - - parser() { - const operators = this.data.operators; - const usernames = operators.map((item: any) => { - return item.name; - }); - const flowResult = { - title: '下级节点提示', - closeable: false, - state: 'success', - items: [ - { - label: '下级审批节点', - value: this.data.flowNode.name - }, - { - label: '下级审批人', - value: usernames.join(',') - } - ] - } - - return flowResult as FlowResultMessage; - } - -} diff --git a/mobile-ui/src/components/flow/domain/FlowStateContext.tsx b/mobile-ui/src/components/flow/domain/FlowStateContext.tsx deleted file mode 100644 index 5f2368c7..00000000 --- a/mobile-ui/src/components/flow/domain/FlowStateContext.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import {FlowStore, UserSelectMode} from "@/components/flow/store/FlowSlice"; -import {FlowResultMessage} from "@/components/flow/types"; - -/** - * 流程的状态数据上下文对象 - */ -export class FlowStateContext { - - private currentState: FlowStore; - private readonly updateFlowStore: (currentState: any) => any; - - constructor(currentState: FlowStore, updateFlowStore: (state: any) => any) { - this.currentState = JSON.parse(JSON.stringify(currentState)); - this.updateFlowStore = updateFlowStore; - } - - private updateState() { - this.updateFlowStore({ - ...this.currentState - }) - } - - setRequestLoading = (requestLoading: boolean) => { - this.currentState = { - ...this.currentState, - requestLoading - } - this.updateState(); - } - - setRecordId = (recordId: string) => { - this.currentState = { - ...this.currentState, - recordId - } - this.updateState(); - } - - setResult = (result: FlowResultMessage) => { - this.currentState = { - ...this.currentState, - result - } - this.updateState(); - } - - clearResult = () => { - this.currentState = { - ...this.currentState, - result: null - } - this.updateState(); - } - - hasRecordId = () => { - return this.currentState.recordId; - } - - getRecordId = () => { - return this.currentState.recordId; - } - - setPostponedVisible(visible: boolean) { - this.currentState = { - ...this.currentState, - postponedVisible: visible - } - this.updateState(); - } - - setUserSelectVisible(visible: boolean) { - this.currentState = { - ...this.currentState, - userSelectVisible: visible - } - this.updateState(); - } - - setUserSelectMode(userSelectMode: UserSelectMode) { - this.currentState = { - ...this.currentState, - userSelectVisible: true, - userSelectMode: userSelectMode - } - this.updateState(); - } - - randomVersion() { - this.currentState = { - ...this.currentState, - version: Math.random() - } - this.updateState(); - } -} diff --git a/mobile-ui/src/components/flow/domain/FlowTriggerContext.ts b/mobile-ui/src/components/flow/domain/FlowTriggerContext.ts deleted file mode 100644 index 7e673682..00000000 --- a/mobile-ui/src/components/flow/domain/FlowTriggerContext.ts +++ /dev/null @@ -1,32 +0,0 @@ -export interface EventTrigger { - trigger: (eventKey: string) => void -} - -// 流程事件触发器上下文对象 -export class FlowTriggerContext { - - private list: EventTrigger[] = []; - - constructor() { - } - - // 添加触发器 - addTrigger(trigger:(eventKey:string)=>void) { - this.list.push({ - trigger - }); - } - - // 触发事件 - triggerEvent(eventKey: string) { - this.list.forEach(trigger => { - trigger.trigger(eventKey); - }) - } - - clear() { - this.list = []; - } - -} - diff --git a/mobile-ui/src/components/flow/nodes/Circulate/index.scss b/mobile-ui/src/components/flow/nodes/Circulate/index.scss deleted file mode 100644 index 0e6b78fd..00000000 --- a/mobile-ui/src/components/flow/nodes/Circulate/index.scss +++ /dev/null @@ -1,49 +0,0 @@ - -$color: #61aa1f; - -.circulate-node { - display: flex; - align-items: center; - max-height: 40px; - border: 1px solid $color; - border-radius: 12px; - padding: 10px; - position: relative; - - .icon { - margin-left: 10px; - position: absolute; - color: $color; - font-size: 16px; - left: 0; - } - - .code { - margin-left: 20px; - font-size: 14px; - color: #4a5e63; - } - - .title { - margin-left: 5px; - font-size: 14px; - color: black; - } - - - .setting { - margin-right: 10px; - font-size: 14px; - position: absolute; - color: $color; - right: 0; - } - - - .state { - margin-right: 10px; - font-size: 14px; - position: absolute; - right: 0; - } -} diff --git a/mobile-ui/src/components/flow/nodes/Circulate/index.tsx b/mobile-ui/src/components/flow/nodes/Circulate/index.tsx deleted file mode 100644 index ed4e8ed9..00000000 --- a/mobile-ui/src/components/flow/nodes/Circulate/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import {HtmlNode, HtmlNodeModel} from '@logicflow/core'; -import React from "react"; -import ReactDOM from "react-dom/client"; -import "./index.scss"; -import {NodeState} from "@/components/flow/nodes/states"; -import StateLabel from "@/components/flow/nodes/StateLabel"; -import {PlayOutline, SetOutline} from "antd-mobile-icons"; - -type CirculateProperties = { - id: string; - name: string; - code: string; - type: string; - view: string; - operatorMatcher: string; - editable: boolean; - titleGenerator: string; - errTrigger: string; - approvalType: string; - timeout: number; - settingVisible?: boolean; - state?: NodeState; -} - -interface CirculateProps { - name: string; - code?: string; - update?: (values: any) => void; - settingVisible?: boolean; - properties?: CirculateProperties; -} - -export const CirculateView: React.FC = (props) => { - const [visible, setVisible] = React.useState(false); - - const state = props.properties?.state; - - return ( -
- -
- - {props.code && ( - <>({props.code}) - )} - - {props.name} -
- - {props.settingVisible && ( - { - setVisible(true); - }} - /> - )} - - {state && ( -
- -
- )} -
- ); -} - -class CirculateModel extends HtmlNodeModel { - setAttributes() { - const name = this.properties.name as string; - this.width = 200 + name.length * 10; - this.height = 45; - this.text.editable = false; - this.menu = []; - - this.sourceRules = [ - { - message: `不允许输出`, - validate: (sourceNode: any, targetNode: any, sourceAnchor, targetAnchor) => { - const edges = this.graphModel.getNodeOutgoingEdge(sourceNode.id); - if (edges.length >= 1) { - return false; - } else { - return true; - } - }, - }, - ]; - - this.anchorsOffset = [ - [this.width / 2, 0], - [0, this.height / 2], - [-this.width / 2, 0], - [0, -this.height / 2], - ]; - } - - -} - -class CirculateNode extends HtmlNode { - setHtml(rootEl: SVGForeignObjectElement) { - const {properties} = this.props.model as HtmlNodeModel; - const div = document.createElement('div'); - const settingVisible = properties.settingVisible !== false; - ReactDOM.createRoot(div).render( - { - this.props.model.setProperties(values); - }}/>, - ); - //需要清空 - rootEl.innerHTML = ''; - rootEl.appendChild(div); - super.setHtml(rootEl); - } -} - -export default { - type: 'circulate-node', - view: CirculateNode, - model: CirculateModel, -}; - diff --git a/mobile-ui/src/components/flow/nodes/Node/index.scss b/mobile-ui/src/components/flow/nodes/Node/index.scss deleted file mode 100644 index 58b6ecb5..00000000 --- a/mobile-ui/src/components/flow/nodes/Node/index.scss +++ /dev/null @@ -1,49 +0,0 @@ - -$color: #6855ef; - -.node-node { - display: flex; - align-items: center; - max-height: 40px; - border: 1px solid $color; - border-radius: 12px; - padding: 10px; - position: relative; - - .icon { - margin-left: 10px; - position: absolute; - color: $color; - font-size: 16px; - left: 0; - } - - .code { - margin-left: 20px; - font-size: 14px; - color: #4a5e63; - } - - .title { - margin-left: 5px; - font-size: 14px; - color: black; - } - - - .setting { - margin-right: 10px; - font-size: 14px; - position: absolute; - color: $color; - right: 0; - } - - .state { - margin-right: 10px; - font-size: 14px; - position: absolute; - right: 0; - } - -} diff --git a/mobile-ui/src/components/flow/nodes/Node/index.tsx b/mobile-ui/src/components/flow/nodes/Node/index.tsx deleted file mode 100644 index fe45b1fe..00000000 --- a/mobile-ui/src/components/flow/nodes/Node/index.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import {HtmlNode, HtmlNodeModel} from '@logicflow/core'; -import React from "react"; -import ReactDOM from "react-dom/client"; -import "./index.scss"; -import {NodeState} from "@/components/flow/nodes/states"; -import StateLabel from "@/components/flow/nodes/StateLabel"; -import {PlayOutline, SetOutline} from "antd-mobile-icons"; - -type NodeProperties = { - id: string; - name: string; - code: string; - type: string; - view: string; - operatorMatcher: string; - editable: boolean; - titleGenerator: string; - errTrigger: string; - approvalType: string; - timeout: number; - settingVisible?: boolean; - state?: NodeState; -} - -interface NodeProps { - name: string; - code?: string; - update?: (values: any) => void; - settingVisible?: boolean; - properties?: NodeProperties; - state?: NodeState; -} - -export const NodeView: React.FC = (props) => { - const [visible, setVisible] = React.useState(false); - - const state = props.properties?.state; - - return ( -
- -
- - {props.code && ( - <>({props.code}) - )} - - {props.name} -
- - {props.settingVisible && ( - { - setVisible(true); - }} - /> - )} - - {state && ( -
- -
- )} - -
- ); -} - -class NodeModel extends HtmlNodeModel { - setAttributes() { - const name = this.properties.name as string; - this.width = 200 + name.length * 10; - this.height = 45; - this.text.editable = false; - this.menu = []; - - this.anchorsOffset = [ - [this.width / 2, 0], - [0, this.height / 2], - [-this.width / 2, 0], - [0, -this.height / 2], - ]; - } - - -} - -class NodeNode extends HtmlNode { - setHtml(rootEl: SVGForeignObjectElement) { - const {properties} = this.props.model as HtmlNodeModel; - const div = document.createElement('div'); - - const settingVisible = properties.settingVisible !== false; - - ReactDOM.createRoot(div).render( - { - this.props.model.setProperties(values); - }}/>, - ); - //需要清空 - rootEl.innerHTML = ''; - rootEl.appendChild(div); - super.setHtml(rootEl); - } -} - -export default { - type: 'node-node', - view: NodeNode, - model: NodeModel, -}; - diff --git a/mobile-ui/src/components/flow/nodes/Over/index.scss b/mobile-ui/src/components/flow/nodes/Over/index.scss deleted file mode 100644 index 771c830f..00000000 --- a/mobile-ui/src/components/flow/nodes/Over/index.scss +++ /dev/null @@ -1,49 +0,0 @@ - -$color: #ea3f21; - -.over-node { - display: flex; - align-items: center; - max-height: 40px; - border: 1px solid $color; - border-radius: 12px; - padding: 10px; - position: relative; - - .icon { - margin-left: 10px; - position: absolute; - color: $color; - font-size: 16px; - left: 0; - } - - .code { - margin-left: 20px; - font-size: 14px; - color: #4a5e63; - } - - .title { - margin-left: 5px; - font-size: 14px; - color: black; - } - - - .setting { - margin-right: 10px; - font-size: 14px; - position: absolute; - color: $color; - right: 0; - } - - .state { - margin-right: 10px; - font-size: 14px; - position: absolute; - right: 0; - } - -} diff --git a/mobile-ui/src/components/flow/nodes/Over/index.tsx b/mobile-ui/src/components/flow/nodes/Over/index.tsx deleted file mode 100644 index 18158067..00000000 --- a/mobile-ui/src/components/flow/nodes/Over/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import {HtmlNode, HtmlNodeModel} from '@logicflow/core'; -import React from "react"; -import ReactDOM from "react-dom/client"; -import "./index.scss"; -import {NodeState} from "@/components/flow/nodes/states"; -import StateLabel from "@/components/flow/nodes/StateLabel"; -import {PlayOutline, SetOutline} from "antd-mobile-icons"; - -type OverProperties = { - id:string; - name: string; - code: string; - type:string; - view: string; - operatorMatcher:string; - editable:boolean; - titleGenerator:string; - errTrigger:string; - approvalType:string; - timeout:number; - settingVisible?: boolean; - state?: NodeState; -} - -interface OverProps { - name: string; - code?: string; - update?: (values: any) => void; - settingVisible?: boolean; - properties?: OverProperties; -} - -export const OverView: React.FC = (props) => { - const [visible, setVisible] = React.useState(false); - - const state = props.properties?.state; - - return ( -
- -
- - {props.code && ( - <>({props.code}) - )} - - {props.name} -
- {props.settingVisible && ( - { - setVisible(true); - }} - /> - )} - - {state && ( -
- -
- )} - -
- ); -} - -class OverModel extends HtmlNodeModel { - - setAttributes() { - const name = this.properties.name as string; - this.width = 200 + name.length * 10; - this.height = 45; - this.text.editable = false; - this.menu = []; - - this.sourceRules = [ - { - message: `不允许输出`, - validate: (sourceNode, targetNode: any, sourceAnchor, targetAnchor) => { - const edges = this.graphModel.getNodeIncomingEdge(targetNode.id); - if (edges.length >= 0) { - return false; - } else { - return true; - } - }, - }, - ]; - - this.anchorsOffset = [ - // [this.width / 2, 0], - // [0, this.height / 2], - // [-this.width / 2, 0], - [0, -this.height / 2], - ]; - } - - -} - -class OverNode extends HtmlNode { - setHtml(rootEl: SVGForeignObjectElement) { - const {properties} = this.props.model as HtmlNodeModel; - const div = document.createElement('div'); - - const settingVisible = properties.settingVisible !== false; - - ReactDOM.createRoot(div).render( - { - this.props.model.setProperties(values); - }}/>, - ); - //需要清空 - rootEl.innerHTML = ''; - rootEl.appendChild(div); - super.setHtml(rootEl); - } -} - -export default { - type: 'over-node', - view: OverNode, - model: OverModel, -}; - diff --git a/mobile-ui/src/components/flow/nodes/Start/index.scss b/mobile-ui/src/components/flow/nodes/Start/index.scss deleted file mode 100644 index 0b511a9d..00000000 --- a/mobile-ui/src/components/flow/nodes/Start/index.scss +++ /dev/null @@ -1,47 +0,0 @@ -$color: #b1ad30; - -.start-node { - display: flex; - align-items: center; - max-height: 40px; - border: 1px solid $color; - border-radius: 12px; - padding: 10px; - position: relative; - - .icon { - margin-left: 10px; - position: absolute; - color: $color; - font-size: 16px; - left: 0; - } - - .code { - margin-left: 20px; - font-size: 14px; - color: #4a5e63; - } - - .title { - margin-left: 5px; - font-size: 14px; - color: black; - } - - .setting { - margin-right: 10px; - font-size: 14px; - position: absolute; - color: $color; - right: 0; - } - - .state { - margin-right: 10px; - font-size: 14px; - position: absolute; - right: 0; - } - -} diff --git a/mobile-ui/src/components/flow/nodes/Start/index.tsx b/mobile-ui/src/components/flow/nodes/Start/index.tsx deleted file mode 100644 index 084440bd..00000000 --- a/mobile-ui/src/components/flow/nodes/Start/index.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import {HtmlNode, HtmlNodeModel} from '@logicflow/core'; -import React from "react"; -import ReactDOM from "react-dom/client"; -import "./index.scss"; -import {NodeState} from "@/components/flow/nodes/states"; -import StateLabel from "@/components/flow/nodes/StateLabel"; -import {PlayOutline, SetOutline} from "antd-mobile-icons"; - -type StartProperties = { - id: string; - name: string; - code: string; - type: string; - view: string; - operatorMatcher: string; - editable: boolean; - titleGenerator: string; - errTrigger: string; - approvalType: string; - timeout: number; - settingVisible?: boolean; - state?: NodeState; -} - -interface StartProps { - name: string; - code?: string; - settingVisible?: boolean; - update?: (values: any) => void; - properties?: StartProperties; -} - -export const StartView: React.FC = (props) => { - const [visible, setVisible] = React.useState(false); - - const state = props.properties?.state; - - return ( -
- -
- - {props.code && ( - <>({props.code}) - )} - - {props.name} -
- {props.settingVisible && ( - { - setVisible(true); - }} - /> - )} - - {state && ( -
- -
- )} - -
- ); -} - -class StartModel extends HtmlNodeModel { - setAttributes() { - const name = this.properties.name as string; - this.width = 200 + name.length * 10; - this.height = 45; - this.text.editable = false; - this.menu = []; - - this.anchorsOffset = [ - // [this.width / 2, 0], - [0, this.height / 2], - // [-this.width / 2, 0], - // [0, -this.height / 2], - ]; - } - -} - -class StartNode extends HtmlNode { - setHtml(rootEl: SVGForeignObjectElement) { - const {properties} = this.props.model as HtmlNodeModel; - const div = document.createElement('div'); - - const settingVisible = properties.settingVisible !== false; - - ReactDOM.createRoot(div).render( - { - this.props.model.setProperties(values); - }}/>, - ); - //需要清空 - rootEl.innerHTML = ''; - rootEl.appendChild(div); - super.setHtml(rootEl); - } -} - -export default { - type: 'start-node', - view: StartNode, - model: StartModel, -}; - diff --git a/mobile-ui/src/components/flow/nodes/StateLabel.tsx b/mobile-ui/src/components/flow/nodes/StateLabel.tsx deleted file mode 100644 index e2dc1006..00000000 --- a/mobile-ui/src/components/flow/nodes/StateLabel.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; -import {NodeState} from "@/components/flow/nodes/states"; -import {Tag} from "antd-mobile"; - -interface StateLabelProps { - state:NodeState; -} - -const StateLabel:React.FC = (props)=>{ - - const color = (state:NodeState) => { - switch (state) { - case "done": - return "success"; - case "undone": - return "processing"; - case "current": - return "warning"; - default: - return "default"; - } - } - - const label = (state:NodeState) => { - switch (state) { - case "done": - return "已执行"; - case "undone": - return "未执行"; - case "current": - return "当前节点"; - default: - return "待执行"; - } - } - - return ( - - {label(props.state)} - - ) -} - -export default StateLabel diff --git a/mobile-ui/src/components/flow/nodes/states.ts b/mobile-ui/src/components/flow/nodes/states.ts deleted file mode 100644 index b43944d0..00000000 --- a/mobile-ui/src/components/flow/nodes/states.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type NodeState = "done" | "wait" | "undone" | "current"; - diff --git a/mobile-ui/src/components/flow/register.tsx b/mobile-ui/src/components/flow/register.tsx new file mode 100644 index 00000000..88ec70cb --- /dev/null +++ b/mobile-ui/src/components/flow/register.tsx @@ -0,0 +1,66 @@ +import {PostponedFormViewKey} from "@codingapi/ui-framework"; +import {UserSelectFormViewKey} from "@codingapi/ui-framework"; +import {ComponentBus} from "@codingapi/ui-framework"; +import PostponedFormView from "@/components/flow/PostponedFormView"; +import UserSelectFormView from "@/components/flow/UserSelectFormView"; +import {FlowApi, FlowApiContent} from "@codingapi/flow-mobile"; +import * as flowApi from "@/api/flow"; + + +ComponentBus.getInstance().registerComponent(PostponedFormViewKey,PostponedFormView); +ComponentBus.getInstance().registerComponent(UserSelectFormViewKey,UserSelectFormView); + + +class DefaultFlowApiImpl implements FlowApi{ + + startFlow(body: any): Promise { + return flowApi.startFlow(body); + } + + submitFlow(body: any): Promise { + return flowApi.submitFlow(body); + } + + removeFlow(body: any): Promise { + return flowApi.removeFlow(body); + } + + saveFlow(body: any): Promise { + return flowApi.saveFlow(body); + } + + postponedFlow(body: any): Promise { + return flowApi.postponed(body); + } + + triggerCustomFlow(body: any): Promise { + return flowApi.custom(body); + } + + transferFlow(body: any): Promise { + return flowApi.transfer(body); + } + + urgeFlow(body: any): Promise { + return flowApi.urge(body); + } + + recallFlow(body: any): Promise { + return flowApi.recall(body); + } + + trySubmitFlow(body: any): Promise { + return flowApi.trySubmitFlow(body); + } + + getDetailById(id: string): Promise { + return flowApi.detail(id,null); + } + + getDetailByWorkCode(workCode: string): Promise { + return flowApi.detail(null,workCode); + } + +} + +FlowApiContent.getInstance().registerFlowApi(new DefaultFlowApiImpl()); diff --git a/mobile-ui/src/components/flow/register/index.tsx b/mobile-ui/src/components/flow/register/index.tsx deleted file mode 100644 index c629f6bd..00000000 --- a/mobile-ui/src/components/flow/register/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import ComponentBus from "@/framework/ComponentBus"; -import {PostponedFormViewKey, UserSelectFormViewKey} from "@/components/flow/types"; -import PostponedFormView from "@/components/flow/plugins/PostponedFormView"; -import UserSelectFormView from "@/components/flow/plugins/UserSelectFormView"; - -ComponentBus.getInstance().registerComponent(PostponedFormViewKey, PostponedFormView); -ComponentBus.getInstance().registerComponent(UserSelectFormViewKey, UserSelectFormView); - diff --git a/mobile-ui/src/components/flow/store/FlowSlice.ts b/mobile-ui/src/components/flow/store/FlowSlice.ts deleted file mode 100644 index ab1f5bdd..00000000 --- a/mobile-ui/src/components/flow/store/FlowSlice.ts +++ /dev/null @@ -1,124 +0,0 @@ -import {configureStore, createSlice, PayloadAction} from '@reduxjs/toolkit'; -import {FlowResultMessage, UserSelectFormType} from "@/components/flow/types"; - -export interface FlowStore { - // 流程记录ID - recordId: string; - // 请求加载中 - requestLoading: boolean; - // 流程结果 - result: FlowResultMessage | null; - // 隐藏page的内容 - contentHiddenVisible: boolean; - // 版本号 - version: number; - // 数据版本号 - dataVersion: number; - - // 意见框展示状态 - opinionVisible: boolean; - - // 延期时间窗口状态 - postponedVisible: boolean; - // 选人窗口状态 - userSelectVisible: boolean; - // 选人状态 - userSelectMode: UserSelectMode | null; -} - -export interface UserSelectMode { - // 指定人员范围 - specifyUserIds?: number[]; - // 当前选择的人员 - currentUserIds?: number[]; - // 选人类型 - userSelectType: UserSelectFormType; - // 是否多选 - multiple: boolean; -} - -export type FlowStoreAction = { - updateState: (state: FlowStore, action: PayloadAction) => void; - initState: (state: FlowStore) => void; -} - -export const flowSlice = createSlice({ - name: 'flow', - initialState: { - recordId: '', - requestLoading: false, - result: null, - contentHiddenVisible: false, - postponedVisible: false, - opinionVisible: true, - userSelectVisible: false, - userSelectMode: null, - version: 0, - dataVersion: 0, - }, - reducers: { - updateState: (state, action) => { - const currentState = action.payload; - const keys = Object.keys(currentState); - if (keys.includes('requestLoading')) { - state.requestLoading = currentState.requestLoading; - } - if (keys.includes('recordId')) { - state.recordId = action.payload.recordId; - } - if (keys.includes('result')) { - state.result = action.payload.result; - state.contentHiddenVisible = state.result != null; - } - if (keys.includes('postponedVisible')) { - state.postponedVisible = action.payload.postponedVisible; - } - - if (keys.includes('userSelectMode')) { - state.userSelectVisible = true; - state.userSelectMode = action.payload.userSelectMode; - } - - if (keys.includes('version')) { - state.version = action.payload.version; - } - - if (keys.includes('dataVersion')) { - state.dataVersion = action.payload.dataVersion; - } - - if (keys.includes('userSelectVisible')) { - state.userSelectVisible = action.payload.userSelectVisible; - if (!state.userSelectVisible) { - state.userSelectMode = null; - } - } - }, - initState: (state) => { - state.recordId = ''; - state.requestLoading = false; - state.result = null; - state.postponedVisible = false; - state.opinionVisible = true; - state.contentHiddenVisible = false; - state.userSelectVisible = false; - state.userSelectMode = null; - state.version = 0; - state.dataVersion = 0; - } - }, -}); - - -export const { - updateState, - initState -} = flowSlice.actions; -export const flowStore = configureStore({ - reducer: { - flow: flowSlice.reducer - }, -}); - -export type FlowReduxState = ReturnType; - diff --git a/mobile-ui/src/components/flow/types.ts b/mobile-ui/src/components/flow/types.ts deleted file mode 100644 index 64d066a9..00000000 --- a/mobile-ui/src/components/flow/types.ts +++ /dev/null @@ -1,129 +0,0 @@ -import React from "react"; -import FormInstance from "@/components/form/domain/FormInstance"; - - -// 流程图中线的类型 -export type EdgeType = 'line' | 'polyline' | 'bezier'; - -// 延期表单视图Key -export const PostponedFormViewKey = 'PostponedFormView'; -// 选人表单视图Key -export const UserSelectFormViewKey = 'UserSelectFormViewKey'; - -// 自定义按钮类型 -export type ButtonType = - 'RELOAD' - | 'SAVE' - | 'START' - | 'SUBMIT' - | 'TRY_SUBMIT' - | 'SPECIFY_SUBMIT' - | 'REJECT' - | 'TRANSFER' - | 'RECALL' - | 'POSTPONED' - | 'URGE' - | 'CUSTOM' - | 'VIEW' - | 'REMOVE'; - -// 流程自定义按钮 -export interface FlowButton { - id: string; - eventKey: string; - groovy: string; - name: string; - order: number; - style: any; - type: ButtonType; -} - -// 延期表单 【拓展视图】 -export interface PostponedFormProps { - visible: boolean; - setVisible: (visible: boolean) => void; - onFinish: (timeout: number) => void; -} - -// 选人表单 【拓展视图】 - -export type UserSelectFormType = -// 选择下级流程节点的人员,约定人员id范围 - 'nextNodeUser' - // 选择转办人员,约定本单位下的人员 - | 'transfer' - // 选择所有人员,在流程配置上使用 - | 'users'; - -export interface UserSelectFormProps { - visible: boolean; - setVisible: (visible: boolean) => void; - onFinish: (values: FlowUser[]) => void; - // 选择模式 - multiple: boolean; - // 指定人员范围 - specifyUserIds?: number[]; - // 当前选择的人员 - currentUserIds?: number[]; - // 选人方式 - userSelectType: UserSelectFormType; -} - - -// 结果展示数据项目 -export interface FlowResultItem { - label: string, - value: string -} - -type FlowResultMessageState = 'success'|'info'|'warning'; - -// 结果展示数据信息 -export interface FlowResultMessage { - title: string, - closeable: boolean, - state: FlowResultMessageState, - items?: FlowResultItem[] -} - -// 流程用户 -export interface FlowUser { - id: string; - name: string; - - [key: string]: any; -} - -// 表单参数 -export interface FlowFormParams { - clazzName: string; - - [key: string]: any; -} - -// 流程表单数据 -export interface FlowFormViewProps { - // 表单数据 - data: FlowFormParams; - // 表单控制对象 - form: FormInstance; - // 数据版本 - dataVersion?: number; -} - -// 表单视图 -export interface FlowFormView { - [key: string]: React.ComponentType; -} - -export interface FlowViewProps { - // 流程编号 - id?: string; - // 流程的设计编号 - workCode?: string; - // 流程的视图数据 - view: React.ComponentType | FlowFormView; - // 表单参数,参数仅当在发起节点时才会传递 - formParams?: FlowFormParams; -} - diff --git a/mobile-ui/src/components/flow/view/index.scss b/mobile-ui/src/components/flow/view/index.scss deleted file mode 100644 index 4c104da0..00000000 --- a/mobile-ui/src/components/flow/view/index.scss +++ /dev/null @@ -1,161 +0,0 @@ -@use "@/config/variables" as *; - -$flow-footer-height: 80px; - -.flow-skeleton-header { - width: 100%; - height: 40px; -} - -.flow-skeleton-body { - height: 60vh; - width: 100%; -} - -.flow-view { - background-color: white !important; - width: 100%; -} - -.flow-view-content { - height: calc(100vh - $page-header-height - $flow-footer-height); - overflow: auto; -} - -.flow-view-footer { - height: $flow-footer-height; - box-shadow: inset 0 1px 0 $body-background-color; - display: flex; - align-items: center; - justify-content: center; - - .flow-view-footer-button { - margin: 10px; - font-size: 14px; - padding: 8px 8px; - width: 100%; - border-color: $theme-primary-color; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -} - -.flow-result-content { - padding: 10px; - - .flow-result-content-item { - display: flex; - font-size: $content-font-size; - margin-top: 10px; - - .flow-result-content-item-label { - flex: 1; - text-align: right; - margin: 5px; - } - - .flow-result-content-item-value { - flex: 1; - text-align: left; - margin: 5px; - } - } - - .flow-result-content-footer { - display: flex; - align-items: center; - justify-content: center; - margin-top: 20px; - - .flow-result-content-button { - margin: 10px; - font-size: $content-font-size; - padding: 8px 8px; - width: 40%; - border-color: $theme-primary-color; - } - } -} - -.flow-history-opinion-item { - padding: 5px; - font-size: $content-font-size; - border-bottom: 1px solid $body-background-color; - - .flow-history-opinion-item-row { - margin-top: 10px; - - span:first-child { - font-weight: bold; - } - } -} - -.flow-history { - .flow-basic { - - } - - .flow-history-list { - margin-top: 10px; - - .flow-history-list-item { - padding: 5px; - display: flex; - flex-direction: row; - margin-left: 10px; - - .flow-history-list-item-line { - width: 30px; - height: 30px; - background-color: $theme-primary-color; - border-radius: 50%; - position: relative; - } - - .flow-history-list-item-dot { - content: ''; - position: absolute; - top: 33px; - left: 15px; - width: 1px; - height: calc(100% + 75px); - background-color: $theme-primary-color; - z-index: 0; - } - - .flow-history-list-item-content { - display: flex; - flex-direction: column; - margin-left: 10px; - padding: 5px; - - .flow-history-list-item-title { - font-size: $title-font-size; - font-weight: bold; - } - - .flow-history-list-item-attr { - margin-top: 5px; - font-size: $content-font-size; - } - } - } - } -} - -.flow-chart { - transition: 0.3s; - border-radius: 12px; - - .flow-chart-content { - width: 0; - height: 0; - } - - .flow-img { - width: 100%; - height: 100%; - } -} diff --git a/mobile-ui/src/components/flow/view/index.tsx b/mobile-ui/src/components/flow/view/index.tsx deleted file mode 100644 index 2cdd5cf5..00000000 --- a/mobile-ui/src/components/flow/view/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, {createContext, useEffect} from "react"; -import {Provider, useDispatch, useSelector} from "react-redux"; -import {FlowReduxState, flowStore, initState, updateState} from "@/components/flow/store/FlowSlice"; -import {FlowViewProps} from "@/components/flow/types"; -import {Skeleton} from "antd-mobile"; -import {FlowRecordContext} from "@/components/flow/domain/FlowRecordContext"; -import {FlowEventContext} from "@/components/flow/domain/FlowEventContext"; -import {detail} from "@/api/flow"; -import {FlowStateContext} from "@/components/flow/domain/FlowStateContext"; -import FlowPage from "@/components/flow/components/FlowPage"; -import {FlowTriggerContext} from "@/components/flow/domain/FlowTriggerContext"; -import {FlowButtonClickContext} from "@/components/flow/domain/FlowButtonClickContext"; -import "./index.scss"; -import FormInstance from "@/components/form/domain/FormInstance"; - -// 流程视图上下文属性 -interface FlowViewReactContextProps { - // 流程的详情控制上下文对象 - flowRecordContext: FlowRecordContext; - // 流程的事件控制上下文对象 - flowEventContext: FlowEventContext; - // 流程的状态数据上下文对象 - flowStateContext: FlowStateContext; - // 流程事件触发控制上下文对象 - flowTriggerContext: FlowTriggerContext; - // 流程按钮点击触发控制器上下文对象 - flowButtonClickContext: FlowButtonClickContext; - // 表单操作对象 - formInstance: FormInstance; - // 审批意见操作对象 - opinionInstance: FormInstance; -} - -export const FlowViewReactContext = createContext(null); - -const $FlowView: React.FC = (props) => { - const [data, setData] = React.useState(null); - - const dispatch = useDispatch(); - - const version = useSelector((state: FlowReduxState) => state.flow.version); - - // 请求流程详情数据 - const loadFlowDetail = () => { - if (props.id) { - detail(props.id, null).then(res => { - if (res.success) { - setData(res.data); - } - }); - return; - } - if (props.workCode) { - detail(null, props.workCode).then(res => { - if (res.success) { - setData(res.data); - } - }); - return; - } - } - - useEffect(() => { - if(data){ - const dataVersion = Math.random(); - dispatch(updateState({dataVersion:dataVersion})); - } - }, [data]); - - useEffect(() => { - return ()=>{ - dispatch(initState()); - } - }, []); - - useEffect(() => { - loadFlowDetail(); - }, [version]); - - - return ( - <> - {!data && ( - <> - - - - )} - {data && ( - - )} - - ) -} - -const FlowView: React.FC = (props) => { - return ( - - <$FlowView {...props} /> - - ) -} - -export default FlowView; diff --git a/mobile-ui/src/components/form/captcha.tsx b/mobile-ui/src/components/form/captcha.tsx deleted file mode 100644 index 1e0db267..00000000 --- a/mobile-ui/src/components/form/captcha.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, {useEffect, useState} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Input} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormCaptcha: React.FC = (props) => { - const [captchaImg, setCaptchaImg] = useState(''); - const {formContext,rules} = formFieldInit(props); - - const reloadCaptcha = () => { - props.onCaptchaRefresh && props.onCaptchaRefresh().then((res) => { - if(res) { - setCaptchaImg(res.url); - props.onCaptchaChange && props.onCaptchaChange(res.code); - } - }); - } - - useEffect(() => { - reloadCaptcha(); - }, []) - - return ( - - ) -} - -export default FormCaptcha; diff --git a/mobile-ui/src/components/form/cascader.tsx b/mobile-ui/src/components/form/cascader.tsx deleted file mode 100644 index 0840151c..00000000 --- a/mobile-ui/src/components/form/cascader.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Cascader, Form} from "antd-mobile"; -import {RightOutline} from "antd-mobile-icons"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const valueToForm = (value: string) => { - if (value && value.length > 0) { - return value.split(","); - } - return value; -} - -const formToValue = (value: string[]) => { - if (value && value.length > 0) { - return value.join(",") - } - return value; -} - -const FormCascader: React.FC = (props) => { - - - const [visible, setVisible] = React.useState(false); - const [options, setOptions] = React.useState(props.options); - - const {formContext, rules} = formFieldInit(props, () => { - reloadOptions(); - }); - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(res => { - setOptions(res); - }); - } - } - - useEffect(() => { - reloadOptions(); - }, []); - - useEffect(() => { - if (visible) { - reloadOptions(); - } - }, [visible]); - - return ( - - ) -} - -export default FormCascader; diff --git a/mobile-ui/src/components/form/checkbox.tsx b/mobile-ui/src/components/form/checkbox.tsx deleted file mode 100644 index 64437625..00000000 --- a/mobile-ui/src/components/form/checkbox.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Checkbox, Form, Space} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const valueToForm = (value: string) => { - if (value && value.length > 0) { - return value.split(","); - } - return value; -} - -const formToValue = (value: string[]) => { - if (value && value.length > 0) { - return value.join(",") - } - return value; -} - -const FormCheckbox: React.FC = (props) => { - const [options, setOptions] = React.useState(props.options); - - const {formContext, rules} = formFieldInit(props, () => { - reloadOptions(); - }); - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(res => { - setOptions(res); - }); - } - } - - useEffect(() => { - reloadOptions(); - }, []); - - return ( - - ) -} - -export default FormCheckbox; diff --git a/mobile-ui/src/components/form/common.tsx b/mobile-ui/src/components/form/common.tsx deleted file mode 100644 index 6aa7bd0b..00000000 --- a/mobile-ui/src/components/form/common.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, {useEffect} from "react"; -import {FormContext} from "@/components/form"; -import {FormItemProps} from "@/components/form/types"; - -const formFieldInit = (props: FormItemProps,reloadOption?:()=>void) => { - const formContext = React.useContext(FormContext) || undefined; - const validateContext = formContext?.getFormValidateContext(); - const [random, setRandom] = React.useState(0); - - const rules= props.required?[{required: true}]:[]; - - useEffect(() => { - if (props.validateFunction) { - if (validateContext) { - if (props.disabled || props.hidden) { - // do nothing - } else { - validateContext.addValidateFunction(props.name, props.validateFunction); - } - } - } - const reloadContext = formContext?.getFormFieldReloadListenerContext(); - if (reloadContext) { - reloadContext.addListener(props.name, () => { - setRandom(Math.random); - }); - } - - const optionContext = formContext?.getFormFieldOptionListenerContext(); - if (optionContext) { - optionContext.addListener(props.name, () => { - if(reloadOption){ - reloadOption(); - } - }); - } - }, [formContext]); - - return {formContext, validateContext, rules}; -} - -export default formFieldInit; diff --git a/mobile-ui/src/components/form/date.tsx b/mobile-ui/src/components/form/date.tsx deleted file mode 100644 index d326d3fc..00000000 --- a/mobile-ui/src/components/form/date.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {DatePicker, Form} from "antd-mobile"; -import {RightOutline} from "antd-mobile-icons"; -import dayjs from "dayjs"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -export const dateLabelRenderer = (type: string, data: number) => { - switch (type) { - case 'year': - return data + '年' - case 'month': - return data + '月' - case 'day': - return data + '日' - case 'hour': - return data + '时' - case 'minute': - return data + '分' - case 'second': - return data + '秒' - default: - return data - } -} - -const FormDate: React.FC = (props) => { - - const {formContext, rules} = formFieldInit(props); - - const format = props.dateFormat || 'YYYY-MM-DD'; - const precision = props.datePrecision || "day"; - const [visible, setVisible] = React.useState(false); - - return ( - - ) -} - -export default FormDate; diff --git a/mobile-ui/src/components/form/domain/FormInstance.tsx b/mobile-ui/src/components/form/domain/FormInstance.tsx deleted file mode 100644 index 7502c8f0..00000000 --- a/mobile-ui/src/components/form/domain/FormInstance.tsx +++ /dev/null @@ -1,314 +0,0 @@ -import {FormValidateContext} from "@/components/form/validate"; -import {FormFieldOptionListenerContext, FormFieldReloadListenerContext} from "@/components/form/listener"; -import {FormInstance as MobileFormInstance} from "rc-field-form/es/interface"; -import {FormField} from "@/components/form/types"; -import {NamePath} from "antd-mobile/es/components/form"; -import {Form as MobileForm} from "antd-mobile"; -import {FiledData, FormAction} from "@/components/form"; -import React from "react"; - -class FormInstance { - private readonly validateContext: FormValidateContext; - private readonly reloadContext: FormFieldReloadListenerContext; - private readonly optionContext: FormFieldOptionListenerContext; - private readonly formInstance: MobileFormInstance; - private readonly formAction: FormAction; - private fields: FormField[]; - - private fieldsUpdateDispatch: React.Dispatch> | undefined; - - public setFieldsUpdateDispatch = (fieldsUpdateDispatch: React.Dispatch>) => { - this.fieldsUpdateDispatch = fieldsUpdateDispatch; - } - - private updateFields = (resetFields:(prevState: FormField[]) => FormField[]) => { - this.fields = resetFields(this.fields); - if (this.fieldsUpdateDispatch) { - this.fieldsUpdateDispatch(resetFields); - } - } - - private namePathEqual = (name1: NamePath, name2: NamePath) => { - if (Array.isArray(name1) && Array.isArray(name2)) { - if (name1.length !== name2.length) { - return false; - } - for (let i = 0; i < name1.length; i++) { - if (name1[i] !== name2[i]) { - return false; - } - } - return true; - } - return name1 === name2; - } - - public submit = async () => { - const res = await this.validateContext.validate(this); - if (res) { - this.formInstance.submit(); - } - } - - public reset = (values?: any) => { - this.formInstance.resetFields(); - if (values) { - this.formInstance.setFieldsValue(values); - this.reloadContext.notifyAll(); - } - } - - public hidden = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name, name)) { - return { - ...field, - props: { - ...field.props, - hidden: true, - required: false - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public required = (name: NamePath, required: boolean) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name,name)) { - return { - ...field, - props: { - ...field.props, - required: required - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public show = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name,name)) { - return { - ...field, - props: { - ...field.props, - hidden: false - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public disable = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name,name)) { - return { - ...field, - props: { - ...field.props, - disabled: true - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public disableAll = () => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - return { - ...field, - props: { - ...field.props, - disabled: true - } - } - })); - this.validateContext.clear(); - } - - public enable = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - if (this.namePathEqual(field.props.name,name)) { - return { - ...field, - props: { - ...field.props, - disabled: false - } - } - } - return field; - })); - this.validateContext.clear(); - } - - public enableAll = () => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.map((field) => { - return { - ...field, - props: { - ...field.props, - disabled: false - } - } - })); - this.validateContext.clear(); - } - - public remove = (name: NamePath) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => prevFields.filter((field) => !this.namePathEqual(field.props.name,name))); - this.validateContext.clear(); - } - - public create = (field: FormField, index?: number) => { - if (this.fields.length == 0) { - return; - } - this.updateFields(prevFields => { - const filteredFields = prevFields.filter((item) => item.props.name !== field.props.name); - if (index === undefined || index < 0) { - return [...filteredFields, field]; - } else { - const newFields = [...filteredFields]; - newFields.splice(index, 0, field); - return newFields; - } - }); - this.validateContext.clear(); - } - - public getFieldValue = (name: NamePath) => { - return this.formInstance.getFieldValue(name); - } - - public getFieldsValue = () => { - return this.formInstance.getFieldsValue(); - } - - public getFieldProps = (name: NamePath) => { - for (const field of this.fields) { - if (this.namePathEqual(field.props.name, name)) { - return field; - } - } - return null; - } - - public reloadOptions = (name: NamePath) => { - this.optionContext.notify(name); - } - - public reloadAllOptions = () => { - this.optionContext.notifyAll(); - } - - public setFieldValue = (name: NamePath, value: any) => { - this.formInstance.setFieldValue(name, value); - this.reloadContext.notify(name); - this.validateContext?.validateField(name, this); - } - - public setFieldsValue = (values: any) => { - this.formInstance.setFieldsValue(values); - this.reloadContext.notifyAll(); - } - - public setFields = (fields: FiledData[]) => { - this.formInstance.setFields(fields); - } - - public validate = () => { - return this.validateContext.validate(this); - } - - public resetFields = (fields:FormField[]) => { - this.fields = fields; - } - - constructor() { - this.validateContext = new FormValidateContext(); - this.reloadContext = new FormFieldReloadListenerContext(); - this.optionContext = new FormFieldOptionListenerContext(); - this.formInstance = MobileForm.useForm()[0]; - this.fields = []; - this.formAction = { - submit: this.submit, - reset: this.reset, - hidden: this.hidden, - show: this.show, - remove: this.remove, - create: this.create, - disable: this.disable, - disableAll: this.disableAll, - enable: this.enable, - enableAll: this.enableAll, - required: this.required, - getFieldValue: this.getFieldValue, - getFieldsValue: this.getFieldsValue, - getFieldProps: this.getFieldProps, - reloadOptions: this.reloadOptions, - reloadAllOptions: this.reloadAllOptions, - setFieldValue: this.setFieldValue, - setFieldsValue: this.setFieldsValue, - setFields: this.setFields, - validate: this.validate, - } - } - - public getFormAction = () => { - return this.formAction; - } - - public getFormValidateContext = () => { - return this.validateContext; - } - - public getFormFieldReloadListenerContext = () => { - return this.reloadContext; - } - - public getFormFieldOptionListenerContext = () => { - return this.optionContext; - } - - public getFormControlInstance = ():MobileFormInstance => { - return this.formInstance; - } - -} - -export default FormInstance; diff --git a/mobile-ui/src/components/form/factory.tsx b/mobile-ui/src/components/form/factory.tsx deleted file mode 100644 index 1007193a..00000000 --- a/mobile-ui/src/components/form/factory.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React from "react"; -import {FormField} from "@/components/form/types"; -import FormInput from "@/components/form/input"; -import FormPassword from "@/components/form/password"; -import FormSelect from "@/components/form/select"; -import FormDate from "@/components/form/date"; -import FormRadio from "@/components/form/radio"; -import FormTextArea from "@/components/form/textarea"; -import FormCascader from "@/components/form/cascader"; -import FormCheckbox from "@/components/form/checkbox"; -import FormUploader from "@/components/form/uploder"; -import FormSwitch from "@/components/form/switch"; -import FormStepper from "@/components/form/stepper"; -import FormSlider from "@/components/form/slider"; -import FormRate from "@/components/form/rate"; -import FormSelector from "@/components/form/selector"; -import FormCaptcha from "@/components/form/captcha"; - -class FormFactory { - - static create = (field: FormField) => { - const type = field.type; - const props = field.props; - - if (type === 'input') { - return ( - - ) - } - - if (type === 'captcha') { - return ( - - ) - } - - if (type === 'password') { - return ( - - ) - } - - if (type === 'cascader') { - return ( - - ) - } - - if (type === 'select') { - return ( - - ) - } - - if (type === 'date') { - return ( - - ) - } - - if (type === 'radio') { - return ( - - ) - } - - if (type === 'checkbox') { - return ( - - ) - } - - if (type === 'textarea') { - return ( - - ) - } - - if (type === 'uploader') { - return ( - - ) - } - - if (type === 'switch') { - return ( - - ) - } - - if (type === 'stepper') { - return ( - - ) - } - - if (type === 'slider') { - return ( - - ) - } - - if (type === 'rate') { - return ( - - ) - } - - if (type === 'selector') { - return ( - - ) - } - } - -} - -export default FormFactory; diff --git a/mobile-ui/src/components/form/form.scss b/mobile-ui/src/components/form/form.scss deleted file mode 100644 index 3e306233..00000000 --- a/mobile-ui/src/components/form/form.scss +++ /dev/null @@ -1,145 +0,0 @@ -@use "../../config/variables" as *; - -.placeholder-span { - color: $from-placeholder-color; -} - -.form-uploader-file { - height: 90px; - width: 90px; - display: flex; - justify-content: center; - align-items: center; - flex-wrap: wrap; /* 允许子元素换行 */ - text-align: center; /* 水平居中 */ - word-wrap: break-word; /* 超出容器时换行 */ - border: 0.5px solid $body-background-color; - position: relative; - - .delete-icon { - margin-top: -5px; - margin-right: -5px; - position: absolute; - right: 0; - top: 0; - width: 15px; - height: 15px; - } - - a { - width: 100%; - font-size: 12px; - } -} - -.form-password { - display: flex; - - .form-password-eye { - flex: none; - margin-left: 8px; - padding: 4px; - cursor: pointer; - - svg { - display: block; - } - } -} - - -.select-popup { - padding: 12px; - width: 100%; - - .select-popup-content { - height: 45vh; - padding: 15px; - width: 100%; - overflow-y: scroll; - - .checkbox-parent { - cursor: pointer; - display: flex; - justify-content: space-between; - } - - - .select-popup-content-custom-form { - - - .select-popup-content-custom-footer { - display: flex; - justify-content: space-between; - align-items: center; - margin: 30px; - - button{ - width: 300px; - margin: 10px; - } - } - - } - - } - - .select-popup-header { - height: 30px; - display: flex; - justify-content: space-between; - align-items: center; - padding: 5px; - - a { - font-weight: bold; - } - } - - .select-popup-navbar { - padding: 10px 5px 0 10px; - height: 20px; - overflow-x: auto; - white-space: nowrap; - width: 100vw; - display: flex; - - .select-popup-navbar-item { - height: 20px; - align-items: center; - - .arrow { - color: $theme-primary-color; - } - - .span { - margin-left: 10px; - color: $theme-primary-color; - } - } - } - - .select-popup-navbar::-webkit-scrollbar { - display: none; - } - - .select-popup-search { - height: 30px; - padding: 5px; - display: flex; - align-items: center; - - .select-popup-search-bar { - flex: 9; - height: 30px; - } - - .select-popup-search-button { - flex: 1; - height: 20px; - } - } - -} - - diff --git a/mobile-ui/src/components/form/index.tsx b/mobile-ui/src/components/form/index.tsx deleted file mode 100644 index 59dec584..00000000 --- a/mobile-ui/src/components/form/index.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, {useEffect} from "react"; -import {Form as MobileForm} from "antd-mobile"; -import FormFactory from "@/components/form/factory"; -import {FormField} from "@/components/form/types"; -import {NamePath} from "antd-mobile/es/components/form"; -import "./form.scss"; -import FormInstance from "@/components/form/domain/FormInstance"; - -export interface FiledData { - name: NamePath; - errors?: string[]; -} - -export interface FormAction { - // 提交表单,提取之前会先校验表单 - submit: () => void; - // 重置表单,恢复表单项最初的状态 - reset: (values?: any) => void; - // 隐藏表单项,隐藏后的表单项不会显示在表单中,但是值会被提交 - hidden: (name: NamePath) => void; - // 展示表单项,展示后的表单项会显示在表单中,值也会被提交 - show: (name: NamePath) => void; - // 删除表单项,删除后的表单项不会显示在表单中,值也不会被提交,在fields配置的情况下生效 - remove: (name: NamePath) => void; - // 添加表单项,添加后的表单项会显示在表单中,值也会被提交,在fields配置的情况下生效 - create: (field: FormField, index?: number) => void; - // 禁用表单项,禁用后的表单项还会被提交 - disable: (name: NamePath) => void; - // 全部禁用,禁用后的表单项还会被提交 - disableAll:()=>void; - // 启用表单项,启用后的表单项还会被提交 - enable: (name: NamePath) => void; - // 全部启用,启用后的表单项还会被提交 - enableAll:()=>void; - // 必填选项控制,true为必填false为非必填提示 - required: (name: NamePath,required:boolean) => void; - // 获取字段的值 - getFieldValue: (name: NamePath) => any; - // 重新加载选项 - reloadOptions: (name: NamePath) => any; - // 重新加载所有选项 - reloadAllOptions: () => any; - // 获取全部字段的值 - getFieldsValue: () => any; - // 设置字段的值 - setFieldValue: (name: NamePath, value: any) => void; - // 设置全部字段的值 - setFieldsValue: (values: any) => void; - // 设置Field字段 - setFields: (fields: FiledData[]) => void; - // 获取Field属性 - getFieldProps: (name: NamePath) => FormField | null; - // 校验表单 - validate: () => Promise; -} - -export interface FormProps { - // 表单字段 - loadFields?: ()=>Promise; - // 表单提交事件 - onFinish?: (values: any) => Promise; - // form布局,默认vertical - layout?: 'horizontal' | 'vertical'; - // children元素 - children?: React.ReactNode; - // footer元素 - footer?: React.ReactNode; - // 初始化值 - initialValues?: any; - // 表单实例 - form?: FormInstance; -} - - -export const FormContext = React.createContext(null); - -const FormComponent: React.FC = (props) => { - - const formInstance = props.form? props.form : new FormInstance(); - - const [fields, setFields] = React.useState([]); - formInstance.setFieldsUpdateDispatch(setFields); - - const formControl = formInstance.getFormControlInstance() ; - - const reloadFields = ()=>{ - if(props.loadFields){ - props.loadFields().then(fields=>{ - setFields(fields); - formInstance.resetFields(fields); - }) - } - } - - useEffect(() => { - reloadFields(); - }, [props.loadFields]); - - - return ( - - { - props.onFinish && props.onFinish(values); - }} - initialValues={props.initialValues} - layout={props.layout} - footer={props.footer} - > - {fields.length > 0 && fields.map((field) => { - return FormFactory.create(field) as React.ReactNode; - })} - - {props.children} - - - - ) -} - -type FormType = typeof FormComponent; -type FormComponentType = FormType & { - useForm: ()=>FormInstance; -}; - -const Form = FormComponent as FormComponentType; -Form.useForm = ()=>{ - return new FormInstance(); -}; - -export default Form; - diff --git a/mobile-ui/src/components/form/input.tsx b/mobile-ui/src/components/form/input.tsx deleted file mode 100644 index e89401ba..00000000 --- a/mobile-ui/src/components/form/input.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Input} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormInput: React.FC = (props) => { - - const inputType = props.inputType || "text"; - const {formContext, rules} = formFieldInit(props); - - return ( - - ) -} - -export default FormInput; diff --git a/mobile-ui/src/components/form/listener.ts b/mobile-ui/src/components/form/listener.ts deleted file mode 100644 index 47c13d06..00000000 --- a/mobile-ui/src/components/form/listener.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {NamePath} from "antd-mobile/es/components/form"; - -// Form字段组件刷新控制监听上下文对象 -export class FormFieldReloadListenerContext { - - private readonly map:Mapvoid>; - - constructor() { - this.map = new Map(); - } - - public addListener(name:NamePath,listener:()=>void){ - const key = Array.isArray(name)?name.join("."):name; - this.map.set(key, listener); - } - - - public notify(name:NamePath){ - const key = Array.isArray(name)?name.join("."):name; - const listener = this.map.get(key); - if(listener){ - listener(); - } - } - - public notifyAll(){ - this.map.forEach(listener=>{ - listener(); - }) - } - -} - -// Form字段组件选项刷新控制监听上下文对象 -export class FormFieldOptionListenerContext { - - private readonly map:Mapvoid>; - - constructor() { - this.map = new Map(); - } - - public addListener(name:NamePath,listener:()=>void){ - const key = Array.isArray(name)?name.join("."):name; - this.map.set(key, listener); - } - - - public notify(name:NamePath){ - const key = Array.isArray(name)?name.join("."):name; - const listener = this.map.get(key); - if(listener){ - listener(); - } - } - - public notifyAll(){ - this.map.forEach(listener=>{ - listener(); - }) - } - -} - diff --git a/mobile-ui/src/components/form/password.tsx b/mobile-ui/src/components/form/password.tsx deleted file mode 100644 index 55ad6ed7..00000000 --- a/mobile-ui/src/components/form/password.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Input} from "antd-mobile"; -import {EyeInvisibleOutline, EyeOutline} from "antd-mobile-icons"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormPassword: React.FC = (props) => { - - const [visible, setVisible] = React.useState(false); - - const {formContext, rules} = formFieldInit(props); - - return ( - - ) -} - -export default FormPassword; diff --git a/mobile-ui/src/components/form/radio.tsx b/mobile-ui/src/components/form/radio.tsx deleted file mode 100644 index 00b0ad08..00000000 --- a/mobile-ui/src/components/form/radio.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Radio, Space} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormRadio: React.FC = (props) => { - const [options, setOptions] = React.useState(props.options); - - const {formContext, rules} = formFieldInit(props, () => { - reloadOptions(); - }); - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(res => { - setOptions(res); - }); - } - } - - useEffect(() => { - reloadOptions(); - }, []); - - return ( - - ) -} - -export default FormRadio; diff --git a/mobile-ui/src/components/form/rate.tsx b/mobile-ui/src/components/form/rate.tsx deleted file mode 100644 index 6936325e..00000000 --- a/mobile-ui/src/components/form/rate.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Rate} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormRate: React.FC = (props) => { - const {formContext, rules} = formFieldInit(props); - - return ( - - ) -} - -export default FormRate; diff --git a/mobile-ui/src/components/form/select.tsx b/mobile-ui/src/components/form/select.tsx deleted file mode 100644 index 8bae7b86..00000000 --- a/mobile-ui/src/components/form/select.tsx +++ /dev/null @@ -1,426 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps, FormOption} from "@/components/form/types"; -import {Button, CheckList, Form as AntdForm, InfiniteScroll, Popup, PullToRefresh, SearchBar} from "antd-mobile"; -import {RightOutline, SetOutline} from "antd-mobile-icons"; -import formFieldInit from "@/components/form/common"; -import Form from "@/components/form"; -import "./form.scss"; - - -const valueToForm = (value: string) => { - if (value && value.length > 0) { - return value.split(","); - } - return value; -} - -const formToValue = (value: string[]) => { - if (value && value.length > 0) { - return value.join(",") - } - return value; -} - -interface CheckboxItemProps { - item: FormOption; - paths: FormOption[]; - setPaths: (paths: FormOption[]) => void; - setOptions: (options: FormOption[]) => void; - -} - -const CheckboxItem: React.FC = (props) => { - const {item, paths, setPaths, setOptions} = props; - if (item.children && item.children.length > 0) { - return ( - -
{ - setPaths([...paths, item]); - setOptions(item.children || []); - }} - > - {item.label} - -
-
- ) - } - return ( - - {item.label} - - ) -} - -interface CheckboxListViewProps { - data: FormOption[]; - paths: FormOption[]; - setPaths: (paths: FormOption[]) => void; - setOptions: (options: FormOption[]) => void; -} - -const CheckboxListView: React.FC = (props) => { - const {data} = props; - const pageSize = 20; - - const [currentPage, setCurrentPage] = React.useState(1); - - const [list, setList] = React.useState([]); - - const [hasMore, setHasMore] = React.useState(true); - - const reload = () => { - const currentPage = 1; - if (data.length > 0) { - const list = data.slice((currentPage - 1) * pageSize, currentPage * pageSize); - setList(list); - setHasMore(true); - } else { - setList([]); - setHasMore(false); - } - setCurrentPage(currentPage); - } - - const loadMore = () => { - setCurrentPage(prevState => { - const newPage = prevState + 1; - if (newPage * pageSize >= data.length) { - setHasMore(false); - } else { - setHasMore(true); - } - setList(prevState => { - const list = data.slice((newPage - 1) * pageSize, newPage * pageSize); - return [...prevState, ...list] - }); - return newPage; - }); - } - - - useEffect(() => { - reload(); - }, [props.data]); - - return ( - <> - { - reload(); - }} - > - {list && list.map((item: FormOption, index: number) => { - return - })} - - {hasMore && ( - { - loadMore(); - }} - hasMore={hasMore} - /> - )} - - - - ) -} - -const FormSelect: React.FC = (props) => { - - const [visible, setVisible] = React.useState(false); - const [searchText, setSearchText] = React.useState(''); - - const {formContext, rules} = formFieldInit(props, () => { - reloadOptions(); - }); - - const currentValue = valueToForm(formContext?.getFieldValue(props.name)) as string[] || valueToForm(props.value) as string[] || []; - - const [selected, setSelected] = React.useState(currentValue); - - const [settingOptionVisible, setSettingOptionVisible] = React.useState(false); - - // 当前页面展示的选项的数据,会随着树级目录进入子数据,从而更新数据 - const [options, setOptions] = React.useState(props.options); - - // 用于展示数据的缓存数据,在数据插入不在更新 - const [optionCaches, setOptionCaches] = React.useState(props.options); - - const [paths, setPaths] = React.useState([]); - - const CheckBoxValueText = () => { - const currentValue = valueToForm(formContext?.getFieldValue(props.name)) as string[] || valueToForm(props.value) as string[] || []; - const optionLabelFetch = (value: string) => { - let label = value; - let fetchState = false; - const loadLabel = (item: FormOption) => { - if (!fetchState) { - if (item.value === value) { - label = item.label; - fetchState = true; - } - if (item.children) { - item.children.forEach(child => { - loadLabel(child); - }) - } - } - } - - optionCaches?.forEach(item => { - loadLabel(item); - }) - return label; - } - const displaySpan = (list: string[]) => { - if (list.length > 0) { - return ( - { - setVisible(true); - }}> - {list && - list.map(item => { - return optionLabelFetch(item); - }).join(",") - } - - ) - } else { - return ( - { - setVisible(true); - }}> - {props.placeholder || "请选择"} - - ) - } - } - return displaySpan(currentValue); - } - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(list => { - setOptions(list); - setOptionCaches(list); - }); - } - } - - useEffect(() => { - setPaths([]); - reloadOptions(); - }, []); - - - useEffect(() => { - setSearchText(''); - if (visible) { - setPaths([]); - setSelected(currentValue); - reloadOptions(); - } - }, [visible]); - - const selectOptionFormEditInstance = Form.useForm(); - - - const handlerOptionFormFinish = () => { - if (props.onSelectOptionFormFinish && selectOptionFormEditInstance && formContext) { - props.onSelectOptionFormFinish( - formContext, - selectOptionFormEditInstance, - reloadOptions, - () => { - setSettingOptionVisible(false); - setVisible(false); - } - ); - } - } - - return ( -
- {!settingOptionVisible && ( -
- { - setSearchText(v); - }} - /> - {props.selectOptionFormEditable && ( - { - setSettingOptionVisible(!settingOptionVisible); - }} - /> - )} -
- )} - - {paths.length > 0 && ( -
- {paths.map((item, index) => { - return ( - - ) - })} -
- )} - -
- - {settingOptionVisible && ( -
- {formContext && props.selectOptionFormEditView && ( - - )} -
- - -
- -
- )} - - {!settingOptionVisible && ( - { - const currentValue = value as string[]; - setSelected(currentValue); - // 单选时,选中即关闭弹框 - if (!props.selectMultiple) { - formContext?.setFieldValue(props.name, formToValue(currentValue)); - props.onChange && props.onChange(formToValue(currentValue), formContext); - - setVisible(false); - } - }} - multiple={props.selectMultiple} - > - { - if (searchText) { - if (item.value.toUpperCase().includes(searchText.toUpperCase())) { - return true; - } - if (item.label.toUpperCase().includes(searchText.toUpperCase())) { - return true; - } - return false; - } - return true; - })) - || []} - setOptions={setOptions} - paths={paths} - setPaths={setPaths} - /> - - )} -
- - - ) -} - -export default FormSelect; diff --git a/mobile-ui/src/components/form/selector.tsx b/mobile-ui/src/components/form/selector.tsx deleted file mode 100644 index 42daecea..00000000 --- a/mobile-ui/src/components/form/selector.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, {useEffect} from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Selector} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const valueToForm = (value: string) => { - if (value && value.length > 0) { - return value.split(","); - } - return value; -} - -const formToValue = (value: string[]) => { - if (value && value.length > 0) { - return value.join(",") - } - return value; -} - - -const FormSelector: React.FC = (props) => { - - const [options, setOptions] = React.useState(props.options); - - const {formContext, rules} = formFieldInit(props, () => { - reloadOptions(); - }); - - const reloadOptions = () => { - if (props.loadOptions) { - props.loadOptions(formContext).then(res => { - setOptions(res); - }); - } - } - - useEffect(() => { - reloadOptions(); - }, []); - - return ( - - ) -} - -export default FormSelector; diff --git a/mobile-ui/src/components/form/slider.tsx b/mobile-ui/src/components/form/slider.tsx deleted file mode 100644 index 08291774..00000000 --- a/mobile-ui/src/components/form/slider.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Slider} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormSlider: React.FC = (props) => { - - const {formContext, rules} = formFieldInit(props); - - return ( - - ) -} - -export default FormSlider; diff --git a/mobile-ui/src/components/form/stepper.tsx b/mobile-ui/src/components/form/stepper.tsx deleted file mode 100644 index 5d6d4843..00000000 --- a/mobile-ui/src/components/form/stepper.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Stepper} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormStepper: React.FC = (props) => { - - const {formContext, rules} = formFieldInit(props); - - return ( - - ) -} - -export default FormStepper; diff --git a/mobile-ui/src/components/form/switch.tsx b/mobile-ui/src/components/form/switch.tsx deleted file mode 100644 index 566b48a5..00000000 --- a/mobile-ui/src/components/form/switch.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, Switch as AntSwitch} from "antd-mobile"; -import {SwitchProps as AntdSwitchProps} from "antd-mobile/es/components/switch/switch"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -interface SwitchProps extends AntdSwitchProps { - value?: boolean; -} - -const Switch: React.FC = ({value, ...props}) => { - return ( - - ) -} - -const FormSwitch: React.FC = (props) => { - - const {formContext, rules} = formFieldInit(props); - - return ( - - ) -} - -export default FormSwitch; diff --git a/mobile-ui/src/components/form/textarea.tsx b/mobile-ui/src/components/form/textarea.tsx deleted file mode 100644 index 0807f0b2..00000000 --- a/mobile-ui/src/components/form/textarea.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import {FormItemProps} from "@/components/form/types"; -import {Form, TextArea} from "antd-mobile"; -import formFieldInit from "@/components/form/common"; -import "./form.scss"; - -const FormTextArea: React.FC = (props) => { - - const {formContext, rules} = formFieldInit(props); - - return ( -