网络日志

xflow流程可视化-designable高级配置

上篇简要介绍了formilyjs以及基于其构建了表单设计器,实现了表单动态设计已经预览功能,本片将介绍表单设计器的一些高级配置,使其能更好的运用于我们的节点编辑以及连线编辑。

designable高级配置

请求及自定义数据注入

一般来说,我们页面的请求都是统一封装好的,在使用时我们如何把我们封装好的请求或者已经有的数据源注入到我们的表单设计器中呢:SchemaField 组件是专门用于解析JSON-Schema动态渲染表单的组件,在使用createSchemaField时我们可以传入scope来注入到全局作用域中链接:

import { FC, Ref, useEffect, useImperativeHandle, useState } from 'react';
import { Modal } from 'antd';
import {
  FormItem,
  Input,
  Form,
  Submit,
  ArrayBase,
  ArrayCards,
  ArrayCollapse,
  ArrayItems,
  ArrayTable,
  ArrayTabs,
  BaseItem,
  Cascader,
  Checkbox,
  DatePicker,
  Editable,
  FormButtonGroup,
  FormCollapse,
  FormGrid,
  FormTab,
  GridColumn,
  NumberPicker,
  Password,
  PreviewText,
  Radio,
  Reset,
  Select,
  SelectTable,
  Space,
  Switch,
  TimePicker,
  Transfer,
  TreeSelect,
  Upload,
} from '@formily/antd';
import api from '@/api';
import { createSchemaField } from '@formily/react';
import { LgetItem } from '@/utils/storage';
import { createForm } from '@formily/core';

interface PreviewProps {
  previewRef: Ref<{ setVisible: (flag: boolean) => void }>;
  modalConfig: { [key: string]: any };
}

const SchemaField = createSchemaField({
  components: {
    Input,
    ArrayBase,
    ArrayCards,
    ArrayCollapse,
    ArrayItems,
    ArrayTable,
    ArrayTabs,
    BaseItem,
    Cascader,
    Checkbox,
    DatePicker,
    Editable,
    Form,
    FormButtonGroup,
    FormCollapse,
    FormGrid,
    FormItem,
    FormTab,
    GridColumn,
    NumberPicker,
    Password,
    PreviewText,
    Radio,
    Reset,
    Select,
    SelectTable,
    Space,
    Submit,
    Switch,
    TimePicker,
    Transfer,
    TreeSelect,
    Upload,
  },
  scope: {
    $fetch: api,
    selectList: [{ label: 'aaa', value: 'aaa' }, { label: 'bbb', value: 'bbb' }]
  }
});

const Preview: FC<PreviewProps> = ({ previewRef, modalConfig }) => {
  const [visible, setVisible] = useState(false);
  useImperativeHandle(previewRef, () => ({
    setVisible,
  }));

  const [params, setParams] = useState({});
  const normalForm = createForm({});
  useEffect(() => {
    if (modalConfig && visible) {
      const playgroundList = LgetItem('playgroundList') || [];
      const data = playgroundList.find((s) => s.id === modalConfig.id);
      setParams(data?.params || {});
    }
  }, [modalConfig, visible]);

  const handleCancel = () => {
    setVisible(false);
  };

  return (
    <Modal
      title="模板预览"
      visible={visible}
      onCancel={handleCancel}
      footer={null}
    >
      <Form form={normalForm} onAutoSubmit={console.log} {...params.form}>
        <SchemaField schema={params.schema} />
        <Submit block>保存</Submit>
      </Form>
    </Modal>
  );
};

export default Preview;

在使用表单设计器时,便可以使用注入的scope值,比如我们准备一个json,选择一个下拉控件,配置相应器,然后通过我们注入的请求获取这个json,展示对应的下拉值:

 $effect(() => {
        $self.loading = true
        $fetch({
            url: '/getSelectList',
            method: 'get',
            params: {}
        }).then(res => {
            $self.loading = false
            // 当返回值不是label和value时转化一下
            $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
        }).catch(() => {
            $self.loading = false
        })
    }, [])

这里的$fetch就是我们注入的请求了

表单配置

  1. form提交的key为字段标识,可自定义修改,默认为随机字符串
  2. 标题为form表单的label
  3. 支持自定义搜索的,比如select,需要在组件属性下筛选器中配置表单式,才能支持label搜索

    (inputValue, option) => {
        return option.label.indexOf(inputValue) !== -1;
    }

  4. 如果配置了响应器规则中和外部一样的配置,比如组件属性,那么响应器规则中的会覆盖外部的配置(外部的组件属性失效)

高级配置

高级配置在响应器规则中,这里主要针对于一些普通配置无法实现的功能进行说明,比如常见的联动,动态取值等场景进行说明。

  • $self为当前选中的表单对象
  • $form为form对象
  • $deps为依赖对象(需在上方依赖字段配置来源字段)
  • $observable声明一个可观察对象
  • $effect和react的useEffect使用类似
  • $values为提交的form表单对象
  1. 动态枚举值,假如我们有一个select控件,这个控件的值是接口返回的

    $effect(() => {
        $self.loading = true
        $fetch({
            url: '/getSelectList',
            method: 'get',
            params: {}
        }).then(res => {
            $self.loading = false
            // 当返回值不是label和value时转化一下
            $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
        }).catch(() => {
            $self.loading = false
        })
    }, [])
  2. 联动变化枚举值,比如我们有两个select,第一个select是mechanism机构,第一个select是user人员,我们选择一个机构后,第二个select会根据第一个select的值来从接口中获取机构下的人员列表,这是一个比较常见的联动选择功能,那么放在表单设计器里面我们该如何实现呢(实现方式不唯一,这里提供思路)。

    // 第一个select,我们监听mechanism的变化,flag主要应用为跳过初次渲染(保证反显正常展示),当mechanism改版(即手动选值)后,清空user的取值。
    $effect(() => {
        $self.loading = true
        $fetch({
            url: '/getMechanismList',
            method: 'get',
            params: {}
        }).then(res => {
            $self.loading = false
            // 当返回值不是label和value时转化一下
            $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
        }).catch(() => {
            $self.loading = false
        })
    }, [])
    const state = $observable({ flag: false });
    $effect(() => {
        if (state.flag) {
            $form.reset('user');
        }
        state.flag = true;
    }, [$self.value])

    mechanism变化时,清空user列表,发起请求获取user列表

        $effect(() => {
            $self.dataSource = []
            if ($deps.mechanism) {
                $self.loading = true
                    $fetch({
                    url: '/getSelectList',
                    method: 'get',
                    params: {
                        mechanism: $deps.mechanism
                    }
                }).then(res => {
                    $self.loading = false
                    // 当返回值不是label和value时转化一下
                    $self.dataSource = res.map(s => ({ label: res.name, value: res.id }))
                }).catch(() => {
                    $self.loading = false
                })
            }
        }, [$deps.mechanism])

小试牛刀

我们使用mock数据简单做一个联动

// mock/api.ts
export default {
  'GET /api/mechanism': [
    {
      value: '1',
      label: '机构1',
    },
    {
      value: '2',
      label: '机构2',
    },
  ],
  'GET /api/users': (req, res) => {
    // 添加跨域请求头
    const query: any = req.query;
    const user: any = {
      '1': [
        {
          value: '1-1',
          label: '机构1-人员1',
        },
        {
          value: '1-2',
          label: '机构1-人员2',
        },
      ],
      '2': [
        {
          value: '2-1',
          label: '机构2-人员1',
        },
        {
          value: '2-2',
          label: '机构2-人员2',
        },
      ],
    };
    res.send(user[query.id]);
  },
};

我们定义了两mock接口,mechanismusers,前者返回一个机构列表,后者根据前者的id返回对应的列表,接着我们在上一期中的预览弹窗中注入请求,这里我们直接使用umi提供的request

// Preview.tsx
import { request } from 'umi';
...

const SchemaField = createSchemaField({
  components: ...,
  scope: {
    $fetch: request,
  },
});

最后我们在表单设计中添加请求

// mechanism
$effect(() => {
  $self.loading = true
  $fetch("/api/mechanism", {})
    .then((res) => {
      $self.loading = false
      // 当返回值不是label和value时转化一下
      $self.dataSource = res
    })
    .catch(() => {
      $self.loading = false
    })
}, [])

// users
$effect(() => {
  $self.dataSource = []
  if ($deps.mechanism) {
    $self.loading = true
    $fetch("/api/users", {
      params: {
        id: $deps.mechanism,
      },
    })
      .then((res) => {
        $self.loading = false
        // 当返回值不是label和value时转化一下
        $self.dataSource = res
      })
      .catch(() => {
        $self.loading = false
      })
  }
}, [$deps.mechanism])

最后再到表单模板预览一下:

好了,本篇介绍的功能都实现了,那么下一篇我们将把流程可是化的编辑功能和我们的表单模板进行关联起来,修改表单模板就能修改节点或连线的属性编辑,达到真正的动态属性编辑效果,尽情期待。

本文地址:链接 本文github地址:链接github demo地址:链接