Form 資料形式配置化設計

語言: CN / TW / HK

表單資料形式配置化設計

在日常的頁面開發中,表單是和我們打交道非常多的名詞。你是否遇到過自己寫的 input 提示文案被測試或 UED 糾正過、是否看到同樣的校驗電話號碼的正則在團隊裡各有各樣。是否希望這些可以規範起來呢?答案是肯定的!

表單展示屬性的關係

例如一個簡單的電話輸入框,有提示暗文 placeholder,校驗規則的提示資訊;再如一個選擇框,它亦也有提示暗文、校驗規則的提示資訊。咋看之下這個什麼暗文呀!什麼校驗提示好像沒什麼關係。觀察下圖,輸入框的暗文提示就是元件本身所表達的意思請輸入,必填的校驗提示資訊則是元件本身含義 + 業務的名稱(及表單元件的label屬性)。其他型別的校驗則提示和校驗規則相互繫結。

流程中表單資料形式

在具有流程節點的頁面中,資料的形式是隨節點變化的。如第一個節點的資料只能在第一個節點是表單元件形式可編輯的,而在其他節點雖然可以看到第一節點的資料,但卻是純文字的形式。這些從表單元件形式資料到文字形式資料的存在如下的對映關係。

表單元件 純文字
input 輸入的文字
textArea 輸入的文字
select 選擇key 的對應label文字,多選情況下一般用分號分割各key對應的label文字
inputNumber 輸入的數字文字
... ...

表單資料收集

在 react + antd 技術棧下,資料收集可以使用表單元件的 onChange 事件收集到你想要存放的變數上,其次是使用 Form 元件提供的資料收集能力。其中 Form 元件提供的資料收集能力並不只侷限對原有常見表單元件,還有自定義的表單元件。舉個較為特別的:chestnut:如下圖的 FormText 元件,Form 可以幫你收集文字形式展示的內容。當然這種表單文字元件只要不是你的 UI 小夥伴要求用來代替表單其他元件的 readonly/disabled 形式的話,就只有透傳資料或處理時簡化資料讀取。

/**
 * 用於 Form 表單內部受控展示文字
 */
export default class FormText extends Component {
  render() {
    const { value, formatMethod = a => a, defaultText = '-', ...resetProps } = this.props;
    return <span {...resetProps}>{formatMethod(value) || defaultText}</span>;
  }
}

// 使用
<FormItem label="姓名">
  {getFieldDecorator('name', {
      initialValue: 'egg',
  })(<FormText />)}
</FormItem>

形式對映異常

我們都知道表單多行文字輸入框一般都會輸入很長很長的文字,但其自身有著已被大家認可的滾動樣式等。但將一段很長的文字框資料展示為文字資料時,你會看到你整個視窗都在展示這個欄位的資料。當然這只是形式變換中的一個較明顯的差異,如何正確變換需我們同 UI 小夥伴一起努力制定一套規範。如當前這個問題就可以給文字一個高度讓他也滾動也不是不行。

配置化的設計方案

經過前面的敘述,童鞋們應該對怎樣設計有了大致的輪廓。主要就是表單元件的選擇、使用什麼形式收集資料、如何對映為 UI想要的文字形式。整體結構如下:

1、形式選擇(表單元件 or 文字)

const renderDataForm = (form, conf = {}) => {
  // customRenderText 自定義文字形式
  const { dataForm = 'form', customRenderText } = conf;
  return (
    <FormItem label={conf.label} {...conf.formItemProps} >
      {dataForm === 'form' ? renderFormItem(form, conf) : 
        customRenderText ? customRenderText(conf) : renderText(conf) }
    </FormItem>
  );
};

2、表單元件選擇

export const renderFormItem = (form, rest) => {
  const { getFieldDecorator } = form;
  const { label = '', field = '', formItemType = 'input', initialValue, required = true, rules = [], ...itemRest } = rest;
  return (getFieldDecorator(field, {
    initialValue: renderInitialValue(initialValue, formItemType),
    rules: [
      // 必填提示
      { required, message: renderMessage(formItemType, label) },
      ...rules,
    ],
    ...(formItemType === 'upload' ? { 
      getValueFromEvent: (e) => {
        if (Array.isArray(e)) {
          return e;
        }
        return e && e.fileList;
      },
      valuePropName: 'fileList' } : {}),
  })(
    renderItem(formItemType, itemRest)
  ));
};

// 選擇表單元件
const renderItem = (formItemType, itemRest) => {
  const { CustomFormItem } = itemRest;
  let item = <Input placeholder="請輸入" {...itemRest} />;
  switch (formItemType) {
  case 'textArea': 
    item = <TextArea placeholder="請輸入" {...itemRest} />; break;
  // ...code
  // 自定義表單元件
  case 'customFormItem': 
    item = (CustomFormItem ? CustomFormItem() : <FormText />); break;
  default: 
  }
  return item;
};

3、對映文字

export const renderText = (rest) => {
  const { formItemType = 'input', initialValue, selectOptions = [], selectMode = '', options = [] } = rest;
  switch (formItemType) {
  case 'radioGroup': 
    return (options.find(item => item.value === initialValue) || {}).label || '-';
  case 'datePick': 
    const { format = 'YYYY-MM-DD HH:mm:ss' } = rest;
    return initialValue !== undefined ? moment(initialValue).format(format) : '-';
  // ...code
  default: 
    return bizStringFormat(initialValue);
  }
}

4、通用校驗規則整理

export const postCode = /^[0-9]{6}$/;
export const phone = /^1\d{10}$/;
// ...code

// form rules
export const postCodeRule = {
  pattern: postCode,
  message: '請輸入6位數字',
};
export const phoneRule = { 
  pattern: phone,
  message: '請輸入11位號碼',
};
// ...code

5、使用 已實現文章最上面的圖列效果為例,如下

const Demo = (props) => {
  const { form } = props;
  // 因為資料的形式預設為表單,所以 dataForm: 'form' 可不配置
  const formConf = [{
    label: '郵箱',
    feild: 'email',
    rules: [emailRule], // emailRule 為郵箱校驗規則
  }, {
    label: '地址',
    field: 'addr',
    formItemType: 'textArea',
    maxLength: 50,
  }, {
    label: '排序',
    field: 'sort',
    formItemType: 'select',
    selectOptions: [{ value: 'up', label: '升序' }, { value: 'down', label: '降序' }]
  }];
  // 將配置遍歷傳入renderDataForm
  // 當然你也可以封裝成組建,直接向組建傳入 form、formConf,減少遍歷的重複書寫和整潔
  return formConf.map(item => renderDataForm(form, item));

結束語

至此,本文設計了一套基礎表單的配置功能,主要目的是提升表單使用的規範化、區分和理解前端資料的形式。

❉ 作者介紹 ❉