diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000000000000000000000000000000000..5e1194ffce846881a9e01e166021ab39d7d94ec4 --- /dev/null +++ b/.babelrc @@ -0,0 +1,21 @@ +{ + "sourceMaps": true, + "presets": [ + [ + "@babel/preset-typescript", + { + "isTSX": true, + "allExtensions": true + } + ], + "@babel/preset-react", + [ + "@babel/preset-env", + { + "targets": { + "node": "10.13.0" + } + } + ] + ] +} diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000000000000000000000000000000000000..72120c163d77c19d94628abd432559c308d59591 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,33 @@ +/** + * @file commitlint 配置 + * commit message: (): (注意冒号后面有空格) + * type 标识commit类别 + * scope 标识commit影响范围 + * subject 本次修改的简单描述 + */ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'init', // 初始提交 + 'feat', // 新功能(feature) + 'perf', // 优化 + 'fix', // 修补bug + 'docs', // 文档 + 'style', // 格式 + 'refactor', // 重构 + 'build', // 编译构建 + 'test', // 增加测试 + 'revert', // 回滚 + 'chore' // 其他改动 + ] + ], + 'type-empty': [2, 'never'], + 'subject-empty': [2, 'never'], + 'subject-full-stop': [0, 'never'], + 'subject-case': [0, 'never'] + } +} diff --git a/.cz-config.js b/.cz-config.js new file mode 100644 index 0000000000000000000000000000000000000000..ac772823ba1388ff11b811199bcb69db40ae4005 --- /dev/null +++ b/.cz-config.js @@ -0,0 +1,30 @@ +'use strict' +module.exports = { + types: [ + { value: 'init', name: '初始化' }, + { value: 'feat', name: '新增: 新功能' }, + { value: 'fix', name: '修复: 修复一个Bug' }, + { value: 'docs', name: '文档: 变更的只有文档' }, + { value: 'style', name: '格式: 空格, 分号等格式修复' }, + { value: 'refactor', name: '重构: 代码重构,注意和特性、修复区分开' }, + { value: 'perf', name: '性能: 提升性能' }, + { value: 'test', name: '测试: 添加一个测试' }, + { value: 'build', name: '工具: 开发工具变动(构建、脚手架工具等)' }, + { value: 'revert', name: '回滚: 代码回退' }, + { value: 'chore', name: '其他' } + ], + scopes: [{ name: 'javascript' }, { name: 'typescript' }, { name: 'React' }, { name: 'node' }], + messages: { + type: '选择一种你的提交类型:', + scope: '选择一个scope (可选):', + customScope: 'Denote the SCOPE of this change:', + subject: '短说明:\n', + body: '长说明,使用"|"换行(可选):\n', + breaking: '非兼容性说明 (可选):\n', + footer: '关联关闭的issue,例如:#31, #34(可选):\n', + confirmCommit: '确定提交说明?(yes/no)' + }, + allowCustomScopes: true, + allowBreakingChanges: ['特性', '修复'], + subjectLimit: 100 +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..c6c8b3621938a4691225a870a59bf382af1883dd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000000000000000000000000000000000..fc8dbf2a8262162f5a4394d8957ee5ab30e0c5c0 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,47 @@ +{ + "env": { + "browser": true, + "es2021": true, + "jest": true, + "node": true, + "commonjs": true + }, + "extends": [ + "airbnb", + "plugin:compat/recommended", + "plugin:jest/recommended", + "plugin:react/recommended", + "plugin:import/typescript", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": ["react", "babel", "jest", "react-hooks", "@typescript-eslint"], + "settings": { + "react": { + "version": "16.13.1" + }, + "polyfills": ["Promise"] + }, + "rules": { + "react/jsx-props-no-spreading": 0, + "no-use-before-define": "off", + "react/display-name": 0, + "import/extensions": 0, + "comma-dangle": ["error", "never"], + "space-before-function-paren": [0, "always"], + "react/jsx-filename-extension": [ + 2, + { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + ] + } +} diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 797152f796db98174899d17db4803e0d27be2f8f..0000000000000000000000000000000000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - jest: true - }, - extends: [ - 'plugin:react/recommended', - 'standard' - ], - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaFeatures: { - jsx: true - }, - ecmaVersion: 12, - sourceType: 'module' - }, - plugins: [ - 'react', - '@typescript-eslint' - ], - rules: { - 'no-use-before-define': 'off', - 'react/display-name': 0 - } -} diff --git a/.fatherrc.js b/.fatherrc.js deleted file mode 100644 index c9b7c09c6ebff95bac86df8f11d980b40099c024..0000000000000000000000000000000000000000 --- a/.fatherrc.js +++ /dev/null @@ -1,21 +0,0 @@ -export default { - entry: 'src/index.tsx', - esm: "rollup", - cjs: { - type: "babel", - lazy: true - }, - cssModules: true, - injectCSS: true, - lessInBabelMode: true, - doc: { - typescript: true - }, - extraBabelPlugins: [ - ['babel-plugin-import', { - libraryName: 'antd', - libraryDirectory: 'es', - style: true, - }], - ] -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index acebde45350599c8a0d74a61039a8371466b247d..b3f0ce94c36bdca90bd40201e02def42c005b051 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ node_modules dist lib coverage -package-lock.json \ No newline at end of file +package-lock.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000000000000000000000000000000000..51fdad212d7d7852c2eeb93a3c0868f354908b71 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "singleQuote": true, + "printWidth": 120, + "useTabs": false, + "tabWidth": 2, + "semi": false, + "trailingComma": "none" +} diff --git a/package.json b/package.json index b0f88ff9a15d01f4704526725a1d12845d4d0577..e64766751397c41044d72990fa396627464d2cb1 100644 --- a/package.json +++ b/package.json @@ -10,80 +10,117 @@ "dist" ], "scripts": { - "build": "father build", - "dev": "father doc dev", - "lint-init": "eslint --init", - "lint": "eslint src --ext .ts", + "dev": "npm-watch build", + "build": "npm run clear && npm run build:rollup && npm run build:babel", + "build:rollup": "rollup -c", + "build:babel": "babel src -d lib -s -x '.ts,.tsx'", + "clear": "rm -rf dist && rm -rf lib", "test": "jest", - "test:u": "npm test -- -u" + "test:u": "npm test -- -u", + "lint": "eslint src/** --fix", + "prettier": "prettier -c --write **/*", + "commit": "git-cz" }, + "husky": { + "hooks": { + "commit-msg": "commitlint --env HUSKY_GIT_PARAMS", + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "eslint --fix", + "git add" + ], + "*.{json,html,md,markdown}": [ + "prettier --write", + "git add -f" + ] + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-customizable" + } + }, + "keywords": [ + "ccms" + ], "author": "niuweb", "license": "MIT", + "dependencies": { + "@types/react": "^16.9.46", + "@types/react-router-dom": "^5.1.5", + "axios": "^0.20.0", + "immer": "^9.0.7", + "lodash": "^4.17.21", + "marked": "^1.2.5", + "moment": "^2.29.0", + "qiankun": "^2.5.1", + "query-string": "^6.13.8", + "rc-table": "^7.9.10", + "react-loadable": "^5.5.0", + "tslib": "^2.3.1" + }, "devDependencies": { - "@babel/core": "^7.11.4", + "@babel/cli": "^7.16.8", + "@babel/core": "^7.16.12", "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-decorators": "^7.10.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-runtime": "^7.11.0", - "@babel/preset-env": "^7.11.0", - "@babel/preset-react": "^7.10.4", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.16.7", + "@commitlint/cli": "^9.0.1", + "@commitlint/config-conventional": "^9.0.1", + "@rollup/plugin-babel": "^5.3.0", + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.1.3", "@testing-library/react": "^11.0.4", - "@types/jest": "^26.0.14", - "@types/lodash": "^4.14.172", + "@types/jest": "^27.4.0", + "@types/lodash": "^4.14.178", "@types/marked": "^1.2.0", "@types/node": "^14.11.2", "@types/react-dom": "^16.9.8", "@types/react-loadable": "^5.5.3", "@types/react-test-renderer": "^16.9.3", - "@typescript-eslint/eslint-plugin": "^4.12.0", - "@typescript-eslint/parser": "^4.12.0", + "@typescript-eslint/eslint-plugin": "^5.10.1", + "@typescript-eslint/parser": "^5.10.1", "awesome-typescript-loader": "^5.2.1", "babel-jest": "^26.3.0", - "babel-loader": "^8.1.0", "babel-plugin-import": "^1.13.0", - "css-loader": "^4.3.0", - "eslint": "^7.17.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", + "commitizen": "^4.2.4", + "commitlint-config-cz": "^0.13.2", + "cz-customizable": "^6.3.0", + "eslint": "^7.32.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-babel": "^5.3.1", + "eslint-plugin-compat": "^4.0.1", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.0.0", + "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-react": "^7.22.0", - "express": "^4.17.1", - "father": "^2.29.11", - "html-webpack-plugin": "^4.5.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^26.4.2", - "less": "^3.12.2", - "less-loader": "^7.0.1", - "loadsh": "0.0.4", - "react-docgen": "^5.3.1", - "react-scripts": "^4.0.1", - "react-test-renderer": "^16.13.1", - "style-loader": "^1.2.1", - "ts-jest": "^26.4.0", - "typedoc": "^0.19.2", - "typescript": "^4.0.2", - "webpack": "^4.44.2", - "webpack-bundle-analyzer": "^3.9.0", - "webpack-cli": "^3.3.12", - "webpack-dev-server": "^3.11.0", - "webpack-merge": "^5.1.4" - }, - "dependencies": { - "@types/react": "^16.9.46", - "@types/react-router-dom": "^5.1.5", - "axios": "^0.20.0", - "immer": "^9.0.7", - "lodash": "^4.17.21", - "marked": "^1.2.5", - "moment": "^2.29.0", - "qiankun": "^2.5.1", - "query-string": "^6.13.8", - "rc-table": "^7.9.10", - "react-loadable": "^5.5.0" + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-promise": "^4.3.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0", + "execa": "^4.1.0", + "husky": "^4.2.5", + "jest": "^27.4.7", + "lint-staged": "^10.2.11", + "npm-watch": "^0.11.0", + "prettier": "^2.5.1", + "rollup": "^2.66.0", + "rollup-plugin-eslint": "^7.0.0", + "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-typescript2": "^0.31.1", + "ts-jest": "^27.1.3", + "typescript": "^4.5.5" }, "peerDependencies": { "react": "^16.13.1", "react-dom": "^16.13.1" } -} \ No newline at end of file +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000000000000000000000000000000000000..53ba1f0be5a747c77618518161f8456f29f4946c --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,42 @@ +import path from 'path' +import ts from 'rollup-plugin-typescript2' +import { nodeResolve } from '@rollup/plugin-node-resolve' +import { babel } from '@rollup/plugin-babel' +import commonjs from '@rollup/plugin-commonjs' +import { eslint } from 'rollup-plugin-eslint' +import json from '@rollup/plugin-json' +import { terser } from 'rollup-plugin-terser' + +export default { + input: 'src/index.tsx', + output: [ + { + format: 'esm', + file: path.resolve('dist/index.esm.js') + } + ], + plugins: [ + json(), + // eslint({ + // throwOnError: true, + // exclude: ['node_modules/**', 'es/**', 'dist/**'] + // }), + ts({ + tsconfig: path.resolve(__dirname, 'tsconfig.json') + }), + babel({ + babelHelpers: 'runtime', + exclude: 'node_modules/**' + }), + + commonjs(), + nodeResolve({ + extensions: ['.js', '.ts', '.tsx'] + }), + terser() + ], + external: ['react', 'react-dom', 'marked', 'lodash', 'axios', 'query-string', 'moment', 'qiankun'], + watch: { + include: 'src/**' + } +} diff --git a/src/components/detail/importSubform/index.tsx b/src/components/detail/importSubform/index.tsx index f27b9ca7ff82390c82882f210774ae08b58795e6..a4360cdfaefcd7cc96a87d6f105cf78ce2896a49 100644 --- a/src/components/detail/importSubform/index.tsx +++ b/src/components/detail/importSubform/index.tsx @@ -1,11 +1,12 @@ import React from 'react' -import { getValue , getChainPath} from '../../../util/value' +import { cloneDeep, isEqual } from 'lodash' +import { setTimeout } from 'timers' +import { getValue, getChainPath } from '../../../util/value' import { DetailField, DetailFieldConfig, DetailFieldProps, IDetailField } from '../common' import { Display } from '../../formFields/common' import { display as getALLComponents, FieldConfigs } from '../../formFields' import { IDetailItem } from '../../../steps/detail' -import { cloneDeep, isEqual } from 'lodash' import ConditionHelper from '../../../util/condition' import InterfaceHelper, { InterfaceConfig } from '../../../util/interface' import { ColumnsConfig } from '../../../interface' @@ -19,7 +20,7 @@ import { ColumnsConfig } from '../../../interface' * - * - interface: 来源接口配置 // 仅type为interface时生效 */ export interface ImportSubformFieldConfig extends DetailFieldConfig { - type: 'import_subform', + type: 'import_subform' configFrom?: ImportSubformConfigFromData | ImportSubformConfigFromInterface childColumns?: ColumnsConfig } @@ -43,23 +44,28 @@ export interface IImportSubformField { interface IImportSubformFieldState { didMount: boolean fields: FieldConfigs[] - formData: { status: 'normal' | 'error' | 'loading', message?: string }[] + formData: { status: 'normal' | 'error' | 'loading'; message?: string }[] } -export default class DetailImportSubformField extends DetailField implements IDetailField { +export default class DetailImportSubformField + extends DetailField + implements IDetailField +{ // 各表单项对应的类型所使用的UI组件的类 getALLComponents = (type: any): typeof Display => getALLComponents[type] // 用于请求防频的判断条件 - requestConfig: string = '' - value: string = '' + requestConfig = '' + + value = '' formFields: Array | null> = [] + formFieldsMounted: Array = [] interfaceHelper = new InterfaceHelper() - constructor (props: DetailFieldProps) { + constructor(props: DetailFieldProps) { super(props) this.state = { @@ -69,8 +75,6 @@ export default class DetailImportSubformField extends DetailField { await this.setState({ didMount: true @@ -86,11 +90,14 @@ export default class DetailImportSubformField extends DetailField { - const withConfigPath = this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField ? `${this.props.config.configFrom.dataField}` : '' + handleValueSet = async ( + formFieldIndex: number, + path: string, + value: any, + options?: { noPathCombination?: boolean } + ) => { + const withConfigPath = + this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField + ? `${this.props.config.configFrom.dataField}` + : '' const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueSet(fullPath, value) } } handleValueUnset = async (formFieldIndex: number, path: string, options?: { noPathCombination?: boolean }) => { - const withConfigPath = this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField ? `${this.props.config.configFrom.dataField}` : '' + const withConfigPath = + this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField + ? `${this.props.config.configFrom.dataField}` + : '' const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueUnset(fullPath) } } - handleValueListAppend = async (formFieldIndex: number, path: string, value: any, options?: { noPathCombination?: boolean }) => { - const withConfigPath = this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField ? `${this.props.config.configFrom.dataField}` : '' + handleValueListAppend = async ( + formFieldIndex: number, + path: string, + value: any, + options?: { noPathCombination?: boolean } + ) => { + const withConfigPath = + this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField + ? `${this.props.config.configFrom.dataField}` + : '' const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueListAppend(fullPath, value) } } - handleValueListSplice = async (formFieldIndex: number, path: string, index: number, count: number, options?: { noPathCombination?: boolean }) => { - const withConfigPath = this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField ? `${this.props.config.configFrom.dataField}` : '' + handleValueListSplice = async ( + formFieldIndex: number, + path: string, + index: number, + count: number, + options?: { noPathCombination?: boolean } + ) => { + const withConfigPath = + this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField + ? `${this.props.config.configFrom.dataField}` + : '' const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueListSplice(fullPath, index, count) } } renderComponent = (props: IImportSubformField) => { - return - 您当前使用的UI版本没有实现ImportSubformField组件。 - + return <>您当前使用的UI版本没有实现ImportSubformField组件。 } /** @@ -150,72 +187,90 @@ export default class DetailImportSubformField extends DetailField { - return - 您当前使用的UI版本没有实现FormItem组件。 - + return <>您当前使用的UI版本没有实现FormItem组件。 } - render = () => { - const { - config, - formLayout, - value, - record, - data, - step - } = this.props + /** + * 处理data 兼容非法json的情况 + * @param {any} data 待处理数据 + * @returns 返回data反序列化形式 + */ + handleDataToUnstringfy = (data: any) => { + let dataToUnstringfy = data + if (Object.prototype.toString.call(data) === '[object String]') { + try { + dataToUnstringfy = JSON.parse(data) + } catch (e) { + console.error('当前动态子表单接口响应数据格式不是合格的json字符串') + dataToUnstringfy = [] + } + } + return dataToUnstringfy + } + getConfigData = () => { + const { config, value } = this.props if (config.configFrom && config.configFrom.type === 'interface' && config.configFrom.interface) { - this.interfaceHelper.request( - config.configFrom.interface, - {}, - { record: this.props.record, data: this.props.data, step: this.props.step }, - { loadDomain: this.props.loadDomain } - ).then((data: any) => { - let dataToUnstringfy = data - let dataToStringfy = JSON.stringify(data) - if (Object.prototype.toString.call(data) === '[object String]') { - try { - dataToStringfy = data - dataToUnstringfy = JSON.parse(data) - } catch (e) { - console.error('当前动态子表单接口响应数据格式不是合格的json字符串') - dataToUnstringfy = [] - dataToStringfy = '[]' + this.interfaceHelper + .request( + config.configFrom.interface, + {}, + { record: this.props.record, data: this.props.data, step: this.props.step }, + { loadDomain: this.props.loadDomain } + ) + .then((data: any) => { + const dataToUnstringfy = this.handleDataToUnstringfy(data) + if (!isEqual(dataToUnstringfy, this.state.fields)) { + this.setState({ + fields: dataToUnstringfy + }) } - } - if (dataToStringfy !== JSON.stringify(this.state.fields)) { - this.setState({ - fields: dataToUnstringfy - }) - } - }) + }) } - let fields = this.state.fields + let { fields } = this.state if (config.configFrom && config.configFrom.type === 'data') { fields = config.configFrom.configField ? getValue(value, config.configFrom.configField) : [] - if (!isEqual(fields, this.state.fields)) { + const dataToUnstringfy = this.handleDataToUnstringfy(fields) + if (!isEqual(dataToUnstringfy, this.state.fields)) { this.setState({ - fields + fields: dataToUnstringfy }) } } + } + + componentDidMount() { + this.getConfigData() + } + + componentDidUpdate() { + this.getConfigData() + } + + render = () => { + const { config, formLayout, value, record, data, step } = this.props + + const { fields } = this.state + if (!fields || !Array.isArray(fields) || fields.length === 0) { - return - } else { - const withConfigPath = this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField ? `${this.props.config.configFrom.dataField}` : '' - return ( - - {this.renderComponent({ - columns: config?.columns?.enable ? config.columns : undefined, - children: this.state.didMount - ? fields.map((formFieldConfig, formFieldIndex) => { + return <> + } + const withConfigPath = + this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField + ? `${this.props.config.configFrom.dataField}` + : '' + return ( + <> + {this.renderComponent({ + columns: config?.columns?.enable ? config.columns : undefined, + children: this.state.didMount + ? fields.map((formFieldConfig, formFieldIndex) => { if (!ConditionHelper(formFieldConfig.condition, { record: value, data, step })) { this.formFieldsMounted[formFieldIndex] = false return null } - let display: boolean = true + let display = true if (formFieldConfig.type === 'hidden' || formFieldConfig.display === 'none') { display = false @@ -252,10 +307,16 @@ export default class DetailImportSubformField extends DetailField this.handleValueSet(formFieldIndex, path, value, options)} + onValueSet={async (path, value, options) => + this.handleValueSet(formFieldIndex, path, value, options) + } onValueUnset={async (path, options) => this.handleValueUnset(formFieldIndex, path, options)} - onValueListAppend={async (path, value, options) => this.handleValueListAppend(formFieldIndex, path, value, options)} - onValueListSplice={async (path, index, count, options) => this.handleValueListSplice(formFieldIndex, path, index, count, options)} + onValueListAppend={async (path, value, options) => + this.handleValueListAppend(formFieldIndex, path, value, options) + } + onValueListSplice={async (path, index, count, options) => + this.handleValueListSplice(formFieldIndex, path, index, count, options) + } baseRoute={this.props.baseRoute} loadDomain={async (domain: string) => await this.props.loadDomain(domain)} /> @@ -264,10 +325,9 @@ export default class DetailImportSubformField extends DetailField - ) - } + : [] + })} + + ) } } diff --git a/src/components/detail/index.tsx b/src/components/detail/index.tsx index b6eed93902da66fce27cadc9ee6e99d3617b1bde..c19ed86a48c83c8760c37e8dc478d9ab272fdc9f 100644 --- a/src/components/detail/index.tsx +++ b/src/components/detail/index.tsx @@ -13,28 +13,28 @@ import TableField, { TableFieldConfig } from './table' * 详情步骤内详情项配置文件格式定义 - 枚举 */ export type DetailFieldConfigs = - TextFieldConfig | - EnumDetailConfig | - StatementDetailConfig | - ImageDetailConfig | - GroupFieldConfig | - ImportSubformFieldConfig | - InfoDetailConfig | - ColorDetailConfig | - TableFieldConfig | - CustomDetailConfig + TextFieldConfig | + EnumDetailConfig | + StatementDetailConfig | + ImageDetailConfig | + GroupFieldConfig | + ImportSubformFieldConfig | + InfoDetailConfig | + ColorDetailConfig | + TableFieldConfig | + CustomDetailConfig export type componentType = - 'text' | - 'group' | - 'detail_enum' | - 'statement' | - 'image' | - 'import_subform' | - 'detail_info' | - 'detail_color' | - 'table' | - 'custom' + 'text' | + 'group' | + 'detail_enum' | + 'statement' | + 'image' | + 'import_subform' | + 'detail_info' | + 'detail_color' | + 'table' | + 'custom' export default { group: GroupField, diff --git a/src/components/formFields/common.tsx b/src/components/formFields/common.tsx index c6118a33b7baf4da0bb1d665021c349fb177946f..ae388d79f1ec6f0f09fb6f2a0b176d6dffd97245 100644 --- a/src/components/formFields/common.tsx +++ b/src/components/formFields/common.tsx @@ -284,7 +284,7 @@ export class Display extends React.Componen export class FieldError { message: string - constructor (message: string) { + constructor(message: string) { this.message = message } } diff --git a/src/components/formFields/form/display.tsx b/src/components/formFields/form/display.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0999d8081cb4a9d40d21485d7d36c6c524ba69c6 --- /dev/null +++ b/src/components/formFields/form/display.tsx @@ -0,0 +1,282 @@ +import React from 'react' +import { display as getALLComponents } from '../' +import { FormFieldConfig } from '.' +import { Display, FieldConfigs, DisplayProps } from '../common' +import { getValue, setValue } from '../../../util/value' +import { cloneDeep } from 'lodash' +import ConditionHelper from '../../../util/condition' + +export interface IFormField { + canCollapse?: boolean + children: React.ReactNode[] +} + +export interface IFormFieldItem { + index: number + title: string + canCollapse?: boolean + children: React.ReactNode[] +} + +export interface IFormFieldItemField { + index: number + label: string + fieldType: string + children: React.ReactNode +} + +interface FormState { + didMount: boolean + showItem: boolean + showIndex: number +} + +export default class FormField extends Display, FormState> { + getALLComponents = (type: any): typeof Display => getALLComponents[type] + + formFieldsList: Array | null>> = [] + formFieldsMountedList: Array> = [] + + constructor (props: DisplayProps) { + super(props) + + this.state = { + didMount: false, + showItem: false, + showIndex: 0 + } + } + + didMount = async () => { + await this.setState({ + didMount: true + }) + } + + set = async (value: any) => { + if (this.props.config.unstringify && this.props.config.unstringify.length > 0 && Array.isArray(value)) { + for (let index = 0; index < value.length; index++) { + if (value[index]) { + for (const field of this.props.config.unstringify) { + const info = getValue(value[index], field) + try { + value[index] = setValue(value[index], field, JSON.parse(info)) + } catch (e) {} + } + } + } + } + + return value + } + + get = async () => { + const data: any[] = [] + + for (let index = 0; index < this.formFieldsList.length; index++) { + if (this.formFieldsList[index]) { + let item: any = {} + + if (Array.isArray(this.props.config.fields)) { + for (const formFieldIndex in this.props.config.fields) { + const formFieldConfig = this.props.config.fields[formFieldIndex] + if (!ConditionHelper(formFieldConfig.condition, { record: this.props.value[index], data: this.props.data, step: this.props.step })) { + continue + } + const formField = this.formFieldsList[index] && this.formFieldsList[index][formFieldIndex] + if (formField) { + const value = await formField.get() + item = setValue(item, formFieldConfig.field, value) + } + } + } + + if (this.props.config.stringify) { + for (const field of this.props.config.stringify) { + const info = getValue(item, field) + item = setValue(item, field, JSON.stringify(info)) + } + } + + data[index] = item + } + } + + return data + } + + handleMount = async (index: number, formFieldIndex: number) => { + if (!this.formFieldsMountedList[index]) { + this.formFieldsMountedList[index] = [] + } + if (this.formFieldsMountedList[index][formFieldIndex]) { + return true + } + this.formFieldsMountedList[index][formFieldIndex] = true + + if (this.formFieldsList[index] && this.formFieldsList[index][formFieldIndex]) { + const formField = this.formFieldsList[index][formFieldIndex] + if (formField) { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + + let value = getValue(this.props.value[index] === undefined ? {} : this.props.value[index], formFieldConfig.field) + const source = value + if ((formFieldConfig.defaultValue) && value === undefined) { + value = await formField.reset() + } + value = await formField.set(value) + if (source !== value) { + this.props.onValueSet(`[${index}]${formFieldConfig.field}`, value) + } + + await formField.didMount() + } + } + } + + handleValueSet = async (index: number, formFieldIndex: number, path: string, value: any, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? path : (formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}`) + await this.props.onValueSet(`[${index}]${fullPath}`, value) + } + } + + handleValueUnset = async (index: number, formFieldIndex: number, path: string, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? path : (formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}`) + await this.props.onValueUnset(`[${index}]${fullPath}`) + } + } + + handleValueListAppend = async (index: number, formFieldIndex: number, path: string, value: any, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? path : (formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}`) + await this.props.onValueListAppend(`[${index}]${fullPath}`, value) + } + } + + handleValueListSplice = async (index: number, formFieldIndex: number, path: string, _index: number, count: number, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? path : (formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}`) + await this.props.onValueListSplice(`[${index}]${fullPath}`, _index, count) + } + } + + /** + * 用于展示子表单组件中的每一子项中的每一个子表单项组件 + * @param props + * @returns + */ + renderItemFieldComponent = (props: IFormFieldItemField) => { + return + 您当前使用的UI版本没有实现FormField组件的renderItemFieldComponent方法。 + + } + + /** + * 用于展示子表单组件中的每一个子项 + * @param props + * @returns + */ + renderItemComponent = (props: IFormFieldItem) => { + return + 您当前使用的UI版本没有实现FormField组件的renderItemComponent方法。 + + } + + /** + * 用于展示子表单组件 + * @param _props + * @returns + */ + renderComponent = (_props: IFormField) => { + return + 您当前使用的UI版本没有实现FormField组件。 + + } + + render = () => { + const { + value = [], + data, + step, + config: { + fields, + primaryField, + canCollapse + } + } = this.props + + return ( + + { + this.renderComponent({ + canCollapse, + children: ( + this.state.didMount + ? (Array.isArray(value) ? value : []).map((itemValue: any, index: number) => { + return + {this.renderItemComponent({ + index, + title: primaryField !== undefined ? getValue(itemValue, primaryField, '').toString() : index.toString(), + canCollapse, + children: (fields || []).map((formFieldConfig, fieldIndex) => { + if (!ConditionHelper(formFieldConfig.condition, { record: itemValue, data: this.props.data, step: this.props.step })) { + if (!this.formFieldsMountedList[index]) this.formFieldsMountedList[index] = [] + this.formFieldsMountedList[index][fieldIndex] = false + return null + } + const FormField = this.getALLComponents(formFieldConfig.type) || Display + + // 渲染表单项容器 + return ( +
+ { + this.renderItemFieldComponent({ + index: fieldIndex, + label: formFieldConfig.label, + fieldType: formFieldConfig.type, + children: ( + | null) => { + if (fieldRef) { + if (!this.formFieldsList[index]) this.formFieldsList[index] = [] + this.formFieldsList[index][fieldIndex] = fieldRef + this.handleMount(index, fieldIndex) + } + }} + value={getValue(value[index], formFieldConfig.field)} + record={value[index]} + data={cloneDeep(data)} + step={step} + config={formFieldConfig} + onValueSet={async (path, value, options) => this.handleValueSet(index, fieldIndex, path, value, options)} + onValueUnset={async (path, options) => this.handleValueUnset(index, fieldIndex, path, options)} + onValueListAppend={async (path, value, options) => this.handleValueListAppend(index, fieldIndex, path, value, options)} + onValueListSplice={async (path, _index, count, options) => this.handleValueListSplice(index, fieldIndex, path, _index, count, options)} + baseRoute={this.props.baseRoute} + loadDomain={async (domain: string) => await this.props.loadDomain(domain)} + /> + ) + }) + } +
+ ) + }) + }) + } +
+ } + ) + : [] + ) + }) + } +
+ ) + } +} diff --git a/src/components/formFields/group/display.tsx b/src/components/formFields/group/display.tsx new file mode 100644 index 0000000000000000000000000000000000000000..48f71a32a903c6b8bcb67d354aa3d7143f3f112d --- /dev/null +++ b/src/components/formFields/group/display.tsx @@ -0,0 +1,207 @@ +import React from 'react' +import { display as getALLComponents, FieldConfigs } from '../' +import { GroupFieldConfig, IGroupField } from '.' +import { setValue, getValue, getBoolean } from '../../../util/value' +import { Display, DisplayProps } from '../common' +import { IFormItem } from '../../../steps/form' +import { cloneDeep } from 'lodash' +import ConditionHelper from '../../../util/condition' +import StatementHelper from '../../../util/statement' + +interface IGroupFieldState { + didMount: boolean +} + +export default class GroupField extends Display { + // 各表单项对应的类型所使用的UI组件的类 + getALLComponents = (type: any): typeof Display => getALLComponents[type] + + formFields: Array | null> = [] + formFieldsMounted: Array = [] + + constructor(props: DisplayProps) { + super(props) + + this.state = { + didMount: false + } + } + + didMount = async () => { + await this.setState({ + didMount: true + }) + } + + get = async () => { + let data: any = {} + + if (Array.isArray(this.props.config.fields)) { + for (let formFieldIndex = 0; formFieldIndex < this.props.config.fields.length; formFieldIndex++) { + const formFieldConfig = this.props.config.fields[formFieldIndex] + if (!ConditionHelper(formFieldConfig.condition, { record: this.props.value, data: this.props.data, step: this.props.step })) { + continue + } + const formField = this.formFields[formFieldIndex] + if (formField) { + const value = await formField.get() + data = setValue(data, formFieldConfig.field, value) + } + } + } + return data + } + + handleMount = async (formFieldIndex: number) => { + if (this.formFieldsMounted[formFieldIndex]) { + return true + } + this.formFieldsMounted[formFieldIndex] = true + + if (this.formFields[formFieldIndex]) { + const formField = this.formFields[formFieldIndex] + if (formField) { + const formFieldConfig = this.props.config.fields[formFieldIndex] + + let value = getValue(this.props.value, formFieldConfig.field) + const source = value + if ((formFieldConfig.defaultValue) && value === undefined) { + value = await formField.reset() + } + value = await formField.set(value) + if (source !== value) { + this.props.onValueSet(formFieldConfig.field, value) + } + + await formField.didMount() + } + } + } + + handleValueSet = async (formFieldIndex: number, path: string, value: any, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueSet(fullPath, value) + } + } + + handleValueUnset = async (formFieldIndex: number, path: string, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueUnset(fullPath) + } + } + + handleValueListAppend = async (formFieldIndex: number, path: string, value: any, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueListAppend(fullPath, value) + } + } + + handleValueListSplice = async (formFieldIndex: number, path: string, index: number, count: number, options?: { noPathCombination?: boolean }) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueListSplice(fullPath, index, count) + } + } + + renderComponent = (props: IGroupField) => { + return + 您当前使用的UI版本没有实现GroupField组件。 + + } + + /** + * 表单项组件 - UI渲染方法 + * 各UI库需重写该方法 + * @param props + */ + renderItemComponent = (props: IFormItem) => { + return + 您当前使用的UI版本没有实现FormItem组件。 + + } + + render = () => { + const { + value, + record, + data, + step + } = this.props + + return ( + + {this.renderComponent({ + children: this.state.didMount + ? (this.props.config.fields || []).map((formFieldConfig, formFieldIndex) => { + if (!ConditionHelper(formFieldConfig.condition, { record: value, data: this.props.data, step: this.props.step })) { + this.formFieldsMounted[formFieldIndex] = false + return null + } + let hidden: boolean = true + let display: boolean = true + + if (formFieldConfig.type === 'hidden') { + hidden = true + display = false + } + + if (formFieldConfig.display === 'none') { + hidden = true + display = false + } + + const FormField = this.getALLComponents(formFieldConfig.type) || Display + + let status: 'normal' = 'normal' + const renderData = { + key: formFieldIndex, + label: formFieldConfig.label, + status, + layout: 'horizontal' as 'horizontal', + extra: StatementHelper(formFieldConfig.extra, { record: this.props.record, data: this.props.data, step: this.props.step }), + required: getBoolean(formFieldConfig.required), + visitable: display, + fieldType: formFieldConfig.type, + children: ( + | null) => { + if (formField) { + this.formFields[formFieldIndex] = formField + this.handleMount(formFieldIndex) + } + }} + value={getValue(value, formFieldConfig.field)} + record={record} + data={cloneDeep(data)} + step={step} + config={formFieldConfig} + onValueSet={async (path, value, options) => this.handleValueSet(formFieldIndex, path, value, options)} + onValueUnset={async (path, options) => this.handleValueUnset(formFieldIndex, path, options)} + onValueListAppend={async (path, value, options) => this.handleValueListAppend(formFieldIndex, path, value, options)} + onValueListSplice={async (path, index, count, options) => this.handleValueListSplice(formFieldIndex, path, index, count, options)} + baseRoute={this.props.baseRoute} + loadDomain={async (domain: string) => await this.props.loadDomain(domain)} + /> + ) + } + // 渲染表单项容器 + return ( + hidden + ? this.renderItemComponent(renderData) + : + ) + }) + : [] + })} + + ) + } +} diff --git a/src/components/formFields/hidden/display.tsx b/src/components/formFields/hidden/display.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0ff145f9294fcdf6cba8167ebba7dd81cb43abf5 --- /dev/null +++ b/src/components/formFields/hidden/display.tsx @@ -0,0 +1,5 @@ +import { HiddenFieldConfig } from '.' +import { Display } from '../common' + +export default class HiddenField extends Display { +} diff --git a/src/components/formFields/importSubform/display.tsx b/src/components/formFields/importSubform/display.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4e0eae57e22d4e27e332e23ea1b9d33f4993f3cd --- /dev/null +++ b/src/components/formFields/importSubform/display.tsx @@ -0,0 +1,273 @@ +import React from 'react' +import { cloneDeep, isEqual } from 'lodash' +import { getValue } from '../../../util/value' + +import { DetailField, DetailFieldConfig, DetailFieldProps, IDetailField } from '../../detail/common' +import { Display } from '../common' +import { display as getALLComponents, FieldConfigs } from '..' +import { IDetailItem } from '../../../steps/detail' +import ConditionHelper from '../../../util/condition' +import InterfaceHelper, { InterfaceConfig } from '../../../util/interface' +/** + * 子表单配置项 + * - withConfig: 拓展配置 + * - * - enable: 是否开启 + * - * - dataField: (序列化)数据 + * - * - configField: (序列化)配置 + */ +export interface ImportSubformFieldConfig extends DetailFieldConfig { + type: 'import_subform' + configFrom?: ImportSubformConfigFromData | ImportSubformConfigFromInterface +} + +interface ImportSubformConfigFromData { + type: 'data' + dataField?: string + configField?: string +} + +interface ImportSubformConfigFromInterface { + type: 'interface' + interface?: InterfaceConfig +} + +export interface IImportSubformField { + children: React.ReactNode[] +} + +interface IImportSubformFieldState { + didMount: boolean + fields: FieldConfigs[] + formData: { status: 'normal' | 'error' | 'loading'; message?: string }[] +} + +export default class ImportSubformFieldDisplay + extends DetailField + implements IDetailField +{ + // 各表单项对应的类型所使用的UI组件的类 + getALLComponents = (type: any): typeof Display => getALLComponents[type] + + // 用于请求防频的判断条件 + requestConfig = '' + + value = '' + + formFields: Array | null> = [] + + formFieldsMounted: Array = [] + + interfaceHelper = new InterfaceHelper() + + constructor(props: DetailFieldProps) { + super(props) + + this.state = { + didMount: false, + fields: [], + formData: [] + } + } + + getFullpath(field: string, path = '') { + const withConfigPath = + this.props.config.configFrom?.type === 'data' && this.props.config.configFrom.dataField + ? `${this.props.config.configFrom.dataField}` + : '' + const _fullPath = `${withConfigPath}.${field}.${path}.` + const fullPath = _fullPath.replace(/(^\.*)|(\.*$)|(\.){2,}/g, '$3') + return fullPath + } + + didMount = async () => { + await this.setState({ + didMount: true + }) + } + + set: (value: any) => Promise = async (value) => { + return value + } + + handleMount = async (formFieldIndex: number) => { + if (this.formFieldsMounted[formFieldIndex]) { + return true + } + this.formFieldsMounted[formFieldIndex] = true + if (this.formFields[formFieldIndex]) { + const formField = this.formFields[formFieldIndex] + if (formField) { + const formFieldConfig = this.state.fields[formFieldIndex] + + let value = getValue(this.props.value, this.getFullpath(formFieldConfig.field)) + const source = value + if (formFieldConfig.defaultValue && value === undefined) { + value = await formField.reset() + } + value = await formField.set(value) + if (source !== value) { + this.props.onValueSet(this.getFullpath(formFieldConfig.field), value) + } + await formField.didMount() + } + } + } + + getConfigData = () => { + const { config, value } = this.props + if (config.configFrom && config.configFrom.type === 'interface' && config.configFrom.interface) { + this.interfaceHelper + .request( + config.configFrom.interface, + {}, + { record: this.props.record, data: this.props.data, step: this.props.step }, + { loadDomain: this.props.loadDomain } + ) + .then((data: any) => { + let dataToUnstringfy = data + let dataToStringfy = JSON.stringify(data) + if (Object.prototype.toString.call(data) === '[object String]') { + try { + dataToStringfy = data + dataToUnstringfy = JSON.parse(data) + } catch (e) { + console.error('当前动态子表单接口响应数据格式不是合格的json字符串') + dataToUnstringfy = [] + dataToStringfy = '[]' + } + } + if (dataToStringfy !== JSON.stringify(this.state.fields)) { + this.setState({ + fields: dataToUnstringfy + }) + } + }) + } + let { fields } = this.state + if (config.configFrom && config.configFrom.type === 'data') { + fields = config.configFrom.configField ? getValue(value, config.configFrom.configField) : [] + if (!isEqual(fields, this.state.fields)) { + this.setState({ + fields + }) + } + } + } + + componentDidMount() { + this.getConfigData() + } + + componentDidUpdate() { + this.getConfigData() + } + + handleValueSet = async (formFieldIndex: number, path: string, value: any) => { + const formFieldConfig = (this.state.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = this.getFullpath(formFieldConfig.field, path) + await this.props.onValueSet(fullPath, value) + } + } + + handleValueUnset = async (formFieldIndex: number, path: string) => { + const formFieldConfig = (this.state.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = this.getFullpath(formFieldConfig.field, path) + await this.props.onValueUnset(fullPath) + } + } + + handleValueListAppend = async (formFieldIndex: number, path: string, value: any) => { + const formFieldConfig = (this.state.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = this.getFullpath(formFieldConfig.field, path) + await this.props.onValueListAppend(fullPath, value) + } + } + + handleValueListSplice = async (formFieldIndex: number, path: string, index: number, count: number) => { + const formFieldConfig = (this.state.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = this.getFullpath(formFieldConfig.field, path) + await this.props.onValueListSplice(fullPath, index, count) + } + } + + renderComponent = (props: IImportSubformField) => { + return <>您当前使用的UI版本没有实现ImportSubformField组件。 + } + + /** + * 表单项组件 - UI渲染方法 + * 各UI库需重写该方法 + * @param props + */ + renderItemComponent = (props: IDetailItem) => { + return <>您当前使用的UI版本没有实现FormItem组件。 + } + + render = () => { + const { config, value, record, data, step } = this.props + const { fields } = this.state + + if (!fields || !Array.isArray(fields) || fields.length === 0) { + return <> + } + return ( + <> + {this.renderComponent({ + children: this.state.didMount + ? fields.map((formFieldConfig, formFieldIndex) => { + if (!ConditionHelper(formFieldConfig.condition, { record: value, data, step })) { + this.formFieldsMounted[formFieldIndex] = false + return null + } + let display = true + + if (formFieldConfig.type === 'hidden' || formFieldConfig.display === 'none') { + display = false + } + + const FormField = this.getALLComponents(formFieldConfig.type) || Display + + const renderData: IDetailItem = { + key: formFieldIndex, + label: formFieldConfig.label, + visitable: display, + fieldType: formFieldConfig.type, + layout: 'horizontal' as const, + children: ( + | null) => { + if (formField) { + this.formFields[formFieldIndex] = formField + this.handleMount(formFieldIndex) + } + }} + value={getValue(value, this.getFullpath(formFieldConfig.field))} + record={record} + data={cloneDeep(data)} + step={step} + config={formFieldConfig} + onValueSet={async (path, value) => this.handleValueSet(formFieldIndex, path, value)} + onValueUnset={async (path) => this.handleValueUnset(formFieldIndex, path)} + onValueListAppend={async (path, value) => this.handleValueListAppend(formFieldIndex, path, value)} + onValueListSplice={async (path, index, count) => + this.handleValueListSplice(formFieldIndex, path, index, count) + } + baseRoute={this.props.baseRoute} + loadDomain={async (domain: string) => await this.props.loadDomain(domain)} + /> + ) + } + // 渲染表单项容器 + return this.renderItemComponent(renderData) + }) + : [] + })} + + ) + } +} diff --git a/src/components/formFields/importSubform/index.tsx b/src/components/formFields/importSubform/index.tsx index e16aa6796388468359965d3002bed8310e4831d2..f2e7feda25c1b4d7e9fa1a9a18ce8d68d9fdf828 100644 --- a/src/components/formFields/importSubform/index.tsx +++ b/src/components/formFields/importSubform/index.tsx @@ -3,7 +3,7 @@ import { isEqual } from 'lodash' import { getValue, getBoolean, getChainPath } from '../../../util/value' import { Field, FieldConfig, FieldError, FieldProps, IField } from '../common' -import getALLComponents, { FieldConfigs } from '../' +import getALLComponents, { FieldConfigs } from '..' import { IFormItem } from '../../../steps/form' import { set, setValue } from '../../../util/produce' import ConditionHelper from '../../../util/condition' @@ -24,7 +24,7 @@ import { ColumnsConfig } from '../../../interface' * - * - configField: (序列化)配置 */ export interface ImportSubformFieldConfig extends FieldConfig { - type: 'import_subform', + type: 'import_subform' interface?: InterfaceConfig configFrom?: ImportSubformConfigFromData | ImportSubformConfigFromInterface withConfig?: { @@ -52,22 +52,28 @@ export interface IImportSubformField { interface IImportSubformFieldState { didMount: boolean fields: FieldConfigs[] - formData: { status: 'normal' | 'error' | 'loading', message?: string }[] + formData: { status: 'normal' | 'error' | 'loading'; message?: string }[] } -export default class ImportSubformField extends Field implements IField { +export default class ImportSubformField + extends Field + implements IField +{ // 各表单项对应的类型所使用的UI组件的类 getALLComponents = (type: any): typeof Field => getALLComponents[type] // 用于请求防频的判断条件 - requestConfig: string = '' - value: string = '' + requestConfig = '' + + value = '' formFields: Array | null> = [] + formFieldsMounted: Array = [] + interfaceHelper = new InterfaceHelper() - constructor (props: FieldProps) { + constructor(props: FieldProps) { super(props) this.state = { @@ -89,19 +95,37 @@ export default class ImportSubformField extends Field => { const errors: FieldError[] = [] let childrenError = 0 - const childrenErrorMsg: Array<{name:string, msg:string}> = [] + const childrenErrorMsg: Array<{ name: string; msg: string }> = [] - let formData = this.state.formData + let { formData } = this.state - for (const fieldIndex in (this.state.fields || [])) { + for (const fieldIndex in this.state.fields || []) { const formItem = this.formFields[fieldIndex] - const formConfig = this.state.fields?.[fieldIndex] - if (formItem !== null && formItem !== undefined) { - const withConfigPath = this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField ? `${this.props.config.withConfig.dataField}` : '' - const validation = await formItem.validate(getValue(value, getChainPath(withConfigPath, (this.state.fields || [])[fieldIndex].field))) + const formConfig = this.state.fields?.[fieldIndex] + if (formItem !== null && formItem !== undefined) { + const withConfigPath = + this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField + ? `${this.props.config.withConfig.dataField}` + : '' + const validation = await formItem.validate( + getValue(value, getChainPath(withConfigPath, (this.state.fields || [])[fieldIndex].field)) + ) if (validation === true || this.formFieldsMounted[fieldIndex] === false) { formData = set(formData, `[${fieldIndex}]`, { status: 'normal' }) @@ -154,7 +182,9 @@ export default class ImportSubformField extends Field 0) { - const errTips = `${this.props.config.label || ''}子项中错误。\n ${childrenErrorMsg.map(err => `${err.name}:${err.msg}`).join('; ')}。` + const errTips = `${this.props.config.label || ''}子项中错误。\n ${childrenErrorMsg + .map((err) => `${err.name}:${err.msg}`) + .join('; ')}。` errors.push(new FieldError(errTips)) } @@ -171,12 +201,15 @@ export default class ImportSubformField extends Field { // const formField = this.formFields[formFieldIndex] // const formFieldConfig = this.state.fields[formFieldIndex] - // const formData = cloneDeep(this.state.formData) - // if (formField && formFieldConfig) { // if (this.props.onChange) { // if (formFieldConfig.field === '') { @@ -208,14 +239,12 @@ export default class ImportSubformField extends Field { - let formData = this.state.formData + let { formData } = this.state if (validation === true) { formData = set(formData, `[${formFieldIndex}]`, { status: 'normal' }) } else { @@ -237,55 +266,106 @@ export default class ImportSubformField extends Field { + handleValueSet = async ( + formFieldIndex: number, + path: string, + value: any, + validation: true | FieldError[], + options?: { noPathCombination?: boolean } + ) => { const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const withConfigPath = this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField ? `${this.props.config.withConfig.dataField}` : '' - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const withConfigPath = + this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField + ? `${this.props.config.withConfig.dataField}` + : '' + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueSet(fullPath, value, true) this.handleValueCallback(formFieldIndex, validation) } } - handleValueUnset = async (formFieldIndex: number, path: string, validation: true | FieldError[], options?: { noPathCombination?: boolean }) => { + handleValueUnset = async ( + formFieldIndex: number, + path: string, + validation: true | FieldError[], + options?: { noPathCombination?: boolean } + ) => { const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const withConfigPath = this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField ? `${this.props.config.withConfig.dataField}` : '' - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const withConfigPath = + this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField + ? `${this.props.config.withConfig.dataField}` + : '' + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueUnset(fullPath, true) this.handleValueCallback(formFieldIndex, validation) } } - handleValueListAppend = async (formFieldIndex: number, path: string, value: any, validation: true | FieldError[], options?: { noPathCombination?: boolean }) => { + handleValueListAppend = async ( + formFieldIndex: number, + path: string, + value: any, + validation: true | FieldError[], + options?: { noPathCombination?: boolean } + ) => { const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const withConfigPath = this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField ? `${this.props.config.withConfig.dataField}` : '' - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const withConfigPath = + this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField + ? `${this.props.config.withConfig.dataField}` + : '' + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueListAppend(fullPath, value, true) this.handleValueCallback(formFieldIndex, validation) } } - handleValueListSplice = async (formFieldIndex: number, path: string, index: number, count: number, validation: true | FieldError[], options?: { noPathCombination?: boolean }) => { + handleValueListSplice = async ( + formFieldIndex: number, + path: string, + index: number, + count: number, + validation: true | FieldError[], + options?: { noPathCombination?: boolean } + ) => { const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const withConfigPath = this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField ? `${this.props.config.withConfig.dataField}` : '' - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const withConfigPath = + this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField + ? `${this.props.config.withConfig.dataField}` + : '' + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueListSplice(fullPath, index, count, true) this.handleValueCallback(formFieldIndex, validation) } } - handleValueListSort = async (formFieldIndex: number, path: string, index: number, sortType: 'up' | 'down', validation: true | FieldError[], options?: { noPathCombination?: boolean }) => { + handleValueListSort = async ( + formFieldIndex: number, + path: string, + index: number, + sortType: 'up' | 'down', + validation: true | FieldError[], + options?: { noPathCombination?: boolean } + ) => { const formFieldConfig = (this.state.fields || [])[formFieldIndex] if (formFieldConfig) { - const withConfigPath = this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField ? `${this.props.config.withConfig.dataField}` : '' - const fullPath = options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) + const withConfigPath = + this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField + ? `${this.props.config.withConfig.dataField}` + : '' + const fullPath = + options && options.noPathCombination ? path : getChainPath(withConfigPath, formFieldConfig.field, path) await this.props.onValueListSort(fullPath, index, sortType, true) this.handleValueCallback(formFieldIndex, validation) @@ -311,9 +391,7 @@ export default class ImportSubformField extends Field { - return - 您当前使用的UI版本没有实现ImportSubformField组件。 - + return <>您当前使用的UI版本没有实现ImportSubformField组件。 } /** @@ -321,22 +399,13 @@ export default class ImportSubformField extends Field { - return - 您当前使用的UI版本没有实现FormItem组件。 - - } + renderItemComponent = (props: IFormItem) => { + return <>您当前使用的UI版本没有实现FormItem组件。 + } - render = () => { - const { - config, - formLayout, - value, - data, - step - } = this.props - - let fields = this.state.fields + getConfigData = () => { + const { config, value } = this.props + let { fields } = this.state let interfaceConfig: InterfaceConfig | undefined if (config.configFrom) { if (config.configFrom.type === 'interface') { @@ -357,118 +426,162 @@ export default class ImportSubformField extends Field { - const dataToUnstringfy = this.handleDataToUnstringfy(data) - if (this.props.config.withConfig?.enable && this.props.config.withConfig?.configField) this.props.onValueSet(this.props.config.withConfig.configField, data, true) - if (!isEqual(dataToUnstringfy, this.state.fields)) { - this.setState({ - fields: dataToUnstringfy - }) - } - }) + this.interfaceHelper + .request( + interfaceConfig, + {}, + { record: this.props.record, data: this.props.data, step: this.props.step }, + { loadDomain: this.props.loadDomain }, + this + ) + .then((data: any) => { + const dataToUnstringfy = this.handleDataToUnstringfy(data) + if (this.props.config.withConfig?.enable && this.props.config.withConfig?.configField) + this.props.onValueSet(this.props.config.withConfig.configField, data, true) + if (!isEqual(dataToUnstringfy, this.state.fields)) { + this.setState({ + fields: dataToUnstringfy + }) + } + }) } + } + + componentDidMount() { + this.getConfigData() + } + + componentDidUpdate() { + this.getConfigData() + } + + render = () => { + const { config, formLayout, value, data, step } = this.props + + const { fields } = this.state if (!fields || !Array.isArray(fields) || fields.length === 0) { - return - } else { - const withConfigPath = this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField ? `${this.props.config.withConfig.dataField}` : '' - return ( - - {this.renderComponent({ - columns: config.columns, - children: this.state.didMount - ? (Array.isArray(this.state.fields) ? this.state.fields : []).map((formFieldConfig, formFieldIndex) => { - if (!ConditionHelper(formFieldConfig.condition, { record: value, data, step, extraContainerPath: this.props.config.field }, this)) { - this.formFieldsMounted = set(this.formFieldsMounted, `[${formFieldIndex}]`, false) - this.formFields && (this.formFields[formFieldIndex] = null) - return null - } - let hidden: boolean = true - let display: boolean = true - - if (formFieldConfig.type === 'hidden') { - hidden = true - display = false - } - - if (formFieldConfig.display === 'none') { - hidden = true - display = false - } - - const FormField = this.getALLComponents(formFieldConfig.type) || Field - - let status = (this.state.formData[formFieldIndex] || {}).status || 'normal' - - if (['group', 'import_subform', 'object', 'tabs', 'form'].some((type) => type === formFieldConfig.type)) { - status = 'normal' - } - - const renderData = { - key: formFieldIndex, - label: formFieldConfig.label, - status, - columns: config.columns?.enable - ? { - type: formFieldConfig.columns?.type || config.childColumns?.type || 'span', - value: formFieldConfig.columns?.value || config.childColumns?.value || 1, - wrap: formFieldConfig.columns?.wrap || config.childColumns?.wrap || false, - gap: config.columns?.gap || 0, - rowGap: config.columns?.rowGap || 0 + return <> + } + const withConfigPath = + this.props.config.withConfig?.enable && this.props.config.withConfig?.dataField + ? `${this.props.config.withConfig.dataField}` + : '' + return ( + <> + {this.renderComponent({ + columns: config.columns, + children: this.state.didMount + ? (Array.isArray(this.state.fields) ? this.state.fields : []).map((formFieldConfig, formFieldIndex) => { + if ( + !ConditionHelper( + formFieldConfig.condition, + { record: value, data, step, extraContainerPath: this.props.config.field }, + this + ) + ) { + this.formFieldsMounted = set(this.formFieldsMounted, `[${formFieldIndex}]`, false) + this.formFields && (this.formFields[formFieldIndex] = null) + return null + } + let hidden = true + let display = true + + if (formFieldConfig.type === 'hidden') { + hidden = true + display = false + } + + if (formFieldConfig.display === 'none') { + hidden = true + display = false + } + + const FormField = this.getALLComponents(formFieldConfig.type) || Field + + let status = (this.state.formData[formFieldIndex] || {}).status || 'normal' + + if ( + ['group', 'import_subform', 'object', 'tabs', 'form'].some((type) => type === formFieldConfig.type) + ) { + status = 'normal' + } + + const renderData = { + key: formFieldIndex, + label: formFieldConfig.label, + status, + columns: config.columns?.enable + ? { + type: formFieldConfig.columns?.type || config.childColumns?.type || 'span', + value: formFieldConfig.columns?.value || config.childColumns?.value || 1, + wrap: formFieldConfig.columns?.wrap || config.childColumns?.wrap || false, + gap: config.columns?.gap || 0, + rowGap: config.columns?.rowGap || 0 + } + : undefined, + message: (this.state.formData[formFieldIndex] || {}).message || '', + extra: StatementHelper( + formFieldConfig.extra, + { + record: this.props.value, + data: this.props.data, + step: this.props.step, + extraContainerPath: this.props.config.field + }, + this + ), + required: getBoolean(formFieldConfig.required), + layout: formLayout, + visitable: display, + fieldType: formFieldConfig.type, + children: ( + | null) => { + if (formField) { + this.formFields = set(this.formFields, `[${formFieldIndex}]`, formField) + this.handleMount(formFieldIndex) } - : undefined, - message: (this.state.formData[formFieldIndex] || {}).message || '', - extra: StatementHelper(formFieldConfig.extra, { record: this.props.value, data: this.props.data, step: this.props.step, extraContainerPath: this.props.config.field }, this), - required: getBoolean(formFieldConfig.required), - layout: formLayout, - visitable: display, - fieldType: formFieldConfig.type, - children: ( - | null) => { - if (formField) { - this.formFields = set(this.formFields, `[${formFieldIndex}]`, formField) - this.handleMount(formFieldIndex) - } - }} - form={this.props.form} - formLayout={formLayout} - value={getValue(value, getChainPath(withConfigPath, formFieldConfig.field))} - record={value} - step={this.props.step} - data={data} - config={formFieldConfig} - onChange={async (value: any) => { await this.handleChange(formFieldIndex, value) }} - onValueSet={async (path, value, validation, options) => this.handleValueSet(formFieldIndex, path, value, validation, options)} - onValueUnset={async (path, validation, options) => this.handleValueUnset(formFieldIndex, path, validation, options)} - onValueListAppend={async (path, value, validation, options) => this.handleValueListAppend(formFieldIndex, path, value, validation, options)} - onValueListSplice={async (path, index, count, validation, options) => this.handleValueListSplice(formFieldIndex, path, index, count, validation, options)} - onValueListSort={async (path, index, sortType, validation, options) => this.handleValueListSort(formFieldIndex, path, index, sortType, validation, options)} - baseRoute={this.props.baseRoute} - loadDomain={async (domain: string) => await this.props.loadDomain(domain)} - containerPath={getChainPath(this.props.containerPath, this.props.config.field)} - onReportFields={async (field: string) => await this.handleReportFields(field)} - /> - ) - } - // 渲染表单项容器 - return ( - hidden - ? this.renderItemComponent(renderData) - : + }} + form={this.props.form} + formLayout={formLayout} + value={getValue(value, getChainPath(withConfigPath, formFieldConfig.field))} + record={value} + step={this.props.step} + data={data} + config={formFieldConfig} + onChange={async (value: any) => { + await this.handleChange(formFieldIndex, value) + }} + onValueSet={async (path, value, validation, options) => + this.handleValueSet(formFieldIndex, path, value, validation, options) + } + onValueUnset={async (path, validation, options) => + this.handleValueUnset(formFieldIndex, path, validation, options) + } + onValueListAppend={async (path, value, validation, options) => + this.handleValueListAppend(formFieldIndex, path, value, validation, options) + } + onValueListSplice={async (path, index, count, validation, options) => + this.handleValueListSplice(formFieldIndex, path, index, count, validation, options) + } + onValueListSort={async (path, index, sortType, validation, options) => + this.handleValueListSort(formFieldIndex, path, index, sortType, validation, options) + } + baseRoute={this.props.baseRoute} + loadDomain={async (domain: string) => await this.props.loadDomain(domain)} + containerPath={getChainPath(this.props.containerPath, this.props.config.field)} + onReportFields={async (field: string) => await this.handleReportFields(field)} + /> ) - }) - : [] - })} - - ) - } + } + // 渲染表单项容器 + return hidden ? this.renderItemComponent(renderData) : + }) + : [] + })} + + ) } } diff --git a/src/components/formFields/index.tsx b/src/components/formFields/index.tsx index 5753e10aaede3aa3fb7ad7877a6474331491d276..87b4a202a6ddd08a8ea0205aced6c7f9f38836ad 100644 --- a/src/components/formFields/index.tsx +++ b/src/components/formFields/index.tsx @@ -27,6 +27,7 @@ import CodeField, { CodeFieldConfig } from './code' import DiffCodeField, { DiffCodeFieldConfig } from './diffCode' import TextDisplay from './text/display' +import FormDisplay from './form/display' import RadioDisplay from './radio/display' import ColorDisplay from './color/display' import UploadDisplay from './upload/display' @@ -36,8 +37,12 @@ import DatetimeDisplay from './datetime/display' import DatetimeRangeDisplay from './datetimeRange/display' import SelectSingleDisplay from './select/single/display' import SelectMultipleDisplay from './select/multiple/display' +import ImportSubformDisplay from './importSubform/display' +import GroupDisplay from './group/display' import SwitchDisplay from './switch/display' +import TabsDisplay from './tabs/display' import MultipleTextDisplay from './multipleText/display' +import HiddenDisplay from './hidden/display' export interface HiddenFieldConfig extends FieldConfig { type: 'hidden' | 'none' @@ -132,14 +137,19 @@ export default { export const display = { text: TextDisplay, longtext: LongtextDisplay, + form: FormDisplay, radio: RadioDisplay, color: ColorDisplay, upload: UploadDisplay, + import_subform: ImportSubformDisplay, + group: GroupDisplay, number: NumberDisplay, datetime: DatetimeDisplay, datetimeRange: DatetimeRangeDisplay, select_single: SelectSingleDisplay, select_multiple: SelectMultipleDisplay, switch: SwitchDisplay, - multiple_text: MultipleTextDisplay + tabs: TabsDisplay, + multiple_text: MultipleTextDisplay, + hidden: HiddenDisplay } diff --git a/src/components/formFields/tabs/display.tsx b/src/components/formFields/tabs/display.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0673de1acfe29f474b846265a30214722a1f1bcd --- /dev/null +++ b/src/components/formFields/tabs/display.tsx @@ -0,0 +1,287 @@ +import React from 'react' +import { display as getALLComponents } from '../' +import { TabsFieldConfig } from '.' +import { Display, FieldConfigs, DisplayProps } from '../common' +import ConditionHelper from '../../../util/condition' +import { getValue, setValue, getBoolean } from '../../../util/value' +import { cloneDeep } from 'lodash' + +export interface ITabsField { + children: React.ReactNode[] +} + +export interface ITabsFieldItem { + key: string + label: string + children: React.ReactNode[] +} + +export interface ITabsFieldItemField { + index: number + label: string + required: boolean + status: 'normal' | 'error' | 'loading' + description?: string + message?: string + extra?: string + fieldType: string + children: React.ReactNode +} +export interface TabsFieldState { + didMount: boolean + extra?: S +} + +export default class TabsField extends Display> { + // 各表单项对应的类型所使用的UI组件的类 + getALLComponents = (type: any): typeof Display => getALLComponents[type] + + formFieldsList: Array | null>> = [] + formFieldsMountedList: Array> = [] + + constructor(props: DisplayProps) { + super(props) + + this.state = { + didMount: false + } + } + + didMount = async () => { + await this.setState({ + didMount: true + }) + } + + get = async () => { + let data: any = {} + + for (let index = 0; index < (this.props.config.tabs || []).length; index++) { + const tab = (this.props.config.tabs || [])[index] + const fields = this.props.config.mode === 'same' ? (this.props.config.fields || []) : (((this.props.config.tabs || [])[index] || {}).fields || []) + + for (let formFieldIndex = 0; formFieldIndex < fields.length; formFieldIndex++) { + const formFieldConfig = fields[formFieldIndex] + if (!ConditionHelper(formFieldConfig.condition, { record: getValue(this.props.value, tab.field), data: this.props.data, step: this.props.step })) { + continue + } + const formField = this.formFieldsList[index] && this.formFieldsList[index][formFieldIndex] + if (formField) { + const value = await formField.get() + const fullPath = tab.field === '' || formFieldConfig.field === '' ? `${tab.field}${formFieldConfig.field}` : `${tab.field}.${formFieldConfig.field}` + data = setValue(data, fullPath, value) + } + } + } + + return data + } + + handleMount = async (index: number, formFieldIndex: number) => { + if (!this.formFieldsMountedList[index]) { + this.formFieldsMountedList[index] = [] + } + if (this.formFieldsMountedList[index][formFieldIndex]) { + return true + } + this.formFieldsMountedList[index][formFieldIndex] = true + + const tab = (this.props.config.tabs || [])[index] + + if (this.formFieldsList[index] && this.formFieldsList[index][formFieldIndex]) { + const formField = this.formFieldsList[index][formFieldIndex] + if (formField) { + const fields = this.props.config.mode === 'same' ? (this.props.config.fields || []) : (((this.props.config.tabs || [])[index] || {}).fields || []) + const formFieldConfig = fields[formFieldIndex] + + const fullPath = tab.field === '' || formFieldConfig.field === '' ? `${tab.field}${formFieldConfig.field}` : `${tab.field}.${formFieldConfig.field}` + + let value = getValue(this.props.value, fullPath) + const source = value + if ((formFieldConfig.defaultValue) && value === undefined) { + value = await formField.reset() + } + value = await formField.set(value) + if (source !== value) { + this.props.onValueSet(fullPath, value) + } + + await formField.didMount() + } + } + } + + handleChange = async (index: number, formFieldIndex: number, value: any) => { + } + + handleValueSet = async (index: number, formFieldIndex: number, path: string, value: any, options?: { noPathCombination?: boolean }) => { + const tab = (this.props.config.tabs || [])[index] + + const fields = this.props.config.mode === 'same' ? (this.props.config.fields || []) : (((this.props.config.tabs || [])[index] || {}).fields || []) + const formFieldConfig = fields[formFieldIndex] + if (formFieldConfig) { + const fieldPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + const fullPath = tab.field === '' || fieldPath === '' ? `${tab.field}${fieldPath}` : `${tab.field}.${fieldPath}` + await this.props.onValueSet(fullPath, value) + } + } + + handleValueUnset = async (index: number, formFieldIndex: number, path: string, options?: { noPathCombination?: boolean }) => { + const tab = (this.props.config.tabs || [])[index] + + const fields = this.props.config.mode === 'same' ? (this.props.config.fields || []) : (((this.props.config.tabs || [])[index] || {}).fields || []) + const formFieldConfig = fields[formFieldIndex] + if (formFieldConfig) { + const fieldPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + const fullPath = tab.field === '' || fieldPath === '' ? `${tab.field}${fieldPath}` : `${tab.field}.${fieldPath}` + await this.props.onValueUnset(fullPath) + } + } + + handleValueListAppend = async (index: number, formFieldIndex: number, path: string, value: any, options?: { noPathCombination?: boolean }) => { + const tab = (this.props.config.tabs || [])[index] + + const fields = this.props.config.mode === 'same' ? (this.props.config.fields || []) : (((this.props.config.tabs || [])[index] || {}).fields || []) + const formFieldConfig = fields[formFieldIndex] + if (formFieldConfig) { + const fieldPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + const fullPath = tab.field === '' || fieldPath === '' ? `${tab.field}${fieldPath}` : `${tab.field}.${fieldPath}` + await this.props.onValueListAppend(fullPath, value) + } + } + + handleValueListSplice = async (index: number, formFieldIndex: number, path: string, _index: number, count: number, options?: { noPathCombination?: boolean }) => { + const tab = (this.props.config.tabs || [])[index] + + const fields = this.props.config.mode === 'same' ? (this.props.config.fields || []) : (((this.props.config.tabs || [])[index] || {}).fields || []) + const formFieldConfig = fields[formFieldIndex] + if (formFieldConfig) { + const fieldPath = options && options.noPathCombination ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + const fullPath = tab.field === '' || fieldPath === '' ? `${tab.field}${fieldPath}` : `${tab.field}.${fieldPath}` + await this.props.onValueListSplice(fullPath, _index, count) + } + } + + /** + * 用于展示子表单组件 + * @param _props + * @returns + */ + renderComponent = (_props: ITabsField) => { + return + 您当前使用的UI版本没有实现FormField组件。 + + } + + /** + * 用于展示子表单组件中的每一个子项 + * @param props + * @returns + */ + renderItemComponent = (props: ITabsFieldItem) => { + return + 您当前使用的UI版本没有实现FormField组件的renderItemComponent方法。 + + } + + /** + * 用于展示子表单组件中的每一子项中的每一个子表单项组件 + * @param props + * @returns + */ + renderItemFieldComponent = (props: ITabsFieldItemField) => { + return + 您当前使用的UI版本没有实现FormField组件的renderItemFieldComponent方法。 + + } + + render = () => { + const { + value = {} + } = this.props + + return ( + + { + this.renderComponent({ + children: ( + this.state.didMount + ? (this.props.config.tabs || []).map((tab: any, index: number) => { + const fields = this.props.config.mode === 'same' ? (this.props.config.fields || []) : (((this.props.config.tabs || [])[index] || {}).fields || []) + return ( + + {this.renderItemComponent({ + key: index.toString(), + label: tab.label, + children: fields.map((formFieldConfig, formFieldIndex) => { + if (!ConditionHelper(formFieldConfig.condition, { record: this.props.record, data: this.props.data, step: this.props.step })) { + if (!this.formFieldsMountedList[index]) this.formFieldsMountedList[index] = [] + this.formFieldsMountedList[index][formFieldIndex] = false + return null + } + let hidden: boolean = true + let display: boolean = true + + if (formFieldConfig.type === 'hidden') { + hidden = true + display = false + } + + if (formFieldConfig.display === 'none') { + hidden = true + display = false + } + + const FormField = this.getALLComponents(formFieldConfig.type) || Display + + let status: 'normal' = 'normal' + // 渲染表单项容器 + if (hidden) { + return ( +
+ {this.renderItemFieldComponent({ + index: formFieldIndex, + label: formFieldConfig.label, + status, + required: getBoolean(formFieldConfig.required), + fieldType: formFieldConfig.type, + children: ( + | null) => { + if (!this.formFieldsList[index]) this.formFieldsList[index] = [] + this.formFieldsList[index][formFieldIndex] = formField + this.handleMount(index, formFieldIndex) + }} + value={getValue(getValue(value, tab.field), formFieldConfig.field)} + record={getValue(value, tab.field)} + data={cloneDeep(this.props.data)} + step={this.props.step} + config={formFieldConfig} + onValueSet={async (path, value, validation) => this.handleValueSet(index, formFieldIndex, path, value, validation)} + onValueUnset={async (path, validation) => this.handleValueUnset(index, formFieldIndex, path, validation)} + onValueListAppend={async (path, value, validation) => this.handleValueListAppend(index, formFieldIndex, path, value, validation)} + onValueListSplice={async (path, _index, count, validation) => this.handleValueListSplice(index, formFieldIndex, path, _index, count, validation)} + baseRoute={this.props.baseRoute} + loadDomain={async (domain: string) => await this.props.loadDomain(domain)} + /> + ) + })} +
+ ) + } else { + return + } + }) + })} + + ) + }) + : [] + ) + }) + } +
+ ) + } +} diff --git a/src/index.tsx b/src/index.tsx index e66ae3d778d7051a2016ed4b2affad561deb6bc9..c25627c0a2d3701babd80ffb2cf5c4a04f2806d8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -30,6 +30,7 @@ export { default as CodeField } from './components/formFields/code' export { default as DiffCodeField } from './components/formFields/diffCode' export { default as TextDisplay } from './components/formFields/text/display' +export { default as FormDisplay } from './components/formFields/form/display' export { default as LongTextDisplay } from './components/formFields/longtext/display' export { default as RadioDisplay } from './components/formFields/radio/display' export { default as ColorDisplay } from './components/formFields/color/display' @@ -40,7 +41,11 @@ export { default as DatetimeDisplay } from './components/formFields/datetime/dis export { default as DatetimeRangeDisplay } from './components/formFields/datetimeRange/display' export { default as SelectSingleDisplay } from './components/formFields/select/single/display' export { default as SelectMultipleDisplay } from './components/formFields/select/multiple/display' +export { default as ImportSubformDisplay } from './components/formFields/importSubform/display' +export { default as GroupDisplay } from './components/formFields/group/display' +export { default as TabsDisplay } from './components/formFields/tabs/display' export { default as MultipleTextDisplay } from './components/formFields/multipleText/display' +export { default as HiddenDisplay } from './components/formFields/hidden/display' export { default as TableStep } from './steps/table' export { default as TextColumn } from './components/tableColumns/text' diff --git a/src/util/produce.tsx b/src/util/produce.tsx index 2d97d10bb473e2d9ab5a20ac2e851afcb6e1b190..60b9ff1e6faf60b6359e0ea61d9867cbde1a70b4 100644 --- a/src/util/produce.tsx +++ b/src/util/produce.tsx @@ -1,7 +1,6 @@ import produce, { setAutoFreeze } from 'immer' import lodash from 'lodash' -import { listItemMove } from '../util/value' - +import { listItemMove } from './value' /** * setAutoFreeze @@ -21,7 +20,8 @@ setAutoFreeze(false) export function set(current: any, path?: string, value?: any) { const target = produce(current, (draft: any) => { if (path) { - if (arguments.length === 2) { // 移除对象路径的属性 参数改动时同步修改这块 + if (arguments.length === 2) { + // 移除对象路径的属性 参数改动时同步修改这块 lodash.unset(draft, path) } else { return lodash.set(draft, path, value) @@ -38,11 +38,12 @@ export function set(current: any, path?: string, value?: any) { * @param value * @returns */ -export const push = (current: any, path: string = '', value?: any) => { +export const push = (current: any, path = '', value?: any) => { const target = produce(current, (draft: any) => { const list = lodash.get(draft, path) - if (!Array.isArray(list)) { // 如果指定路径下不是数组类型 - var tempArr = [] + if (!Array.isArray(list)) { + // 如果指定路径下不是数组类型 + const tempArr: any[] = [] tempArr.push(value) lodash.set(draft, path, tempArr) } else { @@ -60,7 +61,7 @@ export const push = (current: any, path: string = '', value?: any) => { * @param count * @returns */ -export const splice = (current: any, path: string = '', index: number, count: number) => { +export const splice = (current: any, path = '', index: number, count: number) => { const target = produce(current, (draft: any) => { const list = lodash.get(draft, path, []) list.splice(index, count) @@ -76,7 +77,7 @@ export const splice = (current: any, path: string = '', index: number, count: nu * @param sortType * @returns */ -export const sort = (current: any, path: string = '', index: number, sortType: 'up' | 'down') => { +export const sort = (current: any, path = '', index: number, sortType: 'up' | 'down') => { const target = produce(current, (draft: any) => { const list = lodash.get(draft, path, []) listItemMove(list, index, sortType) @@ -98,14 +99,13 @@ const merge = (a: any, b: any): any => { if (lodash.isObject(b)) { if (lodash.isArray(a)) { return merge(a, b).filter((i: any) => i !== undefined) - } else { - return merge(a, b) } + return merge(a, b) } }) } -export const setValue = (obj: any, path: string = '', value: any) => { +export const setValue = (obj: any, path = '', value: any) => { const target = produce(obj, (draft: any) => { if (path === '') { if (Object.prototype.toString.call(value) === '[object Object]') { @@ -115,7 +115,10 @@ export const setValue = (obj: any, path: string = '', value: any) => { } } else { const source = lodash.get(draft, path) - if (Object.prototype.toString.call(value) === '[object Object]' && Object.prototype.toString.call(source) === '[object Object]') { + if ( + Object.prototype.toString.call(value) === '[object Object]' && + Object.prototype.toString.call(source) === '[object Object]' + ) { lodash.set(draft, path, merge(source, value)) } else { lodash.set(draft, path, value) diff --git a/src/util/value.ts b/src/util/value.ts index 6a2eef46e161b32ef003639274b4d99b07c33a0f..6cf1f158fa24065737235ce7d57985742bb8aa85 100644 --- a/src/util/value.ts +++ b/src/util/value.ts @@ -134,8 +134,8 @@ export const getChainPath = (...arg: any[]) => { * @param find 目标字符串 * @returns 返回目标字符串出现在来源字符串中所有索引 */ -function indexes (source: string, find: string) { - const result = [] +function indexes(source: string, find: string) { + const result: number[] = [] for (let i = 0; i < source.length; ++i) { if (source.substring(i, i + find.length) === find) { result.push(i) diff --git a/tsconfig.json b/tsconfig.json index 95deb7c62079b44b4486d3f93e7bcde5b9509741..823189508981ca46123d85443ef09047ac7fb540 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,26 @@ { "compilerOptions": { - "target": "es6", - "module": "esnext", - "jsx": "react", + "target": "ES6", + "module": "ESNext", + "allowJs": true, "sourceMap": true, "outDir": "./lib/", - "strict": true, + "moduleResolution": "node", "esModuleInterop": true, - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, + "declaration": true, + "jsx": "react", + "strict": true, + "lib": ["dom", "dom.iterable", "esnext"], "skipLibCheck": true, "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "noImplicitAny": true, + "noImplicitAny": false, "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, - "types": [ - "node", - "jest" - ], + "types": ["node", "jest"], "experimentalDecorators": true } -} \ No newline at end of file +}