Giter Site home page Giter Site logo

ant-design / sunflower Goto Github PK

View Code? Open in Web Editor NEW
500.0 14.0 43.0 6.98 MB

🦹 Process components for antd4 & antd3 by alipay industry technology

Home Page: https://ant-design.github.io/sunflower

License: MIT License

JavaScript 38.97% TypeScript 61.03%
react-hooks antd ant-design react-component react

sunflower's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar imgbotapp avatar jiacheng9 avatar leviding avatar mr-zyy avatar nitta-honoka avatar twisger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sunflower's Issues

Column排序,筛选该如何处理?

有两个疑问可以麻烦解答一下吗?

  1. Column中的sorteronFilter是通过Table的onChange事件去单独处理吗?
  2. useFormTable配置中search方法的参数如何与Column中的sorteronFilter参数进行合并?

useModelForm spec

import { Modal, Form } from 'antd';
import { useModalForm } from 'sunflower-antd';

function App(props) {
  const { modalProps, formProps, show, close, visible } = useModal({
    defaultVisible: false,  // default  default visible
    autoSubmitClose: true, // default   close modal after submit
    async submit() {},
  })
  return <>
      <Modal okText="submit" {...modalProps}>
        <Form {...formProps} />
      </Modal>
       
       modal visible: {visible}
       <a onClick={() => show()}>show modal</a>
       <a onClick={() => close()}>close modal</a>
  </>
}

export default App;

click Modal "ok", will trigger submit, then close modal

RoadMap

milestone 1

antd hooks

  • useCascadeSelect
  • useFormTable
  • useModal
  • useForm
  • useModalForm
  • useStepsForm

test

  • more test and cov #24

docs and examples

  • i18n
  • more examples

boilerplate

  • boilerplate project with all hooks

search-table need initialValues

用于将值回填到 Form

type StoreBaseValue = string | number | boolean;
type StoreValue = StoreBaseValue | Store | StoreBaseValue[];
interface Store {
  [name: string]: StoreValue;
}

initialValues: () => (Store | Promise<Store>)

const obj = useSearchResult({
  initialValues,
  search,
});

useFormTable spec

修改点:

  1. 默认不返回 HOC,要是需要可通过其他 hooks 使用 useFormTable 来返回。
  2. 返回 formProps, tableProps
  3. 接收 form 实例,不在 useFormTable 创建 form

antd3

import { Form, Table } from 'antd';
import { useFormTable } from 'sunflower-antd';

function App(props) {
  const { formProps, tableProps, form } = useFormTable({
    form: props.form,
    search() {},
  })
  return <>
    <Form layout="inline" {...formProps} >
       <Form.Item>
            {form.getFieldDecorator('username')(
                <Input />,
            )}
       </Form.Item>
    </Form>
    <Table {...tableProps} />
  </>
}

export default Form.create()(App);

antd4

import { Form, Table } from 'antd';
import { useFormTable } from 'sunflower-antd';

function App() {
  const { formProps, tableProps, form } = useFormTable({
    form, // 可选通过 const [form] = Form.useForm() 传入,也可不传
    search() {},
  })
  return <>
    <Form layout="inline" {...formProps} >
       <Form.Item>
           <Input />
       </Form.Item>
    </Form>
    <Table {...tableProps} />
  </>
}

export default App;

useStepsForm does not preserve form field values when returning to Previous step

Hi guys, thanks in advance for the help

I've imported useStepsForm and used it as shown in the example code. Since the very beginning of using this hook I've not been able to preserve the field values when returning to a previous step in my form. The validation does work in this case.

If I then take the form prop returned from the useStepsForm hook and name it something besides 'form', the validation fails but the data from each field is properly preserved on previous steps after progressing further in the form. I suspect the validation fails because in this case the form isn't actually the same one as before, so it doesn't recognize the validation rules.

Any insights on this issue would be greatly appreciated! Let me know if you need any other information

const [rootForm] = Form.useForm();

const { form, current, gotoStep, submit } = useStepsForm({
    submit: async (values) => {
      onFinish(values);
      await new Promise((r) => setTimeout(r, 1000));
      return 'ok';
    },
    form: rootForm,
    total: fieldsKeys.length,
    isBackValidate: false,
  });

