后台管理系统的三驾马车——表单篇

138 阅读3分钟

写在前面

经常开发后台管理系统的前端同学都有这样的感悟,后台系统里充斥着大量的表格、表单和图表;对这三个方向的内容组件化封装,就可以极大的提升开发效率,快速生成页面,快速交付。

今天我们主要来聊一聊表单

表单方案应该考虑什么

性能?校验?开发体验?

我们从实现一个登录表单出发

刀耕火种时代,我们是这么编写的

const login = () => {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const handleSubmit = () => {
        if(!username){
            message.error('账号不能为空')
        }
        /* 校验 */
        ajax({username, password})
    }
    
    return <div>
        <div>
            <span>账号:</span>
            <Input value={username} onChange={e => setUsername(e.target.value)} />
        </div>
        <div>
            <span>密码:</span>
            <Input value={password} onChange={e => setPassword(e.target.value)} type="password" />
        </div>
        <button onClick={handleSubmit}>提交</button>
    </div>
}

不管代码如何,确实能完美的解决我们的业务场景;

如果表单方案能接管输入校验数据,那么真是帮了我们大忙。

查阅一些组件库,不难找到这样的代码

const login = () => {
  const onFinish = (values) => {
      console.log('Success:', values);
  };

  return <Form initialValues={{ remember: true }} onFinish={onFinish}>
        <Form.Item
          label="Username"
          name="username"
          rules={[{ required: true, message: 'Please input your username!' }]}
        >
          <Input />
        </Form.Item>
    
        <Form.Item
          label="Password"
          name="password"
          rules={[{ required: true, message: 'Please input your password!' }]}
        >
          <Input.Password />
        </Form.Item>
    
        <Form.Item name="remember" valuePropName="checked" label={null}>
          <Checkbox>Remember me</Checkbox>
        </Form.Item>
    
        <Form.Item label={null}>
          <Button type="primary" htmlType="submit">
            Submit
          </Button>
        </Form.Item>
      </Form>
  }

初始值使用initialValues,数据的中间态由表单组件消费,校验也统统交给表单来负责,作为组件的使用方只关心表单点击提交后的数据,只需传入onFinish方法,便可以从方法的入参中得到该表单所有的数据。

是的,似乎是完美的方案了。

万恶的PM提出了新的要求,我希望验证码和密码是互斥的,就像大部分的登录页面一样,可以选择手机号+验证码的方式登录,也可以选择账号+密码的方式登录。我们需要把当前使用的方法类型也提交到后端去。

上述方案中我们不关心表单中数据的流向问题,但是我们似乎需要关心关心类型字段了;

当我们选择了账号登录,那么验证码的表单项就应该隐藏;反之密码框则隐藏;

查阅表单库的api文档似乎也能解决现在遇到的问题,但是代码就显得十分臃肿了。那么有没有比较完美的方案来解决遇到的问题呢。

动态表单

前端动态表单是一种在网页浏览器端(即前端)能够根据用户交互或者系统预设的规则动态改变其结构、内容和行为的表单。与传统的静态表单不同,静态表单的字段、布局等在页面加载时就已经固定,而动态表单可以在运行时进行添加、删除、隐藏 / 显示字段,改变字段类型、验证规则等操作。

以上文案复制来源为豆包。

看AI提供的动态表单解释,可以隐藏显示字段、校验规则,似乎比我们之前的方案更加高级了一些,也可以完美解决PM提供更高级别的要求。

我们拿formilyjs来实现类似的效果

const login = () => {
    return <Tabs.TabPane key="1" tab="账密登录">
        <Form
          form={normalForm}
          layout="vertical"
          size="large"
          onAutoSubmit={console.log}
        >
          <Field
            name="username"
            title="用户名"
            required
            decorator={[FormItem]}
            component={[
              Input,
              {
                prefix: <UserOutlined />,
              },
            ]}
          />
          <Field
            name="password"
            title="密码"
            required
            decorator={[FormItem]}
            component={[
              Password,
              {
                prefix: <LockOutlined />,
              },
            ]}
          />
          <Submit block size="large">
            登录
          </Submit>
        </Form>
      </Tabs.TabPane>
}

代码摘录为formilyjs.org,虽然与我们设想有些出入,但是查阅文档可以找到reactions的写法

reactions: async (field: Field) => {
  if (field.query('type').value() === '账号登录') {
        field.hidden = false;
      } else {
        field.hidden = true;
      }
    },
},

只需要简单书写,即可根据表单项来控制表单项的显示隐藏

至此,我们选用formilyjs作为系统的表单方案

我们希望书写上更为简洁一些,对 <Field ``/>组件做一些简单封装

return    <>
      <CustomFieldComponent
        name={name}
        title={title}
        required={required}
        dataSource={dataSource}
        validator={validator}
        reactions={reactions}
        readPretty={readPretty}
        hidden={hidden}
        description={description}
        {...fieldProps}
        decorator={[FormItem, formItemProps]}
        component={[Component, props]}
      />
</>
{/* {tips ? <div className="field-form-tips">{tips}</div> : null} */}

举个例子

我拿我们项目中修改密码的表单做一个例子

目前简单表单都可以使用配置化的方案来实现,工时几乎忽略不计。