From d0488c0edd92d6eabc6ead5e179b327d670dc7c3 Mon Sep 17 00:00:00 2001 From: cuiwenlong7 Date: Fri, 5 Nov 2021 18:35:42 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E8=AF=A6=E6=83=85=E6=AD=A5?= =?UTF-8?q?=E9=AA=A4=E9=85=8D=E7=BD=AE=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/detail/common.tsx | 187 +++++++++++ src/components/detail/group/index.tsx | 285 +++++++++++++++++ src/components/detail/index.tsx | 22 ++ src/components/detail/text/index.tsx | 36 +++ src/index.tsx | 3 + src/steps/detail/index.tsx | 436 ++++++++++++++++++++++++++ src/steps/index.tsx | 6 +- 7 files changed, 973 insertions(+), 2 deletions(-) create mode 100644 src/components/detail/common.tsx create mode 100644 src/components/detail/group/index.tsx create mode 100644 src/components/detail/index.tsx create mode 100644 src/components/detail/text/index.tsx create mode 100644 src/steps/detail/index.tsx diff --git a/src/components/detail/common.tsx b/src/components/detail/common.tsx new file mode 100644 index 0000000..e7cb4fd --- /dev/null +++ b/src/components/detail/common.tsx @@ -0,0 +1,187 @@ +import React from 'react' +import { ParamConfig } from '../../interface' + +import { DetailFieldConfigs as getFieldConfigs } from './' +import ParamHelper from '../../util/param' +/** + * 详情页表单项基类配置文件格式定义 + * - field: 表单项字段名 + * - label: 表单项名称 + * - required: 表单项必填 + * - readonly: 表单项只读 + * - disabled: 表单项不可编辑 + * - default: 表单项默认值 // 改为defaultValue + * - - type: 默认值类型 + * - - * static: 固定值 + * - - * data: 上一步骤数据 + * - - * query: 页面GET方法传参 + * - - * hash: 页面HASH传参 + * - - * interface: 接口入参获取 + * - - value: 默认值(static类型使用) + * - - field: 字段名(data/query/hash类型使用)(hash类型选填) + */ +export interface DetailFieldConfig { + field: string + label: string + required?: boolean + readonly?: boolean + disabled?: boolean + display?: 'none' + defaultValue?: ParamConfig, + condition?: DetailFieldConditionConfig + // styles?: object +} + +export interface DetailFieldConditionConfig { + statement?: string + params?: Array<{ + field?: string + data?: ParamConfig + }> + debug?: boolean +} + +/** + * 详情页表单项配置文件格式定义 - 枚举 + */ +export type DetailFieldConfigs = getFieldConfigs + +/** + * 详情页表单项子类需实现的方法 + * - reset: 表单项重置当前值 + * - set: 表单项设置当前值 + * - get: 表单项获取当前值 + * - validate: 表单项的值校验方法 + */ +export interface IDetailField { + reset: () => Promise + set: (value: T) => Promise + get: () => Promise + validate: (value: T) => Promise + fieldFormat: () => Promise<{}> +} + +/** + * 详情页表单项子类需要的入参 + * - ref: + * - formLayout: + * - value: + * - data: + * - step: + * - config: + * - onChange: + */ +export interface DetailFieldProps { + // 挂载事件 + ref: (instance: DetailField | null) => void + formLayout: 'horizontal' | 'vertical' | 'inline' + value: T, + record: { [field: string]: any }, + data: any[], + step: number, + config: C + // TODO 待删除 + onChange: (value: T) => Promise + // 事件:设置值 + onValueSet: (path: string, value: T, validation: true | DetailFieldError[]) => Promise + // // 事件:置空值 + onValueUnset: (path: string, validation: true | DetailFieldError[]) => Promise + // 事件:修改值 - 列表 - 追加 + onValueListAppend: (path: string, value: any, validation: true | DetailFieldError[]) => Promise + // 事件:修改值 - 列表 - 删除 + onValueListSplice: (path: string, index: number, count: number, validation: true | DetailFieldError[]) => Promise + loadDomain: (domain: string) => Promise +} + +/** + * 详情页配置接口获取数据需要的入参 +* - url: 请求地址 +* - method: 请求类型 +* - withCredentials?: 跨域是否提供凭据信息 +* - response: 返回值 +* - format?: 格式化返回值 +* - responseArrayKey?: format === 'array' 时配置 key 值 +* - responseArrayValue?: format === 'array' 时配置 value 值 + */ +export interface DetailFieldInterface { + interface?: { + url: string + method: 'GET' | 'POST' | 'get' | 'post' + withCredentials?: boolean + response: string + format?: 'array' | 'key' + responseArrayKey?: string + responseArrayValue?: string + } +} + +/** + * 详情项基类 + * - C: 表单项的配置文件类型 + * - E: 表单项的渲染方法入参 + * - T: 表单项的值类型 + * - S: 表单项的扩展状态 + */ +export class DetailField extends React.Component, S> implements IDetailField { + static defaultProps = { + config: {} + }; + + /** + * 获取默认值 + */ + defaultValue = async () => { + const { + config + } = this.props + if (config.defaultValue !== undefined) { + return ParamHelper(config.defaultValue, { record: this.props.record, data: this.props.data, step: this.props.step }) + } + + return undefined + } + + reset: () => Promise = async () => { + return this.defaultValue() + }; + + set: (value: T) => Promise = async (value) => { + const { + onChange + } = this.props + if (onChange) { + onChange(value) + } + }; + + get: () => Promise = async () => { + return this.props.value + } + + validate: (value: T) => Promise = async () => { + return true + }; + + fieldFormat: () => Promise<{}> = async () => { + return {} + } + + renderComponent = (props: E) => { + return + 当前UI库未实现该表单类型 + + } + + render = () => { + return ( + 当前UI库未实现该表单类型 + ) + } +} + +export class DetailFieldError { + message: string + constructor(message: string) { + this.message = message + } +} diff --git a/src/components/detail/group/index.tsx b/src/components/detail/group/index.tsx new file mode 100644 index 0000000..89b8800 --- /dev/null +++ b/src/components/detail/group/index.tsx @@ -0,0 +1,285 @@ +import React from 'react' +import { cloneDeep } from 'lodash' +import { setValue, getValue } from '../../../util/value' +import { DetailField, DetailFieldConfig, DetailFieldError, DetailFieldProps, IDetailField } from '../common' +import getALLComponents, { DetailFieldConfigs } from '../' +import { IFormItem } from '../../../steps/detail' +import ConditionHelper from '../../../util/condition' + +export interface GroupFieldConfig extends DetailFieldConfig { + type: 'group' + fields: DetailFieldConfigs[] +} + +export interface IGroupField { + children: React.ReactNode[] +} + +interface IGroupFieldState { + formData: { status: 'normal' | 'error' | 'loading', message?: string }[] +} + +export default class GroupField extends DetailField implements IDetailField { + // 各表单项对应的类型所使用的UI组件的类 + getALLComponents = (type: any): typeof DetailField => getALLComponents[type] + + formFields: Array | null> = [] + formFieldsMounted: Array = [] + + constructor (props: DetailFieldProps) { + super(props) + + this.state = { + formData: [] + } + } + + get = async () => { + let data: 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, 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) + if ((formFieldConfig.defaultValue) && value === undefined) { + value = await formField.reset() + this.props.onValueSet(formFieldConfig.field, value, true) + } + + const validation = await formField.validate(value) + if (value === undefined || validation === true) { + await this.setState(({ formData }) => { + formData[formFieldIndex] = { status: 'normal' } + return { formData: cloneDeep(formData) } + }) + } else { + await this.setState(({ formData }) => { + formData[formFieldIndex] = { status: 'error', message: validation[0].message } + return { formData: cloneDeep(formData) } + }) + } + } + } + } + + handleChange = async (formFieldIndex: number, value: any) => { + // const formField = this.formFields[formFieldIndex] + // const formFieldConfig = this.props.config.fields[formFieldIndex] + + // const formData = cloneDeep(this.state.formData) + + // if (formField && formFieldConfig) { + // if (this.props.onChange) { + // if (formFieldConfig.field === '') { + // await this.props.onChange(value) + // } else { + // const changeValue = setValue({}, formFieldConfig.field, value) + // await this.props.onChange(changeValue) + // } + // } + + // const validation = await formField.validate(value) + // if (validation === true) { + // formData[formFieldIndex] = { value, status: 'normal' } + // } else { + // formData[formFieldIndex] = { value, status: 'error', message: validation[0].message } + // } + + // await this.setState({ + // formData + // }) + // } + } + + handleValueSet = async (formFieldIndex: number, path: string, value: any, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueSet(fullPath, value, true) + + const formData = cloneDeep(this.state.formData) + if (validation === true) { + formData[formFieldIndex] = { status: 'normal' } + } else { + formData[formFieldIndex] = { status: 'error', message: validation[0].message } + } + + this.setState({ + formData + }) + } + } + + handleValueUnset = async (formFieldIndex: number, path: string, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueUnset(fullPath, true) + + const formData = cloneDeep(this.state.formData) + if (validation === true) { + formData[formFieldIndex] = { status: 'normal' } + } else { + formData[formFieldIndex] = { status: 'error', message: validation[0].message } + } + + this.setState({ + formData + }) + } + } + + handleValueListAppend = async (formFieldIndex: number, path: string, value: any, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueListAppend(fullPath, value, true) + + const formData = cloneDeep(this.state.formData) + if (validation === true) { + formData[formFieldIndex] = { status: 'normal' } + } else { + formData[formFieldIndex] = { status: 'error', message: validation[0].message } + } + + this.setState({ + formData + }) + } + } + + handleValueListSplice = async (formFieldIndex: number, path: string, index: number, count: number, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + await this.props.onValueListSplice(fullPath, index, count, true) + + const formData = cloneDeep(this.state.formData) + if (validation === true) { + formData[formFieldIndex] = { status: 'normal' } + } else { + formData[formFieldIndex] = { status: 'error', message: validation[0].message } + } + + this.setState({ + formData + }) + } + } + + renderComponent = (props: IGroupField) => { + return + 您当前使用的UI版本没有实现GroupField组件。 + + } + + /** + * 表单项组件 - UI渲染方法 + * 各UI库需重写该方法 + * @param props + */ + renderItemComponent = (props: IFormItem) => { + return + 您当前使用的UI版本没有实现FormItem组件。 + + } + + render = () => { + const { + config, + formLayout, + value, + record, + data, + step + } = this.props + + return ( + + {this.renderComponent({ + children: (this.props.config.fields || []).map((formFieldConfig, formFieldIndex) => { + if (!ConditionHelper(formFieldConfig.condition, { record: value, data: this.props.data, step: this.props.step })) { + 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) || DetailField + + const renderData = { + label: formFieldConfig.label, + layout: formLayout, + visitable: display, + fieldType: formFieldConfig.type, + children: ( + | null) => { + if (formFieldIndex !== null) { + this.formFields[formFieldIndex] = formField + this.handleMount(formFieldIndex) + } + }} + formLayout={formLayout} + value={getValue(value, formFieldConfig.field)} + record={record} + data={cloneDeep(data)} + step={step} + config={formFieldConfig} + onChange={async (value: any) => { await this.handleChange(formFieldIndex, value) }} + onValueSet={async (path, value, validation) => this.handleValueSet(formFieldIndex, path, value, validation)} + onValueUnset={async (path, validation) => this.handleValueUnset(formFieldIndex, path, validation)} + onValueListAppend={async (path, value, validation) => this.handleValueListAppend(formFieldIndex, path, value, validation)} + onValueListSplice={async (path, index, count, validation) => this.handleValueListSplice(formFieldIndex, path, index, count, validation)} + loadDomain={async (domain: string) => await this.props.loadDomain(domain)} + /> + ) + } + // 渲染表单项容器 + return ( + hidden + ? this.renderItemComponent(renderData) + : + ) + }) + })} + + ) + } +} diff --git a/src/components/detail/index.tsx b/src/components/detail/index.tsx new file mode 100644 index 0000000..66e8fb4 --- /dev/null +++ b/src/components/detail/index.tsx @@ -0,0 +1,22 @@ + +import TextField, { TextFieldConfig } from './text' + +import { DetailFieldConfig } from './common' +import GroupField, { GroupFieldConfig } from './group' + + +/** + * 详情步骤内详情项配置文件格式定义 - 枚举 + */ +export type DetailFieldConfigs = + TextFieldConfig | + GroupFieldConfig + +export type componentType = + 'text' | + 'group' + +export default { + group: GroupField, + text: TextField +} diff --git a/src/components/detail/text/index.tsx b/src/components/detail/text/index.tsx new file mode 100644 index 0000000..886c414 --- /dev/null +++ b/src/components/detail/text/index.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import { getBoolean } from '../../../util/value' +import { DetailField, DetailFieldConfig, DetailFieldError, IDetailField } from '../common' + +export interface TextFieldConfig extends DetailFieldConfig { + type: 'text' +} + +export interface ITextField { + value: string +} + +export default class TextField extends DetailField implements IDetailField { + + renderComponent = (props: ITextField) => { + return + 您当前使用的UI版本没有实现Text组件。 +
+
+
+ } + + render = () => { + const { + value + } = this.props + + return ( + + {this.renderComponent({ + value + })} + + ) + } +} diff --git a/src/index.tsx b/src/index.tsx index d51d836..a8e8a57 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -37,5 +37,8 @@ export { default as DatetimeRangeColumn } from './components/tableColumns/dateti export { default as ImageColumn } from './components/tableColumns/image' export { default as FetchStep } from './steps/fetch' +export { default as DetailStep } from './steps/detail' +export { default as DetailGroupField } from './components/detail/group' +export { default as DetailTextField } from './components/detail/text' export { default as InterfaceHelper } from "./util/interface" \ No newline at end of file diff --git a/src/steps/detail/index.tsx b/src/steps/detail/index.tsx new file mode 100644 index 0000000..df57cdd --- /dev/null +++ b/src/steps/detail/index.tsx @@ -0,0 +1,436 @@ +import React from 'react' +import { DetailField, DetailFieldConfigs, DetailFieldError } from '../../components/detail/common' +import Step, { StepConfig, StepProps } from '../common' +import getALLComponents from '../../components/detail' +import { getValue, setValue } from '../../util/value' +import { ParamConfig } from '../../interface' +import ParamHelper from '../../util/param' +import { cloneDeep, get, set, unset } from 'lodash' +import ConditionHelper from '../../util/condition' + +/** + * 详情步骤配置文件格式定义 + * - type 对应 detail + * - layout: 表单布局类型 + * - * horizontal: 左侧文本、右侧输入框、纵向排列 + * - * vertical: 顶部文本、底部输入框、纵向排列 + * - * inline: 左侧文本、右侧输入框、横向排列 + * - fields: 详情项配置列表 + * - defaultValue 默认值 + * - hiddenBack 是否隐藏返回按钮 + * - backText 返回按钮文案 + */ +export interface DetailConfig extends StepConfig { + type: 'detail' + layout?: 'horizontal' | 'vertical' | 'inline' + fields?: DetailFieldConfigs[] + defaultValue?: ParamConfig + hiddenBack?: boolean + backText?: string +} + +/** + * 详情步骤组件 - UI渲染方法 - 入参格式 + * - layout: 表单布局类型 + * - * horizontal: 左侧文本、右侧输入框、纵向排列 + * - * vertical: 顶部文本、底部输入框、纵向排列 + * - * inline: 左侧文本、右侧输入框、横向排列 + * - children: 表单内容 + */ +export interface IForm { + layout: 'horizontal' | 'vertical' | 'inline' + children: React.ReactNode[] + onBack?: () => Promise + backText?: string +} + +/** + * 详情项组件 - UI渲染方法 + * - label: 详情项名称 + * - layout: 详情项布局 + * - visitable: 详情项可见性 + * - * horizontal: 左侧文本、右侧输入框、纵向排列 + * - * vertical: 顶部文本、底部输入框、纵向排列 + * - * inline: 左侧文本、右侧输入框、横向排列 + * - children: 详情项内容 + */ +export interface IFormItem { + label: string + layout: 'horizontal' | 'vertical' | 'inline' + visitable: boolean + fieldType: string + children: React.ReactNode +} + +/** + * 详情步骤组件 - 状态 + * - formData: 表单的值 + */ +interface FormState { + ready: boolean + formValue: { [field: string]: any } + formData: { status: 'normal' | 'error' | 'loading', message?: string, name: string }[] +} + +/** + * 表单步骤组件 + */ +export default class DetailStep extends Step { + // 各详情项对应的类型所使用的UI组件的类 + getALLComponents = (type: any): typeof DetailField => getALLComponents[type] + + // 各详情项所使用的UI组件的实例 + formFields: Array | null> = [] + formFieldsMounted: Array = [] + + formValue: { [field: string]: any } = {} + formData: { status: 'normal' | 'error' | 'loading', message?: string, name: string, hidden: boolean }[] = [] + + /** + * 初始化表单的值 + * @param props + */ + constructor(props: StepProps) { + super(props) + this.state = { + ready: false, + formValue: {}, + formData: [] + } + } + + /** + * 重写表单步骤装载事件 + */ + stepPush = async () => { + // 处理表单步骤配置文件的默认值 + const { + config: { + fields: formFieldsConfig = [] + }, + data, + step, + onMount + } = this.props + + const formData = cloneDeep(this.state.formData) + + if (this.props.config.defaultValue) { + const formDefault = ParamHelper(this.props.config.defaultValue, { data, step }) + for (const formFieldIndex in formFieldsConfig) { + const formFieldConfig = formFieldsConfig[formFieldIndex] + const value = getValue(formDefault, formFieldConfig.field) + this.formValue = setValue(this.formValue, formFieldConfig.field, value) + formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label } + } + } + + await this.setState({ + ready: true, + formValue: this.formValue, + formData: cloneDeep(formData) + }) + + // 表单初始化结束,展示表单界面。 + onMount() + } + + handleFormFieldMount = async (formFieldIndex: number) => { + if (this.formFieldsMounted[formFieldIndex]) { + return true + } + this.formFieldsMounted[formFieldIndex] = true + + const formData = cloneDeep(this.state.formData) + + if (this.formFields[formFieldIndex]) { + const formField = this.formFields[formFieldIndex] + if (formField) { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + + let value = getValue(this.formValue, formFieldConfig.field) + if ((formFieldConfig.defaultValue) && value === undefined) { + value = await formField.reset() + } + this.formValue = setValue(this.formValue, formFieldConfig.field, value) + + const validation = await formField.validate(value) + if (validation === true) { + formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label } + } else { + // 首次进入错误提示; + formData[formFieldIndex] = { status: 'error', message: validation[0].message, name: formFieldConfig.label } + } + } + } + + await this.setState({ + formValue: this.formValue, + formData: cloneDeep(formData) + }) + } + + + /** + * 处理表单返回事件 + */ + handleCancel = async () => { + const { + onUnmount + } = this.props + + onUnmount() + } + + /** + * 处理详情项change事件 + * @param field 详情项配置 + * @param value 目标值 + */ + handleChange = async (formFieldIndex: number, value: any) => { + const formData = cloneDeep(this.state.formData) + + const formField = this.formFields[formFieldIndex] + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formField && formFieldConfig) { + this.formValue = setValue(this.formValue, formFieldConfig.field, value) + + const validation = await formField.validate(value) + if (validation === true) { + formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label } + } else { + formData[formFieldIndex] = { status: 'error', message: validation[0].message, name: formFieldConfig.label } + } + + await this.setState({ + formValue: this.formValue, + formData + }) + if (this.props.onChange) { + this.props.onChange(this.formValue) + } + } + } + + handleValueSet = async (formFieldIndex: number, path: string, value: any, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + + set(this.formValue, fullPath, value) + this.setState({ + formValue: this.formValue + }) + if (this.props.onChange) { + this.props.onChange(this.formValue) + } + + if (validation === true) { + this.formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label, hidden: false } + } else { + this.formData[formFieldIndex] = { status: 'error', message: validation[0].message, name: formFieldConfig.label, hidden: false } + } + await this.setState({ + formData: this.formData + }) + } + } + + handleValueUnset = async (formFieldIndex: number, path: string, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + + unset(this.formValue, fullPath) + this.setState({ + formValue: this.formValue + }) + if (this.props.onChange) { + this.props.onChange(this.formValue) + } + + if (validation === true) { + this.formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label, hidden: false } + } else { + this.formData[formFieldIndex] = { status: 'error', message: validation[0].message, name: formFieldConfig.label, hidden: false } + } + + await this.setState({ + formData: this.formData + }) + } + } + + handleValueListAppend = async (formFieldIndex: number, path: string, value: any, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + + const list = get(this.formValue, fullPath, []) + list.push(value) + set(this.formValue, fullPath, list) + this.setState({ + formValue: this.formValue + }) + if (this.props.onChange) { + this.props.onChange(this.formValue) + } + + if (validation === true) { + this.formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label, hidden: false } + } else { + this.formData[formFieldIndex] = { status: 'error', message: validation[0].message, name: formFieldConfig.label, hidden: false } + } + + await this.setState({ + formData: this.formData + }) + } + } + + handleValueListSplice = async (formFieldIndex: number, path: string, index: number, count: number, validation: true | DetailFieldError[]) => { + const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] + if (formFieldConfig) { + const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + + const list = get(this.formValue, fullPath, []) + list.splice(index, count) + set(this.formValue, fullPath, list) + this.setState({ + formValue: this.formValue + }) + if (this.props.onChange) { + this.props.onChange(this.formValue) + } + + if (validation === true) { + this.formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label, hidden: false } + } else { + this.formData[formFieldIndex] = { status: 'error', message: validation[0].message, name: formFieldConfig.label, hidden: false } + } + + await this.setState({ + formData: this.formData + }) + } + } + + /** + * 详情步骤组件 - UI渲染方法 + * 各UI库需重写该方法 + * @param props + */ + renderComponent = (props: IForm) => { + return + 您当前使用的UI版本没有实现Form组件。 + + } + + /** + * 详情项组件 - UI渲染方法 + * 各UI库需重写该方法 + * @param props + */ + renderItemComponent = (props: IFormItem) => { + return + 您当前使用的UI版本没有实现FormItem组件。 + + } + + render () { + const { + data, + step + // config: { + // layout = 'horizontal', + // fields = [] + // } + } = this.props + + const layout = this.props.config?.layout || 'horizontal' + const fields = this.props.config?.fields || [] + + const { + ready, + formValue, + formData + } = this.state + + if (ready) { + return ( + + {/* 渲染表单 */} + {this.renderComponent({ + layout, + onBack: this.props.config.hiddenBack ? undefined : async () => this.handleCancel(), + backText: this.props.config?.backText?.replace(/(^\s*)|(\s*$)/g, ""), + children: fields.map((formFieldConfig, formFieldIndex) => { + if (!ConditionHelper(formFieldConfig.condition, { record: formValue, data, step })) { + 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 + } + + // 隐藏项同时打标录入数据并清空填写项 + if (!hidden) { + this.formData[formFieldIndex] = { status: 'normal', name: formFieldConfig.label, hidden } + } + + const FormField = this.getALLComponents(formFieldConfig.type) || DetailField + + const renderData = { + label: formFieldConfig.label, + // status: formFieldConfig.field !== undefined ? getValue(formData, formFieldConfig.field, {}).status || 'normal' : 'normal', + // message: formFieldConfig.field !== undefined ? getValue(formData, formFieldConfig.field, {}).message || '' : '', + layout, + visitable: display, + fieldType: formFieldConfig.type, + children: ( + | null) => { + if (formFieldIndex !== null) { + this.formFields[formFieldIndex] = formField + this.handleFormFieldMount(formFieldIndex) + } + }} + formLayout={layout} + value={formFieldConfig.field !== undefined ? getValue(formValue, formFieldConfig.field) : undefined} + record={formValue} + data={cloneDeep(data)} + step={step} + config={formFieldConfig} + onChange={async (value: any) => { await this.handleChange(formFieldIndex, value) }} + onValueSet={async (path, value, validation) => await this.handleValueSet(formFieldIndex, path, value, validation)} + onValueUnset={async (path, validation) => await this.handleValueUnset(formFieldIndex, path, validation)} + onValueListAppend={async (path, value, validation) => await this.handleValueListAppend(formFieldIndex, path, value, validation)} + onValueListSplice={async (path, index, count, validation) => await this.handleValueListSplice(formFieldIndex, path, index, count, validation)} + loadDomain={async (domain: string) => await this.props.loadDomain(domain)} + /> + ) + } + // 渲染详情项容器 + return ( + hidden + ? this.renderItemComponent(renderData) + : + ) + }) + })} + + ) + } else { + return <> + } + } +} diff --git a/src/steps/index.tsx b/src/steps/index.tsx index 63fc5ea..99e6203 100644 --- a/src/steps/index.tsx +++ b/src/steps/index.tsx @@ -3,13 +3,15 @@ import FilterStep, { FilterConfig } from './filter' import FormStep, { FormConfig } from './form' import SkipStep, { SkipConfig } from './skip' import TableStep, { TableConfig } from './table' +import DetailStep, { DetailConfig } from './detail' -export type StepConfigs = FetchConfig | FormConfig | SkipConfig | TableConfig | FilterConfig +export type StepConfigs = FetchConfig | FormConfig | SkipConfig | TableConfig | FilterConfig | DetailConfig export default { fetch: FetchStep, form: FormStep, skip: SkipStep, table: TableStep, - filter: FilterStep + filter: FilterStep, + detail: DetailStep } -- Gitee From 834ae5a4b3fb55e53def350739d6de3ff76d816c Mon Sep 17 00:00:00 2001 From: cuiwenlong7 Date: Tue, 9 Nov 2021 17:59:10 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/detail/common.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/detail/common.tsx b/src/components/detail/common.tsx index e7cb4fd..a112c87 100644 --- a/src/components/detail/common.tsx +++ b/src/components/detail/common.tsx @@ -7,10 +7,8 @@ import ParamHelper from '../../util/param' * 详情页表单项基类配置文件格式定义 * - field: 表单项字段名 * - label: 表单项名称 - * - required: 表单项必填 - * - readonly: 表单项只读 - * - disabled: 表单项不可编辑 - * - default: 表单项默认值 // 改为defaultValue + * - defaultValue: 表单项默认值 + * - display: 是否可见 * - - type: 默认值类型 * - - * static: 固定值 * - - * data: 上一步骤数据 @@ -23,9 +21,6 @@ import ParamHelper from '../../util/param' export interface DetailFieldConfig { field: string label: string - required?: boolean - readonly?: boolean - disabled?: boolean display?: 'none' defaultValue?: ParamConfig, condition?: DetailFieldConditionConfig -- Gitee From eb29c494db0e945b786c77ea61ba20472eb9d313 Mon Sep 17 00:00:00 2001 From: cuiwenlong7 Date: Wed, 10 Nov 2021 14:45:57 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20react=E5=BC=80=E5=8F=91=E7=8E=AF?= =?UTF-8?q?=E5=A2=83key=E6=8A=A5=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/detail/group/index.tsx | 1 + src/components/formFields/group/index.tsx | 1 + src/components/formFields/importSubform/index.tsx | 1 + src/steps/detail/index.tsx | 3 +++ src/steps/form/index.tsx | 3 +++ 5 files changed, 9 insertions(+) diff --git a/src/components/detail/group/index.tsx b/src/components/detail/group/index.tsx index 89b8800..7fccfba 100644 --- a/src/components/detail/group/index.tsx +++ b/src/components/detail/group/index.tsx @@ -243,6 +243,7 @@ export default class GroupField extends DetailField { const FormField = this.getALLComponents(formFieldConfig.type) || DetailField const renderData = { + key: formFieldIndex, label: formFieldConfig.label, // status: formFieldConfig.field !== undefined ? getValue(formData, formFieldConfig.field, {}).status || 'normal' : 'normal', // message: formFieldConfig.field !== undefined ? getValue(formData, formFieldConfig.field, {}).message || '' : '', diff --git a/src/steps/form/index.tsx b/src/steps/form/index.tsx index c88a111..6f8688b 100644 --- a/src/steps/form/index.tsx +++ b/src/steps/form/index.tsx @@ -52,6 +52,7 @@ export interface IForm { /** * 表单项容器组件 - UI渲染方法 - 入参格式 + * - key: react需要的unique key * - label: 表单项名称 * - status: 表单项状态 * - * normal 默认状态 @@ -66,6 +67,7 @@ export interface IForm { * - children: 表单项内容 */ export interface IFormItem { + key: string | number, label: string status: 'normal' | 'error' | 'loading' description?: string @@ -442,6 +444,7 @@ export default class FormStep extends Step { const FormField = this.getALLComponents(formFieldConfig.type) || Field const renderData = { + key: formFieldIndex, label: formFieldConfig.label, status: formFieldConfig.field !== undefined ? getValue(formData, formFieldConfig.field, {}).status || 'normal' : 'normal', message: formFieldConfig.field !== undefined ? getValue(formData, formFieldConfig.field, {}).message || '' : '', -- Gitee From e1402b34fcb890065dd264629406c6bbca6aeb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E5=B0=BE=E5=B7=B4?= Date: Mon, 22 Nov 2021 07:41:31 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=E5=8F=98=E9=87=8F=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E9=9B=86=E4=B8=AD=E6=9B=BF=E6=8D=A2=20form=E5=AD=97=E6=A0=B7?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=BAdetail=E5=AD=97=E6=A0=B7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/detail/group/index.tsx | 172 +++++++++--------- src/steps/detail/index.tsx | 250 +++++++++++++------------- 2 files changed, 211 insertions(+), 211 deletions(-) diff --git a/src/components/detail/group/index.tsx b/src/components/detail/group/index.tsx index 7fccfba..5f3e745 100644 --- a/src/components/detail/group/index.tsx +++ b/src/components/detail/group/index.tsx @@ -3,7 +3,7 @@ import { cloneDeep } from 'lodash' import { setValue, getValue } from '../../../util/value' import { DetailField, DetailFieldConfig, DetailFieldError, DetailFieldProps, IDetailField } from '../common' import getALLComponents, { DetailFieldConfigs } from '../' -import { IFormItem } from '../../../steps/detail' +import { IDetailItem } from '../../../steps/detail' import ConditionHelper from '../../../util/condition' export interface GroupFieldConfig extends DetailFieldConfig { @@ -16,21 +16,21 @@ export interface IGroupField { } interface IGroupFieldState { - formData: { status: 'normal' | 'error' | 'loading', message?: string }[] + detailData: { status: 'normal' | 'error' | 'loading', message?: string }[] } export default class GroupField extends DetailField implements IDetailField { // 各表单项对应的类型所使用的UI组件的类 getALLComponents = (type: any): typeof DetailField => getALLComponents[type] - formFields: Array | null> = [] - formFieldsMounted: Array = [] + detailFields: Array | null> = [] + detailFieldsMounted: Array = [] constructor (props: DetailFieldProps) { super(props) this.state = { - formData: [] + detailData: [] } } @@ -38,15 +38,15 @@ export default class GroupField extends DetailField { - if (this.formFieldsMounted[formFieldIndex]) { + handleMount = async (detailFieldIndex: number) => { + if (this.detailFieldsMounted[detailFieldIndex]) { return true } - this.formFieldsMounted[formFieldIndex] = true + this.detailFieldsMounted[detailFieldIndex] = true - if (this.formFields[formFieldIndex]) { - const formField = this.formFields[formFieldIndex] - if (formField) { - const formFieldConfig = this.props.config.fields[formFieldIndex] + if (this.detailFields[detailFieldIndex]) { + const detailField = this.detailFields[detailFieldIndex] + if (detailField) { + const detailFieldConfig = this.props.config.fields[detailFieldIndex] - let value = getValue(this.props.value, formFieldConfig.field) - if ((formFieldConfig.defaultValue) && value === undefined) { - value = await formField.reset() - this.props.onValueSet(formFieldConfig.field, value, true) + let value = getValue(this.props.value, detailFieldConfig.field) + if ((detailFieldConfig.defaultValue) && value === undefined) { + value = await detailField.reset() + this.props.onValueSet(detailFieldConfig.field, value, true) } - const validation = await formField.validate(value) + const validation = await detailField.validate(value) if (value === undefined || validation === true) { - await this.setState(({ formData }) => { - formData[formFieldIndex] = { status: 'normal' } - return { formData: cloneDeep(formData) } + await this.setState(({ detailData }) => { + detailData[detailFieldIndex] = { status: 'normal' } + return { detailData: cloneDeep(detailData) } }) } else { - await this.setState(({ formData }) => { - formData[formFieldIndex] = { status: 'error', message: validation[0].message } - return { formData: cloneDeep(formData) } + await this.setState(({ detailData }) => { + detailData[detailFieldIndex] = { status: 'error', message: validation[0].message } + return { detailData: cloneDeep(detailData) } }) } } @@ -117,78 +117,78 @@ export default class GroupField extends DetailField { - const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] - if (formFieldConfig) { - const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + handleValueSet = async (detailFieldIndex: number, path: string, value: any, validation: true | DetailFieldError[]) => { + const detailFieldConfig = (this.props.config.fields || [])[detailFieldIndex] + if (detailFieldConfig) { + const fullPath = detailFieldConfig.field === '' || path === '' ? `${detailFieldConfig.field}${path}` : `${detailFieldConfig.field}.${path}` await this.props.onValueSet(fullPath, value, true) - const formData = cloneDeep(this.state.formData) + const detailData = cloneDeep(this.state.detailData) if (validation === true) { - formData[formFieldIndex] = { status: 'normal' } + detailData[detailFieldIndex] = { status: 'normal' } } else { - formData[formFieldIndex] = { status: 'error', message: validation[0].message } + detailData[detailFieldIndex] = { status: 'error', message: validation[0].message } } this.setState({ - formData + detailData }) } } - handleValueUnset = async (formFieldIndex: number, path: string, validation: true | DetailFieldError[]) => { - const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] - if (formFieldConfig) { - const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + handleValueUnset = async (detailFieldIndex: number, path: string, validation: true | DetailFieldError[]) => { + const detailFieldConfig = (this.props.config.fields || [])[detailFieldIndex] + if (detailFieldConfig) { + const fullPath = detailFieldConfig.field === '' || path === '' ? `${detailFieldConfig.field}${path}` : `${detailFieldConfig.field}.${path}` await this.props.onValueUnset(fullPath, true) - const formData = cloneDeep(this.state.formData) + const detailData = cloneDeep(this.state.detailData) if (validation === true) { - formData[formFieldIndex] = { status: 'normal' } + detailData[detailFieldIndex] = { status: 'normal' } } else { - formData[formFieldIndex] = { status: 'error', message: validation[0].message } + detailData[detailFieldIndex] = { status: 'error', message: validation[0].message } } this.setState({ - formData + detailData }) } } - handleValueListAppend = async (formFieldIndex: number, path: string, value: any, validation: true | DetailFieldError[]) => { - const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] - if (formFieldConfig) { - const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + handleValueListAppend = async (detailFieldIndex: number, path: string, value: any, validation: true | DetailFieldError[]) => { + const detailFieldConfig = (this.props.config.fields || [])[detailFieldIndex] + if (detailFieldConfig) { + const fullPath = detailFieldConfig.field === '' || path === '' ? `${detailFieldConfig.field}${path}` : `${detailFieldConfig.field}.${path}` await this.props.onValueListAppend(fullPath, value, true) - const formData = cloneDeep(this.state.formData) + const detailData = cloneDeep(this.state.detailData) if (validation === true) { - formData[formFieldIndex] = { status: 'normal' } + detailData[detailFieldIndex] = { status: 'normal' } } else { - formData[formFieldIndex] = { status: 'error', message: validation[0].message } + detailData[detailFieldIndex] = { status: 'error', message: validation[0].message } } this.setState({ - formData + detailData }) } } - handleValueListSplice = async (formFieldIndex: number, path: string, index: number, count: number, validation: true | DetailFieldError[]) => { - const formFieldConfig = (this.props.config.fields || [])[formFieldIndex] - if (formFieldConfig) { - const fullPath = formFieldConfig.field === '' || path === '' ? `${formFieldConfig.field}${path}` : `${formFieldConfig.field}.${path}` + handleValueListSplice = async (detailFieldIndex: number, path: string, index: number, count: number, validation: true | DetailFieldError[]) => { + const detailFieldConfig = (this.props.config.fields || [])[detailFieldIndex] + if (detailFieldConfig) { + const fullPath = detailFieldConfig.field === '' || path === '' ? `${detailFieldConfig.field}${path}` : `${detailFieldConfig.field}.${path}` await this.props.onValueListSplice(fullPath, index, count, true) - const formData = cloneDeep(this.state.formData) + const detailData = cloneDeep(this.state.detailData) if (validation === true) { - formData[formFieldIndex] = { status: 'normal' } + detailData[detailFieldIndex] = { status: 'normal' } } else { - formData[formFieldIndex] = { status: 'error', message: validation[0].message } + detailData[detailFieldIndex] = { status: 'error', message: validation[0].message } } this.setState({ - formData + detailData }) } } @@ -204,9 +204,9 @@ export default class GroupField extends DetailField { + renderItemComponent = (props: IDetailItem) => { return - 您当前使用的UI版本没有实现FormItem组件。 + 您当前使用的UI版本没有实现DetailItem组件。 } @@ -223,51 +223,51 @@ export default class GroupField extends DetailField {this.renderComponent({ - children: (this.props.config.fields || []).map((formFieldConfig, formFieldIndex) => { - if (!ConditionHelper(formFieldConfig.condition, { record: value, data: this.props.data, step: this.props.step })) { + children: (this.props.config.fields || []).map((detailFieldConfig, detailFieldIndex) => { + if (!ConditionHelper(detailFieldConfig.condition, { record: value, data: this.props.data, step: this.props.step })) { return null } let hidden: boolean = true let display: boolean = true - // if (formFieldConfig.type === 'hidden') { + // if (detailFieldConfig.type === 'hidden') { // hidden = true // display = false // } - if (formFieldConfig.display === 'none') { + if (detailFieldConfig.display === 'none') { hidden = true display = false } - const FormField = this.getALLComponents(formFieldConfig.type) || DetailField + const DetailFieldComponent = this.getALLComponents(detailFieldConfig.type) || DetailField const renderData = { - key: formFieldIndex, - label: formFieldConfig.label, + key: detailFieldIndex, + label: detailFieldConfig.label, layout: formLayout, visitable: display, - fieldType: formFieldConfig.type, + fieldType: detailFieldConfig.type, children: ( - | null) => { - if (formFieldIndex !== null) { - this.formFields[formFieldIndex] = formField - this.handleMount(formFieldIndex) + | null) => { + if (detailFieldIndex !== null) { + this.detailFields[detailFieldIndex] = detailField + this.handleMount(detailFieldIndex) } }} formLayout={formLayout} - value={getValue(value, formFieldConfig.field)} + value={getValue(value, detailFieldConfig.field)} record={record} data={cloneDeep(data)} step={step} - config={formFieldConfig} - onChange={async (value: any) => { await this.handleChange(formFieldIndex, value) }} - onValueSet={async (path, value, validation) => this.handleValueSet(formFieldIndex, path, value, validation)} - onValueUnset={async (path, validation) => this.handleValueUnset(formFieldIndex, path, validation)} - onValueListAppend={async (path, value, validation) => this.handleValueListAppend(formFieldIndex, path, value, validation)} - onValueListSplice={async (path, index, count, validation) => this.handleValueListSplice(formFieldIndex, path, index, count, validation)} + config={detailFieldConfig} + onChange={async (value: any) => { await this.handleChange(detailFieldIndex, value) }} + onValueSet={async (path, value, validation) => this.handleValueSet(detailFieldIndex, path, value, validation)} + onValueUnset={async (path, validation) => this.handleValueUnset(detailFieldIndex, path, validation)} + onValueListAppend={async (path, value, validation) => this.handleValueListAppend(detailFieldIndex, path, value, validation)} + onValueListSplice={async (path, index, count, validation) => this.handleValueListSplice(detailFieldIndex, path, index, count, validation)} loadDomain={async (domain: string) => await this.props.loadDomain(domain)} /> ) @@ -276,7 +276,7 @@ export default class GroupField extends DetailField