useModal spec

import { Modal, Form } from 'antd';
import { useModal } from 'sunflower-antd';

function App(props) {
  const { modalProps, show, close, visible } = useModal({
    defaultVisible: false,  // default  default visible
    autoSubmitClose: true, // default   close modal after submit
    async submit() {},
  })
  return (
    <>
      <Modal {...modalProps}>
          {content}
      </Modal>
       
       modal visible: {visible}
       <a onClick={() => show()}>show modal</a>
       <a onClick={() => close()}>close modal</a>
  </>)
}

export default App;

为什么做 sunflower

出发点

sunflower 出发点是为了让使用 antd 开发业务更高效。

业务中有不少看起来差不多的页面,但是这些看起来差不多的流程在不同的应用需要重复使用 antd 进行编码。虽然可采用模板,但是模板的维护性跟更新都是问题,而另外的一个方式是做组件。

最先的方式就是做组件,一个组件对应一个配置。对于使用者,根据文档来对这个组件进行 json 配置使用,比如要做一个搜索表单:

image

之前的做法是这样的,开发一个 SearchResult 组件,进行配置使用:

<FormTable config={{
  fields: [{
    label: 'Username',
    name: 'username'
    type: 'input',
  }],
  columns: [{
     title: 'Username',
     dataIndex: 'username'
  }, {
     title: 'Email',
     dataIndex: 'email'
  }],
  search: () => {}
}}/>

疑惑

在业务中进行使用起来刚开始好像很方便,但其实有不少问题:

  • json 相对 jsx 表现力差。对于复杂的 json,几乎不可维护
  • 业务需求多样式。比如在要 form 跟 table 之间加上一个文本,要在提交按钮的右边加上一个链接。

之前的做法是在配置中给出业务可能需要的点(哪些可能呢?找一个一个的业线来看,还不行就靠猜😆),比如在配置中有了很不靠谱的属性:

{
  tableTop: <div>这里业务可加上文本</div>,
  searchRight: <a>这里业务可加上链接</a>
}

想统一业务线不这样,但是从技术角度限制业务也不太对(产品说这里放个链接就是方便)。

还有一个问题就是 fields 的 type 太多了。一个是 antd 太强大,有不同的输入方式,比如 input,select,dataPicker 等,还有一个是业务中自定义的也很多。做到组件里这个组件也就不可维护了,也许也可采用自定义、插件等方式,比如:

{
  type: 'custom',
  render(value, onChange) {}
}

其实代码又有了 jsx,就成为了 json + jsx,维护性跟易用性也下降了。

探索

那业务中什么是能抽象的呢?比如搜索表单这个流程,用户进行搜索之后查看,是否能只包括这个流程,而 ui 则使用模板的方式。这个是第二次探索。受限于 antd3 的 Form 需要 HOC,新的方式采用 render props 来做,也就是:

{
  formTable(config, ({ getFieldDecorator, onSubmit, dataSource ... }) => <>
      <Form onSubmit={onSubmit}>
        <Form.Item>
          {getFieldDecorator('username', {
            rules: [{ required: true, message: 'Please input your username!' }],
          })(
            <Input />,
          )}
        </Form.Item>
      </Form>

      <Table columns={[{
          title: 'Username',
          dataIndex: 'username',
          key: 'username',
      }]} dataSource={dataSource} />
  </>)
}

对于用户,使用这个流程就不需要再去维护 onChange 等方法,方法跟状态都是流程在维护。而对于 ui 也可灵活自由。但是也有问题,render props 用起来也相对麻烦。接着又尝试修改 render props 到 react hooks。但是 antd3 的 Form 搞不定这个,因此只能暂时先用最新版本的底层 form 来做,使用则为:

function Component() {
  const { form, onFinish, dataSource, onPageChange, currentPage ... } = useFormTable({
    search: (values) => request(values),
  });
  return <div>
    <Form form={form} onFinish={onFinish}>
      <Form.Item
        label="Username"
        name="username"
      >
        <Input placeholder="Username" />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">
          Search
        </Button>
      </Form.Item>
    </Form>

    <Table
      columns={[
        {
          title: 'Username',
          dataIndex: 'username',
          key: 'username',
        },
        {
          title: 'Email',
          dataIndex: 'email',
          key: 'email',
        }
      ]}
      pagination={{
        currentPage,
        onChange: onPageChange,
      }},
      dataSource={dataSource}
      rowKey="id"
    />
  </div>;
}

