4.3 流程设计器
设计办事
节点类型

节点配置


配置审批节点
表单配置
关键组件
<FormBinding
{...props}
title="主表配置"
formType="子表"
xforms={props.current.detailForms}
/>
<FormBinding
{...props}
title="子表配置"
formType="子表"
xforms={props.current.detailForms}
/>组件逻辑
const FormBinding: React.FC<IProps> = (props) => {
const [open, setOpen] = useState<boolean>(false);
const [forms, setForms] = useState<FormInfo[]>([]);
useEffect(() => {
setForms(props.current.forms.filter((a) => a.typeName == props.formType));
}, [props.current]);
const [loaded, allFiles] = useAsyncLoad(async () => {
const reports = await props.work.application.loadAllReports();
const forms = await props.work.application.loadAllForms();
return [...reports, ...forms];
});
const formViewer = React.useCallback((form: schema.XForm) => {
command.emitter(
'executor',
'open',
new SForm({ ...form, id: '_' + form.id }, props.belong.directory),
'preview',
);
}, []);
return (
<>
<Card
type="inner"
title={
<div>
<Divider type="vertical" className={cls['divider']} />
<span>{props.title}</span>
</div>
}
className={cls[`card-info`]}
extra={
<a
onClick={() => {
setOpen(true);
}}>
添加
</a>
}>
{forms.length > 0 && (
<span>
<ShareShowComp
key={'表单'}
departData={props.xforms}
onClick={formViewer}
deleteFuc={(id: string) => {
props.xforms.splice(
props.xforms.findIndex((a) => a.id != id),
1,
);
props.current.forms = props.current.forms.filter((a) => a.id != id);
setForms(props.current.forms.filter((a) => a.typeName == props.formType));
}}
tags={(id) => {
const info = props.current.forms.find((a) => a.id == id);
if (info) {
return (
<FormOption work={props.work} operateRule={info} typeName="主表" />
);
}
}}
/>
</span>
)}
</Card>
<>
{open && loaded && (
<OpenFileDialog
multiple
title={`选择${props.formType}表单`}
rootKey={props.work.application.key}
accepts={['表单', '报表', '表格']}
fileContents={allFiles}
excludeIds={props.xforms.map((i) => i.id)}
leftShow={false}
rightShow={false}
onCancel={() => setOpen(false)}
onOk={(files) => {
if (files.length > 0) {
const forms = (files as unknown[] as IForm[]).map((i) => i.metadata);
props.xforms.push(...forms);
props.current.forms = [
...(props.current.forms ?? []),
...forms.map((item) => {
return {
id: item.id,
typeName: props.formType,
allowAdd: false,
allowEdit: false,
allowSelect: false,
};
}),
];
setForms(props.current.forms.filter((a) => a.typeName == props.formType));
}
setOpen(false);
}}
/>
)}
</>
</>
);
};执行器配置
关键组件
<Card
type="inner"
title={
<div>
<Divider type="vertical" className={cls['divider']} />
<span>执行器配置</span>
</div>
}
className={cls[`card-info`]}
bodyStyle={{ padding: executors && executors.length ? '24px' : '0' }}
extra={
<>
<a
onClick={() => {
setExecutorModal(true);
}}>
添加
</a>
</>
}>
{executors && executors.length > 0 && (
<span>
<ExecutorShowComp
work={props.work}
executors={executors}
deleteFuc={(id: string) => {
var exes = executors.filter((a) => a.id != id);
setExecutors(exes);
props.current.executors = exes;
}}
/>
</span>
)}
</Card>组件逻辑 (1)通过ExecutorShowComp组件显示和删除执行器
const ExecutorShowComp: React.FC<IProps> = (props) => {
return (
<div className={cls.layout}>
<div className={cls.title}>已选{props.executors.length}条数据</div>
<Space direction="vertical">
{props.executors.map((item: model.Executor) => {
switch (item.funcName) {
case '数据申领':
return (
<Acquire
key={item.id}
executor={item}
deleteFuc={props.deleteFuc}
work={props.work}
/>
);
case '字段变更':
return (
<FieldChange
key={item.id}
work={props.work}
executor={item}
deleteFuc={props.deleteFuc}
/>
);
case 'Webhook':
return (
<Webhook key={item.id} executor={item} deleteFuc={props.deleteFuc} />
);
case '资产领用':
return (
<Common key={item.id} executor={item} deleteFuc={props.deleteFuc}>
多用于(公益仓、公物仓、商城等)通过集群办事领用数据
</Common>
);
case '任务状态变更':
return (
<Common key={item.id} executor={item} deleteFuc={props.deleteFuc}>
用于任务状态的回写通知
</Common>
);
case '复制表到子表':
return (
<CopyForm
key={item.id}
work={props.work}
executor={item}
deleteFuc={props.deleteFuc}
/>
);
case '商城订单同步':
return (
<MallOrderSync
key={item.id}
work={props.work}
executor={item}
deleteFuc={props.deleteFuc}
/>
);
default:
return <></>;
}
})}
</Space>
</div>
);
};(2)通过ExecutorConfigModal组件添加新的执行器
const ConfigModal: FC<IProps> = (props) => {
const [executors, setExecutors] = useState<model.Executor[]>([]);
const [form] = Form.useForm();
useEffect(() => {
props.current.executors = props.current.executors || [];
setExecutors(props.current.executors);
}, [props.current]);
const onOk = () => {
form.validateFields().then((val) => {
props.refresh(val);
});
};
return (
<Modal
bodyStyle={{ border: 'none' }}
open={true}
title="执行器配置"
onCancel={() => props.refresh()}
onOk={onOk}
footer={[
<Button key="cancel" onClick={() => props.refresh()}>
取消
</Button>,
<Button type="primary" key="cancel" onClick={() => onOk()}>
确定
</Button>,
]}>
<Form
preserve={false}
form={form}
layout="vertical"
initialValues={{ trigger: 'before', funcName: '', visible: false }}>
<Form.Item
name="trigger"
label="状态"
rules={[
{
required: true,
message: '请选择状态',
},
]}>
<Select
bordered={false}
allowClear
style={{
borderRadius: '3px',
border: '1px solid #DCDCDC',
}}
options={[
{ label: '审核前', value: 'before' },
{ label: '审核后', value: 'after' },
]}
/>
</Form.Item>
<Form.Item
name="funcName"
label="数据"
rules={[
{
required: true,
message: '请选择数据',
},
]}>
<Select
bordered={false}
allowClear
style={{
borderRadius: '3px',
border: '1px solid #DCDCDC',
}}
options={executorNames
.filter((a) => executors.find((s) => s.funcName == a) == undefined)
.map((name: string) => {
return {
label: name,
value: name,
};
})}
/>
</Form.Item>
<Form.Item name="visible" label="显示执行内容">
<Switch checkedChildren="是" unCheckedChildren="否" />
</Form.Item>
</Form>
</Modal>
);
};按钮配置

模态框渲染
return (
<Modal
destroyOnClose
title="按钮配置"
width={480}
open={true}
bodyStyle={{ border: 'none', padding: 0, marginLeft: '32px', marginRight: '32px' }}
onOk={() => {
Object.assign(props.current, form.getFieldsValue());
props.onOk(props.current);
}}
onCancel={props.onCancel}>
{/* 表单内容 */}
</Modal>
);标识和按钮文字
<Form.Item label="标识" name="code" required>
<Input />
</Form.Item>
<Form.Item label="按钮文字" name="name" required>
<Input />
</Form.Item>业务场景选择
<Form.Item name="scene" label="业务场景" rules={[{ required: true, message: '请选择业务场景' }]}>
<Radio.Group defaultValue="pc" onChange={(e) => setScene(e.target.value)}>
<Radio value="pc">PC端</Radio>
<Radio value="mobile">移动端</Radio>
</Radio.Group>
</Form.Item>表单选择
<Form.Item label="绑定表单" name="form" rules={[{ required: true, message: '请选择要绑定的表单' }]}>
<div>
<Button
type="primary"
onClick={() => {
setCenter(
<OpenFileDialog
title={`选择表单`}
onCancel={() => setCenter(<></>)}
onOk={(files) => {
form.setFieldValue('form', {
name: files[0].name,
id: files[0].id,
metadata: files[0].metadata,
});
getFormFields();
setFormName(files[0].name);
form.validateFields(['form']);
setCenter(<></>);
}}
/>,
);
}}>
选择表单
</Button>
{formName}
</div>
</Form.Item>移动字段配置
{formName && scene === 'mobile' ? (
<>
<Form.Item label="关联表单字段" name="correlationCode" rules={[{ required: true, message: '请选择关联表单字段' }]}>
<Select>
{fields.map((option: any, index: number) => (
<Select.Option key={index} value={option.id}>
{option.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label={'字段变更-' + formName} name="fieldChanges" rules={[{ required: true, message: '请选择要变更的字段' }]}>
已设置变更字段{fieldChanges?.length}个
<Button
type="link"
onClick={() =>
setCenter(
<FieldChangeTable
work={props.work}
finished={(e: model.FieldChange[]) => {
setFieldChanges(e);
if (fieldChanges.length) {
form.setFieldValue('fieldChanges', fieldChanges);
} else {
form.setFieldValue('fieldChanges', undefined);
}
form.validateFields(['fieldChanges']);
setCenter(<></>);
}}
formChange={{
...form.getFieldValue('form'),
fieldChanges: fieldChanges,
}}
/>,
)
}>
编辑变更字段
</Button>
</Form.Item>
</>
) : (
<>
<Form.Item label="操作目标" name="type" required>
<Select options={[{ label: '手动规则', value: 'rule' }, { label: '手动执行器', value: 'executor' }, { label: '获取业务数据', value: 'getWorkData' }]} onSelect={setType} />
</Form.Item>
{type === 'rule' ? (
<Form.Item label="规则" name="ruleId" required>
<Select options={rules.map((r) => ({ label: r.name, value: r.id }))} />
</Form.Item>
) : type === 'executor' ? (
<Form.Item label="执行器" name="executorId" required>
<Select options={executors.map((e) => ({ label: e.funcName, value: e.id }))} />
</Form.Item>
) : null}
</>
)}ButtonModal 组件提供了一个灵活的界面,用于配置按钮的行为。它支持以下功能: (1) 基本属性配置:标识、按钮文字、业务场景。 (2) 表单绑定:选择表单并配置相关字段。 (3) 移动端字段变更:在移动端场景下,可以配置字段变更。 (4) 操作目标选择:在 PC 端场景下,可以选择规则、执行器或获取业务数据。
关键组件
<ButtonConfig work={props.work} current={props.current} />组件逻辑
const ButtonConfig: React.FC<IProps> = (props) => {
const [buttons, setButtons] = useState<WorkNodeButton[]>([]);
const [visible, setVisible] = useState(false);
const [openType, setOpenType] = useState('add');
const [row, setRow] = useState<WorkNodeButton | null>(null);
useEffect(() => {
setButtons(props.current.buttons || []);
}, [props.current.buttons]);
function updateButtons(buttons: WorkNodeButton[]) {
setButtons(buttons);
props.current.buttons = buttons;
}
const columns: ProColumns<WorkNodeButton>[] = [
{ title: '序号', valueType: 'index', width: 50 },
{
title: '标识',
dataIndex: 'code',
width: 80,
},
{
title: '名称',
dataIndex: 'name',
width: 120,
},
{
title: '操作对象',
dataIndex: 'type',
render: (_: any, record: WorkNodeButton) => {
return record.type == 'rule' ? '规则' : record.type == 'executor' ? '执行器' : '';
},
width: 80,
},
{
title: '操作',
dataIndex: 'operate',
fixed: 'right',
render: (_: any, record: WorkNodeButton) => {
return (
<div>
<Button
type="link"
size="small"
style={{ marginRight: '4px' }}
className={cls['flowDesign-rule-edit']}
onClick={() => {
setOpenType('edit');
setRow(record);
setVisible(true);
}}>
编辑
</Button>
<Button
type="link"
danger
size="small"
className={cls['flowDesign-rule-delete']}
onClick={() => {
updateButtons(buttons.filter((b) => b.code != record.code));
}}>
删除
</Button>
</div>
);
},
width: 120,
},
];
function open() {
setOpenType('add');
setRow({
code: '',
name: '',
type: 'rule',
});
setVisible(true);
}
function addButton(button: WorkNodeButton) {
if (!button.code || !button.name) {
return;
}
if (buttons.some((b) => b.code == button.code)) {
message.warn('按钮标识重复');
return;
}
updateButtons([...buttons, button]);
}
function renderButtons() {
return (
<div className={cls.layout}>
<CardOrTableComp<WorkNodeButton>
rowKey={'id'}
columns={columns}
dataSource={buttons}
/>
</div>
);
}
return (
<Card
type="inner"
className={cls['card-info']}
title={
<div>
<Divider
type="vertical"
style={{
height: '16px',
borderWidth: '4px',
borderColor: Theme.FocusColor,
marginLeft: '0px',
}}
/>
<span>按钮配置</span>
</div>
}
bodyStyle={{ padding: buttons.length > 0 ? '8px' : 0 }}
extra={
<>
<a
className="primary-color"
onClick={() => {
open();
}}>
+ 添加
</a>
</>
}>
{buttons.length > 0 && renderButtons()}
{visible && (
<ButtonModal
current={row!}
rules={props.current.formRules}
executors={props.current.executors}
work={props.work}
onOk={(button) => {
if (openType == 'add') {
addButton(button);
} else {
updateButtons([...buttons]);
}
setVisible(false);
}}
onCancel={() => setVisible(false)}
/>
)}
</Card>
);
};规则配置
关键组件
<Rule
work={props.work}
current={props.current}
primaryForms={props.current.primaryForms}
detailForms={props.current.detailForms}
/>组件逻辑
const NodeRule: React.FC<IProps> = (props) => {
const [open, setOpen] = useState(false);
const [mainWidth, setMainWidth] = React.useState<number | string>('70%');
return (
<>
<Card
type="inner"
style={{ marginBottom: 10 }}
title={
<div>
<Divider
type="vertical"
style={{
height: '16px',
borderWidth: '4px',
borderColor: Theme.FocusColor,
marginLeft: '0px',
}}
className={cls['flowDesign-rule-divider']}
/>
<span>规则配置</span>
</div>
}
bodyStyle={{ padding: 0 }}
extra={
<>
<a
className="primary-color"
onClick={() => {
setOpen(true);
}}>
添加
</a>
</>
}
/>
<FullScreenModal
centered
fullScreen
open={open}
destroyOnClose
width={'80vw'}
bodyHeight={'80vh'}
title={'规则配置'}
onCancel={() => {
setOpen(false);
}}>
<Layout>
<Resizable
handles={'right'}
width={mainWidth}
maxWidth={800}
minWidth={400}
onResize={(e) => setMainWidth(e.width)}>
<Preview node={props.current} work={props.work} />
</Resizable>
<Layout.Content>
<Layout.Sider
width={'100%'}
style={{ height: '100%', padding: '40px 20px 20px 20px' }}>
<RuleList {...props}></RuleList>
</Layout.Sider>
</Layout.Content>
</Layout>
</FullScreenModal>
</>
);
};文档配置
关键组件
<DocumentConfig formHost={props.work} current={props.current} />组件逻辑
const DocumentConfig: React.FC<IProps> = (props) => {
const [docs, setDocs] = useState<XDocumentTemplate[]>([]);
const [mapping, setMapping] = useState<Dictionary<DocumentPropertyMapping[]>>({});
const [nodeMapping, setNodeMapping] = useState<Dictionary<DocumentNodeMapping[]>>({});
const [visible, setVisible] = useState(false);
const [openType, setOpenType] = useState('add');
const [row, setRow] = useState<XDocumentTemplate | null>(null);
const currentMapping = useMemo(() => {
return row ? [
mapping[row.id] || [],
nodeMapping[row.id] || [],
] as [DocumentPropertyMapping[], DocumentNodeMapping[]] : null;
}, [mapping, row]);
useEffect(() => {
setDocs(props.current.documentConfig?.templates ?? []);
setMapping(props.current.documentConfig?.propMapping ?? {});
setNodeMapping(props.current.documentConfig?.nodeMapping ?? {});
}, [props.current.documentConfig]);
function updateDocs(docs: XDocumentTemplate[]) {
setDocs(docs);
const docIds = docs.map((b) => b.id);
props.current.documentConfig = {
templates: docs,
propMapping: Object.fromEntries(
Object.entries(mapping).filter(([id]) => docIds.includes(id)),
),
nodeMapping: Object.fromEntries(
Object.entries(nodeMapping).filter(([id]) => docIds.includes(id)),
),
};
}
function updateMapping(currentMapping: [DocumentPropertyMapping[], DocumentNodeMapping[]]) {
mapping[row!.id] = currentMapping[0];
nodeMapping[row!.id] = currentMapping[1];
props.current.documentConfig = {
templates: docs,
propMapping: { ...mapping },
nodeMapping: { ...nodeMapping },
};
}
const columns: ColumnsType<XDocumentTemplate> = [
// { title: '序号', valueType: 'index' },
{
title: '标识',
dataIndex: 'code',
width: 80,
},
{
title: '名称',
dataIndex: 'name',
width: 200,
},
{
title: '操作',
dataIndex: 'operate',
fixed: 'right',
render: (_: any, record: XDocumentTemplate) => {
return (
<div>
<Button
type="link"
size="small"
style={{ marginRight: '4px' }}
className={cls['flowDesign-rule-edit']}
onClick={() => {
setOpenType('edit');
setRow(record);
setVisible(true);
}}>
配置映射
</Button>
<Button
type="link"
danger
size="small"
className={cls['flowDesign-rule-delete']}
onClick={() => {
updateDocs(docs.filter((b) => b.code != record.code));
}}>
删除
</Button>
</div>
);
},
width: 120,
},
];
function open() {
if (!isForm(props.formHost)) {
if (props.formHost.primaryForms.length == 0 && props.formHost.detailForms.length == 0) {
message.warning('当前环节没有配置表单!');
return;
}
}
setOpenType('add');
setVisible(true);
}
function renderButtons() {
return (
<div className={cls.layout}>
<Table rowKey="id" columns={columns} dataSource={docs} pagination={false} />
</div>
);
}
return (
<Card
type="inner"
className={cls['card-info']}
title={
<div>
<Divider
type="vertical"
style={{
height: '16px',
borderWidth: '4px',
borderColor: Theme.FocusColor,
marginLeft: '0px',
}}
/>
<span>文档模板配置</span>
</div>
}
bodyStyle={{ padding: docs.length > 0 ? '8px' : 0 }}
extra={
<>
<a
className="primary-color"
onClick={() => {
open();
}}>
添加
</a>
</>
}>
{docs.length > 0 && renderButtons()}
{visible &&
(openType == 'edit' ? (
<DocumentModal
current={row!}
mapping={currentMapping![0]}
nodeMapping={currentMapping![1]}
formHost={props.formHost}
onOk={(mapping, nodeMapping) => {
updateMapping([mapping, nodeMapping]);
setVisible(false);
}}
onCancel={() => setVisible(false)}
/>
) : (
<OpenFileDialog
accepts={['文档模板']}
rootKey={props.formHost.directory.spaceKey}
allowInherited
onOk={async (files) => {
const newDocs = files.map(
(f) => _.omit(f.metadata, ['rootElement']) as XDocumentTemplate,
);
updateDocs([
...docs,
...newDocs,
]);
setVisible(false);
await delay(50);
setOpenType('edit');
setRow(newDocs[0]);
setVisible(true);
}}
onCancel={() => setVisible(false)}
/>
))}
</Card>
);
};打印模板配置
关键组件
<Card
type="inner"
title={
<div>
<Divider type="vertical" className={cls['divider']} />
<span>打印模板设置</span>
</div>
}
className={cls['card-info']}
extra={
<>
<a
onClick={() => {
setPrintModalCreate(true);
}}>
添加
</a>
</>
}>
<Space
direction="vertical"
style={{
width: '100%',
}}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}>
<div style={{ fontSize: 14 }}>审批节点打印控制设置:</div>
<Switch
style={{
width: '150px',
}}
defaultValue={props.current.allowAllNodesPrint??true}
onValueChange={(value)=>{
props.current.allowAllNodesPrint = value;
}}
switchedOnText="允许所有节点打印"
switchedOffText="仅允许流程结束后打印"
/>
</div>
<SelectBox
showClearButton
value={printType}
placeholder={loading ? '加载默认打印模板中...' : '请选择打印模板'}
dataSource={loading ? [] : primaryPrints}
displayExpr={'name'}
valueExpr={'id'}
onFocusIn={() => {
setPrintType('');
}}
onValueChange={(e) => {
props.current.printData.type = e;
setPrintType(e);
if (e == '默认无') {
setPrintModal(false);
} else if (e == null) {
setPrintModal(false);
} else {
setPrintModal(true);
}
}}
itemRender={(data) => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ whiteSpace: 'nowrap' }}>{data.name}</span>
<CloseOutlined
onClick={(e) => {
e.stopPropagation();
if (data.id == '默认无') {
return false;
}
const newPrintData = primaryPrints.filter(
(option) => option.id !== data.id,
);
const newPrintData2 = props.current.printData.attributes.filter(
(option) => option.title !== data.id,
);
props.current.print = newPrintData;
setPrimaryPrints([...newPrintData]);
props.current.printData.attributes = newPrintData2;
}}
/>
</div>
)}
/>
</Space>
</Card>组件逻辑 (1)通过SelectBox组件选择打印模板
<SelectBox
showClearButton
value={printType}
placeholder={loading ? '加载默认打印模板中...' : '请选择打印模板'}
dataSource={loading ? [] : primaryPrints}
displayExpr={'name'}
valueExpr={'id'}
onFocusIn={() => {
setPrintType('');
}}
onValueChange={(e) => {
props.current.printData.type = e;
setPrintType(e);
if (e == '默认无') {
setPrintModal(false);
} else if (e == null) {
setPrintModal(false);
} else {
setPrintModal(true);
}
}}
itemRender={(data) => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ whiteSpace: 'nowrap' }}>{data.name}</span>
<CloseOutlined
onClick={(e) => {
e.stopPropagation();
if (data.id == '默认无') {
return false;
}
const newPrintData = primaryPrints.filter(
(option) => option.id !== data.id,
);
const newPrintData2 = props.current.printData.attributes.filter(
(option) => option.title !== data.id,
);
props.current.print = newPrintData;
setPrimaryPrints([...newPrintData]);
props.current.printData.attributes = newPrintData2;
}}
/>
</div>
)}(2)通过PrintConfigModal组件配置打印模板
const ConfigModal: FC<IProps> = (props) => {
const [print2, setPrint2] = useState<schema.XPrint>();
const [loaded, setLoaded] = useState<boolean>(false);
useEffect(() => {
const fetchData = async () => {
setLoaded(false);
const IPrints = await orgCtrl.loadFindPrint(
props.printType,
props.work?.metadata.shareId,
);
if (IPrints) {
setPrint2(IPrints as schema.XPrint);
}
setLoaded(true);
};
fetchData();
}, []);
return (
<FullScreenModal
open
title={'打印模板配置'}
onCancel={() => props.refresh(props.resource)}
destroyOnClose
width={'80vw'}
bodyHeight={'70vh'}>
<>
{!loaded && (
<div className="loading-page">
<LoadingView text="信息加载中..." />
</div>
)}
{props.type != 'default' && props.work && loaded && print2 && (
<PrintTemplate
resource={props.resource}
printType={props.printType}
work={props.work}
primaryForms={props.primaryForms}
detailForms={props.detailForms}
print={print2}
type={props.type}
/>
)}
{props.type == 'default' && loaded && print2 && (
<Drafts
resource={props.resource}
printType={props.printType}
ser={props.ser}
primaryForms={props.primaryForms}
detailForms={props.detailForms}
print={print2}
/>
)}
</>
</FullScreenModal>
);
};(3)通过OpenFileDialog组件添加新的打印模板
const OpenFileDialog: React.FC<IFileDialogProps> = (props) => {
const [selectedFiles, setSelectedFiles] = useState<IFile[]>([]);
const [key, rootMenu, selectMenu, setSelectMenu] = useMenuUpdate(() => {
if (props.onLoadMenu) {
return props.onLoadMenu();
}
return loadSettingMenu(props.rootKey, props.allowInherited || false);
}, new Controller(props.currentKey ?? orgCtrl.currentKey));
if (!selectMenu || !rootMenu) return <></>;
return (
<FullScreenModal
open
title={props.title ?? '选择文件'}
onCancel={() => {
props.onCancel();
setSelectedFiles([]);
}}
destroyOnClose
width={'80vw'}
bodyHeight={'70vh'}
footer={
<Space split={<Divider type="vertical" />} wrap size={2}>
<Button
type="primary"
onClick={() => {
props.onOk(selectedFiles);
setSelectedFiles([]);
}}>
确认
</Button>
</Space>
}>
<MainLayout
leftShow={props.leftShow === false ? false : true}
rightShow={props.rightShow === false ? false : true}
previewFlag={'dialog'}
selectMenu={selectMenu}
onSelect={(data) => {
setSelectMenu(data);
props.onSelectMenuChanged?.apply(this, [data]);
}}
siderMenuData={rootMenu}>
<Content
key={key}
showFile={props.showFile}
accepts={props.accepts}
selects={selectedFiles}
current={selectMenu.item}
excludeIds={props.excludeIds}
fileContents={props.fileContents}
onFocused={(file) => {
if (!props.multiple) {
if (file) {
setSelectedFiles([file]);
} else {
setSelectedFiles([]);
}
}
}}
onSelected={(files) => {
if (props.multiple) {
if (props.maxCount && files.length > props.maxCount) {
setSelectedFiles(files.slice(-props.maxCount));
} else {
setSelectedFiles(files);
}
}
}}
/>
</MainLayout>
</FullScreenModal>
);
};配置归档节点
组件实现
const EndNode: React.FC<IProps> = (props) => {
props.current.primaryForms = props.current.primaryForms || [];
props.current.detailForms = props.current.detailForms || [];
const [executors, setExecutors] = useState<model.Executor[]>(
props.current.executors ?? [],
);
const [executorModal, setExecutorModal] = useState(false);
return (
<div className={cls[`app-roval-node`]}>
typescript
<div className={cls[`roval-node`]}>
<Card
type="inner"
title={
<div>
<Divider type="vertical" className={cls['divider']} />
<span>执行器配置</span>
</div>
}
className={cls[`card-info`]}
bodyStyle={{ padding: executors && executors.length ? '24px' : '0' }}
extra={
<>
<a
onClick={() => {
setExecutorModal(true);
}}>
+ 添加
</a>
</>
}>
{executors && executors.length > 0 && (
<span>
<ExecutorShowComp
work={props.work}
executors={executors}
deleteFuc={(id: string) => {
var exes = executors.filter((a) => a.id != id);
setExecutors(exes);
props.current.executors = exes;
}}
/>
</span>
)}
</Card>
{executorModal && (
<ExecutorConfigModal
refresh={(param) => {
if (param) {
executors.push({
id: getUuid(),
trigger: param.trigger,
funcName: param.funcName,
changes: [],
hookUrl: '',
belongId: props.belong.id,
acquires: [],
copyForm: []
});
setExecutors([...executors]);
props.current.executors = executors;
}
setExecutorModal(false);
}}
current={props.current}
/>
)}
</div>
</div>
);
};Card 组件用于展示执行器配置。 Divider 组件用于分隔标题和内容。 extra 属性中的 a 标签用于触发添加执行器的模态框。 ExecutorShowComp 组件用于展示已添加的执行器,并允许删除。 ExecutorConfigModal 组件用于配置新的执行器,并在配置完成后刷新执行器列表。
配置条件分支编辑器
该模块允许用户添加、编辑和删除条件分支,并根据条件类型动态显示不同的操作符和输入控件。 组件实现
const ConditionNode: React.FC<Iprops> = (props) => {
if (props.conditions.length < 1) return <>请先选择主表且有特性</>;
const [key, setKey] = useState(0);
const [form] = Form.useForm();
const [currentNode, setCurrentNode] = useState<WorkNodeDisplayModel>();
useEffect(() => {
setCurrentNode(props.current);
form.setFieldsValue({ allContent: props.current.conditions });
}, [props]);
/**点击添加的时候默认增加一行 */
const addConditionGroup = () => {
props.current?.conditions?.push({
pos: props.current?.conditions.length + 1,
paramKey: '',
paramLabel: '',
key: '',
label: '',
type: dataType.NUMERIC,
val: undefined,
display: '',
});
setKey(key + 1);
};
const loadOperateItem = (condition: conditiondType, index: number) => {
if (condition.type != 'NUMERIC') {
return (
<Form.Item name={['allContent', index, 'key']}>
<Select
style={{ width: 80, backgroundColor: '#fff' }}
bordered={false}
placeholder="判断条件"
allowClear
options={[
{ value: 'EQ', label: '=' },
{ value: 'NEQ', label: '≠' },
]}
/>
</Form.Item>
);
} else {
return (
<Form.Item name={['allContent', index, 'key']}>
<Select
style={{ width: 100, backgroundColor: '#fff' }}
bordered={false}
placeholder="判断条件"
allowClear
options={[
{ value: 'EQ', label: '=' },
{ value: 'GT', label: '>' },
{ value: 'GTE', label: '≥' },
{ value: 'LT', label: '<' },
{ value: 'LTE', label: '≤' },
{ value: 'NEQ', label: '≠' },
]}
/>
</Form.Item>
);
}
};
const loadValueItem = (condition: conditiondType, index: number) => {
switch (condition.type) {
case 'DICT':
return (
<Form.Item name={['allContent', index, 'val']}>
<Select
style={{ width: 200, backgroundColor: '#fff' }}
bordered={false}
placeholder="请选择"
allowClear
fieldNames={{
label: 'text',
value: 'value',
}}
options={condition.dict || []}
/>
</Form.Item>
);
case 'NUMERIC':
return (
<Form.Item name={['allContent', index, 'val']}>
<InputNumber
bordered={false}
style={{ width: 200, backgroundColor: '#fff' }}
/>
</Form.Item>
);
default:
return (
<Form.Item name={['allContent', index, 'val']}>
<Input
style={{ width: 200, backgroundColor: '#fff' }}
bordered={false}
placeholder="请输入值"
allowClear
/>
</Form.Item>
);
}
};
const convertType = (valueType?: string) => {
switch (valueType) {
case '分类型':
case '选择型':
return dataType.DICT;
case '日期型':
return dataType.DATE;
case '用户型':
return dataType.BELONG;
case '数值型':
case '货币型':
return dataType.NUMERIC;
}
return dataType.STRING;
};
const onChange = async () => {
const currentValue = await form.getFieldsValue();
const newArr: string[] = []; // 重置当前条件 不然会越来越多 给不上值
currentNode?.conditions.map((item: conditiondType, index: number) => {
/** 怎么知道paramKey有没有变化 */
item.val = String(currentValue.allContent[index].val);
item.label = currentValue.allContent[index].label;
item.paramKey = currentValue.allContent[index].paramKey;
item.paramLabel = currentValue.allContent[index].paramLabel;
/**当前数组得替换一下 */
newArr.push(currentValue.allContent[index].paramKey);
// setParamKeyArr(newArr);
item.type = currentValue.allContent[index].type;
/**当前条件查找,填写paramLabel */
const findCon = props.conditions.find((innItem) => {
return innItem.id === currentValue.allContent[index].paramKey;
});
item.paramLabel = findCon ? findCon?.name : '';
item.type = convertType(findCon?.valueType);
item.key = currentValue.allContent[index].key;
if (findCon) {
/** 大于小于条件查找 */
const conkeys = getConditionKeys(item.type).find(
(innItem: { value: string; label: string }) => {
return innItem.value === currentValue.allContent[index].key;
},
);
item.label = conkeys ? conkeys?.label : '';
/** 查询符合条件的枚举值 */
if (item.type === dataType.DICT) {
const findConLabel = findCon?.lookups?.find((innItem) => {
return innItem.value === currentValue.allContent[index].val;
});
/** 枚举值赋值 */
item.valLabel = findConLabel?.text || '';
}
}
item.display = `${item.paramLabel} ${item.label} ${item.valLabel || item.val} `;
});
props.refresh();
};
return (
<div>
<Card
type="inner"
style={{ border: 'none' }}
headStyle={{
backgroundColor: '#FCFCFC',
padding: '0px 12px',
borderBottom: 'none',
}}
title={
<div>
<Divider
type="vertical"
style={{
height: '16px',
borderWidth: '4px',
borderColor: Theme.FocusColor,
marginLeft: '0px',
}}
className={cls['divider']}
/>
<span>条件分支</span>
</div>
}
className={cls['card-info']}
bodyStyle={{ padding: '0px', border: 'none' }}
extra={<a onClick={addConditionGroup}>+ 添加</a>}>
<Form key={key} form={form} onValuesChange={onChange}>
{(currentNode?.conditions || []).map((condition, index) => (
<div key={index + '_g'} className={cls['group']}>
<div className={cls['group-header']}>
<div
style={{
verticalAlign: 'middle',
lineHeight: '14px',
cursor: 'pointer',
}}
onClick={() => {
currentNode!.conditions.splice(index, 1);
setCurrentNode(currentNode);
setKey(key + 1);
}}>
<AiOutlineDelete />
</div>
<span className={cls['group-name']}>参数 {index + 1}</span>
<div className={cls['group-operation']}></div>
</div>
<div className={cls['group-content']}>
<Form.Item name={['allContent', index, 'paramKey']}>
<Select
style={{ width: 130, backgroundColor: '#fff' }}
bordered={false}
placeholder="请选择参数"
allowClear
options={(props.conditions || []).map((i) => {
return { label: i.name, value: i.id };
})}
onChange={(e) => {
props.conditions.forEach((element) => {
if (element.id == e) {
condition.type = convertType(element.valueType);
condition.paramKey = e;
condition.val = '';
condition.paramLabel = element.name;
if (element.lookups && element.lookups.length > 0) {
condition.dict = element.lookups;
}
setKey(key + 1);
}
});
}}
/>
</Form.Item>
{loadOperateItem(condition, index)}
{loadValueItem(condition, index)}
</div>
</div>
))}
</Form>
</Card>
</div>
);
};配置抄送节点
主要用于在某个工作流程中指定抄送对象,并根据不同的类型(如身份、角色、发起人等)来展示不同的选择方式。 组件定义
const CcNode: React.FC<IProps> = (props) => {
const [destType, setDestType] = useState<string>();
const [openType, setOpenType] = useState<string>(''); // 打开弹窗
const [currentData, setCurrentData] = useState<{ id: string; name: string }>();
const [destTypeSource, setDestTypeSource] = useState<
{
label: string;
value: string;
}[]
>();
useEffect(() => {
if (props.isGroupWork) {
setDestTypeSource([{ value: '角色', label: '指定角色' }]);
setDestType('角色');
} else {
setDestTypeSource([
{ value: '身份', label: '指定角色' },
{ value: '其他办事', label: '其他办事' },
{ value: '发起人', label: '发起人' },
]);
setDestType(props.current.destType ?? '身份');
}
props.current.primaryForms = props.current.primaryForms || [];
props.current.executors = props.current.executors || [];
setCurrentData({
id: props.current.destId,
name: props.current.destName,
});
}, [props.current]);
const loadDestType = () => {
switch (destType) {
case '身份': {
return (
<>
{currentData && (
<ShareShowComp
key={'审核对象'}
departData={[currentData]}
deleteFuc={(_) => {
props.current.destId = '';
props.current.destName = '';
setCurrentData(undefined);
props.refresh();
}}
/>
)}
</>
);
}
case '角色':
return (
<SelectAuth
excludeAll
disableExp={(auth: IAuthority) => {
return auth.metadata.shareId != props.work.metadata.shareId;
}}
space={props.belong}
value={props.current.destId}
onChange={(value, label) => {
if (props.current.destId !== value) {
props.current.destType = '角色';
props.current.destName = '角色: ' + label;
props.current.destId = value;
props.refresh();
}
}}
/>
);
case '发起人':
return <a>发起人</a>;
default:
return (
<>
{currentData && (
<ShareShowComp
key={'审核对象'}
departData={[currentData]}
deleteFuc={(_) => {
props.current.destId = '';
props.current.destName = '';
setCurrentData(undefined);
props.refresh();
}}
/>
)}
</>
);
}
};
const loadDialog = () => {
switch (openType) {
case '身份':
return (
<SelectIdentity
open={openType == '身份'}
exclude={[]}
multiple={false}
space={props.belong}
finished={(selected) => {
if (selected.length > 0) {
const item = selected[0];
props.current.destType = '身份';
props.current.destId = item.id;
props.current.destName = item.name;
setCurrentData(item);
props.refresh();
}
setOpenType('');
}}
/>
);
case '其他办事':
return (
<OpenFileDialog
title={'选择其它办事'}
rootKey={'disk'}
accepts={['办事', '集群模板']}
allowInherited
excludeIds={[props.work.id]}
onCancel={() => setOpenType('')}
onOk={(files) => {
if (files.length > 0) {
const work = files[0] as IWork;
let name = `${work.name} [${work.directory.target.name}]`;
props.current.destName = name;
props.current.destId = work.metadata.primaryId;
props.current.destShareId = work.metadata.shareId;
props.current.destWorkId = work.metadata.id;
setCurrentData({ id: work.id, name: name });
} else {
setCurrentData({
id: '',
name: '',
});
}
setOpenType('');
props.refresh();
}}
/>
);
default:
return <></>;
}
};
return (
<div className={cls[`app-roval-node`]}>
<div className={cls[`roval-node`]}>
<Card
type="inner"
title={
<div>
<Divider type="vertical" className={cls['divider']} />
<span>抄送对象</span>
</div>
}
className={cls[`card-info`]}
extra={
<>
<SelectBox
value={destType}
valueExpr={'value'}
displayExpr={'label'}
style={{ width: 120, display: 'inline-block' }}
onSelectionChanged={(e) => {
switch (e.selectedItem.value) {
case '身份':
props.current.destType = '身份';
setCurrentData(undefined);
break;
case '角色':
props.current.num = 1;
props.current.destType = '角色';
setCurrentData(undefined);
break;
case '其他办事':
props.current.destType = '其他办事';
setCurrentData(undefined);
break;
case '发起人':
props.current.num = 1;
props.current.destId = '1';
props.current.destName = '发起人';
props.current.destType = '发起人';
setCurrentData({ id: '1', name: '发起人' });
break;
default:
break;
}
if (destType != e.selectedItem.value) {
setDestType(e.selectedItem.value);
props.refresh();
}
}}
dataSource={destTypeSource}
/>
{!['发起人', '角色', undefined].includes(destType) && (
<>
<a
style={{ paddingLeft: 10, display: 'inline-block' }}
onClick={() => {
setOpenType(destType!);
}}>
{`+ 选择${destType === '身份' ? '角色' : destType}`}
</a>
</>
)}
</>
}>
{loadDestType()}
</Card>
{loadDialog()}
</div>
</div>
);
};配置并行节点
用于在用户界面中显示一个并行节点,通常用于工作流或审批流程中,表示多个任务可以同时进行。
const ConcurrentNode: React.FC<IProps> = (_props) => {
return (
<div className={cls[`app-roval-node`]}>
<div className={cls[`roval-node`]}>
<div>并行任务(同时进行)</div>
</div>
</div>
);
};字段加载
表单字段加载
for (const xform of [...primaryForms, ...detailForms]) {
const form = new Form({ ...xform, id: xform.id + '_' }, directory);
const xfields = await form.loadFields(false);
// 合并字段属性
fields.push(...xfields.map((a) => {
const baseField = { /* 基础字段配置 */ };
switch (a.valueType) { // 根据数据类型设置不同属性
case '数值型': baseField.xfield.dataType = 'number'; break;
case '选择型':
baseField.xfield.lookup = { /* 下拉选项配置 */ };
// ...其他类型处理
}
return baseField;
}));
}加载主表和子表字段,根据数据类型动态设置字段属性(数值、日期、下拉框等)
固定字段添加
// 办事流程时间
fields.push({
id: '888',
name: '办事流程时间',
value: type === 'work' ? formatZhDate(work.updateTime) : undefined,
xfield: { dataType: 'datetime' }
});
// 办事单位
fields.push({
name: '办事单位',
value: type === 'work' ? work.belong.name : undefined,
xfield: { dataType: 'string' }
});添加与业务流程强相关的固定字段,如“流程最后更新时间”和“所属单位名称”.
任务审核信息处理
if (type === 'work' && work.cacheFlag === 'worktask') {
const tasks = await fetchTasks(work as IWorkTask);
tasks.forEach(task => {
task.tasks.forEach(subTask => {
// 添加审核人字段
fields.push({
name: `${subTask.title}---------------审核人`,
value: getUserName(subTask.records[0].createUser)
});
// 添加审核结果、意见、时间(类似逻辑)
});
});
}遍历任务节点,提取每个审核步骤的“审核人、结果、意见、时间”数据,生成展示字段。
设计模式字段生成
if (type === 'design') {
const workNodes = await getWorkNodes(resource);
workNodes.forEach(node => {
fields.push({
name: `[${node.type}]审核人`,
field: 'createUser',
extra: { ...node } // 携带节点元数据
});
// 类似生成其他字段...
});
}在设计模式下预生成占位字段,用于模板设计时绑定数据。
表单配置处理
// 主表/子表分类处理
const tgs: FormMapping[] = [
...detailForms.map(a => ({
...a,
xform: { type: '子表', name: `[子表]${a.name}` }
})),
...primaryForms.map(a => ({
...a,
xform: { type: '主表', name: `[主表]${a.name}` }
}))
];
setForms(tgs); // 更新表单配置将原始表单分类为主表和子表,并添加标识用于模板渲染。