【图文并茂】ant design pro 如何优雅奇妙地让创建和更新页面共用一个 form

在这里插入图片描述
在这里插入图片描述
如上图所示

在这里插入图片描述
一般来说,新建和编辑页面的内容应该是差不多的。

如果分开来写,每个 form 都写一份代码,那就太复杂了。一改的话,可能要改两次

如何共用呢,还要做到,有时候他们有些差异的,比如用户修改的时候可以不用填密码,但是创建时一定要有密码。

创建页面

src/pages/Auth/Roles/components/Create.tsx

import { useIntl } from '@umijs/max';
import { FormInstance, Modal } from 'antd';
import BasicForm from './BasicForm';
import { values } from 'lodash';

interface Props {
  open: boolean;
  onOpenChange: (visible: boolean) => void;
  onFinish: (formData: any) => Promise<void>;
}

interface Props {
  form?: FormInstance<any>;
  newRecord?: boolean;
  onFinish: (formData: any) => Promise<void>;
  values?: any;
}

const Create: React.FC<Props> = (props) => {
  const intl = useIntl();
  const { open, onOpenChange, onFinish } = props;
  return (
    <Modal
      title={intl.formatMessage({ id: 'add_new' })}
      width="45%"
      open={open}
      onCancel={() => onOpenChange(false)}
      destroyOnClose={true}
      maskClosable={false}
      footer={null}
    >
      <BasicForm values={values} newRecord onFinish={onFinish} />
    </Modal>
  );
};

export default Create;

编辑页面

import { useIntl } from '@umijs/max';
import React from 'react';
import BasicForm from './BasicForm';
import { Modal } from 'antd';

export type FormValueType = Partial<API.ItemData>;

export type UpdateFormProps = {
  onCancel: (visible: boolean) => void;
  onSubmit: (values: FormValueType) => Promise<void>;
  updateModalOpen: boolean;
  values: {
    roles?: { id: number }[];
  } & Partial<API.ItemData>;
};

const UpdateForm: React.FC<UpdateFormProps> = (props) => {
  const intl = useIntl();
  const { updateModalOpen, onCancel, onSubmit, values } = props;
  return (
    <Modal
      maskClosable={false}
      width="50%"
      destroyOnClose
      title={intl.formatMessage({ id: 'modify' })}
      open={updateModalOpen}
      footer={false}
      onCancel={() => onCancel(false)}
    >
      <BasicForm values={values} onFinish={onSubmit} />
    </Modal>
  );
};

export default UpdateForm;

可以看到它们用的同一个 BasicForm ,只是传参可能有些不同。

index 的引用

{(access.canSuperAdmin || access.canCreateRole) && (
        <Create
          open={createModalOpen}
          onOpenChange={handleModalOpen}
          onFinish={async (value) => {
            const success = await handleAdd(value as API.ItemData);
            if (success) {
              handleModalOpen(false);
              if (actionRef.current) {
                actionRef.current.reload();
              }
            }
          }}
        />
      )}
      {(access.canSuperAdmin || access.canUpdateRole) && (
        <Update
          onSubmit={async (value) => {
            const success = await handleUpdate(value);
            if (success) {
              handleUpdateModalOpen(false);
              setCurrentRow(undefined);
              if (actionRef.current) {
                actionRef.current.reload();
              }
            }
          }}
          onCancel={handleUpdateModalOpen}
          updateModalOpen={updateModalOpen}
          values={currentRow || {}}
        />
      )}

共用的 form

src/pages/Auth/Roles/components/BasicForm.tsx

import { useIntl } from '@umijs/max';
import React, { Key, useState } from 'react';
import { ProForm, ProFormText } from '@ant-design/pro-components';
import { Form, Input, Spin, Tree } from 'antd';
import useQueryList from '@/hooks/useQueryList';
import { FormInstance } from 'antd/es/form';
import { Permission } from '@/apiDataStructures/ApiDataStructure';

interface Props {
  form?: FormInstance<any>;
  newRecord?: boolean;
  onFinish: (formData: any) => Promise<void>;
  values?: any;
}