看起来还不错。但流程复杂后,要用户在代码中去绑定的方法跟状态也会比较多,能否更方便一些?

绑定 props 复杂,那就 HOC 来搞,也就是 react hooks 做流程,HOC 做组件方便用户使用

function Component() {
import { useFormTable } from '@sunflower-antd/form-table';

  const { Form, Table } = useFormTable({
    search: (values) => request(values),
  });
  return <div>
    <Form>
      <Form.Item
        label="Username"
        name="username"
      >
        <Input placeholder="Username" />
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">
          Search
        </Button>
      </Form.Item>
    </Form>

    <Table
      columns={[
        {
          title: 'Username',
          dataIndex: 'username',
          key: 'username',
        },
        {
          title: 'Email',
          dataIndex: 'email',
          key: 'email',
        }
      ]}
      rowKey="id"
    />
  </div>;
}

Form, Table 其实就是 antd 的 Form 跟 Table 做的 HOC,让用户不用去绑定太多。用户能传 antd 对应组件的 props 到组件,这样 ui 能力则是 antd 的 ui 能力。而对于自定义的需求,也可使用方法跟状态,比如不想用 Table,想自定义方式:

import { useFormTable } from '@sunflower-antd/form-table';

const { Form, responseData } = useFormTable({
   search: (values) => request(values),
});

return <div>
   
    <Form>
         ...
    </Form>
    
    {
       responseList.list.map(item => <div>
         {item.username}
       </div>)
    }
</div>

设计

经过探索,sunflower 采用底层为 @sunflower-hooks,这一层是不包括 HOC 的 hooks,是描述业务流程的本质,称为“流程hooks”。在上层,使用 @sunflower-antd,这一层是包括 HOC 组件的,方便用户使用,依赖于 @sunflower-hooks,可称为“组件hooks”。

比如 @sunflower-antd/form-table 就是 antd 的表单搜索表格,底层依赖于 @sunflower-hooks/search-result@sunflower-hooks/search-result 去处理搜索返回的流程,用于查找等流程。也就是说 @sunflower-hooks/search-result 也可有不同的组件来依赖,比如 antd mobile 等。

以下是需要沉淀的 hooks:

  • 表单搜索结果页
  • 分布表单
  • 详情及编辑
  • modal 表单
  • 级联选择
  • 表单提交
  • 其他

hook似乎不应该返回一个组件

我自己尝试按照这个思路实现了一下搜索业务,发现存在一个问题,每次render的时候使用的都是不同的组件,更详细的内容可以参考这个讨论
另外useSearchResult的源码似乎有问题,useEffect等hook应该只能在纯函数组件的函数体中调用,我尝试源码中的写法会直接报错。

useForm spec

import React from 'react';
import { useForm } from 'sunflower-antd';
import { Input, Button, Form } from 'antd';


export default Form.create()(props => {
  const { form } = props;
  const { formProps, tableProps } = useForm({
    form,
    async submit(values) {},
    defaultFormValues: {},
  });
  return <div>
    <Form layout="inline" {...formProps}>
      <Form.Item label="Username">
        {
          form.getFieldDecorator('username')(
            <Input placeholder="Username" />
          )
        } 
      </Form.Item>

      <Form.Item label="Email">
        {
          form.getFieldDecorator('email')(
            <Input placeholder="Email" />
          )
        } 
      </Form.Item>

      <Form.Item>
        <Button onClick={() => form.resetFields()}>
          Reset
        </Button>
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit">
          Search
        </Button>
      </Form.Item>
    </Form>

  </div>
});

useStepFrom Steps onChange bug

import React from 'react'
import { useStepsForm } from 'sunflower-antd'
import { Steps, Input, Button, Form, Result } from 'antd'

const { Step } = Steps

