├── .circleci └── config.yml ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .umirc.ts ├── LICENSE ├── README.md ├── README.zh-CN.md ├── docs ├── getting-started │ ├── index.md │ ├── index.zh-CN.md │ ├── installation.md │ └── installation.zh-CN.md ├── index.md └── index.zh-CN.md ├── package.json ├── scripts ├── build.js └── pubDoc.js ├── src ├── form.ts ├── index.ts ├── useCascadeSearch │ └── index.ts ├── useCascadeSelect │ ├── index.md │ ├── index.ts │ └── index.zh-CN.md ├── useForm │ ├── index.md │ ├── index.ts │ └── index.zh-CN.md ├── useFormTable │ ├── index.md │ ├── index.ts │ └── index.zh-CN.md ├── useModal │ ├── index.md │ ├── index.ts │ └── index.zh-CN.md ├── useModalForm │ ├── index.md │ ├── index.ts │ └── index.zh-CN.md ├── useSearchResult │ └── index.ts └── useStepsForm │ ├── index.md │ ├── index.ts │ └── index.zh-CN.md ├── tests ├── useCascadeSearch.test.js ├── useCascadeSelect.test.js ├── useForm.test.js ├── useFormTable.test.js ├── useModal.test.js ├── useModalForm.test.js ├── useSearchResult.test.js └── useStepsForm.test.js ├── tsconfig.json ├── typings.d.ts └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | lint: 4 | docker: 5 | - image: circleci/node:latest 6 | steps: 7 | - checkout 8 | - restore_cache: 9 | keys: 10 | - yarn-packages-{{ checksum "yarn.lock" }} 11 | - run: yarn install --frozen-lockfile 12 | - save_cache: 13 | paths: 14 | - node_modules 15 | key: yarn-packages-{{ checksum "yarn.lock" }} 16 | - run: yarn lint 17 | test: 18 | docker: 19 | - image: circleci/node:latest 20 | steps: 21 | - checkout 22 | - restore_cache: 23 | keys: 24 | - yarn-packages-{{ checksum "yarn.lock" }} 25 | - run: yarn install --frozen-lockfile 26 | - save_cache: 27 | paths: 28 | - node_modules 29 | key: yarn-packages-{{ checksum "yarn.lock" }} 30 | - run: yarn cov 31 | - run: bash <(curl -s https://codecov.io/bash) 32 | build: 33 | docker: 34 | - image: circleci/node:latest 35 | steps: 36 | - checkout 37 | - restore_cache: 38 | keys: 39 | - yarn-packages-{{ checksum "yarn.lock" }} 40 | - run: yarn install --frozen-lockfile 41 | - save_cache: 42 | paths: 43 | - node_modules 44 | key: yarn-packages-{{ checksum "yarn.lock" }} 45 | - run: yarn build 46 | 47 | workflows: 48 | version: 2 49 | lint_test_build: 50 | jobs: 51 | - lint 52 | - test 53 | - build 54 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('@umijs/fabric/dist/eslint')], 3 | rules: { 4 | 'import/no-unresolved': 0, 5 | 'no-underscore-dangle': 0, 6 | 'import/no-extraneous-dependencies': 0, 7 | 'global-require': 0, 8 | 'import/no-dynamic-require': 0, 9 | 'react/sort-comp': 0, 10 | 'jsx-a11y/aria-role': 0, 11 | 'dot-notation': 0, 12 | 'consistent-return': 0, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | yarn-error.log 4 | coverage 5 | dist 6 | es 7 | lib 8 | .DS_Store 9 | .umi 10 | .umi-production 11 | .umi-test 12 | .env.local -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | **/*.ejs 3 | **/*.html 4 | package.json 5 | .umi 6 | .umi-production 7 | .umi-test 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi'; 2 | 3 | export default defineConfig({ 4 | title: 'Sunflower', 5 | mode: 'site', 6 | base: '/sunflower/', 7 | publicPath: '/sunflower/', 8 | exportStatic: {}, 9 | extraBabelPlugins: [ 10 | [ 11 | 'babel-plugin-import', 12 | { 13 | libraryName: 'antd', 14 | libraryDirectory: 'es', 15 | style: true, 16 | }, 17 | ], 18 | ], 19 | navs: { 20 | 'zh-CN': [ 21 | null, 22 | { title: 'GitHub', path: 'https://github.com/ant-design/sunflower' }, 23 | ], 24 | 'en-US': [ 25 | null, 26 | { title: 'GitHub', path: 'https://github.com/ant-design/sunflower' }, 27 | ], 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2019-present Alipay Industry Technology https://www.alipay.com/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sunflower 2 | 3 | Process Components for antd. 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![build status][circleci-image]][circleci-url] 7 | 8 | [circleci-image]: https://img.shields.io/circleci/build/github/ant-design/sunflower/master.svg?style=flat-square 9 | [circleci-url]: https://circleci.com/gh/ant-design/sunflower/tree/master 10 | [npm-image]: https://img.shields.io/npm/v/sunflower-antd.svg?style=flat 11 | [npm-url]: https://www.npmjs.com/package/sunflower-antd 12 | 13 | English | [简体中文](./README.zh-CN.md) 14 | 15 | ## Features 16 | 17 | - Support antd4, antd3 18 | - The relationship between antd components is expressed using react-hooks, and process components are used to simplify development 19 | - Process Components are extracted from the actual business processes and used immediately 20 | 21 | ## Why 22 | 23 | Usually, we use multiple components of antd to complete a process. For example, if you want to complete the function of "using Table to display the list after Form search", you need to deal with the relationship between "Form" and "Table", including query, pagination, etc. 24 | 25 | Is there a way to simplify the maintenance of relationships between components? This is what sunflower is for. React-hooks that describe a scene are called "Process Components". Sunflower is a series of antd-based "Process Components". 26 | 27 | The following is an example of a "Form & Table" scenario. You only need the following code to complete functions such as querying and paging. `useFormTable` is a react-hooks, which will return the props of the antd component, etc. You can give these props to the antd component to complete the connection between the components. 28 | 29 | ```js 30 | import React from 'react'; 31 | import { useFormTable } from 'sunflower-antd'; 32 | import { Input, Button, Table, Form } from 'antd'; 33 | 34 | export default props => { 35 | const { formProps, tableProps } = useFormTable({ 36 | async search(values) { 37 | const res = await request(values); 38 | return res; 39 | }, 40 | }); 41 | 42 | return ( 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 |
55 | 56 | 66 | 67 | ); 68 | }; 69 | ``` 70 | 71 | ## Document 72 | 73 | - [English](https://ant-design.github.io/sunflower/getting-started) 74 | - [简体中文](https://ant-design.github.io/sunflower/zh-CN/getting-started) 75 | 76 | ## Usage 77 | 78 | ``` 79 | $ npm i sunflower-antd --save 80 | // or 81 | $ yarn add sunflower-antd 82 | ``` 83 | 84 | ## Development 85 | 86 | ``` 87 | $ yarn 88 | $ yarn start 89 | ``` 90 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # Sunflower 2 | 3 | antd 流程组件 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![build status][circleci-image]][circleci-url] 7 | 8 | [circleci-image]: https://img.shields.io/circleci/build/github/ant-design/sunflower/master.svg?style=flat-square 9 | [circleci-url]: https://circleci.com/gh/ant-design/sunflower/tree/master 10 | [npm-image]: https://img.shields.io/npm/v/sunflower-antd.svg?style=flat 11 | [npm-url]: https://www.npmjs.com/package/sunflower-antd 12 | 13 | [English](./README.md) | 简体中文 14 | 15 | ## 特定 16 | 17 | - 支持 antd4, antd3 18 | - 将 antd 组件间关系使用 react-hooks 来表述,使用流程组件的方式来简化开发 19 | - 提炼于业务实际流程,拿来即用 20 | 21 | ## 为什么 22 | 23 | 通常,我们使用 antd 的多个组件来完成一个流程。比如想要完成一个 “使用 Form 搜索后 Table 来展示列表” 的功能,则需要去处理 “Form” 跟 “Table” 的关系,包括查询,分页等。 24 | 25 | 是否有个方式来简化组件间关系的维护?这就是 sunflower 的作用。能描述某个场景的 react-hooks,我们称之为 “流程组件”。sunflower 就是一系列基于 antd 的流程组件。 26 | 27 | 以下是一个 “Form & Table” 场景的示例,只需要以下的代码,就可完成包括查询,分页等功能。`useFormTable` 是一个 react-hooks,会返回 antd 组件的 props 等,将这些 props 给到 antd 组件即可完成组件间的联系。 28 | 29 | ```js 30 | import React from 'react'; 31 | import { useFormTable } from 'sunflower-antd'; 32 | import { Input, Button, Table, Form } from 'antd'; 33 | 34 | export default props => { 35 | const { formProps, tableProps } = useFormTable({ 36 | async search(values) { 37 | const res = await request(values); 38 | return res; 39 | }, 40 | }); 41 | 42 | return ( 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 56 |
66 | 67 | ); 68 | }; 69 | ``` 70 | 71 | ## 快速开始 72 | 73 | - [English](https://ant-design.github.io/sunflower/getting-started) 74 | - [简体中文](https://ant-design.github.io/sunflower/zh-CN/getting-started) 75 | 76 | ## 使用 77 | 78 | ``` 79 | $ npm i sunflower-antd --save 80 | // or 81 | $ yarn add sunflower-antd 82 | ``` 83 | 84 | ## 开发 85 | 86 | ``` 87 | $ yarn 88 | $ yarn start 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/getting-started/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Getting Started 4 | order: 1 5 | title: Concept 6 | --- 7 | 8 | Usually, we use multiple components of antd to complete a process. For example, if you want to complete the function of "using Table to display the list after Form search", you need to deal with the relationship between "Form" and "Table", including query, pagination, etc. 9 | 10 | Is there a way to simplify the maintenance of relationships between components? This is what sunflower is for. React-hooks that describe a scene are called "Process Components". Sunflower is a series of antd-based "Process Components". 11 | 12 | The following is an example of a "Form & Table" scenario. You only need the following code to complete functions such as querying and paging. `useFormTable` is a react-hooks, which will return the props of the antd component, etc. You can give these props to the antd component to complete the connection between the components. 13 | 14 | 15 | ```js 16 | import React from 'react'; 17 | import { useFormTable } from 'sunflower-antd'; 18 | import { Input, Button, Table, Form } from 'antd'; 19 | 20 | export default props => { 21 | const { formProps, tableProps } = useFormTable({ 22 | async search(values) { 23 | const res = await request(values); 24 | return res; 25 | }, 26 | }); 27 | 28 | return
29 |
30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 |
52 | 53 | }; 54 | ``` -------------------------------------------------------------------------------- /docs/getting-started/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 快速开始 4 | order: 1 5 | title: 概念 6 | --- 7 | 8 | 通常,我们使用 antd 的多个组件来完成一个流程。比如想要完成一个 “使用 Form 搜索后 Table 来展示列表” 的功能,则需要去处理 “Form” 跟 “Table” 的关系,包括查询,分页等。 9 | 10 | 是否有个方式来简化组件间关系的维护?这就是 sunflower 的作用。能描述某个场景的 react-hooks,我们称之为 “流程组件”。sunflower 就是一系列基于 antd 的流程组件。 11 | 12 | 以下是一个 “Form & Table” 场景的示例,只需要以下的代码,就可完成包括查询,分页等功能。`useFormTable` 是一个 react-hooks,会返回 antd 组件的 props 等,将这些 props 给到 antd 组件即可完成组件间的联系。 13 | 14 | ```js 15 | import React from 'react'; 16 | import { useFormTable } from 'sunflower-antd'; 17 | import { Input, Button, Table, Form } from 'antd'; 18 | 19 | export default props => { 20 | const { formProps, tableProps } = useFormTable({ 21 | async search(values) { 22 | const res = await request(values); 23 | return res; 24 | }, 25 | }); 26 | 27 | return ( 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 |
51 | 52 | ); 53 | }; 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Getting Started 4 | title: Installation 5 | --- 6 | 7 | Make sure you have `antd` installed, then install: 8 | 9 | ```bash 10 | $ npm install sunflower-antd --save 11 | ``` 12 | 13 | Sunflower includes different "Process Components", you can use different ones from "sunflower-antd". 14 | 15 | eg: 16 | 17 | ```js 18 | import { useFormTable } from 'sunflower-antd'; 19 | ``` 20 | 21 | Visit ["Process Components"](process-components) 22 | -------------------------------------------------------------------------------- /docs/getting-started/installation.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 快速开始 4 | title: 安装 5 | --- 6 | 7 | 确保你已经安装了 `antd`,之后安装: 8 | 9 | ```bash 10 | $ npm install sunflower-antd --save 11 | ``` 12 | 13 | Sunflower 包括了不同的流程组件,你可从 `sunflower-antd` 中使用需要的。 14 | 15 | 示例: 16 | 17 | ```js 18 | import { useFormTable } from 'sunflower-antd'; 19 | ``` 20 | 21 | 查看 ["流程组件"](process-components) 使用 22 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sunflower - process component for antd 3 | hero: 4 | title: Sunflower 5 | desc: Process Components for antd 6 | actions: 7 | - text: Getting Started 8 | link: getting-started 9 | features: 10 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/813f5ed9-6bc4-43d4-9f74-ec81ecf35733/k7htg6n4_w144_h144.png 11 | title: Simple to use 12 | desc: The relationship between antd components is expressed using react-hooks, and process components are used to simplify development. 13 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/7659205c-6637-4fa2-8529-d32e5818304b/k7htflfb_w144_h144.png 14 | title: From real business 15 | desc: Process Components are extracted from the actual business processes and used immediately. 16 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/6319a122-e8b8-497f-9b45-37cfbe77edaa/k7htfx7t_w144_h144.png 17 | title: Production available 18 | desc: Used in various industries of Alipay. 19 | footer: Open-source MIT Licensed | Copyright © 2019-present
Powered by [dumi](https://d.umijs.org) 20 | --- 21 | -------------------------------------------------------------------------------- /docs/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sunflower - process component for antd 3 | hero: 4 | title: Sunflower 5 | desc: 基于 antd 的流程组件 6 | actions: 7 | - text: 快速开始 8 | link: getting-started 9 | features: 10 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/813f5ed9-6bc4-43d4-9f74-ec81ecf35733/k7htg6n4_w144_h144.png 11 | title: 使用简单 12 | desc: 将 antd 组件间关系使用 react-hooks 来表述,使用流程组件的方式来简化开发。 13 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/7659205c-6637-4fa2-8529-d32e5818304b/k7htflfb_w144_h144.png 14 | title: 提炼于实际业务 15 | desc: Sunflower 的流程组件都是提炼于业务实际流程,拿来即用。 16 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/6319a122-e8b8-497f-9b45-37cfbe77edaa/k7htfx7t_w144_h144.png 17 | title: 生产可用 18 | desc: 在支付宝多个行业业务使用。 19 | footer: Open-source MIT Licensed | Copyright © 2019-present
Powered by [dumi](https://d.umijs.org) 20 | --- -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sunflower-antd", 3 | "version": "1.0.0-beta.3", 4 | "description": "Process Components for antd", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/ant-design/sunflower" 8 | }, 9 | "keywords": [ 10 | "antd", 11 | "react-hook" 12 | ], 13 | "authors": [ 14 | "JIACHENG9 (https://github.com/JIACHENG9)" 15 | ], 16 | "license": "MIT", 17 | "bugs": "http://github.com/ant-design/sunflower", 18 | "homepage": "https://ant-design.github.io/sunflower/", 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "files": [ 23 | "es", 24 | "lib" 25 | ], 26 | "scripts": { 27 | "start": "dumi dev", 28 | "docs:build": "dumi build", 29 | "build": "rm -rf es lib && node scripts/build.js", 30 | "lint": "eslint --ext .ts,.tsx \"./src\"", 31 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 32 | "test": "umi-test", 33 | "cov": "umi-test --coverage" 34 | }, 35 | "main": "lib/index.js", 36 | "module": "es/index.js", 37 | "typings": "lib/index.d.ts", 38 | "gitHooks": { 39 | "pre-commit": "lint-staged" 40 | }, 41 | "lint-staged": { 42 | "*.{js,jsx,less,md,json}": [ 43 | "prettier --write" 44 | ], 45 | "*.ts?(x)": [ 46 | "prettier --parser=typescript --write" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "@testing-library/react-hooks": "^3.2.1", 51 | "@umijs/fabric": "^2.0.4", 52 | "@umijs/test": "^3.0.5", 53 | "antd": "^4.0.3", 54 | "dumi": "^1.0.8", 55 | "gh-pages": "^2.2.0", 56 | "lint-staged": "^10.0.7", 57 | "mockjs": "^1.1.0", 58 | "prettier": "^1.19.1", 59 | "react": "^16.13.0", 60 | "react-test-renderer": "^16.13.0", 61 | "typescript": "^3.8.3", 62 | "yorkie": "^2.0.0" 63 | }, 64 | "peerDependencies": { 65 | "react": ">=16.9.0", 66 | "react-dom": ">=16.9.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | cp.fork( 6 | path.join(__dirname, '../node_modules/typescript/bin/tsc'), 7 | ['--module', 'esnext', '--outDir', './es'], 8 | { 9 | cwd: __dirname, 10 | }, 11 | ).on('exit', code => { 12 | if (code !== 0) { 13 | process.exit(code); 14 | } 15 | }); 16 | 17 | cp.fork( 18 | path.join(__dirname, '../node_modules/typescript/bin/tsc'), 19 | ['--module', 'commonjs', '--outDir', './lib'], 20 | { 21 | cwd: __dirname, 22 | }, 23 | ).on('exit', code => { 24 | if (code !== 0) { 25 | process.exit(code); 26 | } 27 | 28 | function replace(name) { 29 | const filename = path.join(__dirname, 'lib', name); 30 | const str = fs.readFileSync(filename, 'utf8'); 31 | fs.writeFileSync(filename, str.replace('antd/es/form', 'antd/lib/form')); 32 | } 33 | replace('form.js'); 34 | replace('form.d.ts'); 35 | }); 36 | -------------------------------------------------------------------------------- /scripts/pubDoc.js: -------------------------------------------------------------------------------- 1 | const ghpages = require('gh-pages'); 2 | 3 | ghpages.publish('dist', function(err) { 4 | if (err) { 5 | console.log(err); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /src/form.ts: -------------------------------------------------------------------------------- 1 | import Form from 'antd/es/form'; 2 | 3 | export default Form; 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useFormTable'; 2 | export * from './useCascadeSelect'; 3 | export * from './useModal'; 4 | export * from './useForm'; 5 | export * from './useModalForm'; 6 | export * from './useCascadeSearch'; 7 | export * from './useStepsForm'; 8 | -------------------------------------------------------------------------------- /src/useCascadeSearch/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export interface UseCascadeSearchConfig { 4 | list: ((...args: any) => T | Promise)[]; 5 | } 6 | 7 | export const useCascadeSearch = ({ 8 | list = [], 9 | }: UseCascadeSearchConfig) => { 10 | const [responseDataList, setResponseDataList] = useState([] as T[]); 11 | const [loadingList, setLoadingList] = useState( 12 | list.map(() => false) as boolean[], 13 | ); 14 | const search = (index: number, ...args: any) => { 15 | if (index >= list.length || index < 0) { 16 | return; 17 | } 18 | if (index > 0 && !responseDataList[index - 1]) { 19 | return; 20 | } 21 | const array = [...responseDataList.slice(0, index)]; 22 | setResponseDataList(array); 23 | const loading = [...loadingList]; 24 | loading[index] = true; 25 | setLoadingList(loading); 26 | Promise.resolve(list[index](responseDataList[index - 1], ...args)).then( 27 | value => { 28 | const nextArray = [...responseDataList.slice(0, index + 1)]; 29 | nextArray[index] = value; 30 | const nextLoading = [...loadingList]; 31 | nextLoading[index] = false; 32 | setLoadingList(nextLoading); 33 | setResponseDataList(nextArray); 34 | }, 35 | ); 36 | }; 37 | return { 38 | search, 39 | responseDataList, 40 | loadingList, 41 | setResponseDataList, 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /src/useCascadeSelect/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Process Components 4 | path: /process-components 5 | group: 6 | title: Process Components 7 | path: /process-components 8 | title: useCascadeSelect 9 | order: 2 10 | --- 11 | 12 | ## Overview 13 | 14 | `useCascadeSelect` is a react-hooks. When you want to use cascading select, you can use it. 15 | 16 | ## Examples 17 | 18 | ### Basic 19 | 20 | Change the "Like" select option, the "Type" select values ​​will change. 21 | 22 | ```jsx 23 | import React from 'react'; 24 | import { Input, Select, Form, Button, Table } from 'antd'; 25 | import { useCascadeSelect, useFormTable } from 'sunflower-antd'; 26 | import Mock from 'mockjs'; // mock request 27 | 28 | const layout = { 29 | labelCol: { span: 8 }, 30 | wrapperCol: { span: 16 }, 31 | }; 32 | const tailLayout = { 33 | wrapperCol: { offset: 8, span: 16 }, 34 | }; 35 | 36 | export default props => { 37 | const { formProps, tableProps, form } = useFormTable({ 38 | async search(values) { 39 | const res = await request(values); 40 | return { 41 | dataSource: res.list, 42 | total: res.total, 43 | }; 44 | }, 45 | }); 46 | 47 | const { selects } = useCascadeSelect({ 48 | form, 49 | list: [ 50 | { 51 | name: 'like', 52 | async options() { 53 | // mock 54 | await new Promise(r => setTimeout(r, 1000)); 55 | return [ 56 | { 57 | label: 'This is a Apple', 58 | value: 'apple', 59 | }, 60 | { 61 | label: 'This is a Bananar', 62 | value: 'bananar', 63 | }, 64 | ]; 65 | }, 66 | }, 67 | { 68 | name: 'type', 69 | async options(value) { 70 | // mock 71 | await new Promise(r => setTimeout(r, 1000)); 72 | 73 | if (value === 'apple') { 74 | return [ 75 | { 76 | label: 'Red Apple', 77 | value: '1', 78 | }, 79 | { 80 | label: 'Green Apple', 81 | value: '2', 82 | }, 83 | ]; 84 | } 85 | return [ 86 | { 87 | label: 'Yellow Bananar', 88 | value: '1', 89 | }, 90 | { 91 | label: 'Green Bananar', 92 | value: '2', 93 | }, 94 | ]; 95 | }, 96 | }, 97 | ], 98 | }); 99 | const [like, type] = selects; 100 | 101 | return ( 102 |
103 |
104 | 105 | 106 | 107 | 108 | 109 | 114 | 115 | 116 | 117 | 122 | 123 | 124 | 125 | 128 | 129 | 132 | 133 | 134 | 135 |
158 | 159 | ); 160 | }; 161 | 162 | // mock request 163 | const total = 200; 164 | const db = Mock.mock({ 165 | [`list|${total}`]: [ 166 | { 167 | username: '@name', 168 | 'like|1': ['apple', 'bananar'], 169 | 'type|1': ['1', '2'], 170 | }, 171 | ], 172 | }); 173 | function filter(list, dataIndex, keyword) { 174 | if (!keyword) { 175 | return list; 176 | } 177 | return list.filter( 178 | item => 179 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 180 | -1, 181 | ); 182 | } 183 | function request({ current, pageSize, like, type, username }) { 184 | const start = pageSize * (current - 1); 185 | const end = start + pageSize; 186 | let totalList = db.list; 187 | totalList = filter(totalList, 'username', username); 188 | totalList = filter(totalList, 'like', like); 189 | totalList = filter(totalList, 'type', type); 190 | const list = totalList.slice(start, end); 191 | return new Promise(r => 192 | setTimeout(() => { 193 | r({ 194 | list, 195 | total: totalList.length, 196 | }); 197 | }, 300), 198 | ); 199 | } 200 | ``` 201 | 202 | ### DefaultFormValues 203 | 204 | ## API 205 | 206 | ```js 207 | const Result = useCascadeSelect(Config); 208 | ``` 209 | 210 | ### Config 211 | 212 |
213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 |
KeyDescriptionTypeDefault
listCascading method array. Each method returns a select list of options. When the previous select value change, the subsequent method will be triggered with previous select value.name, options[]
autoFirstSearchWhether the first method in the list will be executed automatically.booleantrue
236 | 237 | ### Result 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 |
KeyDescriptionType
selectsCascade array, according to the length of the config list
searchExecute the method of specifying the index in the config list,usually do not need to use.(index: number, value: string) => void
260 | -------------------------------------------------------------------------------- /src/useCascadeSelect/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useCascadeSearch } from '../useCascadeSearch'; 3 | 4 | export interface OptionData { 5 | value: string; 6 | label: string; 7 | } 8 | 9 | export interface ListItem { 10 | name: string; 11 | options: (...args: any) => OptionData[] | Promise; 12 | } 13 | 14 | export interface Select { 15 | props: { 16 | [key: string]: any; 17 | }; 18 | options: OptionData[]; 19 | } 20 | 21 | export interface UseCascadeSelectConfig { 22 | list: ListItem[]; 23 | autoFirstSearch?: boolean; 24 | form: any; 25 | } 26 | 27 | export const useCascadeSelect = ({ 28 | list = [], 29 | autoFirstSearch = true, 30 | form, 31 | }: UseCascadeSelectConfig) => { 32 | const { 33 | search, 34 | responseDataList, 35 | loadingList, 36 | setResponseDataList, 37 | } = useCascadeSearch({ 38 | list: list.map(item => (lastValue, ...args) => item.options(...args)), 39 | }); 40 | 41 | const selects: Select[] = list.map((item, index) => { 42 | const options = responseDataList[index] || []; 43 | return { 44 | props: { 45 | loading: loadingList[index], 46 | onChange(val) { 47 | if (val) { 48 | search(index + 1, val); 49 | } 50 | if (form) { 51 | const values = {}; 52 | for (let i = index + 1; i < list.length; i += 1) { 53 | values[list[i].name] = undefined; 54 | } 55 | const nextResponseDataList = responseDataList.slice(0, index + 1); 56 | form.setFieldsValue(values); 57 | setResponseDataList(nextResponseDataList); 58 | } 59 | }, 60 | }, 61 | options, 62 | }; 63 | }); 64 | 65 | useEffect(() => { 66 | if (autoFirstSearch && !responseDataList[0]) { 67 | search(0); 68 | } 69 | }, []); 70 | return { 71 | search, 72 | selects, 73 | }; 74 | }; 75 | -------------------------------------------------------------------------------- /src/useCascadeSelect/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 流程组件 4 | path: /zh-CN/process-components 5 | group: 6 | title: 流程组件 7 | path: /zh-CN/process-components 8 | title: useCascadeSelect 9 | order: 2 10 | --- 11 | 12 | ## 说明 13 | 14 | 当需要进行多个 Select 组件的级联选择,可使用这个流程组件。 15 | 16 | ## 示例 17 | 18 | ### 基础 19 | 20 | 修改 “喜欢” 的选项,会去请求所选 “喜欢” 对应的 “类型” 列表。 21 | 22 | ```jsx 23 | import React, { useEffect } from 'react'; 24 | import { Input, Select, Form, Button, Table } from 'antd'; 25 | import { useCascadeSelect, useFormTable } from 'sunflower-antd'; 26 | import Mock from 'mockjs'; // mock request 27 | 28 | const layout = { 29 | labelCol: { span: 8 }, 30 | wrapperCol: { span: 16 }, 31 | }; 32 | const tailLayout = { 33 | wrapperCol: { offset: 8, span: 16 }, 34 | }; 35 | 36 | export default props => { 37 | const { selects, search } = useCascadeSelect({ 38 | list: [ 39 | { 40 | name: 'like', 41 | async options() { 42 | // mock 43 | await new Promise(r => setTimeout(r, 1000)); 44 | return [ 45 | { 46 | label: 'This is a Apple', 47 | value: 'apple', 48 | }, 49 | { 50 | label: 'This is a Bananar', 51 | value: 'bananar', 52 | }, 53 | ]; 54 | }, 55 | }, 56 | { 57 | name: 'type', 58 | async options(value) { 59 | // mock 60 | await new Promise(r => setTimeout(r, 1000)); 61 | 62 | if (value === 'apple') { 63 | return [ 64 | { 65 | label: 'Red Apple', 66 | value: '1', 67 | }, 68 | { 69 | label: 'Green Apple', 70 | value: '2', 71 | }, 72 | ]; 73 | } 74 | return [ 75 | { 76 | label: 'Yellow Bananar', 77 | value: '1', 78 | }, 79 | { 80 | label: 'Green Bananar', 81 | value: '2', 82 | }, 83 | ]; 84 | }, 85 | }, 86 | ], 87 | }); 88 | const [like, type] = selects; 89 | 90 | const { formProps, tableProps, form } = useFormTable({ 91 | async search(values) { 92 | const res = await request(values); 93 | return { 94 | dataSource: res.list, 95 | total: res.total, 96 | }; 97 | }, 98 | }); 99 | 100 | return ( 101 |
102 |
103 | 104 | 105 | 106 | 107 | 108 | 113 | 114 | 115 | 116 | 121 | 122 | 123 | 124 | 127 | 128 | 131 | 132 |
133 | 134 | 157 | 158 | ); 159 | }; 160 | 161 | // mock request 162 | const total = 200; 163 | const db = Mock.mock({ 164 | [`list|${total}`]: [ 165 | { 166 | username: '@name', 167 | 'like|1': ['apple', 'bananar'], 168 | 'type|1': ['1', '2'], 169 | }, 170 | ], 171 | }); 172 | function filter(list, dataIndex, keyword) { 173 | if (!keyword) { 174 | return list; 175 | } 176 | return list.filter( 177 | item => 178 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 179 | -1, 180 | ); 181 | } 182 | function request({ current, pageSize, like, type, username }) { 183 | const start = pageSize * (current - 1); 184 | const end = start + pageSize; 185 | let totalList = db.list; 186 | totalList = filter(totalList, 'username', username); 187 | totalList = filter(totalList, 'like', like); 188 | totalList = filter(totalList, 'type', type); 189 | const list = totalList.slice(start, end); 190 | return new Promise(r => 191 | setTimeout(() => { 192 | r({ 193 | list, 194 | total: totalList.length, 195 | }); 196 | }, 300), 197 | ); 198 | } 199 | ``` 200 | 201 | ### DefaultFormValues 202 | 203 | ## API 204 | 205 | ```js 206 | const Result = useCascadeSelect(Config); 207 | ``` 208 | 209 | ### Config 210 | 211 |
212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 |
名称说明类型默认值
list级联方法数组,之前的值返回后会作为之后的参数。name, options[]
autoFirstSearch是否组件渲染就进行调用表单查询booleantrue
235 | 236 | ### Result 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 |
名称说明类型
selects级联对象,跟 list 长度相同,每一个对象有 props 及 options 属性
search对指定的级联 Select 进行搜索(index: number, value: string) => void
259 | -------------------------------------------------------------------------------- /src/useForm/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Process Components 4 | path: /process-components 5 | group: 6 | title: Process Components 7 | path: /process-components 8 | title: useForm 9 | order: 5 10 | --- 11 | 12 | ## Overview 13 | 14 | When you want to use `antd Form`, you can use it. 15 | 16 | ## Examples 17 | 18 | ### Basic 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useForm } from 'sunflower-antd'; 23 | import { Input, Button, Form, Spin } from 'antd'; 24 | 25 | export default props => { 26 | const [form] = Form.useForm(); 27 | const { formProps, formValues, formResult, formLoading } = useForm({ 28 | form, 29 | async submit({ username, email }) { 30 | console.log('submit'); 31 | await new Promise(r => setTimeout(r, 1000)); 32 | console.log('fewfew'); 33 | return 'ok'; 34 | }, 35 | }); 36 | return ( 37 |
38 |

