Skip to content
Merged

Dev #45

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/app-mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@coding-flow/flow-types": "workspace:*",
"@coding-flow/flow-mobile-ui": "workspace:*",
"@coding-flow/flow-mobile-approval": "workspace:*",
"@coding-flow/flow-approval-presenter": "workspace:*",
"antd-mobile": "^5.42.3",
"dayjs": "^1.11.19",
"react": "^18.3.1",
Expand Down
135 changes: 135 additions & 0 deletions apps/app-mobile/src/components/flow-view/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {EventBus} from "@coding-flow/flow-core";
import type {FormViewProps} from "@coding-flow/flow-types";
import {type ActionInterceptorContext, useApprovalContext} from "@coding-flow/flow-approval-presenter";
import {FormView} from "@coding-form/form-engine";
import {Form, Input} from "antd-mobile";
import React from "react";

/**
* 移动端流程表单视图(与 app-pc 的 FlowView 机制对齐)。
*
* 通过 `register-form-view` 注册为 `ViewBindPlugin` 的 'default' 视图,
* 在审批表单渲染处挂载以下三种自定义能力:
* 1. EventBus 前端自定义事件触发;
* 2. 审批操作拦截器(action type / action id 二选一);
* 3. 审批弹框标题与内容提供器(action type / action id 二选一)。
*/
export const FlowView: React.FC<FormViewProps> = (props) => {

const form = props.form;

const {state, context} = useApprovalContext();

const actionPresenter = context.getPresenter().getFlowActionPresenter();

React.useEffect(() => {
// 场景一:订阅前端自定义事件的触发操作,事件 KEY 在流程配置上设置
EventBus.getInstance().on('CUSTOM', () => {
// 所有的按钮数据
const actions = state.flow?.actionList || [];
// 找到通过的按钮
const pass = actions.find(item => item.type === 'PASS');
// 更新 form 表单数据
form?.setFieldValue('test', '123');
if (pass) {
// 触发某个按钮的交互操作
EventBus.getInstance().emit(pass.id);
}
});

return () => {
// 注销前端自定义事件的触发操作
EventBus.getInstance().off('CUSTOM');
}

}, []);

React.useEffect(() => {

// 场景二:订阅审批操作拦截器:
// - 所有审批按钮点击后、真正提交前会先执行拦截器
// - 返回 true(或 resolve true)放行;返回 false(或 resolve false)拦截
// - 支持异步,例如先弹窗二次确认、做业务校验、调用远程接口等
//
// addActionInterceptor 返回取消订阅函数,必须在组件卸载时调用,
// 否则 StrictMode 或组件重复挂载会导致拦截器被重复添加。
const unsubscribe = actionPresenter.addActionInterceptor(
(interceptorContext: ActionInterceptorContext) => {
const {action} = interceptorContext;

// 如果根据 action?.type 来处理拦截的话,会影响整个视图下所有该类型下的按钮操作
// 如果根据 action.id 来处理拦截的话,只会影响该视图下某一节点的按钮操作业务
console.log('click action:', action);

return true;
}
);

console.log('action unsubscribe:', unsubscribe);

return () => {
// 组件卸载时取消订阅,避免重复添加拦截器
unsubscribe();
};
}, []);

React.useEffect(() => {
// 场景三:自定义按钮操作框标题和内容信息

// 根据按钮类型约定按钮的提示文字信息,将会影响该视图下所有节点类型下确认框信息
// const unsubscribe = actionPresenter.addDialogContentProvider(({action}) => {
// if (action?.type === 'PASS') {
// return {title: '自定义确认'}
// }
// return null;
// }
// );

// 根据按钮 ID 约定按钮的提示文字信息,将只会影响该视图某一节点的按钮操作确认框信息
const unsubscribe = actionPresenter.addDialogContentProvider(({actionId}) => {
if (actionId === 'KYykw8pigO') {
// 当返回content之后,若存在意见框等内容也会完全被content替换,因此使用content时请确保弹框内容已经时空白时在使用。
return {title: '确认小额审批?', content: '提交后不可撤回'}
}
return null;
}
);

console.log('dialog unsubscribe:', unsubscribe);

return () => {
// 组件卸载时取消订阅,避免重复添加拦截器
unsubscribe();
};

}, []);

return (
<>
<FormView
form={form as any}
layout={"vertical"}
meta={props.meta}
review={props.review}
onValuesChange={(_, values) => {
props.onValuesChange?.(values);
}}
>
<Form.Item
key={"recordId"}
name={"recordId"}
hidden={true}
>
<Input/>
</Form.Item>
<Form.Item
key={"test"}
name={"test"}
hidden={true}
>
<Input/>
</Form.Item>
</FormView>
</>
)
}
10 changes: 5 additions & 5 deletions apps/app-mobile/src/hooks/register-form-types.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from "react";
import {FormTypeContext} from "@coding-flow/flow-types";
import {type FormType} from "@coding-flow/flow-types";
import { registerFormItems } from "@coding-form/form-engine";
Expand All @@ -14,12 +13,13 @@ class RegisterRef{

}

export const registerFormTypes = ()=>{
// 模块级单例标记:注册逻辑可能在异步上下文中调用,不能使用 React.useRef
let registerRef: RegisterRef | undefined;

const ref = React.useRef<RegisterRef>(undefined);
export const registerFormTypes = ()=>{

if(!ref.current){
ref.current = new RegisterRef();
if(!registerRef){
registerRef = new RegisterRef();

const types:FormType[] = [
{
Expand Down
15 changes: 15 additions & 0 deletions apps/app-mobile/src/hooks/register-form-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ViewBindPlugin } from "@coding-flow/flow-core";
import { FlowView } from "@/components/flow-view";

class RegisterRef {

}

// 模块级单例标记:注册逻辑可能在异步上下文中调用,不能使用 React.useRef
let registerRef: RegisterRef | undefined;
export const registerFormView = () => {
if (!registerRef) {
registerRef = new RegisterRef();
ViewBindPlugin.getInstance().register('default', FlowView);
}
}
27 changes: 27 additions & 0 deletions apps/app-mobile/src/hooks/registrations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from "react";
import { registerFormTypes } from "@/hooks/register-form-types";
import { registerFormView } from "@/hooks/register-form-view";

/**
* 注册组件。
*
* 注意:@coding-form/form-engine 的 registerFormItems 内部调用了 React.useRef(),
* 因此所有注册逻辑只能在组件渲染期执行,不能放在 useEffect 的异步回调里调用
* (会抛 Invalid hook call 导致整个注册链中断)。
*
* 通过 React.lazy 动态加载本模块,注册相关依赖(antd-mobile Form、表单组件等)
* 不会进入首屏 chunk。
*/
const Registrations: React.FC = () => {

registerFormTypes();
registerFormView();

React.useEffect(() => {
console.log('register flow success.');
}, []);

return null;
};

export default Registrations;
16 changes: 13 additions & 3 deletions apps/app-mobile/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router';
import { hashRouters } from './config/routers';
import {registerFormTypes} from "@/hooks/register-form-types.tsx";


// 注册逻辑动态加载:注册依赖(antd-mobile Form、表单组件等)不进入首屏 chunk
const Registrations = React.lazy(() => import('@/hooks/registrations'));

const App = () => {
registerFormTypes();
return (
<React.StrictMode>
<RouterProvider router={hashRouters}/>
{/* 注册组件必须在渲染期执行(registerFormItems 内部调用了 hook),
不能放在 useEffect 异步回调里调用 */}
<React.Suspense fallback={null}>
<Registrations />
</React.Suspense>
{/* 路由页面均为 React.lazy 动态加载,必须提供 Suspense 兜底 */}
<React.Suspense fallback={null}>
<RouterProvider router={hashRouters}/>
</React.Suspense>
</React.StrictMode>
)
}
Expand Down
58 changes: 53 additions & 5 deletions apps/app-pc/src/components/flow-view/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { FormViewProps } from "@coding-flow/flow-types";
import { useApprovalContext, type ActionInterceptorContext } from "@coding-flow/flow-approval-presenter";
import { FormView } from "@coding-form/form-engine";
import React from "react";
import { Form, Modal } from "antd";
import { Form } from "antd";

export const FlowView: React.FC<FormViewProps> = (props) => {

Expand All @@ -17,15 +17,29 @@ export const FlowView: React.FC<FormViewProps> = (props) => {
// 诊断日志:确认 FlowView 是否被挂载,以及当前流程实际使用的视图名称
console.log('FlowView mounted, flow.view =', state.flow?.view);

// 订阅前端自定义事件的触发操作,事件KEY再流程配置上设置:
EventBus.getInstance().on('CUSTOM', () => {
// 所有的按钮数据
const actions = state.flow?.actionList || [];
// 找到通过的按钮
const pass = actions.find(item => item.type === 'PASS');
// 更新form表单数据
form?.setFieldValue('test', '123');
if (pass) {
// 触发某个按钮的交互操作
EventBus.getInstance().emit(pass.id);
}
});

return () => {
// 注销前端自定义事件的触发操作
EventBus.getInstance().off('CUSTOM');
}

}, []);

React.useEffect(() => {

// 订阅审批操作拦截器:
// - 所有审批按钮点击后、真正提交前会先执行拦截器
// - 返回 true(或 resolve true)放行;返回 false(或 resolve false)拦截
Expand All @@ -37,19 +51,53 @@ export const FlowView: React.FC<FormViewProps> = (props) => {
(interceptorContext: ActionInterceptorContext) => {
const { action } = interceptorContext;

console.log('click action:',action);
// 如果是根据action?.type 来处理拦截的话,会影响整个视图下所有该类型下的按钮操作
// 如果是根据action.id 来处理拦截的话,只会影响该视图下某一节点的按钮操作业务
console.log('click action:', action);

return false;
return true;
}
);

console.log('unsubscribe:',unsubscribe);
console.log('action unsubscribe:', unsubscribe);

return () => {
// 组件卸载时取消订阅,避免重复添加拦截器
unsubscribe();
};
}, []);



React.useEffect(() => {
// 自定义按钮操作框标题和内容信息

// 根据按钮类型约定按钮的提示文字信息,将会影响该视图下所有节点类型下确认框信息
// const unsubscribe = actionPresenter.addDialogContentProvider(({action}) => {
// if (action?.type === 'PASS') {
// return {title: '自定义确认'}
// }
// return null;
// }
// );

// 根据按钮 ID 约定按钮的提示文字信息,将只会影响该视图某一节点的按钮操作确认框信息
const unsubscribe = actionPresenter.addDialogContentProvider(({actionId}) => {
if (actionId === 'KYykw8pigO') {
// 当返回content之后,若存在意见框等内容也会完全被content替换,因此使用content时请确保弹框内容已经时空白时在使用。
return {title: '确认小额审批?', content: '提交后不可撤回'}
}
return null;
}
);

console.log('dialog unsubscribe:', unsubscribe);

return () => {
EventBus.getInstance().off('CUSTOM');
// 组件卸载时取消订阅,避免重复添加拦截器
unsubscribe();
};

}, []);

return (
Expand Down
2 changes: 1 addition & 1 deletion apps/app-pc/src/hooks/register-form-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ let registerRef: RegisterRef | undefined;
export const registerFormView = () => {
if (!registerRef) {
registerRef = new RegisterRef();
// ViewBindPlugin.getInstance().register('default', FlowView);
ViewBindPlugin.getInstance().register('default', FlowView);
}
}
8 changes: 8 additions & 0 deletions packages/flow-approval-presenter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @coding-flow/flow-approval-presenter

## 0.1.8

### Patch Changes

- 审批弹框标题/内容自定义能力(DialogContentProvider)+ 设计器按钮 ID 可复制
- @coding-flow/flow-core@0.1.8
- @coding-flow/flow-types@0.1.8

## 0.1.7

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/flow-approval-presenter/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coding-flow/flow-approval-presenter",
"version": "0.1.7",
"version": "0.1.8",
"description": "flow-engine approval presenter framework ",
"type": "module",
"sideEffects": [
Expand Down
Loading
Loading