Antd实现 JSON form + 动态表单 + 可编辑表格(JSON) + 全类型Tyescript提示

前言

现在的后台的项目大同小异,form + Table。写法也是很多种。要么封装太厚,要么联动不好用,要不ts类型不齐全。因为后台表单场景还是千奇百怪的。用别人的未必通用 遇到问题没法调整,所以最好是自己实现 方便修改。这里就用最简单的代码实现下。

首先 现在大家一般用的方式有

  • 1.按照antd的一把嗦。灵活但麻烦
tsx 复制代码
<Form.Item label='xx' name='xxx'>
  <Input/>
</Form.Item>
  • 2.formity

    • 动态表单每一个动态应该都需要state控制并且传进去
    • 其次ts类型的支持的很差。
    • 没有特别清晰的文档
  • 3.ProComponents Avue 等 JSON多多少少有点小问题

    • ProComponents 的 SchemaForm 和编辑表格都有很多问题
    • Avue 没有具体研究过。但是vue没有一点ts提示,其他缺点应该也差不多

然后看了ProComponentsSchemaForm.shouldUpdate 控制是否渲染是在最外层的。但是何必自己实现呢。我只需要封装一个自定义的shouldUpdate的表单类型即可, 每个表单都可以单独写是否渲染的条件。

tsx 复制代码
 <BetaSchemaForm<DataItem>
        shouldUpdate={(newValues, oldValues) => {
          if (newValues.title !== oldValues?.title) {
            return true;
          }
          return false;
        }}
        layoutType="Form"
        columns={columns}
      />

因此想象中是这样使用的

tsx 复制代码
{
  type: 'update',
  col: 24,
  itemProps: {
    noStyle: true,
    shouldUpdate: (pre, cru) => {
      return !_.isEqual(pre?.xxx, cru?.xxx);
    },
    next: (values, form) => {
      const currentValues = values?.activity;
      if (currentValues?.busiType == '2') {
        return [
          {
            name: 'custName',
            label: '公司',
            type: 'input',
            col: 12,
            allowClear: true,
            placeholder: '请输入',
            rules: [{ required: true }],
            itemProps: ITEM_PROPS,
          },
        ];
      }
      return false;
    },
  },
}

所以就需要设计一个update表单类型。

tsx 复制代码
<Form.Item noStyle shouldUpdate={()=>xxx}>
  {form => {
    const values = form.getFieldsValue()
    const result = next(values, form)
    if(!result) return null;
    return renderFormItem(result)
  }}
</Form.Item>
  • 借助Antd.Form.Item.ShouldUpdate进行实现动态控制是否渲染
  • 通过next 传入当前所有表单的值 及form实例
  • 如果返回false 则不展示内容
  • 如果返回了数组 就调用JSON渲染表单函数渲染

实现

TODO迁移

最终效果

tsx 复制代码
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Col, Form, Input, Row, Switch } from 'antd';
import dayjs from 'dayjs';
import { ISearchesType, renderFormItem } from 'ims-view-pc';
import React, { Fragment } from 'react';

interface Option {
  value: string;
  label: string;
  children?: Option[];
  disabled?: boolean;
}

const dict = [
  { label: '1', value: 1 },
  { label: '2', value: 2 },
  { label: '3', value: 3 },
] as const;
const options: Option[] = [
  {
    value: 'zhejiang',
    label: 'Zhejiang',
    children: [
      {
        value: 'hangzhou',
        label: 'Hangzhou',
        children: [
          {
            value: 'xihu',
            label: 'West Lake',
          },
          {
            value: 'xiasha',
            label: 'Xia Sha',
            disabled: true,
          },
        ],
      },
    ],
  },
  {
    value: 'jiangsu',
    label: 'Jiangsu',
    children: [
      {
        value: 'nanjing',
        label: 'Nanjing',
        children: [
          {
            value: 'zhonghuamen',
            label: 'Zhong Hua men',
          },
        ],
      },
    ],
  },
];
const mentionsOptions = [
  {
    value: 'afc163',
    label: 'afc163',
  },
  {
    value: 'zombieJ',
    label: 'zombieJ',
  },
  {
    value: 'yesmeck',
    label: 'yesmeck',
  },
];
interface IRecord {
  cascader: string[];
  mentions: string;
  input: string;
  radio: (typeof dict)[number]['value'];
  editor: string;
  custom: any;
  update: AudioNode;
  updateInput: string;
  time: dayjs.Dayjs;
  date: dayjs.Dayjs;
  week: dayjs.Dayjs;
  month: dayjs.Dayjs;
  quarter: dayjs.Dayjs;
  year: dayjs.Dayjs;
  timeRange: dayjs.Dayjs[];
  dateRange: dayjs.Dayjs[];
  weekRange: dayjs.Dayjs[];
  monthRange: dayjs.Dayjs[];
  quarterRange: dayjs.Dayjs[];
  yearRange: dayjs.Dayjs[];
}

