Skip to content

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); // 更新表单配置

将原始表单分类为主表和子表,并添加标识用于模板渲染。

云原生应用研究院版权所有