const BasicForm: React.FC<Props> = ({ newRecord, onFinish, values }) => {
  const intl = useIntl();
  const { items: permissionGroups, loading } = useQueryList('/permission-groups/list');

  const [expandedKeys, setExpandedKeys] = useState<Key[]>([]);
  const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
  const [checkedKeys, setCheckedKeys] = useState<Key[] | { checked: Key[]; halfChecked: Key[] }>(
    values.permissions?.map((permission: Permission) => `${permission._id}`) ?? [],
  );
  const [selectedKeys, setSelectedKeys] = useState<Key[]>([]);

  const onExpand = (expandedKeysValue: Key[]) => {
    setExpandedKeys(expandedKeysValue);
    setAutoExpandParent(false);
  };

  const onCheck = (checkedKeysValue: Key[] | { checked: Key[]; halfChecked: Key[] }) => {
    setCheckedKeys(checkedKeysValue);
    console.log('checkedKeysValue', checkedKeysValue);
  };

  const onSelect = (selectedKeysValue: Key[]) => {
    setSelectedKeys(selectedKeysValue);
  };

  return (
    <ProForm
      initialValues={{
        ...values,
        permissions: values?.permissions?.map((permission: Permission) => permission._id),
      }}
      onFinish={async (values) => {
        await onFinish({
          ...values,
          permissions: checkedKeys,
        });
      }}
    >
      <ProForm.Group>
        <ProFormText
          rules={[{ required: true, message: intl.formatMessage({ id: 'enter_name' }) }]}
          width="md"
          label={intl.formatMessage({ id: 'name' })}
          name="name"
        />

        <ProForm.Item name="permissions" label={intl.formatMessage({ id: 'permission_choose' })}>
          <Spin spinning={loading}>
            <Tree
              checkable
              onExpand={onExpand}
              expandedKeys={expandedKeys}
              autoExpandParent={autoExpandParent}
              onCheck={onCheck}
              checkedKeys={checkedKeys}
              onSelect={onSelect}
              selectedKeys={selectedKeys}
              treeData={permissionGroups} // Use filtered top-level groups
              fieldNames={{ title: 'name', key: '_id', children: 'children' }}
            />
          </Spin>
        </ProForm.Item>
      </ProForm.Group>

      {!newRecord && (
        <Form.Item name="_id" label={false}>
          <Input type="hidden" />
        </Form.Item>
      )}
    </ProForm>
  );
};

export default BasicForm;

差异

如果要让创建和编辑页面有不一样的地方

我们只要传不同的值,判断一下即可

比如创建页面

<BasicForm values={values} newRecord onFinish={onFinish} />

更新页面:

<BasicForm values={values} onFinish={onSubmit} />

看到 newRecord 没有,表示是创建页面

然后 basicForm 里

      {!newRecord && (
        <Form.Item name="_id" label={false}>
          <Input type="hidden" />
        </Form.Item>
      )}

我的更新页面并不需要传 _id ,所以做了一个判断即可。

或者密码这里:

        <ProFormText
          rules={[{ required: newRecord, message: intl.formatMessage({ id: 'enter_password' }) }]}
          width="md"
          label={intl.formatMessage({ id: 'password' })}
          name="password"
        />

只有新增页面才是 required: newRecord

完结。

Web前端开发中,我们可以将"介绍热干面"这个主题制作成一个交互式的静态网页,通过HTML、CSS和JavaScript来实现。下面是一个简单的框架: **总页面 (index.html)** ```html <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>热干面之旅</title> <link rel="stylesheet" href="styles.css"> </head> <body> <header> <h1>欢迎来到热干面世界</h1> <nav> <a href="#history">历史</a> | <a href="#recipe">制作工艺</a> </nav> </header> <main id="content"> <!-- 此处内容会通过JS动态加载 --> </main> <script src="scripts.js"></script> </body> </html> ``` **子页面 - 历史 (history.html)** ```html <!DOCTYPE html> <html lang="zh"> <head> <title>热干面的历史</title> </head> <body> <h2>热干面历史概览</h2> <p>图文并茂的内容...<img src="history.jpg" alt="热干面历史图片"></p> <button onclick="loadContent('index')">返回首页</button> </body> </html> ``` **子页面 - 制作工艺 (recipe.html)** ```html <!DOCTYPE html> <html lang="zh"> <head> <title>热干面制作工艺详解</title> </head> <body> <h2>热干面详细步骤</h2> <p>图文教程...<img src="recipe.jpg" alt="制作工艺图解"></p> <button onclick="loadContent('index')">返回首页</button> </body> </html> ``` **scripts.js (部分脚本)** ```javascript function loadContent(targetPage) { var content = document.getElementById('content'); if (targetPage === 'index') { content.innerHTML = '<iframe src="index.html"></iframe>'; } else { content.innerHTML = '<iframe src="' + targetPage + '.html"></iframe>'; } } ``` 在这个示例中,当用户点击导航链接时,`loadContent`函数会切换到对应子页面的内容。总页面利用了IFrame技术,在内部显示其他页面
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员随风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值