You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React JSON Schema Form数组的每个项中添加备注/笔记按钮

如何在React JSON Schema Form数组的每个项中添加备注/笔记按钮

嘿,我来帮你搞定这个需求!要给RJSF数组的每个项加上备注功能,得兼顾UI交互和数据关联——既要让用户能点击按钮调出输入框,还要确保笔记能和对应项绑定、一起保存。下面是具体的实现思路和代码修改方案:

核心思路

RJSF是Schema驱动的表单库,最稳妥的方式是调整Schema结构:把原来单一的数组项(比如JWT字符串)改成对象,这样就能在每个项里同时存原数据和备注。再通过自定义字段组件添加按钮、控制备注输入框的显示/隐藏,完美适配你的需求。

具体修改步骤

1. 调整Auth Schema的结构

先把数组项从string改成包含原数据和备注的对象,以authorization为例:

{
  id: 'authorization',
  title: 'Authorization',
  schema: {
    type: 'array',
    items: {
      type: 'object',
      properties: {
        token: {
          type: 'string',
          pattern: '^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$',
          title: 'JWT Token'
        },
        note: {
          type: 'string',
          title: 'Note',
          description: 'Add a memo for this token'
        }
      },
      required: ['token'] // token必填,note可选
    },
  },
  uiSchema: {
    items: {
      'ui:field': TokenWithNoteField, // 指定自定义字段组件
    },
  },
}

account项也可以用同样逻辑,给每个account对象添加note字段。

2. 自定义数组项组件(带备注按钮)

创建一个组件,用来渲染每个数组项,包含原数据输入框、"Add Note"按钮和备注输入框:

const TokenWithNoteField = (props) => {
  const { formData, onChange, schema, uiSchema } = props;
  const [showNoteInput, setShowNoteInput] = useState(false);

  // 同步token输入变化到formData
  const handleTokenChange = (newTokenValue) => {
    onChange({ ...(formData || {}), token: newTokenValue });
  };

  // 同步备注输入变化到formData
  const handleNoteChange = (newNoteValue) => {
    onChange({ ...(formData || {}), note: newNoteValue });
  };

  return (
    <div style={{ marginBottom: '16px', padding: '8px', border: '1px solid #f0f0f0', borderRadius: '4px' }}>
      {/* 渲染Token的textarea输入框 */}
      <RjsfForm.Field
        name={`${props.name}.token`}
        schema={schema.properties.token}
        uiSchema={{ 'ui:widget': 'textarea', 'ui:placeholder': 'Paste JWT Token here' }}
        formData={formData?.token}
        onChange={handleTokenChange}
      />

      {/* 切换备注框显示/隐藏的按钮 */}
      <Button
        type="text"
        onClick={() => setShowNoteInput(!showNoteInput)}
        style={{ margin: '8px 0' }}
      >
        {showNoteInput ? 'Hide Note' : 'Add Note'}
      </Button>

      {/* 备注输入框,默认隐藏 */}
      {showNoteInput && (
        <RjsfForm.Field
          name={`${props.name}.note`}
          schema={schema.properties.note}
          uiSchema={{ 'ui:placeholder': 'Write a note about this token...' }}
          formData={formData?.note}
          onChange={handleNoteChange}
        />
      )}
    </div>
  );
};

这个组件会处理每个项的UI状态(备注框显隐),同时自动把token和note的变化同步到formData中。

3. 适配数据加载和保存

  • 加载数据:如果数据库里原来存的是纯token数组(比如["token1", "token2"]),在useEffect取数据后要转换成对象数组:
    useEffect(() => {
      const fetchData = async () => {
        // 假设从数据库拿到原始string数组
        const rawData = await fetchAuthData(auth.id, source.id);
        // 转换为带空note的对象数组
        const formattedData = rawData?.map(token => ({ token, note: '' })) || [];
        setFormData(formattedData);
      };
      fetchData();
    }, [auth.id, source.id]);
    
  • 保存数据:handleSubmit里的formData已经包含每个项的token和note,直接保存到数据库即可——它们本来就是同一个对象的属性,关联关系天然存在。

4. 完整整合后的代码片段

把上面的内容整合到现有代码中,关键部分大概是这样:

