关于前端:xflow流程可视化designable表单设计

42次阅读

共计 6641 个字符,预计需要花费 17 分钟才能阅读完成。

本篇次要介绍 formilyjs,它是阿里的对立前端表单解决方案,领有比拟残缺的表单设计和简单的场景解决性能,当然本篇次要通俗的介绍其表单设计及展现性能,将其利用到咱们的流程可视化自定义编辑中,至于 formilyjs 自定义开发控件的话后续有机会独自篇章进行阐明。

designable 可视化表单设计

formilyjs 官网提供了 Formily 设计器,咱们当初把这个表单设计器拉下来变成咱们的一个页面:

新建 src/playground 目录,

yarn add @designable/formily-antd @ant-design/pro-components

去设计器 github,把 playgroundsrc目录下相干文件拷贝下来放入 playground/details 中,调整一下援用构造(略过,见代码)。

// routes
{
  name: '表单模板',
  path: '/playground',
  icon: 'FormOutlined',
  component: '@/pages/playground',
},
{
  name: '表单设计',
  path: '/playground/details',
  component: '@/pages/playground/details',
  headerRender: false,
  footerRender: false,
  menuRender: false,
  hideInMenu: true,
},

表单模板列表

新建 playground/index.tsxplayground/Preview.tsx,前者用作新建表单模板,后者为表单设计后的预览

// playground/index.tsx
import type {ActionType, ProColumns} from '@ant-design/pro-components';
import {ProTable} from '@ant-design/pro-components';
import {PageContainer} from '@ant-design/pro-layout';
import {FormItem, Input, FormDialog, FormLayout} from '@formily/antd';
import {createSchemaField} from '@formily/react';
import {Dropdown, Menu, Popconfirm, Space, Button} from 'antd';
import {FC, useRef, useState} from 'react';
import Preview from './Preview';
import {LgetItem, LsetItem} from '@/utils/storage';
import {uuidv4} from '@antv/xflow';
import {history} from 'umi';

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
  },
});

const schema = {
  type: 'object',
  properties: {
    name: {
      type: 'string',
      title: '模板名',
      required: true,
      'x-decorator': 'FormItem',
      'x-component': 'Input',
    },
  },
};

const Playground: FC = () => {const [modalConfig, setModalConfig] = useState<{[key: string]: any }>({});
  const previewRef = useRef<{setVisible: (flag: boolean) => void }>(null);
  const actionRef = useRef<ActionType>();
  const columns: ProColumns<any>[] = [
    {
      dataIndex: 'name',
      title: '模板名',
    },
    {
      dataIndex: 'params',
      title: '是否配置',
      renderText(text, record, index, action) {return text ? '是' : '否';},
      search: false,
    },
    {
      title: '操作',
      valueType: 'option',
      render: (_, record) => {
        return [
          <a
            key="preview"
            onClick={() => {setModalConfig(record);
              previewRef.current?.setVisible(true);
            }}
          >
            预览
          </a>,
          <a key="edit" onClick={() => handleEdit(record)}>
            配置
          </a>,
          <a key="del" onClick={() => handleDel(record)}>
            删除
          </a>,
        ];
      },
    },
  ];

  const handleEdit = (record: any) => {history.push(`/playground/details?id=${record.id}`);
  };

  const handleDel = (record: any) => {const playgroundList = LgetItem('playgroundList') || [];
    LsetItem(
      'playgroundList',
      playgroundList.filter((s) => s.id !== record.id),
    );
    actionRef.current?.reload();};

  const handleAdd = () => {FormDialog('新增模板', () => {
      return (<FormLayout labelCol={6} wrapperCol={10}>
          <SchemaField schema={schema} />
        </FormLayout>
      );
    })
      .forOpen((payload, next) => {
        next({
          initialValues: {name: '',},
        });
      })
      .forConfirm((payload, next) => {const playgroundList = LgetItem('playgroundList') || [];
        playgroundList.push({name: payload.getFormState().values.name,
          id: uuidv4(),});
        LsetItem('playgroundList', playgroundList);
        actionRef.current?.reload();
        next(payload);
      })
      .forCancel((payload, next) => {next(payload);
      })
      .open()
      .then(console.log);
  };

  const getData = async (params: any) => {const data = LgetItem('playgroundList');
    return {data: data ?? [],
      success: true,
      total: data?.length ?? 0,
    };
  };

  return (
    <PageContainer>
      <ProTable
        columns={columns}
        actionRef={actionRef}
        request={async (params: any, _sorter: any, _filter: any) => {return await getData(params);
        }}
        rowKey="id"
        pagination={{showQuickJumper: true,}}
        toolBarRender={() => [<Button key="button" type="primary" onClick={handleAdd}>
            新增
          </Button>,
        ]}
      />
      <Preview previewRef={previewRef} modalConfig={modalConfig} />
    </PageContainer>
  );
};

export default Playground;
// playground/Preview.tsx
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 * as aaa from '@formily/antd';
import {createSchemaField} from '@formily/react';
import {LgetItem} from '@/utils/storage';
import {createForm} from '@formily/core';
console.log(aaa);
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,
  },
});

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;

这里咱们简略的把建设的表单数据存储在本地 localStorage 中,点击配置后跳转到咱们的设计页面,
调整一下表单设计的保留,我应用设计器拖拽设计实现后,进行数据存储,找到对应 id 的表单模板将设计的 schema 赋值给params

// details/service/schema.ts
import {Engine} from '@designable/core';
import {
  transformToSchema,
  transformToTreeNode,
} from '@designable/formily-transformer';
import {message} from 'antd';
import {LgetItem, LsetItem} from '@/utils/storage';

function fixUrlHash(url: string) {let fixedUrl = new URL(url);
  let search = fixedUrl.search;
  let hash = fixedUrl.hash;
  const position = fixedUrl.hash.indexOf('?');
  if (search.length <= 1 && position >= 0) {search = hash.slice(position);
    hash = hash.slice(0, position);
    fixedUrl.hash = hash;
    fixedUrl.search = search;
    fixedUrl.href = fixedUrl.toString();}
  return fixedUrl;
}

export const saveSchema = (designer: Engine) => {const url = fixUrlHash(window.location.href);
  const searchParams = new URLSearchParams(url.search);
  let playgroundList = LgetItem('playgroundList') || [];
  playgroundList = playgroundList.map((s) => {if (s.id === searchParams.get('id')) {
      return {
        ...s,
        params: transformToSchema(designer.getCurrentTree()),
      };
    }
    return s;
  });
  LsetItem('playgroundList', playgroundList);
  message.success('保留胜利');
};

export const loadInitialSchema = (designer: Engine) => {const url = fixUrlHash(window.location.href);
  const searchParams = new URLSearchParams(url.search);
  const playgroundList = LgetItem('playgroundList') || [];
  const data = playgroundList.find((s) => s.id === searchParams.get('id'));
  try {designer.setCurrentTree(transformToTreeNode(data?.params || []));
  } catch {}};

而后回到模板列表页,点击预览查看咱们设计的表单是否失常显示

下一章我将介绍表单设计器的一些高级配置,比方动静申请,封装接口注入,表单联动配置等,敬请期待。

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

正文完
 0