const layout = {
	labelCol: { span: 8 },
	wrapperCol: { span: 16 },
}
const tailLayout = {
	wrapperCol: { offset: 8, span: 16 },
}

const Example = () => {
	const { form, current, gotoStep, stepsProps, formProps, submit, formLoading } = useStepsForm({
		async submit(values) {
			const { username, email, address } = values
			console.log(username, email, address)
			await new Promise((r) => setTimeout(r, 1000))
			return 'ok'
		},
		total: 4,
	})
	const formList = [
		<>
			<Form.Item
				label='username'
				name='username'
				rules={[
					{
						required: true,
						message: 'Please input username',
					},
				]}>
				<Input placeholder='Username' />
			</Form.Item>
			<Form.Item label='Email' name='email'>
				<Input placeholder='Email' />
			</Form.Item>
			<Form.Item {...tailLayout}>
				<Button onClick={() => gotoStep(current + 1)}>Next</Button>
			</Form.Item>
		</>,
		<>
			<Form.Item
				label='Age'
				name='age'
				rules={[
					{
						required: true,
						message: 'Please input age',
					},
				]}>
				<Input placeholder='age' />
			</Form.Item>
			<Form.Item {...tailLayout}>
				<Button onClick={() => gotoStep(current + 1)}>Next</Button>
			</Form.Item>
			<Button onClick={() => gotoStep(current - 1)}>Prev</Button>
		</>,
		<>
			<Form.Item
				label='Address'
				name='address'
				rules={[
					{
						required: true,
						message: 'Please input address',
					},
				]}>
				<Input placeholder='Address' />
			</Form.Item>
			<Form.Item {...tailLayout}>
				<Button
					style={{ marginRight: 10 }}
					type='primary'
					loading={formLoading}
					onClick={() => {
						submit().then((result) => {
							if (result === 'ok') {
								gotoStep(current + 1)
							}
						})
					}}>
					Submit
				</Button>
				<Button onClick={() => gotoStep(current - 1)}>Prev</Button>
			</Form.Item>
		</>,
	]

	return (
		<div>
			<Steps {...stepsProps}>
				<Step title='Step 1' />
				<Step title='Step 2' />
				<Step title='Step 3' />
				<Step title='Step 4' />
			</Steps>

			<div style={{ marginTop: 60 }}>
				<Form {...layout} {...formProps} style={{ maxWidth: 600 }}>
					{formList[current]}
				</Form>

				{current === 3 && (
					<Result
						status='success'
						title='Submit is succeed!'
						extra={
							<>
								<Button
									type='primary'
									onClick={() => {
										form.resetFields()
										gotoStep(0)
									}}>
									Buy it again
								</Button>
								<Button>Check detail</Button>
							</>
						}
					/>
				)}
			</div>
		</div>
	)
}

export default Example

In a scenario where I have more than two form fields, it sends the form ignoring the required fields because I can change it by clicking on the Steps.

It is a necessary behavior for users to change the form by clicking on it. In this case, the stepper structure does not work properly.

Only when I disable the onChange I can I get the form completely. But I don't think this is the best method.

Thank you for your help.

useStepsForm spec

import { Steps, Form, Button } from 'antd'
import { useStepsForm } from 'sunflower-antd'

const { Step } = Steps

function App(props) {
  const { form } = props
  const { stepProps, current, total, formProps } = useStepsForm({
    form,
    total: 2,
    defaultCurrent: 0,
    async submit() {}, // submit form
  })

  return (
    <>
      <Steps {...stepProps}>
        <Step title="first step" description="this is my first step" />
        <Step title="second step" />
      </Steps>
      <Form {...formProps}>
        {
          current === 0 && (
            <>
              <Form.Item label="username">
                {
                  form.getFieldDecorator('username')(
                    <Input placeholder="Username" />
                  )
                } 
              </Form.Item>
            </>
          )
        }
        {
          current === 1 && (
            <>
              <Form.Item label="Email">
                {
                  form.getFieldDecorator('email')(
                    <Input placeholder="Email" />
                  )
                } 
              </Form.Item>
            </>
          )
        }
      </Form>
      {
        current < total - 1 && <Button onClick={stepProps.goStep(1)}>下一步</Button>
      }
    </>
  )
}