// 先定义自定义组件
const TokenWithNoteField = (props) => {
  const { formData, onChange, schema, uiSchema } = props;
  const [showNoteInput, setShowNoteInput] = useState(false);

  const handleTokenChange = (newTokenValue) => {
    onChange({ ...(formData || {}), token: newTokenValue });
  };

  const handleNoteChange = (newNoteValue) => {
    onChange({ ...(formData || {}), note: newNoteValue });
  };

  return (
    <div style={{ marginBottom: '16px', padding: '8px', border: '1px solid #f0f0f0', borderRadius: '4px' }}>
      <RjsfForm.Field
        name={`${props.name}.token`}
        schema={schema.properties.token}
        uiSchema={{ 'ui:widget': 'textarea', 'ui:placeholder': 'Paste JWT Token here' }}
        formData={formData?.token}
        onChange={handleTokenChange}
      />

      <Button
        type="text"
        onClick={() => setShowNoteInput(!showNoteInput)}
        style={{ margin: '8px 0' }}
      >
        {showNoteInput ? 'Hide Note' : 'Add Note'}
      </Button>

      {showNoteInput && (
        <RjsfForm.Field
          name={`${props.name}.note`}
          schema={schema.properties.note}
          uiSchema={{ 'ui:placeholder': 'Write a note about this token...' }}
          formData={formData?.note}
          onChange={handleNoteChange}
        />
      )}
    </div>
  );
};

// 修改后的authSchema
const auth = {
  authSchema: [
    {
      id: 'authorization',
      title: 'Authorization',
      schema: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            token: {
              type: 'string',
              pattern: '^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$',
              title: 'JWT Token'
            },
            note: {
              type: 'string',
              title: 'Note',
              description: 'Add a memo for this token'
            }
          },
          required: ['token'],
        },
      },
      uiSchema: {
        items: {
          'ui:field': TokenWithNoteField,
        },
      },
    },
    // account项也可类似修改,添加note字段
    {
      id: 'account',
      title: 'Account',
      schema: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            username: {
              type: 'string',
              title: 'Username',
              minLength: 5,
              pattern: '^[a-z0-9]+$',
            },
            password: {
              type: 'string',
              title: 'Password',
              minLength: 8,
            },
            note: {
              type: 'string',
              title: 'Account Note',
            }
          },
          required: ['username', 'password'],
        },
      },
      uiSchema: {
        items: {
          password: {
            'ui:widget': 'password',
          },
        },
      },
    },
  ]
};

// TabItem组件调整数据加载逻辑
function TabItem({source, auth}: TabItemProps): JSX.Element{
  const [formData, setFormData] = useState();
  
  const handleSubmit = async (id: string, event: any): Promise<void> => {
    // 直接保存包含note的formData到数据库
    await saveAuthData(id, source.id, event.formData);
  };
  
  useEffect(() => {
    const fetchData = async () => {
      const rawData = await getAuthData(auth.id, source.id);
      if (auth.id === 'authorization') {
        // 转换为带note的对象数组
        const formatted = rawData?.map(t => ({ token: t, note: '' })) || [];
        setFormData(formatted);
      } else if (auth.id === 'account') {
        // 给现有account项补全note字段
        const formatted = rawData?.map(item => ({ ...item, note: item.note || '' })) || [];
        setFormData(formatted);
      } else {
        setFormData(rawData);
      }
    };
    fetchData();
  }, [auth.id, source.id]);
  
  return (
    <RjsfForm
      formData={formData}
      focusOnFirstError={true}
      liveValidate={true}
      schema={auth.schema}
      uiSchema={auth.uiSchema}
      validator={validator}
      onSubmit={(e) => handleSubmit(auth.id, e)}
      showErrorList={false}
    />
  );
}

常见疑问解答

  • 为什么要修改Schema?:RJSF是Schema驱动的,所有需要持久化的数据都应该定义在Schema里,这样formData会自动包含这些字段,不需要手动维护关联关系,保存和加载都更省心。
  • 能不能不修改Schema?:理论上可以用自定义模板添加UI元素,但笔记数据无法和formData绑定,得自己手动管理状态和关联,后续维护成本很高,不推荐。

备注:内容来源于stack exchange,提问作者VQH DEV

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 19:58:11