39 | submit: username {formValues.username} email {formValues.email} 40 |

41 |

result: {formResult}

42 |

formLoading: {formLoading && }

43 |
44 | 49 | 50 | 51 | 52 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 |
76 |
77 | ); 78 | }; 79 | ``` 80 | 81 | ### defaultFormValues 82 | 83 | ```jsx 84 | import React from 'react'; 85 | import { useForm } from 'sunflower-antd'; 86 | import { Input, Button, Form, Spin } from 'antd'; 87 | 88 | export default props => { 89 | const [form] = Form.useForm(); 90 | const { formProps, formValues, formResult, formLoading } = useForm({ 91 | form, 92 | async submit({ username, email }) { 93 | console.log('submit'); 94 | await new Promise(r => setTimeout(r, 1000)); 95 | console.log('fewfew'); 96 | return 'ok'; 97 | }, 98 | async defaultFormValues() { 99 | await new Promise(r => setTimeout(r, 3000)); 100 | return { 101 | username: 'lily', 102 | }; 103 | }, 104 | }); 105 | return ( 106 |
107 |

108 | submit: username {formValues.username} email {formValues.email} 109 |

110 |

result: {formResult}

111 |

formLoading: {formLoading && }

112 |
113 | 118 | 119 | 120 | 121 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 143 | 144 |
145 |
146 | ); 147 | }; 148 | ``` 149 | 150 | ### submit method 151 | 152 | ```jsx 153 | import React from 'react'; 154 | import { useForm } from 'sunflower-antd'; 155 | import { Input, Button, Form, Spin } from 'antd'; 156 | 157 | export default props => { 158 | const [form] = Form.useForm(); 159 | const { formProps, formValues, formResult, formLoading, submit } = useForm({ 160 | form, 161 | async submit({ username, email }) { 162 | console.log('submit', username, email); 163 | await new Promise(r => setTimeout(r, 1000)); 164 | return 'ok'; 165 | }, 166 | }); 167 | return ( 168 |
169 |

170 | submit: username {formValues.username} email {formValues.email} 171 |

172 |

result: {formResult}

173 |

formLoading: {formLoading && }

174 |
175 | 180 | 181 | 182 | 183 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 205 | 206 |
207 | 208 | 215 |
216 | ); 217 | }; 218 | ``` 219 | 220 | ## API 221 | 222 | ```js 223 | const result = useForm(config); 224 | ``` 225 | 226 | ### Config 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 |
KeyDescriptionTypeDefault
submitsubmit method, the parameter is the value of the form fields(formValues) => Promise<formResult> | formResult
formDecorated by Form.create() will be automatically set this.props.form property(antd3); useForm react hooks(antd4)
defaultFormValuesDefault form values.If the form has data that needs to be backfilled, use it to get the data.object | () => Promise<object>
258 | 259 | ### Result 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 |
KeyDescriptionType
formPropsantd Form props
formForm instance
formLoadingform request loading.boolean
formValuesform valuesobject
initialValuesinitial form valuesobject
formResultsubmit return value
defaultFormValuesLoadingWhen use 'defaultFormValues', the value will be true during the request.boolean
submitwill call the 'submit' method
312 | -------------------------------------------------------------------------------- /src/useForm/index.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import Form from '../form'; 3 | 4 | export declare type StoreBaseValue = string | number | boolean; 5 | export declare type StoreValue = StoreBaseValue | Store | StoreBaseValue[]; 6 | export interface Store { 7 | [name: string]: StoreValue; 8 | } 9 | 10 | export interface UseFormConfig { 11 | defaultFormValues?: Store | (() => Promise | Store); 12 | form?: any; 13 | submit?: (formValues: Store) => any; 14 | } 15 | 16 | export const useForm = (config: UseFormConfig) => { 17 | const [defaultFormValuesLoading, setDefaultFormValuesLoading] = useState( 18 | false, 19 | ); 20 | const [initialValues, setInitialValues] = useState({}); 21 | const { defaultFormValues, form, submit } = config; 22 | const [formValues, setFormValues] = useState({}); 23 | const [formLoading, setFormLoading] = useState(false); 24 | const [formResult, setFormResult] = useState(); 25 | 26 | let version = 3; 27 | // antd4 28 | if (Form['useForm']) { 29 | version = 4; 30 | } 31 | 32 | let formInstance = form; 33 | if (!form) { 34 | if (version === 4) { 35 | [formInstance] = Form['useForm'](); 36 | } else { 37 | throw new Error('"form" need in antd@3'); 38 | } 39 | } 40 | 41 | const onFinish = (formValue: Store) => { 42 | setFormValues(formValue); 43 | setFormLoading(true); 44 | return new Promise((resolve, reject) => { 45 | if (version === 4) { 46 | formInstance 47 | .validateFields() 48 | .then(() => { 49 | resolve( 50 | Promise.resolve(submit(formValue)) 51 | .then(data => { 52 | setFormLoading(false); 53 | setFormResult(data); 54 | return data; 55 | }) 56 | .catch(err => { 57 | setFormLoading(false); 58 | throw err; 59 | }), 60 | ); 61 | }) 62 | .catch(validateErr => { 63 | setFormLoading(false); 64 | reject(validateErr); 65 | }); 66 | } else { 67 | formInstance.validateFields(validateErr => { 68 | if (validateErr) { 69 | setFormLoading(false); 70 | reject(validateErr); 71 | } else { 72 | resolve( 73 | Promise.resolve(submit(formValue)) 74 | .then(data => { 75 | setFormLoading(false); 76 | setFormResult(data); 77 | return data; 78 | }) 79 | .catch(err => { 80 | setFormLoading(false); 81 | throw err; 82 | }), 83 | ); 84 | } 85 | }); 86 | } 87 | }); 88 | }; 89 | 90 | useEffect(() => { 91 | let isUnMounted = false; 92 | if (!defaultFormValues) { 93 | return; 94 | } 95 | let value: Store | Promise; 96 | if (typeof defaultFormValues === 'function') { 97 | setDefaultFormValuesLoading(true); 98 | value = defaultFormValues(); 99 | } else { 100 | value = defaultFormValues; 101 | } 102 | Promise.resolve(value) 103 | .then(data => { 104 | if (!isUnMounted) { 105 | const obj = { ...data }; 106 | Object.keys(data).forEach(name => { 107 | obj[name] = formInstance.isFieldTouched(name) 108 | ? formInstance.getFieldValue(name) 109 | : data[name]; 110 | }); 111 | setDefaultFormValuesLoading(false); 112 | setInitialValues(data); 113 | formInstance.setFieldsValue(obj); 114 | } 115 | }) 116 | .catch(() => { 117 | if (!isUnMounted) { 118 | setDefaultFormValuesLoading(false); 119 | } 120 | }); 121 | return () => { 122 | isUnMounted = true; 123 | }; 124 | }, []); 125 | 126 | const formProps = 127 | version === 4 128 | ? { 129 | form: formInstance, 130 | onFinish, 131 | initialValues, 132 | } 133 | : { 134 | onSubmit(e) { 135 | e.preventDefault(); 136 | onFinish( 137 | formInstance.getFieldsValue(version === 4 ? true : undefined), 138 | ); 139 | }, 140 | }; 141 | 142 | return { 143 | form: formInstance, 144 | formProps, 145 | defaultFormValuesLoading, 146 | formValues, 147 | initialValues, 148 | formResult, 149 | formLoading, 150 | submit: (values?: Store) => { 151 | formInstance.setFieldsValue(values); 152 | return onFinish( 153 | formInstance.getFieldsValue(version === 4 ? true : undefined), 154 | ); 155 | }, 156 | }; 157 | }; 158 | -------------------------------------------------------------------------------- /src/useForm/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 流程组件 4 | path: /zh-CN/process-components 5 | group: 6 | title: 流程组件 7 | path: /zh-CN/process-components 8 | title: useForm 9 | order: 5 10 | --- 11 | 12 | ## 说明 13 | 14 | 当你需要使用`antd Form`,可使用 `useForm`。 15 | 16 | ## 示例 17 | 18 | ### 基础 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useForm } from 'sunflower-antd'; 23 | import { Input, Button, Form, Spin } from 'antd'; 24 | 25 | export default props => { 26 | const [form] = Form.useForm(); 27 | const { formProps, formValues, formResult, formLoading } = useForm({ 28 | form, 29 | async submit({ username, email }) { 30 | console.log('submit'); 31 | await new Promise(r => setTimeout(r, 1000)); 32 | console.log('fewfew'); 33 | return 'ok'; 34 | }, 35 | }); 36 | return ( 37 |
38 |

39 | submit: username {formValues.username} email {formValues.email} 40 |

41 |

result: {formResult}

42 |

formLoading: {formLoading && }

43 |
44 | 49 | 50 | 51 | 52 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 75 |
76 |
77 | ); 78 | }; 79 | ``` 80 | 81 | ### defaultFormValues 82 | 83 | ```jsx 84 | import React from 'react'; 85 | import { useForm } from 'sunflower-antd'; 86 | import { Input, Button, Form, Spin } from 'antd'; 87 | 88 | export default props => { 89 | const [form] = Form.useForm(); 90 | const { formProps, formValues, formResult, formLoading } = useForm({ 91 | form, 92 | async submit({ username, email }) { 93 | console.log('submit'); 94 | await new Promise(r => setTimeout(r, 1000)); 95 | console.log('fewfew'); 96 | return 'ok'; 97 | }, 98 | async defaultFormValues() { 99 | await new Promise(r => setTimeout(r, 3000)); 100 | return { 101 | username: 'lily', 102 | }; 103 | }, 104 | }); 105 | return ( 106 |
107 |

108 | submit: username {formValues.username} email {formValues.email} 109 |

110 |

result: {formResult}

111 |

formLoading: {formLoading && }

112 |
113 | 118 | 119 | 120 | 121 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 143 | 144 |
145 |
146 | ); 147 | }; 148 | ``` 149 | 150 | ### submit method 151 | 152 | ```jsx 153 | import React from 'react'; 154 | import { useForm } from 'sunflower-antd'; 155 | import { Input, Button, Form, Spin } from 'antd'; 156 | 157 | export default props => { 158 | const [form] = Form.useForm(); 159 | const { formProps, formValues, formResult, formLoading, submit } = useForm({ 160 | form, 161 | async submit({ username, email }) { 162 | console.log('submit', username, email); 163 | await new Promise(r => setTimeout(r, 1000)); 164 | return 'ok'; 165 | }, 166 | }); 167 | return ( 168 |
169 |

170 | submit: username {formValues.username} email {formValues.email} 171 |

172 |

result: {formResult}

173 |

formLoading: {formLoading && }

174 |
175 | 180 | 181 | 182 | 183 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 205 | 206 |
207 | 208 | 215 |
216 | ); 217 | }; 218 | ``` 219 | 220 | ## API 221 | 222 | ```js 223 | const result = useForm(config); 224 | ``` 225 | 226 | ### Config 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 |
名称说明类型默认值
submit请求方法, 其参数为 form fields 的值(formValues) => Promise<formResult> | formResult
form在 antd4 中为 useForm 返回的 form 实例。在 antd3 中 Form.create() props.form
defaultFormValues默认的表单回填值,可为一个对象,也可为一个异步方法object | () => Promise<object>
258 | 259 | ### Result 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 |
名称说明类型
formPropsantd Form 组件的 props,作为 Form 组件的 props 即可
formantd Form 的实例,可访问查看 form 实例的方法
formLoading正在提交表单.boolean
formValues对应 form fileds 的值object
initialValues表单默认值. 对应defaultFormValuesobject
formResultsubmit返回值
defaultFormValuesLoading当使用 defaultFormValues 在请求表单回填值的 loading.boolean
submitsubmit 方法. 可主动触发, 提交表单, 其参数为 form fields 的值(formValues) => Promise<formResult>
312 | -------------------------------------------------------------------------------- /src/useFormTable/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Process Components 4 | path: /process-components 5 | group: 6 | title: Process Components 7 | path: /process-components 8 | title: useFormTable 9 | order: 1 10 | --- 11 | 12 | ## Overview 13 | 14 | When you want to use "Form Search Table", you can use it. 15 | 16 | ## Examples 17 | 18 | ### Basic 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useFormTable } from 'sunflower-antd'; 23 | import { Input, Button, Table, Form } from 'antd'; 24 | import Mock from 'mockjs'; // mock request 25 | 26 | export default props => { 27 | const { formProps, tableProps, form } = useFormTable({ 28 | async search(values) { 29 | const res = await request(values); 30 | return { 31 | dataSource: res.list, 32 | total: res.total, 33 | }; 34 | }, 35 | defaultPageSize: 5, 36 | }); 37 | 38 | return ( 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 |
59 | 60 | 77 | 78 | ); 79 | }; 80 | 81 | // mock request 82 | const total = 200; 83 | const db = Mock.mock({ 84 | [`list|${total}`]: [ 85 | { 86 | username: '@name', 87 | email: '@email', 88 | id: '@guid', 89 | 'gender|1': ['male', 'female'], 90 | }, 91 | ], 92 | }); 93 | function filter(list, dataIndex, keyword) { 94 | if (!keyword) { 95 | return list; 96 | } 97 | return list.filter( 98 | item => 99 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 100 | -1, 101 | ); 102 | } 103 | function request({ current, pageSize, filters, sorter, username, email }) { 104 | console.log( 105 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s', 106 | username, 107 | pageSize, 108 | current, 109 | JSON.stringify(filters), 110 | JSON.stringify(sorter), 111 | ); 112 | const start = pageSize * (current - 1); 113 | const end = start + pageSize; 114 | let totalList = db.list; 115 | totalList = filter(totalList, 'username', username); 116 | totalList = filter(totalList, 'email', email); 117 | if (filters) { 118 | Object.keys(filters).forEach(key => { 119 | if (!filters[key]) { 120 | return true; 121 | } 122 | if (filters[key].length === 0) { 123 | return true; 124 | } 125 | totalList = totalList.filter(item => filters[key].includes(item[key])); 126 | }); 127 | } 128 | if (sorter && sorter.column) { 129 | const { dataIndex } = sorter.column; 130 | if (sorter.order === 'descend') { 131 | totalList = [...totalList].sort( 132 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0), 133 | ); 134 | } else { 135 | totalList = [...totalList].sort( 136 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0), 137 | ); 138 | } 139 | } 140 | const list = totalList.slice(start, end); 141 | return new Promise(r => 142 | setTimeout(() => { 143 | r({ 144 | list, 145 | total: totalList.length, 146 | }); 147 | }, 300), 148 | ); 149 | } 150 | ``` 151 | 152 | ### DefaultFormValues 153 | 154 | ```jsx 155 | import React from 'react'; 156 | import { useFormTable } from 'sunflower-antd'; 157 | import { Input, Button, Table, Form } from 'antd'; 158 | import Mock from 'mockjs'; // mock request 159 | 160 | export default props => { 161 | const { formProps, tableProps, form } = useFormTable({ 162 | async search(values) { 163 | const res = await request(values); 164 | return { 165 | dataSource: res.list, 166 | total: res.total, 167 | }; 168 | }, 169 | async defaultFormValues() { 170 | await new Promise(r => setTimeout(r, 200)); 171 | return { 172 | username: 'j', 173 | }; 174 | }, 175 | }); 176 | 177 | return ( 178 |
179 |
180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 196 | 197 | 198 | 199 |
216 | 217 | ); 218 | }; 219 | 220 | // mock request 221 | const total = 200; 222 | const db = Mock.mock({ 223 | [`list|${total}`]: [ 224 | { 225 | username: '@name', 226 | email: '@email', 227 | id: '@guid', 228 | 'gender|1': ['male', 'female'], 229 | }, 230 | ], 231 | }); 232 | function filter(list, dataIndex, keyword) { 233 | if (!keyword) { 234 | return list; 235 | } 236 | return list.filter( 237 | item => 238 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 239 | -1, 240 | ); 241 | } 242 | function request({ current, pageSize, filters, sorter, username, email }) { 243 | console.log( 244 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s', 245 | username, 246 | pageSize, 247 | current, 248 | JSON.stringify(filters), 249 | JSON.stringify(sorter), 250 | ); 251 | const start = pageSize * (current - 1); 252 | const end = start + pageSize; 253 | let totalList = db.list; 254 | totalList = filter(totalList, 'username', username); 255 | totalList = filter(totalList, 'email', email); 256 | if (filters) { 257 | Object.keys(filters).forEach(key => { 258 | if (!filters[key]) { 259 | return true; 260 | } 261 | if (filters[key].length === 0) { 262 | return true; 263 | } 264 | totalList = totalList.filter(item => filters[key].includes(item[key])); 265 | }); 266 | } 267 | if (sorter && sorter.column) { 268 | const { dataIndex } = sorter.column; 269 | if (sorter.order === 'descend') { 270 | totalList = [...totalList].sort( 271 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0), 272 | ); 273 | } else { 274 | totalList = [...totalList].sort( 275 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0), 276 | ); 277 | } 278 | } 279 | const list = totalList.slice(start, end); 280 | return new Promise(r => 281 | setTimeout(() => { 282 | r({ 283 | list, 284 | total: totalList.length, 285 | }); 286 | }, 300), 287 | ); 288 | } 289 | ``` 290 | 291 | ### Filters & Sorter 292 | 293 | ```jsx 294 | import React from 'react'; 295 | import { useFormTable } from 'sunflower-antd'; 296 | import { Input, Button, Form, Table } from 'antd'; 297 | import Mock from 'mockjs'; // mock request 298 | 299 | export default props => { 300 | const { formProps, tableProps, sorter, filters, form } = useFormTable({ 301 | async search(values) { 302 | const res = await request(values); 303 | return { 304 | dataSource: res.list, 305 | total: res.total, 306 | }; 307 | }, 308 | }); 309 | 310 | return ( 311 |
312 |
313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 329 | 330 | 331 | 332 |
374 | 375 | ); 376 | }; 377 | 378 | // mock request 379 | const total = 200; 380 | const db = Mock.mock({ 381 | [`list|${total}`]: [ 382 | { 383 | username: '@name', 384 | email: '@email', 385 | id: '@guid', 386 | 'gender|1': ['male', 'female'], 387 | }, 388 | ], 389 | }); 390 | function filter(list, dataIndex, keyword) { 391 | if (!keyword) { 392 | return list; 393 | } 394 | return list.filter( 395 | item => 396 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 397 | -1, 398 | ); 399 | } 400 | function request({ current, pageSize, filters, sorter, username, email }) { 401 | console.log( 402 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s', 403 | username, 404 | pageSize, 405 | current, 406 | JSON.stringify(filters), 407 | JSON.stringify(sorter), 408 | ); 409 | const start = pageSize * (current - 1); 410 | const end = start + pageSize; 411 | let totalList = db.list; 412 | totalList = filter(totalList, 'username', username); 413 | totalList = filter(totalList, 'email', email); 414 | if (filters) { 415 | Object.keys(filters).forEach(key => { 416 | if (!filters[key]) { 417 | return true; 418 | } 419 | if (filters[key].length === 0) { 420 | return true; 421 | } 422 | totalList = totalList.filter(item => filters[key].includes(item[key])); 423 | }); 424 | } 425 | if (sorter && sorter.column) { 426 | const { dataIndex } = sorter.column; 427 | if (sorter.order === 'descend') { 428 | totalList = [...totalList].sort( 429 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0), 430 | ); 431 | } else { 432 | totalList = [...totalList].sort( 433 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0), 434 | ); 435 | } 436 | } 437 | const list = totalList.slice(start, end); 438 | return new Promise(r => 439 | setTimeout(() => { 440 | r({ 441 | list, 442 | total: totalList.length, 443 | }); 444 | }, 300), 445 | ); 446 | } 447 | ``` 448 | 449 | ## API 450 | 451 | ```js 452 | const result = useFormTable(config); 453 | ``` 454 | 455 | ### Config 456 | 457 |
458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 |
KeyDescriptionTypeDefault
searchRequest method, the parameter is the value of the form fields.The method needs to return an array or Promise of SearchResponseData.(requestData) => Promise<responseData> | responseData
formOptional, decorated by useForm in antd4(Form.create() in antd3)
autoFirstSearchWhether the search method will be executed automatically.booleantrue
defaultPageSizeDefault page sizenumber10
defaultCurrentDefault current pagenumber1
defaultFormValuesDefault form values.If the form has data that needs to be backfilled, use it to get the data.object | () => Promise<object>
505 | 506 | - responseData 507 | 508 | ```js 509 | { 510 | list: [{ 511 | name: 'jack', 512 | }, { 513 | name: 'lily', 514 | }], 515 | total: 10, 516 | } 517 | ``` 518 | 519 | - requestData 520 | 521 | ```js 522 | search({ current, pageSize, filters, sorter, ...formValues }) { 523 | 524 | } 525 | ``` 526 | 527 | ### Result 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 |
KeyDescriptionType
formPropsantd Form props
tablePropsantd Table props
formForm instance
loadingRequest loading.boolean
currentCurrent page.number
pageSizePage size.number
formValuesForm values.object
dataSourceThe value's 'dataSource prop' returned by the search method.array
totalThe value's 'total prop' returned by the search method.number
defaultFormValuesLoadingWhen use 'defaultFormValues', the value will be true during the request.boolean
filtersantd Table filtersobject
sorterantd Table sorterobject
searchwill call the 'search' method with custom request data(customRequestData) => void
605 | -------------------------------------------------------------------------------- /src/useFormTable/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import Form from '../form'; 3 | import { 4 | useSearchResult as useSearchResultHooks, 5 | UseSearchResultConfig, 6 | } from '../useSearchResult'; 7 | 8 | declare type StoreBaseValue = string | number | boolean; 9 | declare type StoreValue = StoreBaseValue | Store | StoreBaseValue[]; 10 | interface Store { 11 | [name: string]: StoreValue; 12 | } 13 | export interface SearchResponseData { 14 | dataSource: Store[]; 15 | total?: number; 16 | } 17 | 18 | export interface UseSearchResultAntdConfig 19 | extends UseSearchResultConfig { 20 | defaultPageSize?: number; 21 | defaultCurrent?: number; 22 | defaultFormValues?: Store | (() => Promise | Store); 23 | form?: any; 24 | } 25 | 26 | export const useFormTable = (config: UseSearchResultAntdConfig) => { 27 | const formTableConfig = config || ({} as UseSearchResultAntdConfig); 28 | const { 29 | search, 30 | autoFirstSearch = true, 31 | defaultPageSize = 10, 32 | defaultCurrent = 1, 33 | defaultFormValues = {}, 34 | form, 35 | } = formTableConfig; 36 | 37 | let version = 3; 38 | // antd4 39 | if (Form['useForm']) { 40 | version = 4; 41 | } 42 | 43 | let formInstance = form; 44 | if (!form) { 45 | if (version === 4) { 46 | [formInstance] = Form['useForm'](); 47 | } else { 48 | throw new Error('"form" need in antd@3'); 49 | } 50 | } 51 | 52 | const [initialValues, setInitialValues] = useState(); 53 | const { 54 | loading, 55 | requestData = {} as Store, 56 | setRequestData, 57 | responseData = {} as SearchResponseData, 58 | defaultRequestDataLoading, 59 | search: searchFunc, 60 | } = useSearchResultHooks({ 61 | search, 62 | autoFirstSearch, 63 | defaultRequestData: () => { 64 | let value: Store | Promise; 65 | if (typeof defaultFormValues === 'function') { 66 | value = defaultFormValues(); 67 | } else { 68 | value = defaultFormValues; 69 | } 70 | return Promise.resolve(value).then(data => { 71 | const touched = formInstance.isFieldsTouched(); 72 | const obj = { ...data }; 73 | Object.keys(data).forEach(name => { 74 | obj[name] = formInstance.isFieldTouched(name) 75 | ? formInstance.getFieldValue(name) 76 | : data[name]; 77 | }); 78 | setInitialValues(data); 79 | formInstance.setFieldsValue(obj); 80 | if (touched) { 81 | setRequestData({ 82 | pageSize: defaultPageSize, 83 | current: defaultCurrent, 84 | ...obj, 85 | }); 86 | throw new Error('will not autoFirstSearch'); 87 | } 88 | return { 89 | pageSize: defaultPageSize, 90 | current: defaultCurrent, 91 | ...obj, 92 | }; 93 | }); 94 | }, 95 | }); 96 | 97 | const onFinish = (values: Store) => { 98 | searchFunc({ 99 | current: 1, 100 | pageSize: requestData.pageSize, 101 | ...values, 102 | }); 103 | }; 104 | 105 | const onChange = (pagination, filters, sorter) => { 106 | searchFunc({ 107 | ...requestData, 108 | current: 109 | pagination.current === requestData.current ? 1 : pagination.current, 110 | pageSize: pagination.pageSize, 111 | filters, 112 | sorter, 113 | }); 114 | }; 115 | 116 | const formProps = 117 | version === 4 118 | ? { 119 | form: formInstance, 120 | onFinish, 121 | initialValues, 122 | } 123 | : { 124 | onSubmit(e) { 125 | e.preventDefault(); 126 | formInstance.validateFields((err, values) => { 127 | if (!err) { 128 | searchFunc({ 129 | current: 1, 130 | pageSize: requestData.pageSize, 131 | ...values, 132 | }); 133 | } 134 | }); 135 | }, 136 | }; 137 | 138 | const tableProps = { 139 | pagination: { 140 | pageSize: (requestData.pageSize || defaultPageSize) as number, 141 | current: requestData.current as number, 142 | defaultPageSize, 143 | defaultCurrent, 144 | total: responseData.total, 145 | }, 146 | loading, 147 | dataSource: responseData.dataSource, 148 | onChange, 149 | }; 150 | 151 | const formValues = { ...requestData }; 152 | delete formValues.current; 153 | delete formValues.pageSize; 154 | delete formValues.filter; 155 | delete formValues.sorter; 156 | 157 | return { 158 | form: formInstance, 159 | formProps, 160 | tableProps, 161 | loading, 162 | defaultFormValuesLoading: defaultRequestDataLoading, 163 | formValues, 164 | filters: requestData.filters, 165 | sorter: requestData.sorter, 166 | current: requestData.current as number, 167 | pageSize: (requestData.pageSize || defaultPageSize) as number, 168 | dataSource: responseData.dataSource, 169 | total: responseData.total, 170 | search: data => { 171 | searchFunc({ 172 | ...requestData, 173 | ...data, 174 | }); 175 | }, 176 | }; 177 | }; 178 | -------------------------------------------------------------------------------- /src/useFormTable/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 流程组件 4 | path: /zh-CN/process-components 5 | group: 6 | title: 流程组件 7 | path: /zh-CN/process-components 8 | title: useFormTable 9 | order: 1 10 | --- 11 | 12 | ## 说明 13 | 14 | 当你需要使用搜索列表这个场景,可使用 `useFormTable`。 15 | 16 | ## 示例 17 | 18 | ### 基础 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useFormTable } from 'sunflower-antd'; 23 | import { Input, Button, Table, Form } from 'antd'; 24 | import Mock from 'mockjs'; // mock request 25 | 26 | export default props => { 27 | const { formProps, tableProps, form } = useFormTable({ 28 | async search(values) { 29 | const res = await request(values); 30 | return { 31 | dataSource: res.list, 32 | total: res.total, 33 | }; 34 | }, 35 | defaultPageSize: 5, 36 | }); 37 | 38 | return ( 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 |
59 | 60 | 77 | 78 | ); 79 | }; 80 | 81 | // mock request 82 | const total = 200; 83 | const db = Mock.mock({ 84 | [`list|${total}`]: [ 85 | { 86 | username: '@name', 87 | email: '@email', 88 | id: '@guid', 89 | 'gender|1': ['male', 'female'], 90 | }, 91 | ], 92 | }); 93 | function filter(list, dataIndex, keyword) { 94 | if (!keyword) { 95 | return list; 96 | } 97 | return list.filter( 98 | item => 99 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 100 | -1, 101 | ); 102 | } 103 | function request({ current, pageSize, filters, sorter, username, email }) { 104 | console.log( 105 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s', 106 | username, 107 | pageSize, 108 | current, 109 | JSON.stringify(filters), 110 | JSON.stringify(sorter), 111 | ); 112 | const start = pageSize * (current - 1); 113 | const end = start + pageSize; 114 | let totalList = db.list; 115 | totalList = filter(totalList, 'username', username); 116 | totalList = filter(totalList, 'email', email); 117 | if (filters) { 118 | Object.keys(filters).forEach(key => { 119 | if (!filters[key]) { 120 | return true; 121 | } 122 | if (filters[key].length === 0) { 123 | return true; 124 | } 125 | totalList = totalList.filter(item => filters[key].includes(item[key])); 126 | }); 127 | } 128 | if (sorter && sorter.column) { 129 | const { dataIndex } = sorter.column; 130 | if (sorter.order === 'descend') { 131 | totalList = [...totalList].sort( 132 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0), 133 | ); 134 | } else { 135 | totalList = [...totalList].sort( 136 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0), 137 | ); 138 | } 139 | } 140 | const list = totalList.slice(start, end); 141 | return new Promise(r => 142 | setTimeout(() => { 143 | r({ 144 | list, 145 | total: totalList.length, 146 | }); 147 | }, 300), 148 | ); 149 | } 150 | ``` 151 | 152 | ### 默认表单回填值 153 | 154 | ```jsx 155 | import React from 'react'; 156 | import { useFormTable } from 'sunflower-antd'; 157 | import { Input, Button, Table, Form } from 'antd'; 158 | import Mock from 'mockjs'; // mock request 159 | 160 | export default props => { 161 | const { formProps, tableProps, form } = useFormTable({ 162 | async search(values) { 163 | const res = await request(values); 164 | return { 165 | dataSource: res.list, 166 | total: res.total, 167 | }; 168 | }, 169 | async defaultFormValues() { 170 | await new Promise(r => setTimeout(r, 200)); 171 | return { 172 | username: 'j', 173 | }; 174 | }, 175 | }); 176 | 177 | return ( 178 |
179 |
180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 196 | 197 | 198 | 199 |
216 | 217 | ); 218 | }; 219 | 220 | // mock request 221 | const total = 200; 222 | const db = Mock.mock({ 223 | [`list|${total}`]: [ 224 | { 225 | username: '@name', 226 | email: '@email', 227 | id: '@guid', 228 | 'gender|1': ['male', 'female'], 229 | }, 230 | ], 231 | }); 232 | function filter(list, dataIndex, keyword) { 233 | if (!keyword) { 234 | return list; 235 | } 236 | return list.filter( 237 | item => 238 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 239 | -1, 240 | ); 241 | } 242 | function request({ current, pageSize, filters, sorter, username, email }) { 243 | console.log( 244 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s', 245 | username, 246 | pageSize, 247 | current, 248 | JSON.stringify(filters), 249 | JSON.stringify(sorter), 250 | ); 251 | const start = pageSize * (current - 1); 252 | const end = start + pageSize; 253 | let totalList = db.list; 254 | totalList = filter(totalList, 'username', username); 255 | totalList = filter(totalList, 'email', email); 256 | if (filters) { 257 | Object.keys(filters).forEach(key => { 258 | if (!filters[key]) { 259 | return true; 260 | } 261 | if (filters[key].length === 0) { 262 | return true; 263 | } 264 | totalList = totalList.filter(item => filters[key].includes(item[key])); 265 | }); 266 | } 267 | if (sorter && sorter.column) { 268 | const { dataIndex } = sorter.column; 269 | if (sorter.order === 'descend') { 270 | totalList = [...totalList].sort( 271 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0), 272 | ); 273 | } else { 274 | totalList = [...totalList].sort( 275 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0), 276 | ); 277 | } 278 | } 279 | const list = totalList.slice(start, end); 280 | return new Promise(r => 281 | setTimeout(() => { 282 | r({ 283 | list, 284 | total: totalList.length, 285 | }); 286 | }, 300), 287 | ); 288 | } 289 | ``` 290 | 291 | ### 过滤、排序 292 | 293 | ```jsx 294 | import React from 'react'; 295 | import { useFormTable } from 'sunflower-antd'; 296 | import { Input, Button, Form, Table } from 'antd'; 297 | import Mock from 'mockjs'; // mock request 298 | 299 | export default props => { 300 | const { formProps, tableProps, sorter, filters, form } = useFormTable({ 301 | async search(values) { 302 | const res = await request(values); 303 | return { 304 | dataSource: res.list, 305 | total: res.total, 306 | }; 307 | }, 308 | }); 309 | 310 | return ( 311 |
312 |
313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 329 | 330 | 331 | 332 |
374 | 375 | ); 376 | }; 377 | 378 | // mock request 379 | const total = 200; 380 | const db = Mock.mock({ 381 | [`list|${total}`]: [ 382 | { 383 | username: '@name', 384 | email: '@email', 385 | id: '@guid', 386 | 'gender|1': ['male', 'female'], 387 | }, 388 | ], 389 | }); 390 | function filter(list, dataIndex, keyword) { 391 | if (!keyword) { 392 | return list; 393 | } 394 | return list.filter( 395 | item => 396 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) > 397 | -1, 398 | ); 399 | } 400 | function request({ current, pageSize, filters, sorter, username, email }) { 401 | console.log( 402 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s', 403 | username, 404 | pageSize, 405 | current, 406 | JSON.stringify(filters), 407 | JSON.stringify(sorter), 408 | ); 409 | const start = pageSize * (current - 1); 410 | const end = start + pageSize; 411 | let totalList = db.list; 412 | totalList = filter(totalList, 'username', username); 413 | totalList = filter(totalList, 'email', email); 414 | if (filters) { 415 | Object.keys(filters).forEach(key => { 416 | if (!filters[key]) { 417 | return true; 418 | } 419 | if (filters[key].length === 0) { 420 | return true; 421 | } 422 | totalList = totalList.filter(item => filters[key].includes(item[key])); 423 | }); 424 | } 425 | if (sorter && sorter.column) { 426 | const { dataIndex } = sorter.column; 427 | if (sorter.order === 'descend') { 428 | totalList = [...totalList].sort( 429 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0), 430 | ); 431 | } else { 432 | totalList = [...totalList].sort( 433 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0), 434 | ); 435 | } 436 | } 437 | const list = totalList.slice(start, end); 438 | return new Promise(r => 439 | setTimeout(() => { 440 | r({ 441 | list, 442 | total: totalList.length, 443 | }); 444 | }, 300), 445 | ); 446 | } 447 | ``` 448 | 449 | ## API 450 | 451 | ```js 452 | const result = useFormTable(config); 453 | ``` 454 | 455 | ### Config 456 | 457 |
458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 |
名称说明类型默认值
search请求方法,其参数为 form fields 的值(requestData) => Promise<responseData> | responseData
form可选。在 antd4 中为 useForm 返回的 form 实例。在 antd3 中 Form.create() props.form.
autoFirstSearch是否组件渲染就进行调用表单查询booleantrue
defaultPageSize默认的分页大小number10
defaultCurrent默认的当前页number1
defaultFormValues默认的表单回填值,可为一个对象,也可为一个异步方法。object | () => Promise<object>
505 | 506 | - responseData 507 | 508 | ```js 509 | { 510 | list: [{ 511 | name: 'jack', 512 | }, { 513 | name: 'lily', 514 | }], 515 | total: 10, 516 | } 517 | ``` 518 | 519 | - requestData 520 | 521 | ```js 522 | search({ current, pageSize, filters, sorter, ...formValues }) { 523 | 524 | } 525 | ``` 526 | 527 | ### Result 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 |
名称说明类型
formPropsantd Form 组件的 props,作为 Form 组件的 props 即可
tablePropsantd Table 组件的 props,作为 Table 组件的 props 即可
formantd Form 的实例,可访问查看 form 实例的方法
loading正在搜索boolean
current当前第几页number
pageSize分页大小number
formValues对应 form fileds 的值object
dataSourcesearch 方法返回的 dataSourcearray
totalsearch 方法返回的 totalnumber
defaultFormValuesLoading当使用 defaultFormValues 在请求表单回填值的 loadingboolean
filtersantd Table 组件的 filtersobject
sorterantd Table 组件的 sorterobject
searchsearch 方法,可主动触发,可传入自定义请求参数(customrequestData) => void
605 | -------------------------------------------------------------------------------- /src/useModal/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Process Components 4 | path: /process-components 5 | group: 6 | title: Process Components 7 | path: /process-components 8 | title: useModal 9 | order: 6 10 | --- 11 | 12 | ## Overview 13 | 14 | When you want to use `antd Modal`, you can use it 15 | 16 | ## Examples 17 | 18 | ### Basic 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useModal } from 'sunflower-antd'; 23 | import { Modal, Input, Button, Form, message } from 'antd'; 24 | 25 | export default props => { 26 | const [form] = Form.useForm(); 27 | const { modalProps, show, close } = useModal({ 28 | defaultVisible: false, 29 | }); 30 | const onSubmit = () => { 31 | form 32 | .validateFields() 33 | .then(async value => { 34 | await new Promise(r => setTimeout(r, 1000)); 35 | form.resetFields(); 36 | message.success('提交成功'); 37 | close(); 38 | }) 39 | .catch(err => { 40 | console.log(err); 41 | }); 42 | }; 43 | return ( 44 |
45 | 52 |
53 | 58 | 59 | 60 | 61 | 72 | 73 | 74 |
75 |
76 | 77 |
78 | ); 79 | }; 80 | ``` 81 | 82 | ## API 83 | 84 | ```js 85 | const result = useModal(config); 86 | ``` 87 | 88 | ### Config 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
KeyDescriptionTypeDefault
defaultVisibleWhether the modal dialog is visible or notbooleanfalse
108 | 109 | ### Result 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
KeyDescriptionType
modalPropsantd Modal props
showSpecify a function that can open the modal() => void
closeSpecify a function that can close the modal() => void
visibleWhether the modal dialog is visible or notboolean
142 | -------------------------------------------------------------------------------- /src/useModal/index.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react'; 2 | 3 | export interface UseModalConfig { 4 | defaultVisible: boolean; 5 | } 6 | 7 | export const useModal = (config: UseModalConfig) => { 8 | const modalConfig = config || ({} as UseModalConfig); 9 | const { defaultVisible = false } = modalConfig; 10 | 11 | const [visible, setVisible] = useState(defaultVisible); 12 | const show = useCallback(() => setVisible(true), [visible]); 13 | const close = useCallback(() => setVisible(false), [visible]); 14 | 15 | const modalProps = { 16 | visible, 17 | onCancel: close, 18 | }; 19 | return { 20 | visible, 21 | show, 22 | close, 23 | modalProps, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/useModal/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 流程组件 4 | path: /zh-CN/process-components 5 | group: 6 | title: 流程组件 7 | path: /zh-CN/process-components 8 | title: useModal 9 | order: 6 10 | --- 11 | 12 | ## 说明 13 | 14 | 当你需要使用`antd Modal`,可使用 `useModal`。 15 | 16 | ## 示例 17 | 18 | ### 基础 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useModal } from 'sunflower-antd'; 23 | import { Modal, Input, Button, Form, message } from 'antd'; 24 | 25 | export default props => { 26 | const [form] = Form.useForm(); 27 | const { modalProps, show, close } = useModal({ 28 | defaultVisible: false, 29 | }); 30 | const onSubmit = () => { 31 | form 32 | .validateFields() 33 | .then(async value => { 34 | await new Promise(r => setTimeout(r, 1000)); 35 | form.resetFields(); 36 | message.success('提交成功'); 37 | close(); 38 | }) 39 | .catch(err => { 40 | console.log(err); 41 | }); 42 | }; 43 | return ( 44 |
45 | 52 |
53 | 58 | 59 | 60 | 61 | 72 | 73 | 74 |
75 |
76 | 77 |
78 | ); 79 | }; 80 | ``` 81 | 82 | ## API 83 | 84 | ```js 85 | const result = useModal(config); 86 | ``` 87 | 88 | ### Config 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
名称说明类型默认值
defaultVisible弹窗是否可见booleanfalse
108 | 109 | ### Result 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
名称说明类型
modalPropsantd Modal 组件的 props,作为 Modal 组件的 props 即可
show打开弹窗() => void
close关闭弹窗() => void
visible弹窗当前显隐状态boolean
142 | -------------------------------------------------------------------------------- /src/useModalForm/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Process Components 4 | path: /process-components 5 | group: 6 | title: Process Components 7 | path: /process-components 8 | title: useModalForm 9 | order: 3 10 | --- 11 | 12 | ## Overview 13 | 14 | When you want to use "Modal Form", you can use it. 15 | 16 | ## Examples 17 | 18 | ### Basic 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useModalForm } from 'sunflower-antd'; 23 | import { Modal, Input, Button, Form, Spin } from 'antd'; 24 | 25 | export default props => { 26 | const [form] = Form.useForm(); 27 | const { 28 | modalProps, 29 | formProps, 30 | show, 31 | formLoading, 32 | formValues, 33 | formResult, 34 | } = useModalForm({ 35 | defaultVisible: false, 36 | autoSubmitClose: true, 37 | autoResetForm: true, 38 | async submit({ username, email }) { 39 | console.log('beforeSubmit'); 40 | await new Promise(r => setTimeout(r, 1000)); 41 | console.log('afterSubmit', username, email); 42 | return 'ok'; 43 | }, 44 | form, 45 | }); 46 | return ( 47 |
48 | 49 | 50 | <> 51 |