auto controlled step state and get the related form props

useFormtable add reset方法

function App(props) {
  const { Form, Table, form } = useFormTable({
    async search({ username, email, currentPage, pageSize, reset }) {
      const res = await queryUsers({ username, email, currentPage, pageSize });
      return {
        list: res.result.list,
        total: res.result.total,
      };
    },
    resetAutoSearch: true,
  });
 const resetForm = () => { 
   reset(['username', 'email'])
 }
  return <>
     <Form layout="inline">
        <Form.Item label="用户名" name="username">
          <Input placeholder="Username" />
        </Form.Item>

        <Form.Item label="邮箱" name="email">
          <Input placeholder="Email" />
        </Form.Item>

        <Form.Item>
          <Button onClick={resetForm}>重置</Button>
        </Form.Item>

        <Form.Item>
          <Button type="primary" htmlType="submit">
            搜索
          </Button>
        </Form.Item>
      </Form>

      <Table
        style={{ marginTop: 12 }}
        bordered
        columns={[
          {
            title: '用户名',
            dataIndex: 'username',
            key: 'username',
          },
          {
            title: '邮箱',
            dataIndex: 'email',
            key: 'email',
          },
        ]}
        rowKey="id"
      />
  </>
}
  • useFormTable 接收resetAutoSearch默认为false, 暴露出reset 方法,若resetAutoSearch传值为true, 在清空表单时,再调用一次搜索接口,刷新表格

  • reset方法接收一个数组传参,只清除指定formFileds。默认清空整个表单。

import from @sunflower-antd

@sunflower-antd 依赖 @sunflower-hooks

用户使用一般情况只用管 @sunflower-antd

比如:

import { useSearchResult } from '@sunflower-antd/search-result';

function Component() {
  const { Form, Table } = useSearchResult(config);

  return <div>
      <Form />
      <Table />
  </div>
}

这样让 @sunflower-hooks 也更好测试,以及可被其他依赖,比如 antd mobile 等。

[Error on Development] Can't find module

Hi, I ran yarn then yarn start after forking this repo, but I have no idea why it still fails like this :

image

Steps to reproduce :

  1. Fork this repo.
  2. In the root directory of this repo, run yarn to install dependencies.
  3. Run yarn start.
  4. See the error above.

The error said :

[BABEL] /workspace/sunflower/.umirc.ts: Cannot find module '/workspace/sunflower/node_modules/@babel/preset-env/node_modules/@babel/compat-data/data/corejs3-shipped-proposals' (While processing: "/workspace/sunflower/node_modules/@umijs/babel-preset-umi/node.js$0")
Error: [BABEL] /workspace/sunflower/.umirc.ts: Cannot find module '/workspace/sunflower/node_modules/@babel/preset-env/node_modules/@babel/compat-data/data/corejs3-shipped-proposals' (While processing: "/workspace/sunflower/node_modules/@umijs/babel-preset-umi/node.js$0")

Any help will be appreciated, thank you.

search-result spec

image

const  obj  = useSearchResult(config);

config:

{
   search: (payload: Store) => (Promise<SearchResponseData> | SearchResponseData); // 发送请求,返回值必须为 SearchResponseData
   defaultPageSize?: number; // 默认的 pageSize,默认为 10
   defaultCurrentPage?: number; // 默认的当前页,默认为 1
   firstAutoSearch?: boolean; // 第一次默认请求,默认为 true
}

responseData

{
  list: object[];      // 列表
  total?: number;  // 总数 
}

obj

{
  Form, // antd form 组件
  Table, // antd Table 组件
  loading, // 是否在请求中
  requestData, // 请求参数
  setRequestData,   // 设置请求参数,比如用于自定义的一些请求 data 处理
  responseData, // 返回数据
  form, // antd form 实例
}

其中 Form.Item 有属性:

<Form.Item
  label="Username"
  name="username"
  rules={[{ required: true, message: 'Please input your username!' }]}
>
  <Input placeholder="Username" />
</Form.Item>
  • Input 为 antd 组件,value 跟 onChange 不用用户处理
  • rules 为 antd form rules
  • name 为 values key,跟 antd3 中使用 getFieldDecorator(name) 相同

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.