├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── codevoc.yml │ ├── gh-pages.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── commitlint.config.js ├── config └── config.js ├── docs ├── apis │ ├── demo │ │ └── modal_base.tsx │ ├── formModal.md │ ├── hooks.md │ └── index.md ├── assets │ └── qrcode.jpg ├── changelog.md ├── examples │ ├── demo │ │ ├── demo1.tsx │ │ ├── demo2.tsx │ │ ├── demo3.tsx │ │ ├── demo4.tsx │ │ ├── demo5.tsx │ │ ├── diff.tsx │ │ ├── edit.tsx │ │ ├── index.less │ │ ├── list.tsx │ │ ├── renderField.tsx │ │ ├── search.tsx │ │ ├── search2.tsx │ │ └── shouldUpdate.tsx │ ├── forms.md │ ├── index.md │ ├── list.md │ └── search.md ├── guide │ ├── getting-started.md │ └── index.md ├── index.md ├── index.tsx └── types │ ├── card.md │ ├── custom.md │ ├── demo │ ├── card.tsx │ ├── custom.tsx │ ├── getOptions.tsx │ ├── index.tsx │ ├── list.tsx │ ├── oneLine.tsx │ ├── secureButton.tsx │ ├── select.tsx │ ├── space.tsx │ └── submit.tsx │ ├── index.md │ ├── list.md │ ├── oneLine.md │ ├── secureButton.md │ ├── select.md │ ├── space.md │ └── submit.md ├── jest.config.js ├── lerna.json ├── mock └── .gitkeep ├── package.json ├── packages └── yforms │ ├── .fatherrc.ts │ ├── README.md │ ├── package.json │ ├── src │ ├── YForm │ │ ├── Context.ts │ │ ├── Form.tsx │ │ ├── FormModal.tsx │ │ ├── Item.tsx │ │ ├── ItemChildren.tsx │ │ ├── Items.tsx │ │ ├── ItemsType.tsx │ │ ├── ItemsTypeModify.tsx │ │ ├── __test__ │ │ │ ├── Submit.test.tsx │ │ │ ├── Utils.test.tsx │ │ │ ├── YFormItems.test.tsx │ │ │ ├── __snapshots__ │ │ │ │ ├── YFormItems.test.tsx.snap │ │ │ │ └── index.test.tsx.snap │ │ │ ├── fields.tsx │ │ │ ├── index.test.tsx │ │ │ └── utils.tsx │ │ ├── component │ │ │ ├── Card.tsx │ │ │ ├── CheckboxGroup.tsx │ │ │ ├── ComponentView.tsx │ │ │ ├── Diff.tsx │ │ │ ├── List.tsx │ │ │ ├── Money.tsx │ │ │ ├── OneLine.tsx │ │ │ ├── Radio.tsx │ │ │ ├── SecureButton.tsx │ │ │ ├── Select.tsx │ │ │ ├── Space.tsx │ │ │ ├── Submit.tsx │ │ │ ├── TextArea.tsx │ │ │ └── Typography.tsx │ │ ├── hooks │ │ │ └── index.tsx │ │ ├── index.less │ │ ├── index.ts │ │ ├── scenes.tsx │ │ ├── scenesComps.tsx │ │ ├── useForm.tsx │ │ ├── useSubmit.tsx │ │ └── utils.ts │ ├── index.tsx │ └── style │ │ ├── index.less │ │ └── index.tsx │ └── webpack.config.js ├── public ├── favicon.ico └── logo.png ├── scripts └── getChangeLog.js ├── setupTests.js ├── tsconfig.json ├── typings.d.ts └── vercel.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # 🎨 editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none 2 | ESLINT=1 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | /packages/*/es 5 | /packages/*/lib 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('@umijs/fabric/dist/eslint')], 3 | rules: { 4 | 'import/no-extraneous-dependencies': 0, 5 | 'import/no-unresolved': 0, 6 | 'no-underscore-dangle': 0, 7 | '@typescript-eslint/consistent-type-definitions': 0, 8 | 'consistent-return': 0, 9 | '@typescript-eslint/consistent-type-imports': 0, 10 | '@typescript-eslint/no-shadow': 0, 11 | 'no-template-curly-in-string': 0, 12 | 'prefer-promise-reject-errors': 0, 13 | 'no-lonely-if': 0, 14 | 'no-console': 0, 15 | 'no-void': 0, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/codevoc.yml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | 10 | test: 11 | name: test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 10 18 | registry-url: https://registry.npmjs.org/ 19 | - run: yarn 20 | - run: yarn test 21 | - uses: codecov/codecov-action@v1 22 | with: 23 | flags: unittests # optional 24 | name: codecov-umbrella # optional 25 | # file: ./codecov.yml # optional 26 | token: ${{ secrets.CODECOV_TOKEN }} 27 | # fail_ci_if_error: true # optional (default = false) 28 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # default branch 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - run: yarn install 14 | - run: yarn run docs:build 15 | - run: echo yforms.crazyair.cn >> ./dist/CNAME 16 | - name: Deploy 17 | uses: peaceiris/actions-gh-pages@v3 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | publish_dir: ./dist 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | # branches: 7 | # - master 8 | 9 | name: Create Release 10 | 11 | jobs: 12 | publish-npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 10 19 | registry-url: https://registry.npmjs.org/ 20 | - run: yarn 21 | - run: yarn pub from-package --force-publish --yes 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 24 | NPM_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 25 | build: 26 | needs: publish-npm 27 | name: Create Release 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@master 32 | - name: get changelog 33 | id: changelog 34 | run: node ./scripts/getChangeLog.js 35 | - name: Create Release 36 | id: create_release 37 | uses: actions/create-release@v1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | tag_name: ${{ github.ref }} 42 | release_name: ${{ github.ref }} 43 | # see https://github.com/marketplace/actions/autochangelog 44 | body: ${{ steps.changelog.outputs.changelog}} 45 | draft: false 46 | prerelease: false 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - master 7 | 8 | jobs: 9 | 10 | test: 11 | name: test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 10 18 | registry-url: https://registry.npmjs.org/ 19 | - run: yarn 20 | - run: yarn test:ci 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | .umi 18 | .umi-production 19 | 20 | /.history 21 | /.vscode 22 | /coverage 23 | 24 | /.changelog 25 | 26 | /packages/*/node_modules 27 | /packages/*/yarn.lock 28 | /packages/*/dist 29 | /packages/*/es 30 | /packages/*/lib 31 | 32 | .now 33 | .vercel -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /packages/*/node_modules 2 | /packages/*/.local 3 | /packages/**/*.test.ts 4 | /packages/**/*.test.js 5 | /node_modules 6 | /coverage 7 | /.idea 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | **/*.ejs 3 | **/*.html 4 | .umi 5 | .umi-production 6 | 7 | /.github -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | const fabric = require('@umijs/fabric'); 2 | 3 | module.exports = { 4 | ...fabric.prettier, 5 | }; 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 叶枫 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![codecov](https://codecov.io/gh/crazyair/yforms/branch/master/graph/badge.svg)](https://codecov.io/gh/crazyair/yforms) 2 | 3 | ## Usage & Guide 4 | 5 | To check out live examples and docs, visit [yforms](https://yforms.crazyair.cn/). 6 | 7 | ## Development 8 | 9 | ```bash 10 | $ yarn 11 | $ yarn start 12 | ``` 13 | 14 | ## LICENSE 15 | 16 | [MIT](https://github.com/umijs/umi/blob/master/LICENSE) 17 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | hash: true, 3 | mode: 'site', 4 | title: 'yforms', 5 | favicon: '/favicon.ico', 6 | logo: '/logo.png', 7 | analytics: { 8 | baidu: 'e371ec7cfcef646cea084692d993074f', 9 | }, 10 | menus: { 11 | '/guide': [ 12 | { 13 | title: '介绍', 14 | children: ['guide/index', 'guide/getting-started'], 15 | }, 16 | ], 17 | }, 18 | theme: { 19 | // ['primary-color']: 'red', 20 | // ['ant-prefix']: 'ant-v4', 21 | }, 22 | extraBabelPlugins: [ 23 | [ 24 | 'import', 25 | { 26 | libraryName: 'antd', 27 | libraryDirectory: 'es', 28 | style: true, 29 | }, 30 | ], 31 | ], 32 | }; 33 | -------------------------------------------------------------------------------- /docs/apis/demo/modal_base.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React, { useState } from 'react'; 3 | import { YForm } from 'yforms'; 4 | import { Button } from 'antd'; 5 | 6 | const Demo = () => { 7 | const [visible, setVisible] = useState(false); 8 | const onFinish = (values: any) => { 9 | console.log('Success:', values); 10 | }; 11 | 12 | return ( 13 |
14 | 17 | setVisible(false)} 20 | destroyOnClose 21 | title="表单弹窗" 22 | formProps={{ onFinish }} 23 | > 24 | {[{ type: 'input', name: 'age', label: '姓名' }]} 25 | 26 |
27 | ); 28 | }; 29 | export default Demo; 30 | -------------------------------------------------------------------------------- /docs/apis/formModal.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FormModal 3 | nav: 4 | title: 文档 5 | --- 6 | 7 | # FormModal 8 | 9 | 弹窗的表单 10 | 11 | # 示例 12 | 13 | ## 基础使用 14 | 15 | 16 | 17 | ## API 18 | 19 | | 参数 | 说明 | 类型 | 默认值 | 20 | | --- | --- | --- | --- | 21 | | formFooter | 用来渲染到 `footer` 下面的按钮 | - | `[{ type: 'submit', componentProps: { reverseBtns: true, spaceProps: { noStyle: true } } }]` | 22 | | formProps | `YForm` 所属参数 | `YFormProps` | - | 23 | -------------------------------------------------------------------------------- /docs/apis/hooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hooks 3 | nav: 4 | title: 文档 5 | --- 6 | 7 | # Hooks 8 | 9 | ## useForm 10 | 11 | - 获取表单实例 12 | 13 | ### 用例 14 | 15 | ```jsx | pure 16 | const [form] = YForm.useForm(); 17 | // 获取表单格式化后的值 18 | form.getFormatFieldsValue(); 19 | ``` 20 | 21 | ### API 22 | 23 | | 参数 | 说明 | 类型 | 默认值 | 24 | | -------------------- | -------------------- | ---- | ------ | 25 | | getFormatFieldsValue | 获取表单格式化后的值 | - | - | 26 | 27 | ## useFormatFieldsValue 28 | 29 | - 表单提交前处理数据 30 | 31 | ### 用例 32 | 33 | ```tsx 34 | import React from 'react'; 35 | import { Input } from 'antd'; 36 | import { YForm } from 'yforms'; 37 | 38 | export default () => { 39 | const { formatFieldsValue, onFormatFieldsValue } = YForm.useFormatFieldsValue(); 40 | 41 | const onFinish = (values) => { 42 | console.log('Success:', values); 43 | }; 44 | 45 | onFormatFieldsValue([ 46 | { name: 'append_field', format: () => '追加字段' }, 47 | { name: 'name', format: (value) => `${value}_改变字段值` }, 48 | ]); 49 | 50 | return ( 51 | 52 | {[ 53 | { type: 'input', label: '姓名', name: 'name' }, 54 | { 55 | dataSource: [ 56 | { 57 | type: 'button', 58 | noStyle: true, 59 | componentProps: { type: 'primary', htmlType: 'submit', children: 'submit' }, 60 | }, 61 | ], 62 | }, 63 | ]} 64 | 65 | ); 66 | }; 67 | ``` 68 | 69 | ### API 70 | 71 | | 参数 | 说明 | 类型 | 默认值 | 72 | | ------------------- | --------------------- | ---- | ------ | 73 | | formatFieldsValue | 在 `YForm` 添加该属性 | - | - | 74 | | onFormatFieldsValue | 添加需要格式化的字段 | - | - | 75 | 76 | ## useSubmit 77 | 78 | - 提交表单相关联的按钮 79 | - 提交、保存按钮防连点 80 | - 需要与 `submit` 结合使用 81 | 82 | ### 用例 83 | 84 | ```tsx 85 | import React from 'react'; 86 | import { Input } from 'antd'; 87 | import { YForm } from 'yforms'; 88 | 89 | export default () => { 90 | const [form] = YForm.useForm(); 91 | const { 92 | submit, 93 | submit: { 94 | params: { typeName }, 95 | }, 96 | } = YForm.useSubmit({ params: { type: 'view', id: '1' } }); 97 | 98 | const onFinish = (values) => { 99 | console.log('Success:', values); 100 | }; 101 | 102 | return ( 103 | <> 104 |

{typeName}