export default () => {
  const [form] = Form.useForm();
  const formList: ISearchesType<IRecord, { customParams?: '2' }> = [
    {
      name: 'cascader',
      label: 'cascader',
      type: 'cascader',
      dict: options as any[],
    },
    {
      name: 'mentions',
      label: 'mentions',
      type: 'mentions',
      initialValue: '@afc163',
      controlProps: {},
      fetchConfig: {
        request(params) {
          return new Promise((resolve) => {
            setTimeout(() => {
              resolve(mentionsOptions);
            }, 2000);
          });
        },
      },
    },
    {
      name: 'time',
      label: 'time',
      type: 'time',
    },
    {
      name: 'date',
      label: 'date',
      type: 'date',
    },
    {
      name: 'week',
      label: 'week',
      type: 'week',
    },
    {
      name: 'month',
      label: 'month',
      type: 'month',
    },
    {
      name: 'quarter',
      label: 'quarter',
      type: 'quarter',
    },
    {
      name: 'year',
      label: 'year',
      type: 'year',
    },
    {
      name: 'timeRange',
      label: 'timeRange',
      type: 'timeRange',
    },
    {
      name: 'dateRange',
      label: 'dateRange',
      type: 'dateRange',
      itemProps: {
        rules: [{ required: true }],
      },
    },
    {
      name: 'weekRange',
      label: 'weekRange',
      type: 'weekRange',
    },
    {
      name: 'monthRange',
      label: 'monthRange',
      type: 'monthRange',
    },
    {
      name: 'quarterRange',
      label: 'quarterRange',
      type: 'quarterRange',
    },
    {
      name: 'yearRange',
      label: 'yearRange',
      type: 'yearRange',
    },

    {
      name: 'input',
      label: 'input',
      type: 'input',
      initialValue: 'input',
      customParams: '2',
    },
    {
      name: 'radio',
      label: 'radio',
      type: 'radio',
      dict,
      itemProps: {
        extra: '当值为3时 显示update表单',
      },
      controlProps: {
        buttonStyle: 'solid',
      },
      initialValue: 1,
    },
    {
      name: 'updateInput',
      label: 'update',
      type: 'update',
      itemProps: {
        noStyle: true,
        shouldUpdate: (pre, cru) => pre.radio != cru.radio,
        next: (values, form) => {
          if (values?.radio != 3) return false;
          return [
            {
              label: 'update',
              name: 'updateInput',
              type: 'input',
            },
          ];
        },
      },
    },
    {
      name: 'editor',
      label: 'editor',
      type: 'editor',
      initialValue: '<h1>editor</h1>',
    },
    {
      name: 'custom',
      label: 'custom',
      type: 'custom',
      initialValue: true,
      Component: (props) => {
        return (
          <div>
            <Switch checked={!!props?.value} onChange={(e) => props.onChange(!props?.value)} />
            <div>{!!props?.value ? 'light' : 'dark'}</div>
          </div>
        );
      },
    },
    {
      label: 'list',
      type: 'custom',
      itemProps: {},
      Component(props) {
        return (
          <Form.List
            initialValue={[{ name: 1 }]}
            name="names"
            rules={[
              {
                validator: async (_, names) => {
                  if (!names || names.length < 1) {
                    return Promise.reject(new Error('At least 1 passengers'));
                  }
                },
              },
            ]}
          >
            {(fields, { add, remove }, { errors }) => (
              <>
                {fields.map((field, index) => (
                  <Form.Item noStyle key={field.key}>
                    <Row gutter={16}>
                      <Col>
                        <Form.Item name={[field.name, 'name']}>
                          <Input placeholder="passenger name" />
                        </Form.Item>
                      </Col>
                      <Col>
                        <MinusCircleOutlined onClick={() => remove(field.name)} />
                      </Col>
                    </Row>
                  </Form.Item>
                ))}
                <Form.Item noStyle>
                  <Button type="dashed" onClick={() => add()} icon={<PlusOutlined />}>
                    Add field
                  </Button>

                  <Form.ErrorList errors={errors} />
                </Form.Item>
              </>
            )}
          </Form.List>
        );
      },
    },
  ];

  return (
    <Form
      scrollToFirstError
      labelAlign="right"
      labelCol={{ span: 3 }}
      wrapperCol={{ span: 21 }}
      style={{ overflow: 'auto' }}
      form={form}
      onFinish={(value) => console.log(value)}
    >
      {formList.map((item, index) => (
        <Fragment key={index}>{renderFormItem({ ...item, form })}</Fragment>
      ))}
      <div>
        <Button type="primary" htmlType="submit">
          submit
        </Button>
      </div>
    </Form>
  );
};
相关推荐
狂炫一碗大米饭8 分钟前
Vue 3 的最佳开源分页库
前端·程序员·设计
你听得到1125 分钟前
告别重复造轮子!我从 0 到 1 封装一个搞定全场景的弹窗库!
前端·flutter·性能优化
Ali酱31 分钟前
2周斩获远程offer!我的高效求职秘诀全公开
前端·后端·面试
Cyanto1 小时前
Vue浅学
前端·javascript·vue.js
一只小风华~1 小时前
CSS aspect-ratio 属性
前端·css
Silver〄line1 小时前
以鼠标位置为中心进行滚动缩放
前端
LaiYoung_1 小时前
深入解析 single-spa 微前端框架核心原理
前端·javascript·面试
Danny_FD2 小时前
Vue2 + Node.js 快速实现带心跳检测与自动重连的 WebSocket 案例
前端
uhakadotcom2 小时前
将next.js的分享到twitter.com之中时,如何更新分享卡片上的图片?
前端·javascript·面试