52 | submit: username {formValues.username} email {formValues.email} 53 |

54 |

result: {formResult}

55 |
56 | 61 | 62 | 63 | 64 | 75 | 76 | 77 |
78 | 79 |
80 |
81 | 82 |
83 | ); 84 | }; 85 | ``` 86 | 87 | ### DefaultFormValues 88 | 89 | ```jsx 90 | import React from 'react'; 91 | import { useModalForm } from 'sunflower-antd'; 92 | import { Modal, Input, Button, Form, Spin } from 'antd'; 93 | 94 | export default props => { 95 | const [form] = Form.useForm(); 96 | const { 97 | modalProps, 98 | formProps, 99 | show, 100 | formLoading, 101 | defaultFormValuesLoading, 102 | } = useModalForm({ 103 | defaultVisible: false, 104 | autoSubmitClose: true, 105 | form, 106 | async submit({ username, email }) { 107 | console.log('beforeSubmit'); 108 | await new Promise(r => setTimeout(r, 1000)); 109 | console.log('afterSubmit', `username:${username}, email:${email}`); 110 | return 'ok'; 111 | }, 112 | async defaultFormValues() { 113 | await new Promise(r => setTimeout(r, 3000)); 114 | return { 115 | username: 'lily', 116 | }; 117 | }, 118 | }); 119 | return ( 120 |
121 | 122 | 123 |
124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
132 |
133 |
134 | 135 |
136 | ); 137 | }; 138 | ``` 139 | 140 | ### Submit method 141 | 142 | ```jsx 143 | import React from 'react'; 144 | import { useModalForm } from 'sunflower-antd'; 145 | import { Modal, Input, Button, Form, Spin } from 'antd'; 146 | 147 | export default props => { 148 | const [form] = Form.useForm(); 149 | const { 150 | modalProps, 151 | formProps, 152 | show, 153 | formLoading, 154 | submit: formSubmit, 155 | } = useModalForm({ 156 | defaultVisible: false, 157 | autoSubmitClose: true, 158 | async submit({ username, email }) { 159 | console.log('beforeSubmit'); 160 | await new Promise(r => setTimeout(r, 1000)); 161 | console.log('afterSubmit', username, email); 162 | return 'ok'; 163 | }, 164 | form, 165 | }); 166 | return ( 167 |
168 | 169 | 170 |
171 | 172 | 173 | 174 | 175 | 186 | 187 | 188 | 189 | 190 | 201 | 202 |
203 |
204 |
205 | 206 |
207 | ); 208 | }; 209 | ``` 210 | 211 | ### Default form submit 212 | 213 | ```jsx 214 | import React from 'react'; 215 | import { useModalForm } from 'sunflower-antd'; 216 | import { Modal, Input, Button, Form, Spin } from 'antd'; 217 | 218 | export default props => { 219 | const [form] = Form.useForm(); 220 | const { 221 | modalProps, 222 | formProps, 223 | show, 224 | formLoading, 225 | form: formInstance, 226 | } = useModalForm({ 227 | defaultVisible: false, 228 | autoSubmitClose: true, 229 | async submit({ username, email }) { 230 | console.log('beforeSubmit'); 231 | await new Promise(r => setTimeout(r, 1000)); 232 | console.log('afterSubmit', username, email); 233 | return 'ok'; 234 | }, 235 | form, 236 | }); 237 | return ( 238 |
239 | 240 | 241 |
242 | 243 | 244 | 245 | 246 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 268 | 269 |
270 |
271 |
272 | 273 |
274 | ); 275 | }; 276 | ``` 277 | 278 | ## API 279 | 280 | ```js 281 | const result = useModalForm(config); 282 | ``` 283 | 284 | ### Config 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 |
KeyDescriptionTypeDefault
defaultVisibleWhether the modal dialog is visible or notbooleanfalse
autoSubmitCloseClick Modal "ok", will trigger submit, then close modalbooleantrue
autoResetFormReset the specified fields' value(to initialValue) and statusbooleantrue
submitsubmit method, the parameter is the value of the form fields(formValues) => Promise<formResult> | formResult
formDecorated by Form.create() will be automatically set this.props.form property(antd3); useForm react hooks(antd4)
defaultFormValuesDefault form values.If the form has data that needs to be backfilled, use it to get the data.object | () => Promise<object>
334 | 335 | ### Result 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 |
KeyDescriptionType
modalPropsantd Modal props
showSpecify a function that can open the modal() => void
closeSpecify a function that can close the modal() => void
visibleWhether the modal dialog is visible or notboolean
formPropsantd Form props
formForm instance
formLoadingform request loading.boolean
formValuesform valuesobject
initialValuesinitial form valuesobject
formResultsubmit return value
defaultFormValuesLoadingWhen use 'defaultFormValues', the value will be true during the request.boolean
submitwill call the 'submit' method
408 | -------------------------------------------------------------------------------- /src/useModalForm/index.ts: -------------------------------------------------------------------------------- 1 | import { useModal } from '../useModal'; 2 | import { useForm, UseFormConfig } from '../useForm'; 3 | 4 | export interface UseModalFormConfig extends UseFormConfig { 5 | defaultVisible?: boolean; 6 | autoSubmitClose?: boolean; 7 | autoResetForm?: boolean; 8 | } 9 | 10 | export const useModalForm = (config: UseModalFormConfig) => { 11 | const modalFormConfig = config || ({} as UseModalFormConfig); 12 | const { 13 | defaultVisible = false, 14 | autoSubmitClose = true, 15 | autoResetForm = true, 16 | submit, 17 | form, 18 | defaultFormValues, 19 | } = modalFormConfig; 20 | 21 | const { visible, show, close, modalProps } = useModal({ 22 | defaultVisible, 23 | }); 24 | 25 | const { 26 | form: formInstance, 27 | formProps, 28 | formLoading, 29 | defaultFormValuesLoading, 30 | formValues, 31 | initialValues, 32 | formResult, 33 | submit: formSubmit, 34 | } = useForm({ 35 | form, 36 | submit, 37 | defaultFormValues, 38 | }); 39 | 40 | const modalFormProps = { 41 | ...modalProps, 42 | onOk: () => { 43 | formSubmit().then(() => { 44 | if (autoSubmitClose) { 45 | close(); 46 | } 47 | 48 | if (autoResetForm) { 49 | formInstance.resetFields(); 50 | } 51 | }); 52 | }, 53 | }; 54 | 55 | return { 56 | form: formInstance, 57 | visible, 58 | show, 59 | close, 60 | modalProps: modalFormProps, 61 | formProps, 62 | formLoading, 63 | defaultFormValuesLoading, 64 | formValues, 65 | initialValues, 66 | formResult, 67 | submit: formSubmit, 68 | }; 69 | }; 70 | -------------------------------------------------------------------------------- /src/useModalForm/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 流程组件 4 | path: /zh-CN/process-components 5 | group: 6 | title: 流程组件 7 | path: /zh-CN/process-components 8 | title: useModalForm 9 | order: 3 10 | --- 11 | 12 | ## 说明 13 | 14 | 当你需要在关闭弹窗时自动提交表单,可使用 `useModalForm`。 15 | 16 | ## 示例 17 | 18 | ### 基础 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useModalForm } from 'sunflower-antd'; 23 | import { Modal, Input, Button, Form, Spin } from 'antd'; 24 | 25 | export default props => { 26 | const [form] = Form.useForm(); 27 | const { 28 | modalProps, 29 | formProps, 30 | show, 31 | formLoading, 32 | formValues, 33 | formResult, 34 | } = useModalForm({ 35 | defaultVisible: false, 36 | autoSubmitClose: true, 37 | autoResetForm: true, 38 | async submit({ username, email }) { 39 | console.log('beforeSubmit'); 40 | await new Promise(r => setTimeout(r, 1000)); 41 | console.log('afterSubmit', username, email); 42 | return 'ok'; 43 | }, 44 | form, 45 | }); 46 | return ( 47 |
48 | 49 | 50 | <> 51 |

52 | submit: username {formValues.username} email {formValues.email} 53 |

54 |

result: {formResult}

55 |
56 | 61 | 62 | 63 | 64 | 75 | 76 | 77 |
78 | 79 |
80 |
81 | 82 |
83 | ); 84 | }; 85 | ``` 86 | 87 | ### DefaultFormValues 88 | 89 | ```jsx 90 | import React from 'react'; 91 | import { useModalForm } from 'sunflower-antd'; 92 | import { Modal, Input, Button, Form, Spin } from 'antd'; 93 | 94 | export default props => { 95 | const [form] = Form.useForm(); 96 | const { 97 | modalProps, 98 | formProps, 99 | show, 100 | formLoading, 101 | defaultFormValuesLoading, 102 | } = useModalForm({ 103 | defaultVisible: false, 104 | autoSubmitClose: true, 105 | form, 106 | async submit({ username, email }) { 107 | console.log('beforeSubmit'); 108 | await new Promise(r => setTimeout(r, 1000)); 109 | console.log('afterSubmit', `username:${username}, email:${email}`); 110 | return 'ok'; 111 | }, 112 | async defaultFormValues() { 113 | await new Promise(r => setTimeout(r, 3000)); 114 | return { 115 | username: 'lily', 116 | }; 117 | }, 118 | }); 119 | return ( 120 |
121 | 122 | 123 |
124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
132 |
133 |
134 | 135 |
136 | ); 137 | }; 138 | ``` 139 | 140 | ### Submit method 141 | 142 | ```jsx 143 | import React from 'react'; 144 | import { useModalForm } from 'sunflower-antd'; 145 | import { Modal, Input, Button, Form, Spin } from 'antd'; 146 | 147 | export default props => { 148 | const [form] = Form.useForm(); 149 | const { 150 | modalProps, 151 | formProps, 152 | show, 153 | formLoading, 154 | submit: formSubmit, 155 | } = useModalForm({ 156 | defaultVisible: false, 157 | autoSubmitClose: true, 158 | async submit({ username, email }) { 159 | console.log('beforeSubmit'); 160 | await new Promise(r => setTimeout(r, 1000)); 161 | console.log('afterSubmit', username, email); 162 | return 'ok'; 163 | }, 164 | form, 165 | }); 166 | return ( 167 |
168 | 169 | 170 |
171 | 172 | 173 | 174 | 175 | 186 | 187 | 188 | 189 | 190 | 201 | 202 |
203 |
204 |
205 | 206 |
207 | ); 208 | }; 209 | ``` 210 | 211 | ### Default form submit 212 | 213 | ```jsx 214 | import React from 'react'; 215 | import { useModalForm } from 'sunflower-antd'; 216 | import { Modal, Input, Button, Form, Spin } from 'antd'; 217 | 218 | export default props => { 219 | const [form] = Form.useForm(); 220 | const { 221 | modalProps, 222 | formProps, 223 | show, 224 | formLoading, 225 | form: formInstance, 226 | } = useModalForm({ 227 | defaultVisible: false, 228 | autoSubmitClose: true, 229 | async submit({ username, email }) { 230 | console.log('beforeSubmit'); 231 | await new Promise(r => setTimeout(r, 1000)); 232 | console.log('afterSubmit', username, email); 233 | return 'ok'; 234 | }, 235 | form, 236 | }); 237 | return ( 238 |
239 | 240 | 241 |
242 | 243 | 244 | 245 | 246 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 268 | 269 |
270 |
271 |
272 | 273 |
274 | ); 275 | }; 276 | ``` 277 | 278 | ## API 279 | 280 | ```js 281 | const result = useModalForm(config); 282 | ``` 283 | 284 | ### Config 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 |
名称说明类型默认值
defaultVisible弹窗默认是否可见booleanfalse
autoSubmitClose点击确认, 提交表单后是否自动关闭弹窗booleantrue
autoResetForm点击确认, 提交表单后是否重置initialValuesbooleantrue
submit请求方法, 其参数为 form fields 的值(formValues) => Promise<formResult> | formResult
form在 antd4 中为 useForm 返回的 form 实例。在 antd3 中 Form.create() props.form
defaultFormValues默认的表单回填值,可为一个对象,也可为一个异步方法object | () => Promise<object>
334 | 335 | ### Result 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 |
名称说明类型
modalPropsantd Modal 组件的 props,作为 Modal 组件的 props 即可
show打开弹窗() => void
close关闭弹窗() => void
visible弹窗当前显隐状态boolean
formPropsantd Form 组件的 props,作为 Form 组件的 props 即可
formantd Form 的实例,可访问查看 form 实例的方法
formLoading正在提交表单boolean
formValues对应 form fileds 的值object
initialValues表单默认值. 对应defaultFormValuesobject
formResultsubmit返回值any
defaultFormValuesLoading当使用 defaultFormValues 在请求表单回填值的 loading.boolean
submitsubmit 方法. 可主动触发, 提交表单, 其参数为 form fields 的值(formValues) => Promise<formResult>
408 | -------------------------------------------------------------------------------- /src/useSearchResult/index.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | export interface UseSearchResultConfig { 4 | search?: (requestData: S) => Promise | T; 5 | autoFirstSearch?: boolean; 6 | defaultRequestData?: S | (() => Promise | S); 7 | } 8 | 9 | export const useSearchResult = ({ 10 | search, 11 | autoFirstSearch = true, 12 | defaultRequestData, 13 | }: UseSearchResultConfig) => { 14 | const [requestData, setRequestData] = useState(); 15 | const [responseData, setResponseData] = useState(); 16 | const [loading, setLoading] = useState(false); 17 | const [defaultRequestDataLoading, setDefaultRequestDataLoading] = useState( 18 | false, 19 | ); 20 | 21 | const searchFunc = (data: S) => { 22 | setRequestData(data); 23 | setLoading(true); 24 | return Promise.resolve(search && search(data)).then(response => { 25 | setResponseData(response); 26 | setLoading(false); 27 | }); 28 | }; 29 | 30 | useEffect(() => { 31 | setDefaultRequestDataLoading(true); 32 | let value; 33 | if (typeof defaultRequestData === 'function') { 34 | value = (defaultRequestData as any)(); 35 | } else { 36 | value = defaultRequestData; 37 | } 38 | Promise.resolve(value).then(data => { 39 | setRequestData(data); 40 | setDefaultRequestDataLoading(false); 41 | if (autoFirstSearch) { 42 | searchFunc(data); 43 | } 44 | }).catch(() => { 45 | setDefaultRequestDataLoading(false); 46 | }); 47 | }, []); 48 | 49 | return { 50 | loading, 51 | requestData, 52 | setRequestData, 53 | responseData, 54 | defaultRequestDataLoading, 55 | search: searchFunc, 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /src/useStepsForm/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Process Components 4 | path: /process-components 5 | group: 6 | title: Process Components 7 | path: /process-components 8 | title: useStepsForm 9 | order: 4 10 | --- 11 | 12 | ## Overview 13 | 14 | `useStepsForm` is a react-hook. When you want to use "Steps Form", you can use it. 15 | 16 | ## Examples 17 | 18 | ### Basic 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useStepsForm } from 'sunflower-antd'; 23 | import { Steps, Input, Button, Form, Result } from 'antd'; 24 | 25 | const { Step } = Steps; 26 | 27 | const layout = { 28 | labelCol: { span: 8 }, 29 | wrapperCol: { span: 16 }, 30 | }; 31 | const tailLayout = { 32 | wrapperCol: { offset: 8, span: 16 }, 33 | }; 34 | 35 | export default props => { 36 | const { 37 | form, 38 | current, 39 | gotoStep, 40 | stepsProps, 41 | formProps, 42 | submit, 43 | formLoading, 44 | } = useStepsForm({ 45 | async submit(values) { 46 | const { username, email, address } = values; 47 | console.log(username, email, address); 48 | await new Promise(r => setTimeout(r, 1000)); 49 | return 'ok'; 50 | }, 51 | total: 3, 52 | }); 53 | 54 | const formList = [ 55 | <> 56 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | , 75 | 76 | <> 77 | 87 | 88 | 89 | 90 | 104 | 105 | 106 | , 107 | ]; 108 | 109 | return ( 110 |
111 | 112 | 113 | 114 | 115 | 116 | 117 |
118 |
119 | {formList[current]} 120 |
121 | 122 | {current === 2 && ( 123 | 128 | 137 | 138 | 139 | } 140 | /> 141 | )} 142 |
143 |
144 | ); 145 | }; 146 | ``` 147 | 148 | ## API 149 | 150 | ```js 151 | const Result = useStepsForm(Config); 152 | ``` 153 | 154 | ### Config 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
KeyDescriptionTypeDefault
submitsubmit method, the parameter is the value of the form fields(formValues) => Promise<formResult> | formResult
formDecorated by Form.create() will be automatically set this.props.form property(antd3); useForm react hooks(antd4)
defaultFormValuesDefault form values.If the form has data that needs to be backfilled, use it to get the data.object
defaultCurrentDefault step, counting from 0.number
totaltotal counting for steps.number
isBackValidateshould validate if go to prev stepbooleantrue
204 | 205 | ### Result 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 |
KeyDescriptionType
currentcurrent step, counting from 0.number
gotoStepgoto the target step. When use it, the hook will validate current form at first.(step: number) => void
stepsPropsantd Steps propsobject
formPropsantd Form propsobject
formForm instance
formLoadingform request loading.boolean
formValuesform valuesobject
initialValuesinitial form valuesobject
formResultsubmit return value
defaultFormValuesLoadingWhen use 'defaultFormValues', the value will be true during the request.boolean
submitwill call the 'submit' method
273 | -------------------------------------------------------------------------------- /src/useStepsForm/index.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useForm, UseFormConfig } from '../useForm'; 3 | 4 | export interface UseStepsFormConfig extends UseFormConfig { 5 | defaultCurrent?: number; 6 | total?: number; 7 | isBackValidate?: boolean; 8 | } 9 | 10 | export const useStepsForm = (config: UseStepsFormConfig) => { 11 | const { 12 | form, 13 | defaultFormValues, 14 | defaultCurrent = 0, 15 | submit, 16 | total, 17 | isBackValidate = true, 18 | } = config || ({} as UseStepsFormConfig); 19 | const [current, setCurrent] = useState(defaultCurrent); 20 | 21 | const { 22 | form: formInstance, 23 | formProps, 24 | formLoading, 25 | defaultFormValuesLoading, 26 | formValues, 27 | initialValues, 28 | formResult, 29 | submit: formSubmit, 30 | } = useForm({ 31 | form, 32 | submit, 33 | defaultFormValues, 34 | }); 35 | 36 | const go = step => { 37 | let targetStep = step; 38 | 39 | if (step > total - 1) { 40 | targetStep = total - 1; 41 | } 42 | 43 | if (step < 0) { 44 | targetStep = 0; 45 | } 46 | 47 | setCurrent(targetStep); 48 | }; 49 | 50 | const gotoStep = step => { 51 | if (step === current) { 52 | return true; 53 | } 54 | 55 | if (step < current && !isBackValidate) { 56 | go(step); 57 | return true; 58 | } 59 | 60 | // goto the target step after passing validate 61 | return formInstance.validateFields().then(values => { 62 | go(step); 63 | return values; 64 | }); 65 | }; 66 | 67 | const handleStepChange = currentStep => gotoStep(currentStep); 68 | 69 | return { 70 | current, 71 | gotoStep, 72 | stepsProps: { 73 | current, 74 | onChange: handleStepChange, 75 | }, 76 | formProps, 77 | formLoading, 78 | defaultFormValuesLoading, 79 | formValues, 80 | initialValues, 81 | formResult, 82 | form: formInstance, 83 | submit: formSubmit, 84 | }; 85 | }; 86 | -------------------------------------------------------------------------------- /src/useStepsForm/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 流程组件 4 | path: /zh-CN/process-components 5 | group: 6 | title: 流程组件 7 | path: /zh-CN/process-components 8 | title: useStepsForm 9 | order: 4 10 | --- 11 | 12 | ## 说明 13 | 14 | 当你需要使用分步表单时,可使用 `useStepsForm` 15 | 16 | ## 示例 17 | 18 | ### 基础 19 | 20 | ```jsx 21 | import React from 'react'; 22 | import { useStepsForm } from 'sunflower-antd'; 23 | import { Steps, Input, Button, Form, Result } from 'antd'; 24 | 25 | const { Step } = Steps; 26 | 27 | const layout = { 28 | labelCol: { span: 8 }, 29 | wrapperCol: { span: 16 }, 30 | }; 31 | const tailLayout = { 32 | wrapperCol: { offset: 8, span: 16 }, 33 | }; 34 | 35 | export default props => { 36 | const { 37 | form, 38 | current, 39 | gotoStep, 40 | stepsProps, 41 | formProps, 42 | submit, 43 | formLoading, 44 | } = useStepsForm({ 45 | async submit(values) { 46 | const { username, email, address } = values; 47 | console.log(username, email, address); 48 | await new Promise(r => setTimeout(r, 1000)); 49 | return 'ok'; 50 | }, 51 | total: 3, 52 | }); 53 | 54 | const formList = [ 55 | <> 56 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | , 75 | 76 | <> 77 | 87 | 88 | 89 | 90 | 104 | 105 | 106 | , 107 | ]; 108 | 109 | return ( 110 |
111 | 112 | 113 | 114 | 115 | 116 | 117 |
118 |
119 | {formList[current]} 120 |
121 | 122 | {current === 2 && ( 123 | 128 | 137 | 138 | 139 | } 140 | /> 141 | )} 142 |
143 |
144 | ); 145 | }; 146 | ``` 147 | 148 | ## API 149 | 150 | ```js 151 | const Result = useStepsForm(Config); 152 | ``` 153 | 154 | ### Config 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
名称说明类型默认值
submit请求方法, 其参数为 form fields 的值(formValues) => Promise<formResult> | formResult
form在 antd4 中为 `useForm` 返回的 `form` 实例。在 antd3 中 `Form.create()` 会自动注入 `props.form`
defaultFormValues默认的表单回填值,可为一个对象,也可为一个异步方法object
defaultCurrent默认开始的步骤,从 0 开始number0
total分步表单的总步骤number
isBackValidate返回上一步时是否校验表单booleantrue
204 | 205 | ### Result 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 |
名称说明类型
current当前步骤,从 0 开始number
gotoStep切换到目标步骤,使用该方法时,将首先校验当前步骤的表单 void'}}>
stepsPropsantd Steps 组件的 props,作为 Steps 组件的 props 即可object
formPropsantd Form 组件的 props,作为 Form 组件的 props 即可object
formForm 实例
formLoading正在提交表单boolean
formValues对应 form fileds 的值object
initialValues表单默认值. 对应 defaultFormValuesobject
formResultsubmit 方法返回值
defaultFormValuesLoading当使用 defaultFormValues 在请求表单回填值的 loadingboolean
submitsubmit 方法. 可主动触发, 提交表单, 其参数为 form fields 的值
273 | -------------------------------------------------------------------------------- /tests/useCascadeSearch.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useCascadeSearch } from '../src/useCascadeSearch'; 3 | 4 | test('useCascadeSearch', async () => { 5 | const config = { 6 | list: [ 7 | () => { 8 | return [ 9 | { 10 | label: '1', 11 | value: '1', 12 | }, 13 | ]; 14 | }, 15 | async (lastValue, value) => { 16 | await new Promise(r => setTimeout(r, 200)); 17 | return value === '1' 18 | ? [ 19 | { 20 | label: '2', 21 | value: '2', 22 | }, 23 | ] 24 | : []; 25 | }, 26 | ], 27 | autoFirstSearch: false, 28 | }; 29 | const { result, waitForNextUpdate } = renderHook(() => 30 | useCascadeSearch(config), 31 | ); 32 | result.current.search(0); 33 | expect(result.current.loadingList).toEqual([true, false]); 34 | await waitForNextUpdate(); 35 | expect(result.current.loadingList).toEqual([false, false]); 36 | expect(result.current.responseDataList).toEqual([ 37 | [{ label: '1', value: '1' }], 38 | ]); 39 | result.current.search(1, '1'); 40 | expect(result.current.loadingList).toEqual([false, true]); 41 | await waitForNextUpdate(); 42 | expect(result.current.loadingList).toEqual([false, false]); 43 | expect(result.current.responseDataList).toEqual([ 44 | [{ label: '1', value: '1' }], 45 | [{ label: '2', value: '2' }], 46 | ]); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/useCascadeSelect.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useCascadeSelect } from '../src/useCascadeSelect'; 3 | 4 | test('useCascadeSelect', async () => { 5 | const setFieldsValue = jest.fn(); 6 | const config = { 7 | list: [ 8 | { 9 | name: '1', 10 | options: async () => { 11 | await new Promise(r => setTimeout(r, 200)); 12 | return [ 13 | { 14 | label: '1', 15 | value: '1', 16 | }, 17 | ]; 18 | }, 19 | }, 20 | { 21 | name: '2', 22 | options: async value => { 23 | await new Promise(r => setTimeout(r, 200)); 24 | return value === '1' 25 | ? [ 26 | { 27 | label: '2', 28 | value: '2', 29 | }, 30 | ] 31 | : []; 32 | }, 33 | }, 34 | ], 35 | autoFirstSearch: true, 36 | form: { 37 | setFieldsValue, 38 | }, 39 | }; 40 | const { result, waitForNextUpdate } = renderHook(() => 41 | useCascadeSelect(config), 42 | ); 43 | expect(result.current.selects.length).toBe(2); 44 | expect(result.current.selects[0].options).toEqual([]); 45 | expect(result.current.selects[1].options).toEqual([]); 46 | await waitForNextUpdate(); 47 | expect(result.current.selects[0].options).toEqual([ 48 | { 49 | label: '1', 50 | value: '1', 51 | }, 52 | ]); 53 | result.current.selects[0].props.onChange('1'); 54 | expect(setFieldsValue).toBeCalled(); 55 | await waitForNextUpdate(); 56 | expect(result.current.selects[1].options).toEqual([ 57 | { 58 | label: '2', 59 | value: '2', 60 | }, 61 | ]); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/useForm.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useForm } from '../src/useForm'; 3 | 4 | test('useForm', async () => { 5 | const submit = jest.fn(); 6 | const mockForm = {}; 7 | const config = { 8 | form: mockForm, 9 | submit: values => { 10 | submit({ 11 | ...values, 12 | }); 13 | return { 14 | list: [ 15 | { 16 | name: 'lily', 17 | }, 18 | { 19 | name: 'jack', 20 | }, 21 | ], 22 | total: 10, 23 | }; 24 | }, 25 | defaultFormValues: { 26 | username: 'lily', 27 | }, 28 | }; 29 | const { result } = renderHook(() => useForm(config)); 30 | const { formProps, form } = result.current; 31 | expect(typeof formProps.onFinish).toBe('function'); 32 | expect(form).toBe(mockForm); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/useFormTable.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useFormTable } from '../src/useFormTable'; 3 | 4 | test('useFormTable', async () => { 5 | const search = jest.fn(); 6 | const mockForm = {}; 7 | const config = { 8 | form: mockForm, 9 | search: values => { 10 | search({ 11 | ...values, 12 | }); 13 | return { 14 | list: [ 15 | { 16 | name: 'lily', 17 | }, 18 | { 19 | name: 'jack', 20 | }, 21 | ], 22 | total: 10, 23 | }; 24 | }, 25 | defaultFormValues: { 26 | username: 'lily', 27 | }, 28 | autoFirstSearch: true, 29 | }; 30 | const { result } = renderHook(() => useFormTable(config)); 31 | const { formProps, tableProps, form } = result.current; 32 | expect(typeof formProps.onFinish).toBe('function'); 33 | const props = { ...tableProps }; 34 | delete props.onChange; 35 | expect(props).toEqual({ 36 | pagination: { 37 | pageSize: 10, 38 | current: undefined, 39 | defaultPageSize: 10, 40 | defaultCurrent: 1, 41 | total: undefined, 42 | }, 43 | loading: false, 44 | dataSource: undefined, 45 | }); 46 | expect(form).toBe(mockForm); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/useModal.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useModal } from '../src/useModal'; 3 | 4 | test('useModal', async () => { 5 | const config = { 6 | defaultVisible: false, 7 | }; 8 | const { result } = renderHook(() => useModal(config)); 9 | const { modalProps, show, close, visible } = result.current; 10 | expect(typeof modalProps.onCancel).toBe('function'); 11 | expect(typeof show).toBe('function'); 12 | expect(typeof close).toBe('function'); 13 | expect(typeof visible).toBe('boolean'); 14 | const props = { ...modalProps }; 15 | delete props.onCancel; 16 | expect(props).toEqual({ 17 | visible: false, 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/useModalForm.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { useModalForm } from '../src/useModalForm'; 3 | 4 | test('useModalForm', async () => { 5 | const submit = jest.fn(); 6 | const mockForm = {}; 7 | const config = { 8 | form: mockForm, 9 | submit: values => { 10 | submit({ 11 | ...values, 12 | }); 13 | return { 14 | list: [ 15 | { 16 | name: 'lily', 17 | }, 18 | { 19 | name: 'jack', 20 | }, 21 | ], 22 | total: 10, 23 | }; 24 | }, 25 | defaultFormValues: { 26 | username: 'lily', 27 | }, 28 | defaultVisible: false, 29 | autoSubmitClose: true, 30 | }; 31 | const { result } = renderHook(() => useModalForm(config)); 32 | const { formProps, modalProps, form } = result.current; 33 | expect(typeof formProps.onFinish).toBe('function'); 34 | expect(typeof modalProps.onOk).toBe('function'); 35 | expect(form).toBe(mockForm); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/useSearchResult.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import { useSearchResult } from '../src/useSearchResult'; 3 | 4 | test('useSearchResult', async () => { 5 | const config = { 6 | search: () => 7 | new Promise(r => 8 | setTimeout( 9 | () => 10 | r({ 11 | list: [ 12 | { 13 | name: 'lily', 14 | }, 15 | { 16 | name: 'jack', 17 | }, 18 | ], 19 | total: 10, 20 | }), 21 | 200, 22 | ), 23 | ), 24 | defaultRequestData: new Promise(r => 25 | setTimeout(() => { 26 | r({ 27 | a: 1, 28 | b: 2, 29 | }); 30 | }, 20), 31 | ), 32 | autoFirstSearch: false, 33 | }; 34 | const { result, waitForNextUpdate } = renderHook(() => 35 | useSearchResult(config), 36 | ); 37 | 38 | expect(result.current.requestData).toEqual(undefined); 39 | await waitForNextUpdate(); 40 | expect(result.current.requestData).toEqual({ 41 | a: 1, 42 | b: 2, 43 | }); 44 | 45 | act(() => { 46 | result.current.search({ 47 | ...result.current.requestData, 48 | currentPage: 2, 49 | pageSize: 2, 50 | }); 51 | }); 52 | 53 | expect(result.current.requestData).toEqual({ 54 | a: 1, 55 | b: 2, 56 | currentPage: 2, 57 | pageSize: 2, 58 | }); 59 | 60 | await waitForNextUpdate(); 61 | 62 | expect(result.current.responseData).toEqual({ 63 | list: [ 64 | { 65 | name: 'lily', 66 | }, 67 | { 68 | name: 'jack', 69 | }, 70 | ], 71 | total: 10, 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/useStepsForm.test.js: -------------------------------------------------------------------------------- 1 | import { renderHook, act } from '@testing-library/react-hooks'; 2 | import { useStepsForm } from '../src'; 3 | 4 | function excuteHook(defaultCurrent = 0) { 5 | const submit = jest.fn(); 6 | const mockForm = { 7 | validateFields: () => 8 | new Promise(resolve => { 9 | resolve(); 10 | }), 11 | }; 12 | const config = { 13 | form: mockForm, 14 | submit: values => { 15 | submit({ 16 | ...values, 17 | }); 18 | }, 19 | defaultFormValues: { 20 | username: 'test', 21 | }, 22 | defaultCurrent, 23 | total: 2, 24 | }; 25 | const { result } = renderHook(() => useStepsForm(config)); 26 | 27 | return result; 28 | } 29 | 30 | describe('useStepsForm', () => { 31 | test('base use', async () => { 32 | const result = excuteHook(); 33 | const { 34 | submit: formSubmit, 35 | current, 36 | gotoStep, 37 | stepsProps, 38 | formProps, 39 | } = result.current; 40 | expect(typeof formSubmit).toBe('function'); 41 | expect(typeof stepsProps).toBe('object'); 42 | expect(typeof formProps).toBe('object'); 43 | expect(typeof stepsProps.onChange).toBe('function'); 44 | expect(typeof gotoStep).toBe('function'); 45 | expect(typeof current).toBe('number'); 46 | expect(current).toBe(0); 47 | 48 | await act(() => gotoStep(1)); 49 | expect(result.current.current).toBe(1); 50 | }); 51 | 52 | test('goto prev step', async () => { 53 | const result = excuteHook(1); 54 | const { current, gotoStep } = result.current; 55 | expect(current).toBe(1); 56 | 57 | await act(() => gotoStep(0)); 58 | expect(result.current.current).toBe(0); 59 | }); 60 | 61 | test('goto a step more than total', async () => { 62 | const result = excuteHook(); 63 | const { gotoStep } = result.current; 64 | 65 | await act(() => gotoStep(3)); 66 | expect(result.current.current).toBe(1); 67 | }); 68 | 69 | test('goto current step', () => { 70 | const result = excuteHook(1); 71 | const { gotoStep } = result.current; 72 | 73 | act(() => gotoStep(1)); 74 | expect(result.current.current).toBe(1); 75 | }); 76 | 77 | test('goto a negative step', async () => { 78 | const result = excuteHook(); 79 | const { gotoStep } = result.current; 80 | 81 | await act(() => gotoStep(-2)); 82 | expect(result.current.current).toBe(0); 83 | }); 84 | 85 | test('stepsProps onChange', async () => { 86 | const result = excuteHook(); 87 | const { stepsProps } = result.current; 88 | 89 | await act(() => stepsProps.onChange(1)); 90 | expect(result.current.current).toBe(1); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "moduleResolution": "node", 7 | "importHelpers": true, 8 | "jsx": "react", 9 | "esModuleInterop": true, 10 | "baseUrl": "./", 11 | "paths": { 12 | "@/*": ["src/*"], 13 | "@@/*": ["src/.umi/*"] 14 | }, 15 | "allowSyntheticDefaultImports": true 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | "lib", 20 | "es", 21 | "dist", 22 | "typings", 23 | "**/__test__", 24 | "test" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | --------------------------------------------------------------------------------