105 | 106 | {[{ type: 'input', label: '姓名', name: 'name' }, { type: 'submit' }]} 107 | 108 | 109 | ); 110 | }; 111 | ``` 112 | 113 | ### API 114 | 115 | - `useSubmit` 参数 116 | 117 | | 参数 | 说明 | 类型 | 默认值 | 118 | | ------ | --------------------------- | ------------------------------ | ------ | 119 | | params | 页面参数,包括 `id`、`type` | { id?: string; type?: 'create' | 'edit' | 'view';} | - | 120 | 121 | - `useSubmit` 返回 122 | 123 | | 参数 | 说明 | 类型 | 默认值 | 124 | | ------ | -------- | ------------------------------- | ------ | 125 | | params | 表单状态 | [ParamsObjType](#ParamsObjType) | - | 126 | 127 | ### ParamsObjType 128 | 129 | | 参数 | 说明 | 类型 | 默认值 | 130 | | -------- | ------------ | ------- | ------ | 131 | | id | id | string | - | 132 | | create | 新建 | boolean | - | 133 | | edit | 编辑 | boolean | - | 134 | | view | 查看 | boolean | - | 135 | | typeName | 当前状态文案 | string | - | 136 | -------------------------------------------------------------------------------- /docs/apis/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 文档 3 | nav: 4 | title: 文档 5 | order: 2 6 | --- 7 | 8 | # 文档 9 | 10 | - 以下内容不包含 `Antd Form API` 如需查看请[点击此处](https://next.ant.design/components/form-cn/#API) 11 | 12 | ## 共同的 API 13 | 14 | - 以下 API 为 `YForm`、`YForm.Items`、`type` 共享的 API。 15 | 16 | | 参数 | 说明 | 类型 | 默认值 | 17 | | -------- | ---------------- | -------------------------- | ------ | 18 | | isShow | 是否渲染 | boolean | - | 19 | | disabled | 字段是否全部禁用 | boolean | - | 20 | | children | 数据源 | YFormItemProps['children'] | - | 21 | 22 | ## YForm 23 | 24 | | 参数 | 说明 | 类型 | 默认值 | 25 | | --- | --- | --- | --- | 26 | | loading | 为 `true` 则显示加载效果,用于判断初始化数据是否加载完成再渲染 `Form` | boolean | - | 27 | | itemsType | 追加类型 | YFormItemsType | - | 28 | | formatFieldsValue | 表单提交前格式化,说明[见下](#formatFieldsValue) | FormatFieldsValue[] | - | 29 | | onSave | 保存方法,不校验表单数据(需用到 `submit` 类型) | (values: { [key: string]: any }) => void | - | 30 | | onCancel | 点击取消、返回调用的方法(需用到 `submit` 类型) | () => void | - | 31 | | params | 当前表单状态(需用到 `submit` 类型) | ParamsObjType | - | 32 | | submitComponentProps | 与 `submit` 类型参数结合使用(用户无需传) | YFormSubmitComponentProps | - | 33 | | minBtnLoadingTime | 按钮最低 loading 时间 | number | 500 | 34 | | getInitialValues | 获取表单默认值 | (p:[getInitialValuesParamsType](/apis/#getInitialValuesParamsType)) => Promise \| Object | - | 35 | 36 | ## YForm.Items 37 | 38 | | 参数 | 说明 | 类型 | 默认值 | 39 | | ------- | ----------------------------- | ------- | ------ | 40 | | offset | 相对 `YForm` 的 `layout` 位移 | number | - | 41 | | noStyle | 是否添加 `yform-items` 样式 | boolean | - | 42 | 43 | ## type 44 | 45 | | 参数 | 说明 | 类型 | 默认值 | 46 | | --- | --- | --- | --- | 47 | | isShow | 是否隐藏, `isShow` 为 `function` 情况下实现该功能 查看 | boolean \|(values: any) => boolean | - | 48 | | componentProps | 当前类型下组件的参数 | - | - | 49 | 50 | ## getInitialValuesParamsType 51 | 52 | | 参数 | 说明 | 类型 | 默认值 | 53 | | ------ | ---------------------- | ------- | ------ | 54 | | isInit | 是否第一次获取表单数据 | boolean | - | 55 | 56 | ## 字段 dataSource 57 | 58 | - 无类型,无 `type`,用于渲染多个字段或元素 59 | 60 | ```tsx | pure 61 | { 62 | dataSource: [ 63 | { 64 | type: 'button', 65 | noStyle: true, 66 | componentProps: { type: 'primary', htmlType: 'submit', children: '提交' }, 67 | }, 68 | { 69 | type: 'button', 70 | noStyle: true, 71 | componentProps: { children: '取消' } 72 | }, 73 | ], 74 | } 75 | ``` 76 | 77 | ## 添加自定义类型 78 | 79 | - 可以在项目 `src/index.ts` 添加类型,之后全局使用。 80 | - 自定义类型推荐添加前缀或后缀,方便与默认区分。 81 | 82 | ```tsx | pure 83 | import { YForm } from 'yforms'; 84 | import { YFormItemsType } from 'yforms/lib/YForm/ItemsType'; 85 | 86 | declare module 'yforms/lib/YForm/ItemsType' { 87 | export interface YFormItemsTypeDefine { 88 | my_demo: { componentProps?: { str?: string } }; 89 | } 90 | } 91 | 92 | export const itemsType: YFormItemsType = { 93 | my_demo: { component:
demo string
}, 94 | }; 95 | YForm.Config({ itemsType }); 96 | ``` 97 | -------------------------------------------------------------------------------- /docs/assets/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazyair/yforms/21881ee841298d3a66281e0c80c42d7868c58254/docs/assets/qrcode.jpg -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 更新日志 3 | nav: 4 | title: 更新日志 5 | order: 5 6 | --- 7 | 8 | ## [1.3.9](https://github.com/crazyair/yforms/compare/v1.3.8...v1.3.9) (2021-01-07) 9 | 10 | ### Bug Fixes 11 | 12 | - prefix 变量问题 ([22dd90a](https://github.com/crazyair/yforms/commit/22dd90a2e6a904590499c60084c65898157bac4a)) 13 | 14 | ## [1.3.8](https://github.com/crazyair/yforms/compare/v1.3.7...v1.3.8) (2020-12-29) 15 | 16 | ### Features 17 | 18 | - add esm ([f1182f9](https://github.com/crazyair/yforms/commit/f1182f978092ee8a85741ec8d0684ee1300cd295)) 19 | 20 | ## [1.3.7](https://github.com/crazyair/yforms/compare/v1.3.5...v1.3.7) (2020-12-09) 21 | 22 | ### Chore 23 | 24 | - 升级 antd 版本 25 | 26 | ## [1.3.5](https://github.com/crazyair/yforms/compare/v1.3.4...v1.3.5) (2020-09-27) 27 | 28 | ### Bug Fixes 29 | 30 | - 删除 Space 特殊判断 ([446b376](https://github.com/crazyair/yforms/commit/446b3765a62731b2f952fd85f84a0d13c8532403)) 31 | 32 | ## [1.3.4](https://github.com/crazyair/yforms/compare/v1.3.0...v1.3.4) (2020-09-27) 33 | 34 | ### Bug Fixes 35 | 36 | - 修复 deFormat 对数据处理失效 ([5972e6f](https://github.com/crazyair/yforms/commit/5972e6f415fdf9b60d0f6146c1a146fabb45c06a)) 37 | - sideEffects ([f5d86d7](https://github.com/crazyair/yforms/commit/f5d86d70ca2b0eb1818867b39a2adf8d2f13bd22)) 38 | - 修复部署丢失样式 ([07ff570](https://github.com/crazyair/yforms/commit/07ff570841491c7bc4012a3172512daf1ce5a050)) 39 | 40 | ## [1.3.0](https://github.com/crazyair/yforms/compare/v1.2.9...v1.3.0) (2020-08-27) 41 | 42 | ### Features 43 | 44 | - getInitialValues 方法添加提交成功后的 reload 操作 ([09c1f27](https://github.com/crazyair/yforms/commit/09c1f27b11f93ba40d058ddedc920df338b41bde)) 45 | - 修改 loadData 执行位置 ([0164a5c](https://github.com/crazyair/yforms/commit/0164a5c83978bc7b744753ccae3d2b4719bda863)) 46 | 47 | ## [1.2.9](https://github.com/crazyair/yforms/compare/v1.2.8...v1.2.9) (2020-08-26) 48 | 49 | ### Features 50 | 51 | - form 添加 getInitialValues 方法 ([#45](https://github.com/crazyair/yforms/issues/45)) ([c1a17ed](https://github.com/crazyair/yforms/commit/c1a17edfff84f22f6f9ef3166c93e8297f21efce)) 52 | 53 | ## [1.2.8](https://github.com/crazyair/yforms/compare/v1.2.7...v1.2.8) (2020-08-25) 54 | 55 | ### Features 56 | 57 | - 添加 card 类型、提交按钮添加时间控制 ([#44](https://github.com/crazyair/yforms/issues/44)) ([e44595e](https://github.com/crazyair/yforms/commit/e44595eded7853538ebb6125a4038403abd32ba7)) 58 | 59 | ## [1.2.7](https://github.com/crazyair/yforms/compare/v1.2.6...v1.2.7) (2020-08-24) 60 | 61 | ### Bug Fixes 62 | 63 | - 修复 required 合并问题 ([a889e6d](https://github.com/crazyair/yforms/commit/a889e6d14e40f7a9c2a892db93a08254157b57af)) 64 | 65 | ## [1.2.6](https://github.com/crazyair/yforms/compare/v1.2.5...v1.2.6) (2020-08-20) 66 | 67 | ### Bug Fixes 68 | 69 | - 获取上级数据没有则返回 undefined ([cb95d15](https://github.com/crazyair/yforms/commit/cb95d158a9d0dd512deeca535d58a99423b703b8)) 70 | 71 | ## [1.2.5](https://github.com/crazyair/yforms/compare/v1.2.4...v1.2.5) (2020-08-19) 72 | 73 | ### Bug Fixes 74 | 75 | - deFormat 可以在 scenes 使用 ([528da40](https://github.com/crazyair/yforms/commit/528da401c1c8ef7aff223c766ae75fa2cc99f99c)) 76 | 77 | ## [1.2.4](https://github.com/crazyair/yforms/compare/v1.2.3...v1.2.4) (2020-08-06) 78 | 79 | ### Bug Fixes 80 | 81 | - 修复数组 format 为空 ([#42](https://github.com/crazyair/yforms/issues/42)) ([bcc91d9](https://github.com/crazyair/yforms/commit/bcc91d91bf1fc2a2aa816c57eeac697668c3a4b6)) 82 | 83 | ## [1.2.2](https://github.com/crazyair/yforms/compare/v1.2.1...v1.2.2) (2020-08-06) 84 | 85 | ### Refactor 86 | 87 | - refactor: unFormat 更改为 deFormat ([6af5ce8](https://github.com/crazyair/yforms/commit/6af5ce8c74828ccf8e0e4784c14b34c6e827e5f4)) 88 | 89 | ## [1.2.1](https://github.com/crazyair/yforms/compare/v1.2.0...v1.2.1) (2020-08-04) 90 | 91 | ### Bug Fixes 92 | 93 | - 过滤 initialValues 的 merge 处理 ([b9fc580](https://github.com/crazyair/yforms/commit/b9fc580056f18c6fa9ba5fe8a2f72c64420c41ef)) 94 | 95 | ## [1.2.0](https://github.com/crazyair/yforms/compare/v1.1.8...v1.2.0) (2020-07-29) 96 | 97 | ### Bug Fixes 98 | 99 | - 修复 getOptions 每次变更都会调用请求 ([#40](https://github.com/crazyair/yforms/issues/40)) ([5dc1313](https://github.com/crazyair/yforms/commit/5dc131305feb714824b9f3588280fbc8ac351f98)) 100 | 101 | ## [1.1.8](https://github.com/crazyair/yforms/compare/v1.1.7...v1.1.8) (2020-07-28) 102 | 103 | ### Bug Fixes 104 | 105 | - 修复 money 类型不能改值 ([#39](https://github.com/crazyair/yforms/issues/39)) ([25a13c5](https://github.com/crazyair/yforms/commit/25a13c57a410cc9f65c61c0a76d5374df2088f77)) 106 | 107 | ## [1.1.7](https://github.com/crazyair/yforms/compare/v1.1.6...v1.1.7) (2020-07-25) 108 | 109 | ### Features 110 | 111 | - Select、Radio 添加 getOptions ([#38](https://github.com/crazyair/yforms/issues/38)) ([d9a2175](https://github.com/crazyair/yforms/commit/d9a21752db6ac886cf20e008db8d56089329e7c1)) 112 | 113 | ## [1.1.6](https://github.com/crazyair/yforms/compare/v1.1.5...v1.1.6) (2020-07-05) 114 | 115 | ### Bug Fixes 116 | 117 | - 修复 isShow 中 getFieldsValue 未拿 Store ([#36](https://github.com/crazyair/yforms/issues/36)) ([b3be944](https://github.com/crazyair/yforms/commit/b3be9444951d3cc7bbca81eb85984916304486b0)) 118 | 119 | ## [1.1.5](https://github.com/crazyair/yforms/compare/v1.1.4...v1.1.5) (2020-07-03) 120 | 121 | ### Features 122 | 123 | - 表单组件添加 ref ([#35](https://github.com/crazyair/yforms/issues/35)) ([d7aa4e3](https://github.com/crazyair/yforms/commit/d7aa4e3820dac562ff87156ca7df5ae0db21c2cf)) 124 | 125 | ## [1.1.4](https://github.com/crazyair/yforms/compare/v1.1.3...v1.1.4) (2020-07-03) 126 | 127 | ### Bug Fixes 128 | 129 | - 修复 form 类型 ([#34](https://github.com/crazyair/yforms/issues/34)) ([269caec](https://github.com/crazyair/yforms/commit/269caecb7266a432350502df1393d6a7c52791ed)) 130 | 131 | ## [1.1.2](https://github.com/crazyair/yforms/compare/v1.1.1...v1.1.2) (2020-06-21) 132 | 133 | ### Bug Fixes 134 | 135 | - 修复 children 类型 136 | 137 | ## [1.1.1](https://github.com/crazyair/yforms/compare/v1.1.0...v1.1.1) (2020-06-20) 138 | 139 | ### Features 140 | 141 | - 添加 mergeWithDom 碰到 dom 情况下使用原值 ([#32](https://github.com/crazyair/yforms/issues/32)) ([ccfd34f](https://github.com/crazyair/yforms/commit/ccfd34f94c2b86e148da684a74f13442f3e073eb)) 142 | 143 | # [1.1.0](https://github.com/crazyair/yforms/compare/v1.0.7...v1.1.0) (2020-06-18) 144 | 145 | ### Features 146 | 147 | - hash ([f4d1054](https://github.com/crazyair/yforms/commit/f4d10540ee13399887c4adf0d4fa102f25bde5a3)) 148 | - 搜索场景优化、type 组件从 Context 获取 Props ([#31](https://github.com/crazyair/yforms/issues/31)) ([cbe40c1](https://github.com/crazyair/yforms/commit/cbe40c14f849dad2e462b0d9cc3dd4e0190fb145)) 149 | - 优化 deFormat 逻辑 ([dcbaddc](https://github.com/crazyair/yforms/commit/dcbaddc66269921bbad97d675b4a3f5c8ff079a2)) 150 | - 优化代码 ([ba4e6cb](https://github.com/crazyair/yforms/commit/ba4e6cb6c05a7415894eb43e61c4987373778cef)) 151 | - 支持 jsx 方式配置表单 ([#30](https://github.com/crazyair/yforms/issues/30)) ([137dcd0](https://github.com/crazyair/yforms/commit/137dcd05433f4b962da9588860382669ceb565d7)) 152 | 153 | ## [1.0.7](https://github.com/crazyair/yforms/compare/v1.0.6...v1.0.7) (2020-06-13) 154 | 155 | ### Features 156 | 157 | - 优化 Space 对于空内容显示、YForm 添加默认 layout ([#29](https://github.com/crazyair/yforms/issues/29)) ([8b6a86d](https://github.com/crazyair/yforms/commit/8b6a86dd51fce5d94d51b56a907c91093ce05518)) 158 | 159 | ## [1.0.6](https://github.com/crazyair/yforms/compare/v1.0.5...v1.0.6) (2020-06-12) 160 | 161 | ### Features 162 | 163 | - 添加 format deFormat ([#28](https://github.com/crazyair/yforms/issues/28)) ([a866a14](https://github.com/crazyair/yforms/commit/a866a1468615cf911c8b2ee56b0525e7568a5df3)) 164 | 165 | ## [1.0.5](https://github.com/crazyair/yforms/compare/v1.0.4...v1.0.5) (2020-06-11) 166 | 167 | ### Bug Fixes 168 | 169 | - 修复提交按钮 loading 状态闪一下 ([13bf16c](https://github.com/crazyair/yforms/commit/13bf16c844f9f8d1b1d69fcf6b83832e347e1b07)) 170 | 171 | ## [1.0.4](https://github.com/crazyair/yforms/compare/v1.0.3...v1.0.4) (2020-06-11) 172 | 173 | ### Bug Fixes 174 | 175 | - 修复编辑按钮未改变 disabled ([760ddaa](https://github.com/crazyair/yforms/commit/760ddaa2ad42cb2c738c6ac02e3a42daa04b5e0f)) 176 | 177 | ## [1.0.3](https://github.com/crazyair/yforms/compare/v1.0.2...v1.0.3) (2020-06-11) 178 | 179 | ### Features 180 | 181 | - 重新整理 useSubmit 逻辑、优化组件 TypeScript 定义 ([#27](https://github.com/crazyair/yforms/issues/27)) ([2ca1a06](https://github.com/crazyair/yforms/commit/2ca1a06762835e2d3c0a9c898c0b3747885664aa)) 182 | 183 | ## [1.0.2](https://github.com/crazyair/yforms/compare/v1.0.1...v1.0.2) (2020-06-10) 184 | 185 | ### Features 186 | 187 | - 添加 FormModal ([#26](https://github.com/crazyair/yforms/issues/26)) ([12a7816](https://github.com/crazyair/yforms/commit/12a7816219a3d25bc1cd44221e9e75f59c806b5b)) 188 | 189 | ## [1.0.1](https://github.com/crazyair/yforms/compare/v1.0.0...v1.0.1) (2020-06-08) 190 | 191 | ### Bug Fixes 192 | 193 | - submit typescript ([#25](https://github.com/crazyair/yforms/issues/25)) ([96462f7](https://github.com/crazyair/yforms/commit/96462f7d42e9d78496d761be8c14094691a06cda)) 194 | - 修复 now ([75259f7](https://github.com/crazyair/yforms/commit/75259f714196aa07f7773f42e8a59089edc2496a)) 195 | 196 | ## [1.0.0](https://github.com/crazyair/yforms/compare/v0.8.7...v1.0.0) (2020-06-08) 197 | 198 | ### Features 199 | 200 | - add view ([#23](https://github.com/crazyair/yforms/issues/23)) ([0643144](https://github.com/crazyair/yforms/commit/06431449e6b6ab7b5b8d95143f39bfb97cf01845)) 201 | - now ([#22](https://github.com/crazyair/yforms/issues/22)) ([7e1939a](https://github.com/crazyair/yforms/commit/7e1939a8bc84bbcdaee87fd909fb360aedc70db2)) 202 | 203 | ## [0.8.7](https://github.com/crazyair/yforms/compare/v0.8.5...v0.8.7) (2020-05-25) 204 | 205 | ### Bug Fixes 206 | 207 | - 修复搜索样式 ([#21](https://github.com/crazyair/yforms/issues/21)) ([a28e214](https://github.com/crazyair/yforms/commit/a28e21436b43eb9e514d939a88598e585f5de075)) 208 | 209 | ## [0.8.5](https://github.com/crazyair/yforms/compare/v0.8.4...v0.8.5) (2020-05-25) 210 | 211 | ### Features 212 | 213 | - 重构 ([#20](https://github.com/crazyair/yforms/issues/20)) ([3c5309e](https://github.com/crazyair/yforms/commit/3c5309ea6394c99b05d78200c51d222cee9bf3b5)) 214 | - 删除 plugins 改而使用 scenes 215 | 216 | ## [0.8.4](https://github.com/crazyair/yforms/compare/v0.8.3...v0.8.4) (2020-05-20) 217 | 218 | ### Bug Fixes 219 | 220 | - `moment` value 为空直接 return ([#18](https://github.com/crazyair/yforms/issues/18)) ([9882bf3](https://github.com/crazyair/yforms/commit/9882bf3c103fc809b45f883c31d6e895c03d5d0b)) 221 | 222 | ## [0.8.3](https://github.com/crazyair/yforms/compare/v0.8.1...v0.8.3) (2020-05-20) 223 | 224 | ### Bug Fixes 225 | 226 | - 修复依赖问题 ([#15](https://github.com/crazyair/yforms/issues/15)) ([4772dc0](https://github.com/crazyair/yforms/commit/4772dc0b9f4a5f2fc4805e6bb86d4331f01c63df)) 227 | 228 | ## [0.8.1](https://github.com/crazyair/yforms/compare/v0.6.0...v0.8.1) (2020-05-20) 229 | 230 | - 改名 yforms ([#14](https://github.com/crazyair/yforms/issues/14)) ([1c20a9f](https://github.com/crazyair/yforms/commit/1c20a9fb96aa532750a628a1befde8d6ef6bbf40)) 231 | - 新增 `format` 功能 232 | - `useForm` 添加 `getFormatFieldsValue` 233 | 234 | ## [0.6.0](https://github.com/crazyair/yforms/compare/v0.5.9...v0.6.0) (2020-05-16) 235 | 236 | - submitFormatValues 根据 name 长度排序 ([#13](https://github.com/crazyair/yforms/issues/13)) ([fc1c411](https://github.com/crazyair/yforms/commit/fc1c41107d10fa798e9298266f7144e2cc5697f8)) 237 | 238 | ## 0.5.9 239 | 240 | `2020-04-29` 241 | 242 | - 修复 `Submit` 默认按钮显示逻辑 243 | 244 | ## 0.5.8 245 | 246 | `2020-04-25` 247 | 248 | - 修复 `disabled` 覆盖逻辑 249 | 250 | ## 0.5.7 251 | 252 | `2020-04-16` 253 | 254 | - `onCancel` 添加 `type` 参数 255 | - `YForm` 参数梳理 256 | 257 | ## 0.5.5 258 | 259 | `2020-04-14` 260 | 261 | - 修复 `component props` 权重问题 262 | 263 | ## 0.5.4 264 | 265 | `2020-04-13` 266 | 267 | - 修复 `component props` 未使用问题 268 | 269 | ## 0.5.3 270 | 271 | `2020-04-12` 272 | 273 | - `useSubmit` 返回 `params` 类型修改 274 | 275 | ## 0.5.2 276 | 277 | `2020-04-08` 278 | 279 | - `submit` 点击取消默认执行 `resetFields` 280 | 281 | ## 0.5.1 282 | 283 | `2020-04-08` 284 | 285 | - `submit` 样式修改 286 | 287 | ## 0.5.0 288 | 289 | `2020-04-08` 290 | 291 | - 添加 `scene` 功能,可以自定义展示场景 292 | - 优化 `items` 代码逻辑 293 | 294 | ## 0.4.3 295 | 296 | `2020-02-28` 297 | 298 | - `oneLine` 样式修改 299 | - 更新 `antd`、`dumi` 版本 300 | 301 | ## 0.4.2 302 | 303 | `2020-02-27` 304 | 305 | - 添加 `password` 类型 306 | - `type` `isShow` 参数增加 `function` 类型,更方便写隐藏显示字段代码 307 | 308 | ## 0.4.0 309 | 310 | `2020-02-18` 311 | 312 | - 添加 `Modal Form` 场景示例 313 | - 更改文档模式 314 | 315 | ## 0.3.0 316 | 317 | `2020-02-14` 318 | 319 | - 添加 `submit` 类型 320 | - 添加 `secureButton` 类型 321 | - 添加 `useSubmit` 钩子 322 | -------------------------------------------------------------------------------- /docs/examples/demo/demo1.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React, { useState, useEffect } from 'react'; 3 | import { YForm } from 'yforms'; 4 | import { message, Input } from 'antd'; 5 | import moment from 'moment'; 6 | 7 | const options = [ 8 | { id: '1', name: '语文' }, 9 | { id: '2', name: '数学' }, 10 | ]; 11 | 12 | const initialValues = { 13 | name: '张三', 14 | age: '10', 15 | textarea: '这里是长文本', 16 | text: '这里是文本', 17 | custom: '这里是自定义文本', 18 | money: '999999999', 19 | 单选: true, 20 | 开关: true, 21 | 多选: ['1', '2'], 22 | 下拉框多选: ['1', '2'], 23 | 下拉框: '1', 24 | radio: '1', 25 | users: [ 26 | { name: '张三', age: '10' }, 27 | { name: '李四', age: '20' }, 28 | ], 29 | phones: [{ phone: '18888888888' }, { phone: '18888888888' }], 30 | date: moment(), 31 | }; 32 | 33 | const Demo = () => { 34 | const [data, setData] = useState({}); 35 | const [loading, setLoading] = useState(true); 36 | const [form] = YForm.useForm(); 37 | 38 | const { 39 | submit, 40 | submit: { disabled }, 41 | } = YForm.useSubmit({ params: { type: 'view', id: '1' } }); 42 | useEffect(() => { 43 | setTimeout(() => { 44 | setData(initialValues); 45 | setLoading(false); 46 | }, 10); 47 | }, []); 48 | 49 | const onFinish = (values: any) => { 50 | console.log('Success:', values); 51 | }; 52 | const onFinishFailed = (errorInfo: any) => { 53 | console.log('Failed:', errorInfo); 54 | }; 55 | const onSave = (values: any) => { 56 | console.log('values:', values); 57 | }; 58 | 59 | return ( 60 | 71 | {[ 72 | // { 73 | // type: 'button', 74 | // scenes: { disabled: false }, 75 | // componentProps: { onClick: () => setView((c) => !c), children: '查看表单' }, 76 | // }, 77 | // { type: 'custom', children: 1, label: 'xx' }, 78 | { type: 'input', label: '空值', name: 'names', scenes: { base: false } }, 79 | { 80 | type: 'list', 81 | name: 'phones', 82 | componentProps: { 83 | showIcons: { showBottomAdd: { text: '添加手机号' }, showAdd: true, showRemove: false }, 84 | onShowIcons: () => ({ 85 | showAdd: true, 86 | showRemove: true, 87 | }), 88 | }, 89 | items: ({ index }) => { 90 | return [ 91 | { 92 | label: index === 0 && '手机号', 93 | type: 'input', 94 | name: [index, 'phone'], 95 | componentProps: { placeholder: '请输入手机号' }, 96 | }, 97 | ]; 98 | }, 99 | }, 100 | { 101 | label: '用户', 102 | type: 'list', 103 | name: 'users', 104 | items: ({ index }) => { 105 | return [ 106 | { 107 | type: 'oneLine', 108 | componentProps: { oneLineStyle: ['50%', 8, '50%'] }, 109 | items: () => [ 110 | { label: '姓名', type: 'input', name: [index, 'name'] }, 111 | { type: 'custom', children: }, 112 | { label: '年龄', type: 'input', name: [index, 'age'] }, 113 | ], 114 | }, 115 | ]; 116 | }, 117 | }, 118 | { 119 | type: 'input', 120 | label: '姓名', 121 | name: 'name', 122 | format: (value) => `${value} 修改了`, 123 | }, 124 | { 125 | type: 'datePicker', 126 | label: '日期', 127 | name: 'date', 128 | componentProps: { style: { width: '100%' } }, 129 | format: (date) => moment(date).format('YYYY-MM-DD'), 130 | }, 131 | { 132 | type: 'money', 133 | label: '金额', 134 | componentProps: { suffix: '元' }, 135 | name: 'money', 136 | }, 137 | { type: 'textarea', label: '文本域', name: 'textarea' }, 138 | { type: 'checkbox', label: '单选', name: '单选', componentProps: { children: '已阅读' } }, 139 | { 140 | type: 'checkboxGroup', 141 | label: '多选', 142 | name: '多选', 143 | componentProps: { options }, 144 | }, 145 | { 146 | type: 'switch', 147 | label: '开关', 148 | name: '开关', 149 | componentProps: { checkedChildren: '开', unCheckedChildren: '关' }, 150 | }, 151 | { 152 | type: 'select', 153 | label: '下拉框', 154 | name: '下拉框', 155 | componentProps: { 156 | optionLabelProp: 'checked-value', 157 | onAddProps: (item) => ({ 'checked-value': `(${item.name})` }), 158 | showField: (record) => `${record.id}-${record.name}`, 159 | options, 160 | }, 161 | }, 162 | { 163 | type: 'select', 164 | label: '下拉框多选', 165 | name: '下拉框多选', 166 | componentProps: { mode: 'multiple', options }, 167 | }, 168 | { 169 | type: 'radio', 170 | label: '单选按钮', 171 | name: 'radio', 172 | componentProps: { showField: (record) => `${record.id}-${record.name}`, options }, 173 | }, 174 | { label: '文本', name: 'text', type: 'text' }, 175 | { 176 | label: '自定义渲染', 177 | type: 'custom', 178 | name: 'custom', 179 | children: , 180 | }, 181 | { 182 | type: 'space', 183 | scenes: { disabled: false }, 184 | items: [ 185 | { type: 'submit' }, 186 | { 187 | type: 'button', 188 | componentProps: { 189 | onClick: () => message.success(JSON.stringify(form.getFormatFieldsValue())), 190 | children: '获取提交前数据', 191 | }, 192 | }, 193 | ], 194 | }, 195 | ]} 196 | 197 | ); 198 | }; 199 | export default Demo; 200 | -------------------------------------------------------------------------------- /docs/examples/demo/demo2.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: format deFormat 使用 3 | * desc: 日期场景下修改初始化值,提交修改提交值,数据对比也可以共享该配置。 4 | */ 5 | 6 | import React, { useCallback, useEffect, useState, useRef } from 'react'; 7 | import { YForm } from 'yforms'; 8 | import moment from 'moment'; 9 | import { Map } from 'immutable'; 10 | 11 | const Demo = () => { 12 | const [form] = YForm.useForm(); 13 | const [loading, setLoading] = useState(true); 14 | const [data, setData] = useState({}); 15 | const count = useRef(1); 16 | const [diff, setDiff] = useState(false); 17 | 18 | const loadData = useCallback(() => { 19 | setTimeout(() => { 20 | setData({ 21 | map: Map({ a: 1, b: 2, c: 3 }), 22 | name: `原值_${count.current}`, 23 | start: '1591943666', 24 | end: '1592116466', 25 | date: '1591943666', 26 | phones: [{ start: '1591943666', end: '1592116466' }], 27 | list: [{ age: '10' }], 28 | }); 29 | count.current += 1; 30 | setLoading(false); 31 | }, 10); 32 | }, []); 33 | 34 | useEffect(() => { 35 | loadData(); 36 | }, [loadData]); 37 | 38 | const onFinish = (values: any) => { 39 | loadData(); 40 | console.log('Success:', values); 41 | }; 42 | 43 | return ( 44 | 59 | {[ 60 | { 61 | type: 'input', 62 | label: '支持多层对象', 63 | name: ['first', 'second'], 64 | deFormat: (value) => `${value || ''} first 为 undefined `, 65 | }, 66 | { 67 | type: 'input', 68 | label: 'immutable', 69 | name: 'map', 70 | deFormat: (value) => value && value.set('d', 4), 71 | format: (value) => value && value.set('e', 5), 72 | }, 73 | { 74 | type: 'input', 75 | label: '全格式化', 76 | name: 'name', 77 | deFormat: (value) => value && `${value}_deFormat`, 78 | format: (value) => value && `${value}_format`, 79 | }, 80 | { 81 | type: 'datePicker', 82 | label: '日期', 83 | name: 'date', 84 | deFormat: (value) => value && moment.unix(value), 85 | format: (value) => value && `${moment(value).unix()}`, 86 | }, 87 | { 88 | type: 'rangePicker', 89 | name: 'range', 90 | label: '日期区间', 91 | deFormat: (_, { start, end }) => { 92 | return [start && moment.unix(start), end && moment.unix(end)]; 93 | }, 94 | format: [ 95 | { name: 'range', isOmit: true }, 96 | { 97 | name: 'start', 98 | format: (_, { range = [] }) => range[0] && `${moment(range[0]).unix()}`, 99 | }, 100 | { 101 | name: 'end', 102 | format: (_, { range = [] }) => range[1] && `${moment(range[1]).unix()}`, 103 | }, 104 | ], 105 | }, 106 | { 107 | type: 'list', 108 | label: '动态数组日期', 109 | name: 'phones', 110 | componentProps: { isUseIconStyle: false }, 111 | items: ({ index }) => { 112 | return [ 113 | { 114 | type: 'rangePicker', 115 | name: [index, 'range'], 116 | deFormat: (_, { start, end }) => { 117 | return [start && moment.unix(start), end && moment.unix(end)]; 118 | }, 119 | format: [ 120 | { name: [index, 'range'], isOmit: true }, 121 | { 122 | name: [index, 'start'], 123 | format: (_, { range = [] }) => range[0] && `${moment(range[0]).unix()}`, 124 | }, 125 | { 126 | name: [index, 'end'], 127 | format: (_, { range = [] }) => range[1] && `${moment(range[1]).unix()}`, 128 | }, 129 | ], 130 | }, 131 | ]; 132 | }, 133 | }, 134 | { 135 | type: 'list', 136 | label: '动态数组日期', 137 | name: 'list', 138 | items: ({ index }) => { 139 | return [ 140 | { 141 | type: 'input', 142 | name: [index, 'age'], 143 | deFormat: (value) => { 144 | return value && `${value}_deFormat`; 145 | }, 146 | }, 147 | ]; 148 | }, 149 | }, 150 | { type: 'submit' }, 151 | { 152 | scenes: { disabled: false }, 153 | type: 'space', 154 | items: [ 155 | { 156 | type: 'button', 157 | componentProps: { 158 | onClick: () => console.log(form.getFieldsValue(true)), 159 | children: '获取表单当前数据', 160 | }, 161 | }, 162 | { 163 | type: 'button', 164 | componentProps: { 165 | onClick: () => console.log(form.getFormatFieldsValue()), 166 | children: '获取表单提交时候数据', 167 | }, 168 | }, 169 | { 170 | type: 'button', 171 | componentProps: { 172 | onClick: () => setDiff((c) => !c), 173 | children: '切换数据对比', 174 | }, 175 | }, 176 | ], 177 | }, 178 | ]} 179 | 180 | ); 181 | }; 182 | export default Demo; 183 | -------------------------------------------------------------------------------- /docs/examples/demo/demo3.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React, { useState } from 'react'; 3 | import { Button, Modal } from 'antd'; 4 | import { RouteComponentProps } from 'react-router-dom'; 5 | import { YForm } from 'yforms'; 6 | 7 | const Demo: React.FC = () => { 8 | const [visible, setVisible] = useState(false); 9 | 10 | const handleCancel = () => { 11 | console.log('Clicked cancel button'); 12 | setVisible(false); 13 | }; 14 | 15 | const onFinish = async (values: any) => { 16 | console.log('Success:', values); 17 | }; 18 | const onFinishFailed = (errorInfo: any) => { 19 | console.log('Failed:', errorInfo); 20 | }; 21 | 22 | return ( 23 | <> 24 | 27 | 36 | 42 | 43 | {[{ type: 'input', name: 'age', label: '姓名' }]} 44 | 45 | 46 | {[ 47 | { 48 | type: 'submit', 49 | componentProps: { reverseBtns: true, spaceProps: { noStyle: true } }, 50 | }, 51 | ]} 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | export default Demo; 59 | -------------------------------------------------------------------------------- /docs/examples/demo/demo4.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: jsx 写法 3 | * desc: 原生方式 4 | */ 5 | 6 | import React from 'react'; 7 | import { YForm } from 'yforms'; 8 | import { Input, Button } from 'antd'; 9 | 10 | const Demo = () => { 11 | const [form] = YForm.useForm(); 12 | const onFinish = (values: any) => { 13 | console.log('Success:', values); 14 | }; 15 | 16 | return ( 17 |
18 | 19 | 20 | 21 | 22 | 23 | { 28 | return [{ type: 'input', name: [index, 'age'] }]; 29 | }} 30 | /> 31 | 32 | 33 | 34 | 35 |
36 | ); 37 | }; 38 | export default Demo; 39 | -------------------------------------------------------------------------------- /docs/examples/demo/demo5.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: getInitialValues 使用 3 | * desc: 快捷方式用来获取表单初始化数据 4 | */ 5 | import React from 'react'; 6 | import { YForm } from 'yforms'; 7 | 8 | export default () => { 9 | const onFinish = (values: any) => { 10 | console.log('Success:', values); 11 | }; 12 | 13 | return ( 14 | Promise.resolve({ name: '张飞', money: '99' })} 17 | > 18 | {[ 19 | { type: 'input', label: '姓名', name: 'name' }, 20 | { type: 'money', label: '金额', name: 'money' }, 21 | { type: 'submit' }, 22 | ]} 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /docs/examples/demo/diff.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable no-console */ 3 | import React, { useState, useEffect } from 'react'; 4 | import { YForm } from 'yforms'; 5 | import { message, Input } from 'antd'; 6 | import { UserOutlined } from '@ant-design/icons'; 7 | import moment from 'moment'; 8 | import { omit } from 'lodash'; 9 | import { YFormItemProps } from 'yforms/lib/YForm/Items'; 10 | 11 | moment.locale('zh-cn'); 12 | 13 | const options = [ 14 | { id: '1', name: '语文' }, 15 | { id: '2', name: '数学' }, 16 | ]; 17 | 18 | const initialValues = { 19 | name: '张三', 20 | age: '10', 21 | 文本域: '这里是长文本', 22 | text: '这里是文本', 23 | custom: '这里是自定义文本', 24 | money: '999999999', 25 | 单选: true, 26 | 开关: true, 27 | 多选: ['1', '2'], 28 | 下拉框多选: ['1', '2'], 29 | 下拉框: '1', 30 | 单选按钮: '1', 31 | users: [ 32 | { name: '张三', age: '10' }, 33 | { name: '李四', age: '20' }, 34 | ], 35 | // phones: [{ phone: '17777777777', users: [{ name: 'aaa' }] }, { phone: '18888888888' }], 36 | phones: [ 37 | { phone: '17777777777', users: [{ name: 'aaa' }] }, 38 | // { phone: '18888888888' }, 39 | { phone: '18888888888' }, 40 | ], 41 | date: moment(), 42 | range: [moment(), moment()], 43 | }; 44 | const oldValues = { 45 | name: '张三1', 46 | phones: [ 47 | { phone: '17777777777', users: [{ name: 'aaa' }, { name: 'bbb' }] }, 48 | { phone: '18888888881' }, 49 | { phone: '18888888888', users: [{ name: '被删掉的' }] }, 50 | ], 51 | users: [{ name: '李四2', age: '30' }], 52 | 多选: ['1'], 53 | money: '999999998', 54 | 下拉框: '2', 55 | 单选: true, 56 | 单选按钮: '2', 57 | 文本域: '这里是长文本2', 58 | 下拉框多选: ['2'], 59 | date: moment('2020-01-01 12:00:00'), 60 | range: [moment('2020-01-01 12:00:00'), moment('2020-01-01 12:00:00')], 61 | custom: 'xxx', 62 | }; 63 | 64 | const Demo = () => { 65 | const [data, setData] = useState({}); 66 | const [loading, setLoading] = useState(true); 67 | const [form] = YForm.useForm(); 68 | 69 | const { submit } = YForm.useSubmit({ params: { type: 'view', id: '1' } }); 70 | 71 | useEffect(() => { 72 | setTimeout(() => { 73 | setData(initialValues); 74 | setLoading(false); 75 | }, 10); 76 | }, []); 77 | 78 | const onFinish = (values: any) => { 79 | console.log('Success:', values); 80 | }; 81 | const onFinishFailed = (errorInfo: any) => { 82 | console.log('Failed:', errorInfo); 83 | }; 84 | const onSave = (values: any) => { 85 | console.log('values:', values); 86 | }; 87 | return ( 88 | submit.onDisabled(!submit.disabled)} 99 | scenes={{ view: submit.disabled, diff: true }} 100 | > 101 | {[ 102 | // { type: 'input', label: '空值', name: 'names' }, 103 | { type: 'input', label: '姓名', name: 'name' }, 104 | { 105 | type: 'list', 106 | name: 'phones', 107 | componentProps: { showIcons: { showBottomAdd: { text: '添加手机号' } } }, 108 | items: ({ index }): YFormItemProps['children'] => { 109 | return [ 110 | { 111 | label: index === 0 && '手机号', 112 | type: 'input', 113 | name: [index, 'phone'], 114 | rules: [{ required: true, message: '请输入手机号' }], 115 | componentProps: { placeholder: '请输入手机号' }, 116 | }, 117 | { 118 | label: '用户', 119 | type: 'list', 120 | offset: 2, 121 | name: [index, 'users'], 122 | items: ({ index }): YFormItemProps['children'] => [ 123 | { 124 | type: 'oneLine', 125 | componentProps: { oneLineStyle: ['50%', 8, '50%'] }, 126 | items: (): YFormItemProps['children'] => [ 127 | { label: '姓名', type: 'input', name: [index, 'name'] }, 128 | { type: 'custom', children: }, 129 | { label: '年龄', type: 'input', name: [index, 'age'] }, 130 | ], 131 | }, 132 | ], 133 | }, 134 | ]; 135 | }, 136 | }, 137 | { 138 | label: '用户', 139 | type: 'list', 140 | name: 'users', 141 | items: ({ index }) => { 142 | return [ 143 | { 144 | type: 'oneLine', 145 | componentProps: { oneLineStyle: ['50%', 8, '50%'] }, 146 | items: () => [ 147 | { label: '姓名', type: 'input', name: [index, 'name'] }, 148 | { type: 'custom', children: }, 149 | { label: '年龄', type: 'input', name: [index, 'age'] }, 150 | ], 151 | }, 152 | ]; 153 | }, 154 | }, 155 | { 156 | type: 'input', 157 | label: '姓名', 158 | name: 'name', 159 | format: (value) => `${value} 修改了`, 160 | }, 161 | { 162 | type: 'datePicker', 163 | label: '日期', 164 | name: 'date', 165 | // initialValue: moment('2020-01-01 12:00:00'), 166 | // componentProps: { showTime: true }, 167 | componentProps: { picker: 'quarter' }, 168 | // viewProps: { format: (value) => moment(value).format('YYYY-MM-DD') }, 169 | // format: (date) => moment(date).format('YYYY-MM-DD'), 170 | // deFormat: () => moment('2021-01-01 12:00:00'), 171 | }, 172 | { label: '开关', type: 'radio', name: 'type', componentProps: { options } }, 173 | { 174 | label: '精简使用', 175 | type: 'input', 176 | name: 'children_field2', 177 | format: (date) => moment(date).format('YYYY-MM-DD'), 178 | shouldUpdate: (prevValues, curValues) => prevValues.type !== curValues.type, 179 | isShow: (values) => values.type === '2', 180 | }, 181 | { 182 | type: 'rangePicker', 183 | label: '日期区间', 184 | name: 'range', 185 | scenes: { required: false }, 186 | // format: (value = []) => { 187 | // return `${moment(value[0]).format('YYYY-MM-DD')}-${moment(value[1]).format( 188 | // 'YYYY-MM-DD', 189 | // )}`; 190 | // }, 191 | format: (value = []) => { 192 | return { 193 | start: `${moment(value[0]).format('YYYY-MM-DD')}-${moment(value[1]).format( 194 | 'YYYY-MM-DD', 195 | )}`, 196 | }; 197 | }, 198 | // format: [ 199 | // { name: 'range', format: () => undefined }, 200 | // { name: 'start', format: (_, { range }) => moment(range[0]).format('YYYY-MM-DD') }, 201 | // { name: 'end', format: (_, { range }) => moment(range[1]).format('YYYY-MM-DD') }, 202 | // ], 203 | }, 204 | { 205 | type: 'money', 206 | label: '金额', 207 | componentProps: { addonAfter: '其它', suffix: '元' }, 208 | name: 'money', 209 | }, 210 | { type: 'textarea', label: '文本域', name: '文本域', componentProps: { inputMax: 2 } }, 211 | { type: 'checkbox', label: '单选', name: '单选', componentProps: { children: '已阅读' } }, 212 | { 213 | type: 'checkboxGroup', 214 | label: '多选', 215 | name: '多选', 216 | componentProps: { options }, 217 | }, 218 | { 219 | type: 'switch', 220 | label: '开关', 221 | name: '开关', 222 | componentProps: { checkedChildren: '开', unCheckedChildren: '关' }, 223 | }, 224 | { 225 | type: 'select', 226 | label: '下拉框', 227 | name: '下拉框', 228 | componentProps: { 229 | // optionLabelProp: 'checked-value', 230 | // onAddProps: (item) => ({ 'checked-value': `(${item.name})` }), 231 | // // onAddProps: (item) => ({ 'checked-value': ({item.name}) }), 232 | // showField: (record) => `${record.id}-${record.name}`, 233 | options, 234 | }, 235 | }, 236 | { 237 | type: 'select', 238 | label: '下拉框多选', 239 | name: '下拉框多选', 240 | componentProps: { mode: 'multiple', options }, 241 | }, 242 | { 243 | type: 'radio', 244 | label: '单选按钮', 245 | name: '单选按钮', 246 | componentProps: { showField: (record) => `${record.id}-${record.name}`, options }, 247 | }, 248 | { label: '文本', name: 'text', type: 'text' }, 249 | { 250 | label: '自定义渲染', 251 | type: 'custom', 252 | name: 'custom', 253 | children: , 254 | }, 255 | { type: 'submit' }, 256 | { 257 | type: 'space', 258 | scenes: { disabled: false }, 259 | items: [ 260 | { 261 | type: 'button', 262 | noStyle: true, 263 | componentProps: { 264 | onClick: () => submit.onDisabled(!submit.disabled), 265 | children: `${submit.disabled ? '启用' : '禁用'}表单`, 266 | }, 267 | }, 268 | { 269 | type: 'button', 270 | noStyle: true, 271 | componentProps: { onClick: () => form.resetFields(), children: '重置表单' }, 272 | }, 273 | ], 274 | }, 275 | // { 276 | // type: 'button', 277 | // componentProps: { 278 | // onClick: () => message.success(JSON.stringify(form.getFormatFieldsValue())), 279 | // children: '获取提交前数据', 280 | // }, 281 | // }, 282 | // { 283 | // type: 'button', 284 | // componentProps: { 285 | // onClick: () => setIsShow((c) => !c), 286 | // children: '获取提交前数据', 287 | // }, 288 | // }, 289 | ]} 290 | 291 | ); 292 | }; 293 | export default Demo; 294 | -------------------------------------------------------------------------------- /docs/examples/demo/edit.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { message } from 'antd'; 3 | import type { RouteComponentProps } from 'react-router-dom'; 4 | import { YForm } from 'yforms'; 5 | 6 | const Demo: React.FC = (props) => { 7 | const { match = {} as RouteComponentProps['match'] } = props; 8 | const [data, setData] = useState({}); 9 | const [loading, setLoading] = useState(true); 10 | 11 | const [form] = YForm.useForm(); 12 | const { formatFieldsValue, onFormatFieldsValue } = YForm.useFormatFieldsValue(); 13 | 14 | const { 15 | submit, 16 | submit: { 17 | params: { id, typeName }, 18 | }, 19 | } = YForm.useSubmit({ params: match.params }); 20 | 21 | useEffect(() => { 22 | setTimeout(() => { 23 | if (id) { 24 | setData({ name: '张三', age: '10' }); 25 | } 26 | setLoading(false); 27 | }, 10); 28 | }, [id]); 29 | 30 | onFormatFieldsValue([ 31 | { name: 'append_field', format: () => '提交前追加字段' }, 32 | { name: 'name', format: (value) => `${value}_改变了` }, 33 | ]); 34 | 35 | const onFinish = async (values: any) => { 36 | console.log('Success:', values); 37 | await new Promise((resolve) => setTimeout(resolve, 500)); 38 | await new Promise((resolve, reject) => { 39 | // 请求随机成功或者失败 40 | if (Math.round(Math.random()) === 0) { 41 | reject(); 42 | message.error('提交错误', 0.5); 43 | } else { 44 | resolve(''); 45 | message.success('提交成功', 0.5); 46 | } 47 | }); 48 | }; 49 | 50 | const onSave = async (values: any) => { 51 | console.log('values:', values); 52 | await new Promise((resolve) => setTimeout(resolve, 500)); 53 | await message.success('保存成功', 0.5); 54 | }; 55 | 56 | const onFinishFailed = (errorInfo: any) => { 57 | console.log('Failed:', errorInfo); 58 | }; 59 | 60 | return ( 61 | <> 62 |

{typeName}

63 | 75 | {[ 76 | { type: 'input', label: 'name', name: 'name' }, 77 | { type: 'input', label: 'age', name: 'age', componentProps: { suffix: '岁' } }, 78 | { type: 'money', label: 'money', name: 'money' }, 79 | { type: 'submit' }, 80 | ]} 81 | 82 | 83 | ); 84 | }; 85 | export default Demo; 86 | -------------------------------------------------------------------------------- /docs/examples/demo/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazyair/yforms/21881ee841298d3a66281e0c80c42d7868c58254/docs/examples/demo/index.less -------------------------------------------------------------------------------- /docs/examples/demo/list.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: 列表页+表单使用示例 3 | * desc: | 4 | * 适用于列表页面有新建、编辑、查看交互(打开此 demo 会找不到 edit.tsx)
5 | * 此 demo 仅为了实现跳转 6 | */ 7 | 8 | import React from 'react'; 9 | import { Link } from 'umi'; 10 | 11 | import { BrowserRouter as Router, useLocation, useHistory } from 'react-router-dom'; 12 | 13 | import Edit from './edit'; 14 | 15 | export default function QueryParamsExample() { 16 | return ( 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | function useQuery() { 24 | return new URLSearchParams(useLocation().search); 25 | } 26 | 27 | function QueryParamsDemo() { 28 | const query = useQuery(); 29 | 30 | const match = { params: { type: query.get('type'), id: query.get('id') } }; 31 | 32 | const hash = useLocation().hash.split('?')[0]; 33 | return ( 34 |
35 | {match.params.type ? ( 36 | 37 | ) : ( 38 |
39 |

列表

40 |
    41 |
  • 42 | 新建 43 |
  • 44 |
  • 45 | 查看 46 |
  • 47 |
  • 48 | 编辑 49 |
  • 50 |
51 |
52 | )} 53 |
54 | ); 55 | } 56 | 57 | function Child({ match }: any) { 58 | return ; 59 | } 60 | -------------------------------------------------------------------------------- /docs/examples/demo/renderField.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: 全局拿到 form fieldsValue 3 | * desc: 小表单场景下,希望组件任何地方能拿到实时字段值。 4 | */ 5 | 6 | import React from 'react'; 7 | import { YForm } from 'yforms'; 8 | 9 | const Demo = () => { 10 | const onFinish = async (values: any) => { 11 | console.log('Success:', values); 12 | }; 13 | 14 | return ( 15 | <> 16 | 17 | prevValues !== curValues}> 18 | {(form) => { 19 | const fieldsValue = form.getFieldsValue(); 20 | return ( 21 | 22 | {[ 23 | { type: 'input', label: 'name', name: 'name' }, 24 | { 25 | type: 'input', 26 | label: 'age', 27 | name: 'age', 28 | isShow: fieldsValue.name === '张三', 29 | }, 30 | { type: 'submit' }, 31 | ]} 32 | 33 | ); 34 | }} 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default Demo; 42 | -------------------------------------------------------------------------------- /docs/examples/demo/search.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: 一套字段配置用于搜索和表单场景 3 | * desc: '只需要 `scenes={{ search: true }}` 即可' 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | import React from 'react'; 8 | import { YForm } from 'yforms'; 9 | import { Button, Row, Col } from 'antd'; 10 | import { DownOutlined, UpOutlined } from '@ant-design/icons'; 11 | import QueueAnim from 'rc-queue-anim'; 12 | import { YFormItemProps } from 'yforms/es/YForm/Items'; 13 | 14 | export default () => { 15 | const [expand, setExpand] = React.useState(false); 16 | const [form] = YForm.useForm(); 17 | 18 | const onFinish = (values: any) => { 19 | console.log('Success:', values); 20 | }; 21 | 22 | // 表单字段可以单独抽离单独组建兼容表单搜索交互 23 | const fieldDom: YFormItemProps['children'] = [ 24 | { type: 'input', name: 'field1', label: 'field1' }, 25 | { type: 'input', name: 'field2', label: 'field2' }, 26 | { type: 'input', name: 'field2', label: 'field2' }, 27 | ]; 28 | const fieldDom2: YFormItemProps['children'] = [ 29 | { type: 'input', name: 'field4', label: 'field4' }, 30 | { type: 'input', name: 'field5', label: 'field5' }, 31 | { type: 'input', name: 'field6', label: 'field6' }, 32 | { type: 'input', name: 'field7', label: 'field7' }, 33 | ]; 34 | return ( 35 |
36 |

搜索场景

37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 51 | setExpand(!expand)}> 52 | {expand ? : } 更多 53 | 54 | 55 | 56 | 57 | {expand && ( 58 | 59 | 60 | 61 | )} 62 | 63 | 64 |

普通场景

65 | 66 | {fieldDom} 67 | {fieldDom2} 68 | {[{ type: 'submit' }]} 69 | 70 |
71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /docs/examples/demo/search2.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: 搜索场景 2 3 | * desc: 搜索场景 2 4 | */ 5 | 6 | /* eslint-disable no-console */ 7 | import React from 'react'; 8 | import { YForm } from 'yforms'; 9 | import { Row, Col } from 'antd'; 10 | import { DownOutlined, UpOutlined } from '@ant-design/icons'; 11 | import { YFormItemProps } from 'yforms/es/YForm/Items'; 12 | 13 | export default () => { 14 | const [expand, setExpand] = React.useState(false); 15 | const [form] = YForm.useForm(); 16 | 17 | const onFinish = (values: any) => { 18 | console.log('Success:', values); 19 | }; 20 | 21 | // 表单字段可以单独抽离单独组建兼容表单搜索交互 22 | const fieldDom: YFormItemProps['children'] = [ 23 | { type: 'input', name: 'field1', label: 'field1' }, 24 | { type: 'input', name: 'field2', label: 'field2' }, 25 | { type: 'input', name: 'field2', label: 'field2' }, 26 | { type: 'rangePicker', name: 'field3', label: 'field3' }, 27 | ]; 28 | const fieldDom2: YFormItemProps['children'] = [ 29 | { type: 'input', name: 'field4', label: 'field4' }, 30 | { type: 'input', name: 'field5', label: 'field5' }, 31 | { type: 'input', name: 'field6', label: 'field6' }, 32 | { type: 'input', name: 'field7', label: 'field7' }, 33 | ]; 34 | return ( 35 |
36 |

搜索场景 2

37 | 38 | 39 | {[...fieldDom, ...(expand ? fieldDom2 : [])]} 40 | 41 | 42 | 43 | setExpand(!expand)}> 55 | {expand ? : } 更多 56 | 57 | ), 58 | }, 59 | ], 60 | }, 61 | ]} 62 | /> 63 | 64 | 65 | 66 |
67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /docs/examples/demo/shouldUpdate.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: 依赖字段判断隐藏显示 3 | * desc: isShow 为 function 可以得到 fieldsValue 判断该 type 隐藏显示 4 | */ 5 | 6 | import React from 'react'; 7 | import { YForm } from 'yforms'; 8 | 9 | const Demo = () => { 10 | return ( 11 | 12 | {[ 13 | { 14 | label: '开关', 15 | type: 'radio', 16 | name: 'type', 17 | componentProps: { 18 | options: [ 19 | { name: '开', id: '1' }, 20 | { name: '关', id: '2' }, 21 | ], 22 | }, 23 | }, 24 | { label: '密码', type: 'password', name: 'password' }, 25 | { 26 | label: '确认密码', 27 | type: 'password', 28 | name: 'confirm', 29 | dependencies: ['password'], 30 | rules: [ 31 | ({ getFieldValue }) => ({ 32 | validator(_, value) { 33 | if (!value || getFieldValue('password') === value) { 34 | return Promise.resolve(); 35 | } 36 | return Promise.reject('Two passwords that you enter is inconsistent!'); 37 | }, 38 | }), 39 | ], 40 | }, 41 | { 42 | noStyle: true, 43 | shouldUpdate: (prevValues, curValues) => prevValues.type !== curValues.type, 44 | children: ({ getFieldsValue }) => { 45 | const fieldsValue = getFieldsValue(true); 46 | return [ 47 | { 48 | noStyle: true, 49 | isShow: fieldsValue.type === '2', 50 | dataSource: [{ label: '原生使用', type: 'input', name: 'children_field1' }], 51 | }, 52 | ]; 53 | }, 54 | }, 55 | { 56 | label: '精简使用', 57 | type: 'input', 58 | name: 'children_field2', 59 | shouldUpdate: (prevValues, curValues) => prevValues.type !== curValues.type, 60 | isShow: (values) => values.type === '2', 61 | }, 62 | ]} 63 | 64 | ); 65 | }; 66 | 67 | export default Demo; 68 | -------------------------------------------------------------------------------- /docs/examples/forms.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 全刷新表单 3 | nav: 4 | title: Examples 5 | --- 6 | 7 | # 全刷新表单 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 示例 3 | nav: 4 | title: Examples 5 | order: 3 6 | --- 7 | 8 | # 示例 9 | 10 | ## 基础使用 11 | 12 | 13 | 14 | ## 弹窗使用 15 | 16 | 17 | 18 | ## 依赖使用 19 | 20 | 21 | 22 | ## 新旧数据对比使用 23 | 24 | 25 | 26 | ## format deFormat 使用 27 | 28 | 29 | 30 | ## getInitialValues 31 | 32 | 33 | 34 | ## jsx 写法 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/examples/list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 列表页面交互 3 | nav: 4 | title: Examples 5 | --- 6 | 7 | # 列表页面交互 8 | 9 | 10 | 11 | ## FAQ 12 | 13 | - `submit` 文档点此 14 | -------------------------------------------------------------------------------- /docs/examples/search.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 搜索场景 3 | nav: 4 | title: Examples 5 | --- 6 | 7 | # 搜索场景 8 | 9 | 10 | 11 | # 搜索场景 2 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 快速上手 3 | order: 9 4 | nav: 5 | order: 10 6 | --- 7 | 8 | # 快速上手 9 | 10 | ## 安装 11 | 12 | 在项目目录下执行以下命令进行安装: 13 | 14 | ```bash 15 | $ yarn add yforms 16 | ``` 17 | 18 | ## 示例 19 | 20 | ```jsx | pure 21 | import React from 'react'; 22 | import { YForm } from 'yforms'; 23 | 24 | const Demo = () => { 25 | return ( 26 | 27 | {[ 28 | { type: 'input', label: 'name', name: 'name' }, 29 | { type: 'money', label: 'money', name: 'money' }, 30 | { 31 | dataSource: [ 32 | { 33 | type: 'button', 34 | noStyle: true, 35 | componentProps: { type: 'primary', htmlType: 'submit', children: 'submit' }, 36 | }, 37 | ], 38 | }, 39 | ]} 40 | 41 | ); 42 | }; 43 | export default Demo; 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 介绍 3 | nav: 4 | title: 指南 5 | order: 1 6 | --- 7 | 8 | ## 什么是 yforms 9 | 10 | yforms 是一个基于 antd v4 开发,为 Form 场景提供可 DIY 配置的表单库,用户可在全局设置每个字段类型的默认行为。 11 | 12 | ## 为什么做 yforms 13 | 14 | - 前端项目中对代码统一有了 eslint、prettier、tslint。而表单在系统中各有各的样,而现在为了解决各个业务模块表单交互不统一 `yforms` 诞生了。 15 | - 常常遇到有个表单中 `placeholder` 文案不统一,有的是:请输入姓名,而有的是:姓名必填、`rules` 中有的有 `whitespace` 有的则没有。 16 | - 同样一个字段,在表单中需要添加 `{label:'姓名' placeholder:'请输入姓名'}`,而搜索场景下就变成只需要 `{placeholder:'姓名'}`。 17 | - 开发项目中表单字段有太多例如:input、select、radio、switch、upload 等,每个表单单独引入 Input、Upload,慢慢的一个表单中引用多个版本的组件展现形式、api、以及交互不统一。 18 | 19 | ## yforms 解决了什么问题 20 | 21 | - 减少模板代码,一个表单中的字段可无缝应用于表单和搜索场景,2 种场景下的区别交给全局配置。 22 | - 表单交互可全局自定义配置一套,其他人使用时候不用写 placeholder。 23 | - 比如搜索场景下产品突然有一天需要每个字段要加可清空功能,这时只需要改全局配置。 24 | 25 | ## 使用 Form 和 YForm 对比 26 | 27 | 一个字段从表单中复制到搜索中的场景 28 | 29 | > Form 用法 30 | 31 | - 表单代码 32 | 33 | ```jsx | pure 34 |
35 | 36 | 37 | 38 |
39 | ``` 40 | 41 | - 搜索代码 42 | 43 | ```jsx | pure 44 |
45 | 46 | 47 | 48 |
49 | ``` 50 | 51 | 按照正常使用情况,当 name 字段复制到搜索场景下需要把 label 删掉,required 删掉,message 中的请输入删掉,非常麻烦。 52 | 53 | > YForm 用法 54 | 55 | - 表单代码 56 | 57 | ```jsx | pure 58 | {[{ type: 'input', label: 'name', name: 'name' }]} 59 | ``` 60 | 61 | - 搜索代码 62 | 63 | ```jsx | pure 64 | {[{ type: 'input', label: 'name', name: 'name' }]} 65 | ``` 66 | 67 | 只需要 YForm 设置相关参数即可,name 字段完全一样。可定制个`search`场景并使用该场景。 68 | 69 | > 当然这只是个示例,其它更多可自行 DIY。 70 | 71 | ## FAQ 72 | 73 | ### 74 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: yforms 3 | order: 10 4 | hero: 5 | title: yforms 6 | desc: 一个基于 antd v4 封装的可以 DIY 的 Form,起名叫 yforms 7 | 8 | actions: 9 | - text: 快速上手 10 | link: /guide/getting-started 11 | features: 12 | - title: 13 | desc: 14 | --- 15 | 16 | ## 特性 17 | 18 | - 简单快速创建复杂表单 19 | - 兼容原生 `Antd Form API` 20 | - 可自行定义 `type` 21 | - 插件机制 22 | - 使用 `TypeScript` 开发,提供完整的类型定义文件 23 | - (未完待续...) 24 | 25 | ## Todo 26 | 27 | - 纯展示样式 28 | - 新旧数据对比 29 | - 可用配置和 JSX 方式编写表单 30 | - 可双向格式化字段值 31 | - 可定义场景,一套表单适用于多个场景下 32 | - ... 33 | 34 | ## 示例 35 | 36 | 37 | 38 | ## 参与贡献 39 | 40 | 欢迎加入到 yforms 的建设队伍中来,请访问 https://github.com/crazyair/yforms 。 41 | 42 | ```jsx | inline 43 | import React from 'react'; 44 | import gif from './assets/qrcode.jpg'; 45 | 46 | export default () => ; 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'antd'; 3 | import { YForm } from 'yforms'; 4 | import { YFormItemProps } from 'yforms/es/YForm/Items'; 5 | 6 | const Demo = () => { 7 | return ( 8 |
9 | 10 | JSX 11 | 12 | 13 | 14 | { 19 | const label = '数组'; 20 | return ( 21 | {icons}
} 28 | /> 29 | ); 30 | }} 31 | /> 32 | 配置 33 | {[ 34 | { type: 'input', label: '姓名', name: 'name2' }, 35 | { 36 | type: 'list', 37 | name: 'list2', 38 | label: '数组', 39 | items: ({ index }): YFormItemProps['children'] => [ 40 | { type: 'input', name: [index, 'age'] }, 41 | ], 42 | }, 43 | ]} 44 | 45 | 46 | ); 47 | }; 48 | export default Demo; 49 | -------------------------------------------------------------------------------- /docs/types/card.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: card 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # Card 8 | 9 | 卡片表单 10 | 11 | ## 何时使用 12 | 13 | - 卡片样式展示表单使用 14 | 15 | ## 用例 16 | 17 | 18 | 19 | ## API 20 | 21 | ### items 22 | 23 | 为 YForm children 类型 24 | 25 | ### componentProps 26 | 27 | 为 Card 类型 28 | -------------------------------------------------------------------------------- /docs/types/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: custom 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # custom 8 | 9 | 自定义渲染元素。 10 | 11 | ## 何时使用 12 | 13 | - 需要自定义渲染元素的时候用到。 14 | - 多用于用户自己封装的字段组件(参数有 `value` `onChange`)。 15 | 16 | ## 用例 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/types/demo/card.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { YForm } from 'yforms'; 3 | import { YFormProps } from 'yforms/es/YForm/Form'; 4 | 5 | export default () => { 6 | const onFinish = (values: any) => { 7 | console.log('Success:', values); 8 | }; 9 | 10 | return ( 11 | 12 | {[ 13 | { 14 | type: 'card', 15 | label: '卡片', 16 | items: [{ type: 'input', name: 'phone', label: '手机号' }], 17 | }, 18 | { 19 | type: 'list', 20 | name: 'card', 21 | label: '卡片', 22 | componentProps: { showRightIcons: false }, 23 | items: ({ index, icons }): YFormProps['children'] => { 24 | return [ 25 | { 26 | type: 'card', 27 | componentProps: { title: `card_${index + 1}`, extra: icons }, 28 | items: [{ type: 'input', name: [index, 'phone'], label: '手机号' }], 29 | }, 30 | ]; 31 | }, 32 | }, 33 | { type: 'submit' }, 34 | ]} 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /docs/types/demo/custom.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { YForm } from 'yforms'; 3 | 4 | export default () => { 5 | return ( 6 | 7 | {[ 8 | { 9 | label: '自定义渲染', 10 | type: 'custom', 11 | name: 'custom', 12 | children:
这是自定义渲染
, 13 | }, 14 | { label: '文字场景', type: 'custom', children: '文字' }, 15 | ]} 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /docs/types/demo/getOptions.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: 动态获取数据 3 | * desc: '`getOptions` 用于接口返回数据,并能根据表单当前值对数处理(尝试修改 optionName 字段值)' 4 | */ 5 | 6 | import React from 'react'; 7 | import { YForm } from 'yforms'; 8 | import { OptionsType } from 'yforms/lib/YForm/ItemsType'; 9 | 10 | const getOptions = async () => { 11 | await new Promise((resolve) => setTimeout(resolve, 1)); 12 | return [ 13 | { id: '1', name: '男' }, 14 | { id: '2', name: '女' }, 15 | ]; 16 | }; 17 | 18 | const Demo = () => { 19 | return ( 20 | 21 | {[ 22 | { type: 'input', name: 'optionName', label: 'optionName' }, 23 | { 24 | type: 'radio', 25 | label: 'list', 26 | name: 'list', 27 | shouldUpdate: (prev, current) => prev.optionName !== current.optionName, 28 | componentProps: { 29 | getOptions: async (value, values) => { 30 | const data = await getOptions(); 31 | if (values.optionName) { 32 | data[0].name = values.optionName; 33 | } 34 | return data; 35 | }, 36 | }, 37 | }, 38 | { 39 | type: 'radio', 40 | label: '已选中', 41 | name: 'check', 42 | shouldUpdate: (prev, current) => prev.check !== current.check, 43 | componentProps: { 44 | getOptions: async (value) => { 45 | const data: OptionsType[] = await getOptions(); 46 | data.map((item) => { 47 | if (item.id === value) { 48 | item.name = `${item.name}(已选中)`; 49 | item.disabled = true; 50 | } else { 51 | item.name = `${item.name}(未选中)`; 52 | item.disabled = false; 53 | } 54 | return item; 55 | }); 56 | return data; 57 | }, 58 | }, 59 | }, 60 | ]} 61 | 62 | ); 63 | }; 64 | 65 | export default Demo; 66 | -------------------------------------------------------------------------------- /docs/types/demo/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React from 'react'; 3 | import { Input } from 'antd'; 4 | import { YForm } from 'yforms'; 5 | 6 | export default () => { 7 | const onFinish = (values: any) => { 8 | console.log('Success:', values); 9 | }; 10 | const onFinishFailed = (errorInfo: any) => { 11 | console.log('Failed:', errorInfo); 12 | }; 13 | const onSave = (values: any) => { 14 | console.log('values:', values); 15 | }; 16 | 17 | return ( 18 | 19 | {[ 20 | { type: 'input', label: '文本', name: 'input' }, 21 | { 22 | type: 'datePicker', 23 | label: '日期', 24 | name: 'date', 25 | componentProps: { picker: 'date', mode: 'date' }, 26 | }, 27 | { type: 'money', label: '金额', name: 'money' }, 28 | { 29 | type: 'textarea', 30 | label: '文本域', 31 | name: 'textarea', 32 | extra: '有长度校验(中文为 2 个字符)', 33 | componentProps: { inputMax: 2 }, 34 | }, 35 | { type: 'checkbox', label: '单选', name: '单选', componentProps: { children: '已阅读' } }, 36 | { 37 | type: 'checkboxGroup', 38 | label: '多选', 39 | name: '多选', 40 | componentProps: { 41 | options: [ 42 | { id: '1', name: '开' }, 43 | { id: '2', name: '关' }, 44 | ], 45 | }, 46 | }, 47 | { 48 | type: 'switch', 49 | label: '开关', 50 | name: '开关', 51 | componentProps: { checkedChildren: '开', unCheckedChildren: '关' }, 52 | }, 53 | { 54 | type: 'select', 55 | label: '下拉框', 56 | name: '下拉框', 57 | componentProps: { 58 | optionLabelProp: 'label', 59 | onAddProps: (item) => ({ label: `(${item.name})` }), 60 | showField: (record) => ( 61 |
62 |
{record.id}
-{record.name} 63 |
64 | ), 65 | options: [ 66 | { id: '1', name: '开' }, 67 | { id: '2', name: '关' }, 68 | ], 69 | }, 70 | }, 71 | { 72 | type: 'radio', 73 | label: '开关', 74 | name: '开关', 75 | componentProps: { 76 | showField: (record) => `${record.id}-${record.name}`, 77 | postField: 'id', 78 | options: [ 79 | { id: '1', name: '开' }, 80 | { id: '2', name: '关' }, 81 | ], 82 | }, 83 | }, 84 | { label: '文本', name: 'text', type: 'text' }, 85 | { label: '自定义渲染', type: 'custom', name: 'custom', children: }, 86 | { type: 'submit' }, 87 | ]} 88 |
89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /docs/types/demo/list.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React, { useState } from 'react'; 3 | import { Card } from 'antd'; 4 | import { YForm } from 'yforms'; 5 | import { YFormListComponentProps, YFormListProps } from 'yforms/lib/YForm/component/List'; 6 | 7 | export default () => { 8 | const [disabled, setDisabled] = useState(false); 9 | 10 | const onFinish = (values: any) => { 11 | console.log('Success:', values); 12 | }; 13 | 14 | return ( 15 | 20 | {[ 21 | { 22 | type: 'list', 23 | name: 'phones', 24 | componentProps: { 25 | showIcons: { showBottomAdd: { text: '添加手机号' }, showAdd: true, showRemove: false }, 26 | onShowIcons: (): ReturnType['onShowIcons']> => ({ 27 | showAdd: true, 28 | showRemove: true, 29 | }), 30 | }, 31 | items: ({ index }): ReturnType> => { 32 | return [ 33 | { 34 | label: index === 0 && '手机号', 35 | type: 'input', 36 | name: [index, 'phone'], 37 | // index > 0 后没有 label,无法得到 label 内容,需要用户自己添加 placeholder 38 | componentProps: { placeholder: '请输入手机号' }, 39 | }, 40 | ]; 41 | }, 42 | }, 43 | { 44 | type: 'list', 45 | name: 'card', 46 | label: '卡片', 47 | componentProps: { showRightIcons: false }, 48 | items: ({ index, icons }): ReturnType> => { 49 | return [ 50 | { 51 | dataSource: [ 52 | 53 | 54 | {[{ label: '手机号', type: 'input', name: [index, 'phone'] }]} 55 | 56 | , 57 | ], 58 | }, 59 | ]; 60 | }, 61 | }, 62 | { 63 | label: '用户', 64 | type: 'list', 65 | name: 'users', 66 | items: ({ index }): ReturnType> => { 67 | return [ 68 | { 69 | type: 'oneLine', 70 | componentProps: { oneLineStyle: ['50%', 8, '50%'] }, 71 | items: () => [ 72 | { label: '姓名', type: 'input', name: [index, 'name'] }, 73 | { type: 'custom', children: }, 74 | { label: '年龄', type: 'input', name: [index, 'age'] }, 75 | ], 76 | }, 77 | ]; 78 | }, 79 | }, 80 | { type: 'submit' }, 81 | { 82 | scenes: { disabled: false }, 83 | dataSource: [ 84 | { 85 | type: 'button', 86 | noStyle: true, 87 | componentProps: { 88 | type: 'primary', 89 | onClick: () => setDisabled((c) => !c), 90 | children: '更改禁用状态', 91 | }, 92 | }, 93 | ], 94 | }, 95 | ]} 96 | 97 | ); 98 | }; 99 | -------------------------------------------------------------------------------- /docs/types/demo/oneLine.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { YForm } from 'yforms'; 3 | import { YFormItemProps } from 'yforms/lib/YForm/Items'; 4 | 5 | export default () => { 6 | return ( 7 | 8 | {[ 9 | { 10 | label: '用户 1', 11 | type: 'oneLine', 12 | // name: 'xxx', 13 | componentProps: { oneLineStyle: ['50%', 8, '50%'] }, 14 | items: (): YFormItemProps['children'] => [ 15 | { label: '姓名', type: 'input', name: 'name' }, 16 | { type: 'custom', children: }, 17 | { label: '年龄', type: 'input', name: 'age' }, 18 | ], 19 | }, 20 | { 21 | label: '用户 2', 22 | type: 'oneLine', 23 | componentProps: { oneLineStyle: ['50%', 8, '50%'] }, 24 | items: ({ style }): YFormItemProps['children'] => { 25 | return [ 26 | { label: '姓名', type: 'input', name: 'name2' }, 27 | { type: 'custom', children: }, 28 | { 29 | noStyle: true, 30 | shouldUpdate: true, 31 | children: (): YFormItemProps['children'] => { 32 | return [{ style: style[2], label: '年龄', type: 'input', name: 'age2' }]; 33 | }, 34 | }, 35 | ]; 36 | }, 37 | }, 38 | ]} 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /docs/types/demo/secureButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { YForm } from 'yforms'; 3 | 4 | export default () => { 5 | const onFinish = (values: any) => { 6 | // eslint-disable-next-line no-console 7 | console.log('Success:', values); 8 | }; 9 | 10 | const onClick = () => { 11 | // eslint-disable-next-line no-console 12 | console.log('ok'); 13 | }; 14 | return ( 15 | 16 | {[{ type: 'secureButton', componentProps: { children: '测试', onClick } }]} 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /docs/types/demo/select.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: 动态获取数据 3 | * desc: '`getOptions` 用于接口返回数据,并能根据表单当前值对数处理(尝试修改 optionName 字段值)' 4 | */ 5 | 6 | import React from 'react'; 7 | import { YForm } from 'yforms'; 8 | 9 | const options = [ 10 | { id: '1', name: '男' }, 11 | { id: '2', name: '女' }, 12 | ]; 13 | 14 | const Demo = () => { 15 | return ( 16 | 17 | {[ 18 | { type: 'select', label: 'select', name: 'list', componentProps: { options } }, 19 | { type: 'radio', label: 'radio', name: 'list', componentProps: { options } }, 20 | { 21 | type: 'checkboxGroup', 22 | label: 'checkboxGroup', 23 | name: 'list', 24 | componentProps: { options }, 25 | }, 26 | ]} 27 | 28 | ); 29 | }; 30 | 31 | export default Demo; 32 | -------------------------------------------------------------------------------- /docs/types/demo/space.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { YForm } from 'yforms'; 3 | 4 | export default () => { 5 | return ( 6 | 7 | {[ 8 | { 9 | label: '用户', 10 | type: 'space', 11 | className: 'mb0', 12 | items: [ 13 | { label: '姓名', type: 'input', name: 'name2' }, 14 | { label: '年龄', type: 'input', name: 'age2' }, 15 | ], 16 | }, 17 | { 18 | type: 'space', 19 | items: [ 20 | { type: 'button', componentProps: { children: '按钮 1', type: 'primary' } }, 21 | { type: 'button', componentProps: { children: '按钮 2' } }, 22 | ], 23 | }, 24 | ]} 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /docs/types/demo/submit.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React from 'react'; 3 | import { YForm } from 'yforms'; 4 | 5 | export default () => { 6 | const onFinish = (values: any) => { 7 | console.log('Success:', values); 8 | }; 9 | return ( 10 | 11 | {[{ type: 'input', label: '姓名', name: 'name' }, { type: 'submit' }]} 12 | 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /docs/types/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 基础类型 3 | nav: 4 | title: Type 5 | order: 4 6 | --- 7 | 8 | # 基础类型 9 | 10 | ## 用例 11 | 12 | 13 | 14 | ## API 15 | 16 | | 参数 | 说明 | 类型 | 默认值 | 17 | | -------- | ---------------- | ----------------------------------- | ------ | 18 | | deFormat | 获取前格式化数据 | (value:T,parentValues:T,value:T)=>T | - | 19 | | format | 提交前格式化数据 | (value:T,parentValues:T,value:T)=>T | - | 20 | -------------------------------------------------------------------------------- /docs/types/list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: list 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # list 8 | 9 | 动态增删单字段多字段或者一个组件。 10 | 11 | ## 何时使用 12 | 13 | - 需要动态增减字段。 14 | 15 | ## 用例 16 | 17 | 18 | 19 | ## API 20 | 21 | ### items 22 | 23 | - 动态增删内容 24 | 25 | items 参数 26 | 27 | | 参数 | 说明 | 类型 | 默认值 | 28 | | ------ | -------------------------- | -------------------------------- | ------ | 29 | | index | 当前索引 | number | - | 30 | | add | 添加一条数据 | ()=>void | - | 31 | | remove | 删除一条数据 | (index: number)=>void | - | 32 | | move | 移动一条数据 | (from: number, to: number)=>void | - | 33 | | icons | 用于自定义显示右侧增删按钮 | React.ReactNode | - | 34 | 35 | items 返回为 YForm 返回类型 36 | 37 | ### componentProps 38 | 39 | | 参数 | 说明 | 类型 | 默认值 | 40 | | --- | --- | --- | --- | 41 | | minNum | 最小条数 | number | - | 42 | | maxNum | 最大条数 | number | - | 43 | | showRightIcons | 是否显示右侧按钮 | boolean | true | 44 | | showIcons | 控制右侧底部按钮是否显示(权重在 `showRightIcons` 之上) | [ShowIconsType](#ShowIconsType) | true | 45 | | onShowIcons | 根据索引控制右侧按钮是否显示(权重在 `showIcons` 之上) | (p: { index: number }) =>showAdd \| showRemove | - | 46 | 47 | ### ShowIconsType 48 | 49 | | 参数 | 说明 | 类型 | 默认值 | 50 | | --- | --- | --- | --- | 51 | | showBottomAdd | 显示底部添加按钮(`text` 可以控制按钮文案) | boolean \| { text?: string } | true | 52 | | showAdd | 显示右侧添加按钮 | boolean | true | 53 | | showRemove | 显示右侧删除按钮 | boolean | true | 54 | -------------------------------------------------------------------------------- /docs/types/oneLine.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: oneLine 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # oneLine 8 | 9 | 一行多个字段。 10 | 11 | ## 何时使用 12 | 13 | - 需要一行显示多个字段或者元素的时候。 14 | 15 | ## 用例 16 | 17 | 18 | 19 | ## API 20 | 21 | ### items 22 | 23 | - 动态增删内容 24 | 25 | items 参数 26 | 27 | | 参数 | 说明 | 类型 | 默认值 | 28 | | ----- | ------------------ | --------------------- | ------ | 29 | | style | 注入每个元素的样式 | React.CSSProperties[] | - | 30 | 31 | items 返回为 YForm 返回类型 32 | 33 | ### componentProps 34 | 35 | | 参数 | 说明 | 类型 | 默认值 | 36 | | ------------ | ------------------ | -------------------- | ------ | 37 | | oneLineStyle | 设置每个元素的宽度 | (string \| number)[] | - | 38 | 39 | `oneLineStyle` 设置说明 40 | 41 | - 计算每个元素的占比使用的是 [css calc 函数]() 42 | - 带`%` 的所有加起来和不大于 `100%` 43 | 44 | - 例:`oneLineStyle: ['50%', 8, '50%']` 左边`50%` 中间`8px` 右边 `50%` 45 | -------------------------------------------------------------------------------- /docs/types/secureButton.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: secureButton 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # secureButton 8 | 9 | 防连击按钮 10 | 11 | ## 何时使用 12 | 13 | - 点击按钮请求接口时候使用 14 | 15 | ## 用例 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | ----------------- | --------------------- | ------ | ------ | 23 | | onLoaded | 请求成功后的回调 | - | true | 24 | | minBtnLoadingTime | 按钮最低 loading 时间 | number | 500 | 25 | 26 | ## FAQ 27 | 28 | ### `onLoaded` 什么时候执行 29 | 30 | 点击按钮后最低 0.5s,之后再调用 `onLoaded`。 31 | -------------------------------------------------------------------------------- /docs/types/select.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: select|radio|checkboxGroup 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # select|radio|checkboxGroup 8 | 9 | 下拉、单选、多选类型 10 | 11 | ## 用例 12 | 13 | 14 | 15 | ## getOptions 16 | 17 | 18 | 19 | ### 共同的 API 20 | 21 | - 以下 API 为 `checkboxGroup` `radio` `select` 共享的 API。 22 | 23 | ### componentProps 24 | 25 | | 参数 | 说明 | 类型 | 默认值 | 26 | | --- | --- | --- | --- | 27 | | options | 数据源,说明[见下](#options) | Array | - | 28 | | getOptions | 动态获取 options | (value:any, parentValues: any, values: any) => OptionsType[] | - | 29 | | showField | 默认显示字段(为 `function` 则为返回值) | string \| ((record: T, index: number) => React.ReactNode) | id | 30 | | postField | 默认提交字段(为 `function` 则为返回值) | string \| ((record: T, index: number) => React.ReactNode) | name | 31 | | renderOption | 自定义显示内容 | (item: any) => any | - | 32 | | onAddProps | 对每一项追加参数 | (item: T, index: number) => { disabled?: boolean; [key: string]: React.ReactNode } | - | 33 | 34 | ### options 35 | 36 | - `id` `name` 为默认字段,可以使用 `showField` `postField` 修改默认字段。 37 | 38 | | 参数 | 说明 | 类型 | 默认值 | 39 | | -------- | -------- | --------------- | ------ | 40 | | id | 提交字段 | React.ReactNode | - | 41 | | name | 显示字段 | React.ReactNode | - | 42 | | disabled | 禁用当前 | boolean | - | 43 | -------------------------------------------------------------------------------- /docs/types/space.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: space 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # space 8 | 9 | 内联样式。 10 | 11 | ## 何时使用 12 | 13 | - 需要一行显示多个元素的时候。 14 | 15 | ## 用例 16 | 17 | 18 | 19 | ## API 20 | 21 | | 参数 | 说明 | 类型 | 默认值 | 22 | | ----- | ------------------- | ---- | ------ | 23 | | items | YForm children 类型 | - | - | 24 | 25 | [componentProps](https://ant-design.gitee.io/components/space-cn/#API) 26 | -------------------------------------------------------------------------------- /docs/types/submit.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: submit 3 | nav: 4 | title: Type 5 | --- 6 | 7 | # submit 8 | 9 | 提交、保存、取消、编辑、返回按钮。 10 | 11 | ## 何时使用 12 | 13 | - 列表页面新增、编辑情况下。 14 | - `Modal Form` 情况下。 15 | 16 | ## 用例 17 | 18 | 19 | 20 | ## API 21 | 22 | | 参数 | 说明 | 类型 | 默认值 | 23 | | -------- | -------- | -------------------------------- | ------ | 24 | | showBtns | 按钮控制 | [showBtns](#showBtns) \| boolean | true | 25 | 26 | ### showBtns 27 | 28 | | 参数 | 说明 | 类型 | 默认值 | 29 | | ---------- | -------- | ------------------------------------------------------------- | ------ | 30 | | showSubmit | 提交按钮 | ButtonProps | - | 31 | | showSave | 保存按钮 | YFormSecureButtonProps | - | 32 | | showCancel | 取消按钮 | ButtonProps | - | 33 | | showEdit | 编辑按钮 | ButtonProps | - | 34 | | showBack | 返回按钮 | ButtonProps | - | 35 | 36 | ## FAQ 37 | 38 | ### 按钮显示与交互规则 39 | 40 | - `disabled` 为 `true` 显示 编辑、返回 41 | - `disabled` 为 `false` 显示 提交、保存、取消 42 | - `create` 页面下点击提交、保存,成功后会返回上一页面 43 | - `create` 页面下点击取消,会返回上一页面 44 | - `edit` 页面下点击提交、保存,成功后会设置 `disabled=true` 45 | - `edit` 页面下点击返回,会返回上一页面 46 | - `view` 页面下点击编辑,会设置 `disabled=false` 47 | - `view` 页面下点击返回,会返回上一页面 48 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // umi-test default set 2 | // https://github.com/umijs/umi/blob/master/packages/umi-test/src/index.js 3 | module.exports = { 4 | verbose: true, 5 | setupFiles: ['./setupTests.js'], 6 | modulePathIgnorePatterns: ['/docs/', '/__snapshots__/', 'lib'], 7 | // coveragePathIgnorePatterns: ['/src/JackBox/'], 8 | coveragePathIgnorePatterns: ['/__test__/', 'lib'], 9 | // testPathIgnorePatterns: ['/packages/yforms'], 10 | // collectCoverage: true, 11 | }; 12 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "changelog": { 3 | "labels": { 4 | "pr(enhancement)": ":rocket: Enhancement", 5 | "pr(bug)": ":bug: Bug Fix", 6 | "pr(documentation)": ":book: Documentation", 7 | "pr(dependency)": ":deciduous_tree: Dependency", 8 | "pr(chore)": ":turtle: Chore" 9 | }, 10 | "repo": "crazyair/yforms", 11 | "cacheDir": ".changelog" 12 | }, 13 | "packages": ["packages/*"], 14 | "npmClient": "yarn", 15 | "command": { 16 | "version": { 17 | "exact": true 18 | }, 19 | "publish": { 20 | "message": "chore(release): publish", 21 | "registry": "https://registry.npmjs.org" 22 | } 23 | }, 24 | "publishConfig": { 25 | "registry": "https://registry.npmjs.org" 26 | }, 27 | "useWorkspaces": true, 28 | "version": "independent" 29 | } 30 | -------------------------------------------------------------------------------- /mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazyair/yforms/21881ee841298d3a66281e0c80c42d7868c58254/mock/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspaces": [ 3 | "packages/*" 4 | ], 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/crazyair/yforms.git" 9 | }, 10 | "scripts": { 11 | "start": "dumi dev", 12 | "build": "father-build && cd packages/yforms && yarn compile && cd ../..", 13 | "docs:build": "dumi build", 14 | "fa:build": "father build", 15 | "test": "umi-test -u --coverage", 16 | "test:ci": "umi-test", 17 | "lint": "eslint --ext .js,jsx,ts,tsx packages/**/src --fix", 18 | "lint:es": "eslint --ext .js,jsx,ts,tsx packages/**/src", 19 | "lint:tsc": "tsc -p tsconfig.json --noEmit", 20 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", 21 | "pub": "npm run build && lerna publish" 22 | }, 23 | "lint-staged": { 24 | "*.{ts,tsx,js,jsx}": [ 25 | "eslint --fix", 26 | "prettier --write" 27 | ], 28 | ".{less,css,json}": [ 29 | "prettier --write" 30 | ] 31 | }, 32 | "husky": { 33 | "hooks": { 34 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 35 | "pre-commit": "lint-staged" 36 | } 37 | }, 38 | "devDependencies": { 39 | "@babel/plugin-proposal-optional-chaining": "^7.12.7", 40 | "@babel/polyfill": "^7.12.1", 41 | "@commitlint/cli": "^11.0.0", 42 | "@commitlint/config-conventional": "^11.0.0", 43 | "@testing-library/react": "^11.1.1", 44 | "@testing-library/react-hooks": "^3.4.2", 45 | "@types/classnames": "^2.2.11", 46 | "@types/enzyme": "^3.10.8", 47 | "@types/jest": "^26.0.18", 48 | "@types/lodash": "^4.14.165", 49 | "@types/react": "^16.9.56", 50 | "@types/react-dom": "^16.9.9", 51 | "@types/react-router-dom": "^5.1.6", 52 | "@types/react-test-renderer": "^16.9.3", 53 | "@types/warning": "^3.0.0", 54 | "@typescript-eslint/eslint-plugin": "^4.12.0", 55 | "@typescript-eslint/parser": "^4.12.0", 56 | "@umijs/fabric": "^2.4.11", 57 | "@umijs/test": "^3.3.1", 58 | "babel-eslint": "^10.1.0", 59 | "babel-loader": "^8.2.2", 60 | "babel-plugin-import": "^1.13.3", 61 | "clean-webpack-plugin": "^3.0.0", 62 | "conventional-changelog": "^3.1.24", 63 | "cross-env": "^7.0.3", 64 | "css-loader": "^5.0.1", 65 | "dumi": "^1.1.0", 66 | "enzyme": "^3.11.0", 67 | "enzyme-adapter-react-16": "^1.15.5", 68 | "eslint": "7.15", 69 | "eslint-config-umi": "^1.6.0", 70 | "eslint-plugin-eslint-comments": "^3.2.0", 71 | "eslint-plugin-flowtype": "^5.2.0", 72 | "eslint-plugin-import": "^2.22.1", 73 | "eslint-plugin-jest": "^24.1.3", 74 | "eslint-plugin-jsx-a11y": "^6.4.1", 75 | "eslint-plugin-react": "^7.21.5", 76 | "eslint-plugin-react-hooks": "^4.2.0", 77 | "eslint-plugin-unicorn": "^23.0.0", 78 | "father-build": "^1.19.1", 79 | "husky": "^4.3.5", 80 | "lerna": "^3.22.1", 81 | "lerna-changelog": "^1.0.1", 82 | "less": "^3.12.2", 83 | "less-loader": "^7.2.1", 84 | "lint-staged": "^10.5.3", 85 | "mini-css-extract-plugin": "^1.3.3", 86 | "prettier": "^2.2.1", 87 | "rc-util": "^5.5.1", 88 | "react": "^16.13.1", 89 | "react-dom": "^16.13.1", 90 | "react-router-dom": "^5.2.0", 91 | "react-test-renderer": "^16.13.1", 92 | "typescript": "^4.1.2", 93 | "webpack": "^5.11.1", 94 | "webpack-cli": "^4.3.1" 95 | }, 96 | "license": "MIT", 97 | "dependencies": { 98 | "antd": "^4.9.2", 99 | "immutable": "^4.0.0-rc.12", 100 | "rc-queue-anim": "^1.8.5" 101 | } 102 | } -------------------------------------------------------------------------------- /packages/yforms/.fatherrc.ts: -------------------------------------------------------------------------------- 1 | import { IBundleOptions } from 'father-build/src/types'; 2 | 3 | const options: IBundleOptions = { 4 | // entry: 'src/index.tsx', 5 | // cjs: 'rollup', 6 | // esm: 'rollup', 7 | // target: 'node', 8 | runtimeHelpers: true, 9 | cjs: { type: 'babel', lazy: true }, 10 | esm: 'babel', 11 | cssModules: false, // https://github.com/umijs/father/issues/131 12 | // extractCSS: true, 13 | // lessInBabelMode: true, 14 | extraBabelPlugins: [ 15 | ['babel-plugin-import', { libraryName: 'antd', libraryDirectory: 'es', style: true }], 16 | ], 17 | }; 18 | 19 | export default options; 20 | -------------------------------------------------------------------------------- /packages/yforms/README.md: -------------------------------------------------------------------------------- 1 | # yforms 2 | 3 | See our [main repo](https://github.com/crazyair/yforms) for more information. 4 | -------------------------------------------------------------------------------- /packages/yforms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yforms", 3 | "version": "1.4.3", 4 | "description": "自定义表单组件", 5 | "main": "lib/index.js", 6 | "typings": "lib/index.d.ts", 7 | "authors": { 8 | "name": "crazyair", 9 | "email": "645381995@qq.com" 10 | }, 11 | "files": [ 12 | "es", 13 | "lib", 14 | "dist" 15 | ], 16 | "sideEffects": [ 17 | "./src/**/style/**", 18 | "es/**/style/*", 19 | "lib/**/style/*", 20 | "*.less" 21 | ], 22 | "scripts": { 23 | "lint:tsc": "tsc -p tsconfig.json --noEmit", 24 | "build": "father-build", 25 | "compile": "webpack --progress --profile" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "http://github.com/crazyair/yforms", 30 | "directory": "yforms" 31 | }, 32 | "homepage": "http://github.com/crazyair/yforms", 33 | "bugs": "http://github.com/crazyair/yforms/issues", 34 | "publishConfig": { 35 | "access": "public", 36 | "registry": "https://registry.npmjs.org/" 37 | }, 38 | "peerDependencies": { 39 | "antd": "4.x", 40 | "@ant-design/icons": "^4.x" 41 | }, 42 | "dependencies": { 43 | "@babel/runtime": "^7.12.5", 44 | "ahooks": "^2.9.2", 45 | "immutable": "^4.0.0-rc.12", 46 | "lodash": "^4.17.20", 47 | "moment": "^2.29.1", 48 | "numbro": "^2.3.2", 49 | "nzh": "^1.0.4", 50 | "warning": "^4.0.3" 51 | }, 52 | "license": "MIT" 53 | } 54 | -------------------------------------------------------------------------------- /packages/yforms/src/YForm/Context.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { YFormProps } from './Form'; 3 | import { YFormItemsProps, YFormItemProps, YFormDataSource } from './Items'; 4 | import { YFormListItems } from './component/List'; 5 | 6 | export const YFormContext = React.createContext({}); 7 | 8 | export const YFormListContent = React.createContext<{ 9 | isList?: boolean; 10 | field?: YFormListItems['field']; 11 | prefixName?: YFormItemProps['name']; 12 | }>({}); 13 | 14 | export const YFormItemContext = React.createContext({}); 15 | export const YFormItemsContext = React.createContext({}); 16 | -------------------------------------------------------------------------------- /packages/yforms/src/YForm/Form.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Spin } from 'antd'; 2 | import React, { useRef, useEffect, useCallback, useState } from 'react'; 3 | import { merge, concat, mapKeys, omit, find, get, set, forEach } from 'lodash'; 4 | import classNames from 'classnames'; 5 | import { FormProps, FormInstance } from 'antd/lib/form'; 6 | import { usePersistFn } from 'ahooks'; 7 | 8 | import baseItemsType, { YFormItemsType, modifyType } from './ItemsType'; 9 | import Items, { FormatFieldsValue, YFormItemProps } from './Items'; 10 | import { YFormContext } from './Context'; 11 | import { 12 | onFormatFieldsValue, 13 | submitFormatValues, 14 | paramsType, 15 | getParentNameData, 16 | mergeWithDom, 17 | } from './utils'; 18 | import { YFormSubmitComponentProps } from './component/Submit'; 19 | import useForm from './useForm'; 20 | import defaultScene from './scenes'; 21 | import { YFormUseSubmitReturnProps } from './useSubmit'; 22 | 23 | export type KeyValue = Record; 24 | export type FieldsType = { [K in keyof T]: string }; 25 | 26 | export type YFormScene = { 27 | form?: (props: Required>) => Pick; 28 | items?: ( 29 | props: Required>, 30 | ) => Pick; 31 | item?: (props: Required) => Pick; 32 | }; 33 | 34 | export interface YFormConfig { 35 | itemsType?: YFormItemsType; 36 | getScene?: Record; 37 | defaultFormProps?: YFormProps; 38 | scenes?: { 39 | labelLayout?: boolean; 40 | noCol?: boolean; 41 | required?: boolean; 42 | placeholder?: boolean; 43 | disabled?: boolean; 44 | view?: boolean; 45 | diff?: boolean; 46 | search?: boolean; 47 | [key: string]: boolean; 48 | }; 49 | } 50 | 51 | export interface ParamsType { 52 | id?: string; 53 | type?: 'create' | 'edit' | 'view'; 54 | } 55 | 56 | export interface ParamsObjType { 57 | create?: boolean; 58 | edit?: boolean; 59 | view?: boolean; 60 | id?: string; 61 | typeName?: string; 62 | } 63 | 64 | export interface YFormInstance extends FormInstance { 65 | getFormatFieldsValue: (value?: T) => T; 66 | } 67 | 68 | type CancelType = 'onSave' | 'onSubmit' | 'onCancel'; 69 | 70 | type getInitialValuesParamsType = { 71 | // 是否是第一次加载 72 | isInit: boolean; 73 | }; 74 | 75 | export interface YFormProps extends Omit, YFormConfig { 76 | isShow?: boolean; 77 | disabled?: boolean; 78 | loading?: boolean; 79 | form?: YFormInstance; 80 | submit?: YFormUseSubmitReturnProps['submit']; 81 | formatFieldsValue?: FormatFieldsValue[]; 82 | onFormatFieldsValue?: (f: FormatFieldsValue[]) => (f: FormatFieldsValue[]) => FormatFieldsValue[]; 83 | children?: YFormItemProps['children']; 84 | onDeFormatFieldsValue?: (p?: FormatFieldsValue) => void; 85 | onSave?: (values: Record) => void; 86 | submitComponentProps?: YFormSubmitComponentProps; 87 | onCancel?: (p: { type: CancelType }) => void; 88 | params?: ParamsType; 89 | oldValues?: T; 90 | offset?: YFormItemProps['offset']; 91 | minBtnLoadingTime?: number; 92 | getInitialValues?: ( 93 | p: getInitialValuesParamsType, 94 | ) => Promise> | Record; 95 | } 96 | 97 | export function useFormatFieldsValue() { 98 | const formatFieldsValue = useRef([]); 99 | return { 100 | onFormatFieldsValue: onFormatFieldsValue(formatFieldsValue.current), 101 | formatFieldsValue: formatFieldsValue.current, 102 | }; 103 | } 104 | 105 | // 全局默认值 106 | let globalConfig: YFormConfig = { 107 | getScene: defaultScene.getScene, 108 | scenes: { labelLayout: true, disabled: true, placeholder: true, required: true }, 109 | // 如果 4 不够,推荐使用 offset 110 | defaultFormProps: { labelCol: { span: 4 }, wrapperCol: { span: 20 } }, 111 | }; 112 | 113 | export const Config = (options: YFormConfig) => { 114 | globalConfig = merge({}, globalConfig, options); 115 | }; 116 | 117 | const InternalForm = React.memo((thisProps) => { 118 | const props = { ...globalConfig.defaultFormProps, ...thisProps }; 119 | const { scenes, getScene = globalConfig.getScene, offset = 0 } = props; 120 | const _scenes = merge({}, globalConfig.scenes, scenes); 121 | const _defaultData = { formProps: props }; 122 | mapKeys(_scenes, (value: boolean, key: string) => { 123 | if (value && getScene[key] && getScene[key].form) { 124 | const data = getScene[key].form(_defaultData); 125 | if (data) { 126 | _defaultData.formProps = { ..._defaultData.formProps, ...data.formProps }; 127 | } 128 | } 129 | }); 130 | const _props = _defaultData.formProps; 131 | 132 | const { 133 | disabled, 134 | loading, 135 | itemsType, 136 | children, 137 | onFinish, 138 | onSave, 139 | formatFieldsValue: formFormatFieldsValue, 140 | onCancel, 141 | params, 142 | form: propsForm, 143 | className, 144 | submitComponentProps, 145 | submit, 146 | initialValues, 147 | minBtnLoadingTime = 500, 148 | getInitialValues, 149 | ...rest 150 | } = _props; 151 | 152 | const [form] = useForm(propsForm); 153 | const formatRef = useRef([]); 154 | const { resetFields, getFieldsValue } = form; 155 | const _params = submit ? submit.params : paramsType(params); 156 | const { create, edit, view } = _params; 157 | // 同 useSubmit 使用 view 当默认值 158 | const [thisDisabled, setDisabled] = useState(view); 159 | const [submitLoading, setSubmitLoading] = useState(false); 160 | const timeOut = useRef(null); 161 | // 下面地方请使用 _thisDisabled 162 | let _thisDisabled = thisDisabled; 163 | if (submit) { 164 | _thisDisabled = submit.disabled; 165 | } 166 | // 改变状态 167 | const handleOnDisabled = useCallback( 168 | (disabled) => { 169 | if (submit) { 170 | submit.onDisabled(disabled); 171 | } else { 172 | setDisabled(disabled); 173 | } 174 | }, 175 | [submit], 176 | ); 177 | const [_getInitialValues, setGetInitialValues] = useState({}); 178 | const [getLoading, setGetLoading] = useState(true); 179 | const immutableGetDetail = usePersistFn(getInitialValues); 180 | 181 | // 传了 getInitialValues 则使用该数据,没传则使用 initialValues、loading 182 | const _initialValues = getInitialValues ? _getInitialValues : initialValues; 183 | const _loading = getInitialValues ? getLoading : loading; 184 | 185 | const hasGetInitialValues = typeof getInitialValues === 'function'; 186 | const loadData = useCallback( 187 | async (params: getInitialValuesParamsType) => { 188 | // 只有传了 getInitialValues 调用 189 | if (hasGetInitialValues) { 190 | setGetInitialValues(await immutableGetDetail(params)); 191 | setGetLoading(false); 192 | } 193 | }, 194 | [hasGetInitialValues, immutableGetDetail], 195 | ); 196 | 197 | useEffect(() => { 198 | loadData({ isInit: true }); 199 | }, [loadData]); 200 | 201 | useEffect(() => { 202 | return () => { 203 | clearTimeout(timeOut.current); 204 | }; 205 | }, []); 206 | 207 | const goBack = () => { 208 | window.history.back(); 209 | }; 210 | 211 | const handleReset: (p: { type: CancelType }) => void = useCallback( 212 | async ({ type }) => { 213 | if (typeof onCancel === 'function') { 214 | onCancel({ type }); 215 | } else { 216 | resetFields(); 217 | if (create) { 218 | goBack(); 219 | } else if (edit || view) { 220 | handleOnDisabled(true); 221 | } 222 | } 223 | }, 224 | [create, edit, handleOnDisabled, onCancel, resetFields, view], 225 | ); 226 | const itemsTypeAll = { ...baseItemsType, ...globalConfig.itemsType, ...itemsType }; 227 | 228 | // 内部格式化功能 229 | const { formatFieldsValue, onFormatFieldsValue } = useFormatFieldsValue(); 230 | 231 | const handleFormatFieldsValue = (value) => { 232 | const _value = value || getFieldsValue(true); 233 | const _formatFieldsValue = concat(formFormatFieldsValue, formatFieldsValue).filter((x) => x); 234 | 235 | // 忽略字段 236 | const omitNames = []; 237 | forEach(_formatFieldsValue, (item) => { 238 | if (item.isOmit) omitNames.push(item.name); 239 | }); 240 | const formatValues = { ...submitFormatValues(_value, _formatFieldsValue) }; 241 | return omit(formatValues, omitNames); 242 | }; 243 | 244 | if (!form.getFormatFieldsValue) { 245 | form.getFormatFieldsValue = handleFormatFieldsValue; 246 | } 247 | 248 | const handleOnFinish = async (value: KeyValue) => { 249 | if (onFinish) { 250 | if (submitLoading) return; 251 | const begin = new Date().getTime(); 252 | setSubmitLoading(true); 253 | try { 254 | await onFinish(form.getFormatFieldsValue(value)); 255 | await loadData({ isInit: false }); 256 | const end = new Date().getTime(); 257 | timeOut.current = window.setTimeout( 258 | () => { 259 | setSubmitLoading(false); 260 | handleReset({ type: 'onSubmit' }); 261 | }, 262 | // loading 时间不到 0.5s 的加载 0.5s,超过的立刻结束。 263 | end - begin > minBtnLoadingTime ? 0 : minBtnLoadingTime, 264 | ); 265 | } catch (error) { 266 | console.log('error', error); 267 | setSubmitLoading(false); 268 | } 269 | } 270 | }; 271 | 272 | const handleOnEdit = (e) => { 273 | e.preventDefault(); 274 | handleOnDisabled(false); 275 | }; 276 | const { 277 | formatFieldsValue: deFormatFieldsValue, 278 | onFormatFieldsValue: onDeFormatFieldsValue, 279 | } = useFormatFieldsValue(); 280 | 281 | // deFormatFieldsValue 第一次为空需要下面 set(deFormatValues, name, value) 设置值 282 | // 当执行 resetFields 后,就需要 deFormatFieldsValue 的格式化。 283 | const deFormatValues = submitFormatValues(_initialValues, deFormatFieldsValue); 284 | 285 | const handleDeFormatFieldsValue = useCallback( 286 | (data: FormatFieldsValue) => { 287 | const { name, format } = data; 288 | const parentValue = getParentNameData(_initialValues, name); 289 | const value = format(get(_initialValues, name), parentValue, _initialValues); 290 | if (!find(formatRef.current, { name })) { 291 | form.setFields([{ name, value }]); 292 | formatRef.current.push({ name, value }); 293 | // 初始化使用 deFormat 后的数据 294 | set(deFormatValues, name, value); 295 | onDeFormatFieldsValue([{ name, format }]); 296 | } 297 | }, 298 | [_initialValues, form, deFormatValues, onDeFormatFieldsValue], 299 | ); 300 | const providerProps = mergeWithDom( 301 | { 302 | form, 303 | scenes: _scenes, 304 | disabled: _thisDisabled, 305 | getScene, 306 | onFormatFieldsValue, 307 | onDeFormatFieldsValue: handleDeFormatFieldsValue, 308 | submitComponentProps: { 309 | showBtns: { 310 | // form submit 触发后设置 loading = true 311 | showSubmit: { loading: submitLoading }, 312 | showEdit: { onClick: handleOnEdit }, 313 | showCancel: { onClick: () => handleReset({ type: 'onCancel' }) }, 314 | showSave: { onLoaded: () => handleReset({ type: 'onSave' }) }, 315 | showBack: { onClick: goBack }, 316 | }, 317 | }, 318 | }, 319 | { ...omit(_props, ['name', 'initialValues']) }, 320 | { initialValues: deFormatValues }, 321 | ); 322 | if ('isShow' in _props && !_props.isShow) { 323 | return null; 324 | } 325 | if (_loading) { 326 | return ( 327 |
328 | 329 |
330 | ); 331 | } 332 | return ( 333 |
340 | 341 | {children} 342 | 343 |
344 | ); 345 | }); 346 | 347 | export default InternalForm; 348 | -------------------------------------------------------------------------------- /packages/yforms/src/YForm/FormModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'antd'; 3 | import { ModalProps } from 'antd/lib/modal'; 4 | import { ConfigContext } from 'antd/lib/config-provider'; 5 | 6 | import { YForm } from '..'; 7 | import { YFormItemProps } from './Items'; 8 | import { YFormProps } from './Form'; 9 | 10 | interface YFormModalProps extends ModalProps { 11 | children?: YFormItemProps['children']; 12 | formFooter?: YFormItemProps['children']; 13 | formProps?: YFormProps; 14 | } 15 | 16 | const FormModal = (props: YFormModalProps) => { 17 | const AntdConfig = React.useContext(ConfigContext); 18 | 19 | const { 20 | children, 21 | formFooter = [ 22 | { type: 'submit', componentProps: { reverseBtns: true, spaceProps: { noStyle: true } } }, 23 | ], 24 | formProps, 25 | ...rest 26 | } = props; 27 | 28 | const { onCancel } = rest; 29 | 30 | const prefixCls = AntdConfig.getPrefixCls(''); 31 | 32 | return ( 33 | 34 | {/* YForm onCancel 无 e ,这里暂时给 null */} 35 | onCancel(null)} {...formProps}> 36 |
37 | {children} 38 |
39 |
40 | {formFooter} 41 |
42 |
43 |
44 | ); 45 | }; 46 | export default FormModal; 47 | -------------------------------------------------------------------------------- /packages/yforms/src/YForm/Item.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, isValidElement } from 'react'; 2 | import { Form } from 'antd'; 3 | import { concat, map, get, pick, omit, mapKeys } from 'lodash'; 4 | import warning from 'warning'; 5 | import { YForm, mergeWithDom } from '..'; 6 | import ItemChildren from './ItemChildren'; 7 | import Items, { YFormRenderChildren, YFormDataSource } from './Items'; 8 | import { getParentNameData } from './utils'; 9 | import { YFormInstance } from './Form'; 10 | 11 | const Item: React.FC = (props) => { 12 | // 这里解析出来的参数最好不要在 scenes 中更改 13 | const { scenes, ...rest } = props; 14 | 15 | const { name, children } = rest; 16 | const formProps = useContext(YForm.YFormContext); 17 | 18 | const { 19 | itemsType = {}, 20 | onDeFormatFieldsValue, 21 | oldValues, 22 | getScene, 23 | onFormatFieldsValue, 24 | } = formProps; 25 | 26 | const itemsProps = useContext(YForm.YFormItemsContext); 27 | const { scenes: thisScenes } = itemsProps; 28 | 29 | const listContext = useContext(YForm.ListContent); 30 | const { prefixName } = listContext; 31 | 32 | // List 会有拼接 name ,这里获取 all name path 33 | const allName = prefixName ? concat(prefixName, name) : name; 34 | 35 | const mergeProps = mergeWithDom( 36 | {}, 37 | pick(formProps, ['scenes', 'offset', 'disabled']), 38 | itemsProps, 39 | props, 40 | ); 41 | 42 | if ('isShow' in props && !props.isShow) return null; 43 | 44 | const _scenes = mergeWithDom({}, thisScenes, scenes); 45 | let _props = mergeWithDom({}, mergeProps, rest, { 46 | offset: (props.offset || 0) + (itemsProps.offset || 0), 47 | }); 48 | 49 | let _componentProps = { ...props.componentProps }; 50 | 51 | const typeProps = get(itemsType, props.type) || {}; 52 | // 原类型 53 | typeProps.type = props.type; 54 | const defaultData = { 55 | formProps, 56 | itemsProps: mergeProps, 57 | itemProps: _props, 58 | componentProps: _componentProps, 59 | typeProps, 60 | }; 61 | 62 | // 参数修改 63 | const _defaultData = defaultData; 64 | const { modifyProps } = typeProps; 65 | if (modifyProps) { 66 | mergeWithDom(_defaultData, modifyProps(defaultData)); 67 | } 68 | 69 | mapKeys(_scenes, (value: boolean, key: string) => { 70 | if (value && getScene[key] && getScene[key].item) { 71 | const data = getScene[key].item(_defaultData); 72 | if (data) { 73 | _defaultData.itemProps = { ..._defaultData.itemProps, ...data.itemProps }; 74 | _defaultData.componentProps = { ..._defaultData.componentProps, ...data.componentProps }; 75 | } 76 | } 77 | }); 78 | 79 | _props = { ..._defaultData.itemProps }; 80 | _componentProps = _defaultData.componentProps; 81 | 82 | const { type, dataSource, componentProps, format, ...formItemProps } = _props; 83 | const _formItemProps = formItemProps; 84 | const { isShow, shouldUpdate } = _formItemProps; 85 | 86 | const { deFormat } = _defaultData.itemProps; 87 | 88 | // 获取前格式化 89 | if (deFormat) { 90 | onDeFormatFieldsValue({ name: allName, format: deFormat }); 91 | if (oldValues && _scenes.diff) { 92 | _defaultData.itemProps = { 93 | oldValue: deFormat( 94 | get(oldValues, allName), 95 | getParentNameData(oldValues, allName), 96 | oldValues, 97 | ), 98 | ..._defaultData.itemProps, 99 | }; 100 | } 101 | } 102 | 103 | // 提交前格式化 104 | if (format) { 105 | let _format = []; 106 | if (typeof format === 'function') { 107 | _format = [{ name: allName, format }]; 108 | } else { 109 | _format = map(format, (item) => { 110 | const _item = { ...item }; 111 | if (item.name) { 112 | _item.name = prefixName ? concat(prefixName, item.name) : item.name; 113 | } 114 | return _item; 115 | }); 116 | } 117 | onFormatFieldsValue(_format); 118 | } 119 | 120 | let _children; 121 | // 默认用 FormItem 包裹 122 | let _hasFormItem = true; 123 | const thisComponentProps = _componentProps; 124 | if (type) { 125 | const _fieldData = itemsType[type]; 126 | if (_fieldData) { 127 | const { component } = _fieldData; 128 | _hasFormItem = 'hasFormItem' in _fieldData ? _fieldData.hasFormItem : _hasFormItem; 129 | const _component = children || component; 130 | _children = isValidElement(_component) 131 | ? React.cloneElement(_component, { 132 | ...(_component.props as Record), 133 | ...thisComponentProps, 134 | }) 135 | : _component; 136 | } else { 137 | warning(false, `[YFom.Items] ${type} 类型未找到`); 138 | } 139 | } else { 140 | // 没有 type 单独有 dataSource 情况 141 | if (dataSource) { 142 | _children = ( 143 | 144 | {dataSource} 145 | 146 | ); 147 | } else { 148 | _children = isValidElement(children) 149 | ? React.cloneElement(children, { ...children.props, ...thisComponentProps }) 150 | : children; 151 | } 152 | } 153 | const domChildren = 154 | typeof _children === 'function' 155 | ? (form: YFormInstance) => { 156 | return ( 157 | 158 | {(_children as YFormRenderChildren)(form)} 159 | 160 | ); 161 | } 162 | : _children; 163 | let dom = domChildren; 164 | if (_hasFormItem) { 165 | dom = ( 166 | 179 | {domChildren} 180 | 181 | ); 182 | } 183 | 184 | const render = (props?: any) => { 185 | return ( 186 | 187 | {dom} 188 | 189 | ); 190 | }; 191 | 192 | if (shouldUpdate) { 193 | let reRender = false; 194 | return ( 195 | 196 | {(form) => { 197 | if (typeof isShow === 'function') { 198 | const fieldsValue = form.getFieldsValue(true); 199 | const parentValue = getParentNameData(fieldsValue, name); 200 | if (!isShow(parentValue, fieldsValue)) { 201 | return; 202 | } 203 | } 204 | reRender = !reRender; 205 | return render({ reRender }); 206 | }} 207 | 208 | ); 209 | } 210 | return render(); 211 | }; 212 | 213 | export default Item; 214 | -------------------------------------------------------------------------------- /packages/yforms/src/YForm/ItemChildren.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form } from 'antd'; 3 | import { forEach, omit, isArray } from 'lodash'; 4 | import { FormItemProps } from 'antd/lib/form'; 5 | import { YFormItemProps } from './Items'; 6 | import { YForm } from '..'; 7 | 8 | export default React.memo((props) => { 9 | const context = React.useContext(YForm.ListContent); 10 | const { isList, field } = context; 11 | 12 | const { children, addonAfter, _addonAfter, addonBefore, isShow, ...rest } = props; 13 | const { name } = rest; 14 | 15 | let _required: boolean | undefined = false; 16 | // 根据 rules required 判断外部 Form.Item 是否必填 17 | forEach(props.rules, (item) => { 18 | if ('required' in item) _required = item.required; 19 | }); 20 | 21 | let itemProps = {}; 22 | if (isList) { 23 | const _name = [...(isArray(name) ? name : [name])]; 24 | if (isArray(name) && typeof name[0] === 'number') { 25 | _name[0] = 'keep'; 26 | } 27 | itemProps = { 28 | isListField: true, 29 | fieldKey: [field.fieldKey, ..._name], 30 | }; 31 | } 32 | const ItemDom = children ? ( 33 | 37 | <>{addonBefore} 38 | 39 | {children as FormItemProps['children']} 40 | 41 | <>{_addonAfter} 42 | <>{addonAfter} 43 | 44 | ) : null; 45 | 46 | return ItemDom; 47 | }); 48 | -------------------------------------------------------------------------------- /packages/yforms/src/YForm/Items.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, isValidElement } from 'react'; 2 | import classNames from 'classnames'; 3 | import { Form } from 'antd'; 4 | import { forEach, isArray, mapKeys, pick, merge, isObject } from 'lodash'; 5 | import { FormItemProps } from 'antd/lib/form'; 6 | 7 | import { YForm } from '..'; 8 | import { YFormProps, YFormInstance } from './Form'; 9 | import { YFormItemsTypeArray, YFormFieldBaseProps } from './ItemsType'; 10 | import { mergeWithDom } from './utils'; 11 | 12 | export type YFormDataSource = YFormItemsTypeArray; 13 | export type YFormRenderChildren = (form: YFormInstance) => YFormItemProps['children']; 14 | 15 | type isShowFunc = (parentValues: any, values: any) => boolean; 16 | 17 | export interface YFormItemProps 18 | extends Omit, 19 | Pick { 20 | isShow?: boolean | isShowFunc; 21 | className?: string; 22 | oldValue?: T; 23 | addonAfter?: React.ReactNode; 24 | addonBefore?: React.ReactNode; 25 | format?: FormatFieldsValue['format'] | FormatFieldsValue[]; 26 | deFormat?: FormatFieldsValue['format']; 27 | style?: React.CSSProperties; 28 | offset?: number; 29 | children?: 30 | | (YFormDataSource | YFormDataSource[] | boolean)[] 31 | | React.ReactElement 32 | | React.ReactFragment[] 33 | | YFormRenderChildren 34 | | boolean 35 | | string; 36 | dataSource?: YFormItemProps['children']; 37 | viewProps?: YFormFieldBaseProps['viewProps']; 38 | diffProps?: YFormFieldBaseProps['diffProps']; 39 | hideLable?: React.ReactNode; // 隐藏类型 label 用于得到 placeholder 和 rules required 的 message 40 | } 41 | 42 | export interface FormatFieldsValue { 43 | name: FormItemProps['name']; 44 | isOmit?: boolean; 45 | format?: (value: any, parentsValue: any, fieldsValue: any) => any; 46 | } 47 | 48 | export interface YFormItemsProps 49 | extends Omit { 50 | isShow?: YFormItemProps['isShow']; 51 | scenes?: YFormItemProps['scenes']; 52 | shouldUpdate?: YFormItemProps['shouldUpdate']; 53 | offset?: number; 54 | noStyle?: boolean; 55 | } 56 | 57 | const Items = (props: YFormItemsProps) => { 58 | const formProps = useContext(YForm.YFormContext); 59 | const { getScene } = formProps; 60 | const itemsProps = useContext(YForm.YFormItemsContext); 61 | 62 | const mergeProps = merge( 63 | {}, 64 | pick(formProps, ['scenes', 'offset', 'disabled']), 65 | itemsProps, 66 | props, 67 | ); 68 | const { scenes: thisScenes } = mergeProps; 69 | const _defaultData = { formProps, itemsProps: props }; 70 | mapKeys(thisScenes, (value: boolean, key: string) => { 71 | if (value && getScene[key] && getScene[key].items) { 72 | const data = getScene[key].items(_defaultData); 73 | if (data) { 74 | _defaultData.itemsProps = { ..._defaultData.itemsProps, ...data.itemsProps }; 75 | } 76 | } 77 | }); 78 | 79 | const _props = mergeWithDom({}, mergeProps, _defaultData.itemsProps, { 80 | offset: (props.offset || 0) + (itemsProps.offset || 0), 81 | }); 82 | 83 | const { isShow, className, style, children, noStyle, shouldUpdate } = _props; 84 | 85 | const itemList = []; 86 | const eachItem = ( 87 | list: YFormItemProps['children'][] | React.ReactFragment[], 88 | pIndex?: number, 89 | ) => { 90 | if (isArray(list)) { 91 | forEach(list, (item, index) => { 92 | // 如果还是是数组就回调该方法 93 | // if (isArray(item)) return eachItem(item, index); 94 | if (isArray(item)) { 95 | return eachItem(item, index); 96 | } 97 | const _index = pIndex ? `${pIndex}_${index}` : index; 98 | // 如果是 dom 直接渲染 99 | if (isValidElement(item)) { 100 | return itemList.push(item); 101 | } 102 | // 如果不是对象直接渲染 103 | if (!isObject(item)) { 104 | return itemList.push(item); 105 | } 106 | return itemList.push(); 107 | }); 108 | } 109 | }; 110 | // 遍历元素 111 | eachItem(isArray(children) ? children : [children]); 112 | const child = ( 113 | 114 | {itemList} 115 | 116 | ); 117 | const dom = noStyle ? ( 118 | child 119 | ) : ( 120 |
121 | {child} 122 |
123 | ); 124 | 125 | if (typeof isShow === 'function') { 126 | return ( 127 | 128 | {(form) => isShow(form.getFieldsValue(true)) && dom} 129 | 130 | ); 131 | } 132 | if ('isShow' in props && !props.isShow) return null; 133 | 134 | return dom; 135 | }; 136 | 137 | export default React.memo(Items); 138 | -------------------------------------------------------------------------------- /packages/yforms/src/YForm/ItemsType.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Checkbox, Switch, Button, DatePicker } from 'antd'; 3 | import { TextProps } from 'antd/lib/typography/Text'; 4 | import { InputProps, PasswordProps } from 'antd/lib/input'; 5 | import { SwitchProps } from 'antd/lib/switch'; 6 | import { ButtonProps } from 'antd/lib/button'; 7 | import { CheckboxProps } from 'antd/lib/checkbox'; 8 | import { CheckboxValueType } from 'antd/lib/checkbox/Group'; 9 | import { DatePickerProps, RangePickerProps } from 'antd/lib/date-picker'; 10 | 11 | import { YFormItemProps, YFormItemsProps } from './Items'; 12 | import { searchSelect } from './utils'; 13 | import CustomTypography from './component/Typography'; 14 | import OneLine, { YFormOneLineProps } from './component/OneLine'; 15 | import Radio, { YRadioProps } from './component/Radio'; 16 | import List, { YFormListProps } from './component/List'; 17 | import CheckboxGroup, { YCheckGroupProps } from './component/CheckboxGroup'; 18 | import Select, { YSelectProps } from './component/Select'; 19 | import TextArea, { YTextAreaProps } from './component/TextArea'; 20 | import Money, { YMoneyProps } from './component/Money'; 21 | import Submit, { YFormSubmitProps } from './component/Submit'; 22 | import SecureButton, { YFormSecureButtonProps } from './component/SecureButton'; 23 | import { YFormProps, YFormConfig } from './Form'; 24 | import ComponentView, { YFormComponentViewProps } from './component/ComponentView'; 25 | import { 26 | datePickerModify, 27 | rangePickerModify, 28 | checkboxModify, 29 | switchModify, 30 | textModify, 31 | oneLineModify, 32 | submitModify, 33 | checkboxGroupModify, 34 | selectModify, 35 | moneyModify, 36 | radioModify, 37 | SpaceModify, 38 | CustomModify, 39 | } from './ItemsTypeModify'; 40 | import Space, { YFormSpaceProps } from './component/Space'; 41 | import Card, { YFormCardProps } from './component/Card'; 42 | 43 | export interface YFormFieldBaseProps { 44 | component?: React.ReactElement; 45 | type?: string; 46 | formItemProps?: YFormItemProps; 47 | formatStr?: string; 48 | hasFormItem?: boolean; 49 | scenes?: YFormConfig['scenes']; 50 | modifyProps?: ( 51 | props: Required>, 52 | ) => Pick, 'itemProps' | 'componentProps'>; 53 | viewProps?: { format?: (value: any, pureValue?: boolean) => React.ReactNode }; 54 | diffProps?: { onEqual?: (value: any, oldValue?: any) => boolean }; 55 | } 56 | 57 | export type modifyType = { 58 | formProps?: YFormProps; 59 | itemsProps?: YFormItemsProps; 60 | itemProps?: YFormItemProps; 61 | componentProps?: T; 62 | typeProps?: YFormFieldBaseProps; 63 | }; 64 | 65 | export interface BaseComponentProps { 66 | className?: string; 67 | style?: React.CSSProperties; 68 | } 69 | 70 | export type stringAndFunc = string | ((record: T, index: number) => React.ReactNode); 71 | export type OptionsType = { 72 | id?: React.ReactNode; 73 | name?: CheckboxValueType; 74 | disabled?: boolean; 75 | [key: string]: any; 76 | }; 77 | 78 | export interface OptionsProps { 79 | options?: OptionsType[]; 80 | getOptions?: (value: any, parentValues: any, values: any) => OptionsType[]; 81 | postField?: stringAndFunc; 82 | showField?: stringAndFunc; 83 | renderOption?: (item: any) => any; 84 | onAddProps?: (item: T, index: number) => { disabled?: boolean; [key: string]: React.ReactNode }; 85 | } 86 | 87 | export interface YFormItemsTypeDefine { 88 | // antd 类型 89 | input: { componentProps?: InputProps }; 90 | datePicker: { componentProps?: DatePickerProps }; 91 | rangePicker: { componentProps?: RangePickerProps }; 92 | password: { componentProps?: PasswordProps }; 93 | checkbox: { componentProps?: CheckboxProps }; 94 | switch: { componentProps?: SwitchProps }; 95 | text: { componentProps?: TextProps }; 96 | button: { componentProps?: ButtonProps }; 97 | // 封装类型 98 | textarea: { componentProps?: YTextAreaProps }; 99 | view: YFormComponentViewProps; 100 | money: { componentProps?: YMoneyProps }; 101 | checkboxGroup: { componentProps?: YCheckGroupProps }; 102 | select: { componentProps?: YSelectProps }; 103 | radio: { componentProps?: YRadioProps }; 104 | oneLine: YFormOneLineProps; 105 | card: YFormCardProps; 106 | space: YFormSpaceProps; 107 | list: YFormListProps; 108 | submit: YFormSubmitProps; 109 | secureButton: YFormSecureButtonProps; 110 | custom: { componentProps?: any }; 111 | } 112 | 113 | export type YFormItemsType = { 114 | [P in keyof YFormItemsTypeDefine]?: { type?: P } & YFormItemsTypeDefine[P] & T; 115 | }; 116 | 117 | export type YFormItemsTypeArray = YFormItemsType[keyof YFormItemsType]; 118 | 119 | export const itemsType: YFormItemsType = { 120 | // 纯文本类 121 | input: { component: , formatStr: '请输入${label}' }, 122 | password: { component: , formatStr: '请输入${label}' }, 123 | textarea: { component: