diff --git a/package.json b/package.json index b1fc05e9cf651f1de1d46f9c1b13042ea065e0bb..a420bb764744d2f0cef52e073f3793c98d943ce0 100644 --- a/package.json +++ b/package.json @@ -85,4 +85,4 @@ "react": "^16.13.1", "react-dom": "^16.13.1" } -} +} \ No newline at end of file diff --git a/src/components/formFields/select/common.tsx b/src/components/formFields/select/common.tsx index 528b21d335fcb614216e17a0cbb4b14ae9adf729..039461a7be0ea49176f9898508918059f9ba3c17 100644 --- a/src/components/formFields/select/common.tsx +++ b/src/components/formFields/select/common.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react' -import EnumerationHelper, { EnumerationOptionsConfig } from '../../../util/enumeration' +import EnumerationHelper, { EnumerationOptionsConfig, InterfaceEnumerationOptionsKVConfig, InterfaceEnumerationOptionsListConfig } from '../../../util/enumeration' import InterfaceHelper from '../../../util/interface' import { Field, FieldConfig, FieldProps, IField, Display, DisplayProps } from '../common' @@ -25,7 +25,7 @@ interface SelectSingleFieldState { export default class SelectField extends Field implements IField { interfaceHelper = new InterfaceHelper() - constructor (props: FieldProps) { + constructor(props: FieldProps) { super(props) this.state = { @@ -37,7 +37,11 @@ export default class SelectField extends Fiel config: EnumerationOptionsConfig | undefined ) => { if (config) { - EnumerationHelper.options(config, (config, source) => this.interfaceHelper.request(config, source, { record: this.props.record, data: this.props.data, step: this.props.step }, { loadDomain: this.props.loadDomain })).then((options) => { + EnumerationHelper.options( + config, + (config, source) => this.interfaceHelper.request(config, source, { record: this.props.record, data: this.props.data, step: this.props.step }, { loadDomain: this.props.loadDomain }), + { record: this.props.record, data: this.props.data, step: this.props.step } + ).then((options) => { if (JSON.stringify(this.state.options) !== JSON.stringify(options)) { this.setState({ options @@ -54,7 +58,7 @@ export default class SelectField extends Fiel export class SelectDisplay extends Display { interfaceHelper = new InterfaceHelper() - constructor (props: DisplayProps) { + constructor(props: DisplayProps) { super(props) this.state = { @@ -66,7 +70,11 @@ export class SelectDisplay extends Display { if (config) { - EnumerationHelper.options(config, (config, source) => this.interfaceHelper.request(config, source, { record: this.props.record, data: this.props.data, step: this.props.step }, { loadDomain: this.props.loadDomain })).then((options) => { + EnumerationHelper.options( + config, + (config, source) => this.interfaceHelper.request(config, source, { record: this.props.record, data: this.props.data, step: this.props.step }, { loadDomain: this.props.loadDomain }), + { record: this.props.record, data: this.props.data, step: this.props.step } + ).then((options) => { if (JSON.stringify(this.state.options) !== JSON.stringify(options)) { this.setState({ options diff --git a/src/components/formFields/select/multiple/index.tsx b/src/components/formFields/select/multiple/index.tsx index d2dd26346e084f9d3264716aab19da728c22aa94..ad0aca98b6dcf8985bcf908916772c5df40285f8 100644 --- a/src/components/formFields/select/multiple/index.tsx +++ b/src/components/formFields/select/multiple/index.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { getBoolean } from '../../../../util/value' +import { getBoolean, transformValueType } from '../../../../util/value' import { FieldError } from '../../common' import SelectField, { ISelectFieldOption, SelectFieldConfig } from '../common' @@ -17,7 +17,8 @@ interface SelectMultipleArrayConfig { interface SelectMultipleSplitConfig { type: 'split', - split?: string + split?: string, + valueType?: 'string' | 'number' | 'boolean' | undefined } export interface ISelectMultipleField { @@ -114,7 +115,13 @@ export default class SelectMultipleField extends SelectField { await this.props.onValueSet('', value, await this.validate(value)) }, + onChange: async (value: string | Array | undefined) => { + let useV = value + if (Array.isArray(useV) && multiple !== true && multiple?.type === 'split') { + useV = useV.join(multiple.split || ',') + } + return await this.props.onValueSet('', useV, await this.validate(useV)) + }, onClear: this.props.config.canClear ? async () => { await this.props.onValueSet('', undefined, await this.validate(undefined)) } : undefined, disabled: getBoolean(disabled), readonly: getBoolean(readonly), @@ -129,8 +136,8 @@ export default class SelectMultipleField extends SelectField option.value) props.value.filter((v) => { - if (props.options.map((option) => option.value).includes(v.toString())) { + if (values.includes(v)) { return true } else { console.warn(`选择框的当前值中${v}不在选项中。`) diff --git a/src/components/formFields/treeSelect/index.tsx b/src/components/formFields/treeSelect/index.tsx index 6d3973ad1f9f559c9ada26bc685d458ec4a8406d..00874d090655f0893f673dff60387caa9e173192 100644 --- a/src/components/formFields/treeSelect/index.tsx +++ b/src/components/formFields/treeSelect/index.tsx @@ -2,20 +2,44 @@ import React, { ReactNode } from 'react' import { get } from 'lodash' import { Field, FieldConfig, IField, FieldError, FieldProps } from '../common' import InterfaceHelper, { InterfaceConfig } from '../../../util/interface' +import ParamHelper from '../../../util/param' +import { RecordParamConfig, DataParamConfig, StepParamConfig, SourceParamConfig } from '../../../interface' +import { transformValueType } from '../../../util/value' +type OptionsConfigDefaultValue = RecordParamConfig | DataParamConfig | StepParamConfig | SourceParamConfig export interface TreeSelectFieldConfig extends FieldConfig { type: 'tree_select' - treeData?: ManualOptionsConfig | InterfaceOptionsConfig + mode?: 'tree' | 'table' | 'treeSelect' + multiple?: true | TreeSelectMultipleArrayConfig | TreeSelectMultipleSplitConfig, + titleColumn?: string, + treeData?: ManualOptionsConfig | InterfaceOptionsConfig | DataOptionsConfig +} + +interface TreeSelectMultipleArrayConfig { + type: 'array' +} + +interface TreeSelectMultipleSplitConfig { + type: 'split', + split?: string, + valueType?: 'string' | 'number' | 'boolean' | undefined +} +export interface DataOptionsConfig { + from: 'data' + sourceConfig?: OptionsConfigDefaultValue, + format?: InterfaceOptionsListConfig } export interface ManualOptionsConfig { from: 'manual' defaultIndex?: string | number - data?: Array<{ - value: string | number - title: string - [extra: string]: any - }> + data?: treeTableDataConfig[] +} + +export interface treeTableDataConfig { + value: string | number + title: string + children: treeTableDataConfig[] } export interface InterfaceOptionsConfig { @@ -35,37 +59,35 @@ export interface InterfaceOptionsListConfig { childrenField?: string } -export interface ISelectFieldOption { +export interface TreeSelectFieldOption { + key?: string | number value: string | number, - title: ReactNode, - children?: Array + title: ReactNode + children?: Array } -interface treeData { - value: any, - title: string, - children?: treeData[] +interface TreeSelectFieldState { + interfaceOptionsData: TreeSelectFieldOption[] } -interface SelectSingleFieldState { - interfaceOptionsData: treeData[] -} +type TreeSelectValueType = string | Array | undefined export interface ITreeSelectField { - value?: string, - treeData: Array - onChange: (value: string) => Promise + value: TreeSelectValueType, + treeData: Array + titleColumn?: string + onChange: (value: TreeSelectValueType) => Promise } -export default class TreeSelectField extends Field implements IField { +export default class TreeSelectField extends Field { interfaceHelper = new InterfaceHelper() interfaceOptionsConfig: string = '' - state: SelectSingleFieldState = { + state: TreeSelectFieldState = { interfaceOptionsData: [] } - constructor (props: FieldProps) { + constructor(props: FieldProps) { super(props) this.state = { @@ -73,17 +95,26 @@ export default class TreeSelectField extends Field { - const rsMenu: treeData[] = [] + optionsData = (sourceConfig: OptionsConfigDefaultValue) => { + if (sourceConfig !== undefined) { + return ParamHelper(sourceConfig, { record: this.props.record, data: this.props.data, step: this.props.step }) + } + return undefined + } - treeList.forEach((val: any) => { - const theMenu: treeData = { + formatTree = (treeList: Array, value: string, title: string, children: string) => { + const rsMenu: TreeSelectFieldOption[] = [] + + treeList.forEach((val: TreeSelectFieldOption) => { + const theMenu: TreeSelectFieldOption = { title: '', - value: null + value: '', + key: '' } theMenu.title = get(val, title) theMenu.value = get(val, value) + theMenu.key = get(val, value) if (get(val, children)) { theMenu.children = this.formatTree(get(val, children), value, title, children) @@ -95,7 +126,7 @@ export default class TreeSelectField extends Field { if (config) { - if (config.from === 'manual') { + if (config.from === 'data') { + if (config.sourceConfig && config.sourceConfig.source && config.sourceConfig.field) { + const data = this.optionsData(config.sourceConfig) + if (Array.isArray(data)) { + return this.formatTree( + data, + config.format?.keyField || 'value', + config.format?.titleField || 'title', + config.format?.childrenField || 'children' + ) + } + } + } else if (config.from === 'manual') { if (config.data) { return this.formatTree(config.data, 'value', 'title', 'children') } @@ -151,7 +194,7 @@ export default class TreeSelectField extends Field => { + validate = async (_value: TreeSelectValueType): Promise => { const { config: { required @@ -171,35 +214,93 @@ export default class TreeSelectField extends Field { return - 您当前使用的UI版本没有实现TreeSelectSingleField组件的SelectSingle模式。 + 您当前使用的UI版本没有实现TreeSelectField组件的treeSelect模式。
} + renderTreeComponent = (props: ITreeSelectField) => { + return + 您当前使用的UI版本没有实现TreeSelectField组件的tree模式。 +
+ +
+
+ } + + renderTableComponent = (props: ITreeSelectField) => { + return + 您当前使用的UI版本没有实现TreeSelectField组件的table模式。 +
+ +
+
+ } + render = () => { const { value, config: { + multiple, + mode, + titleColumn, treeData: optionsConfig }, - onChange, record, data, step } = this.props - this.options(optionsConfig, { record, data, step }) - - return ( - - {this.renderComponent({ - value, - treeData: this.state.interfaceOptionsData, - onChange: async (value: string) => await this.props.onValueSet('', value, await this.validate(value)) - })} - - ) + const temp = this.options(optionsConfig, { record, data, step }) + const props: ITreeSelectField = { + value: undefined, + treeData: this.state.interfaceOptionsData, + onChange: async (value: TreeSelectValueType) => { + let useV = value + if (Array.isArray(useV) && multiple !== true && multiple?.type === 'split') { + useV = useV.join(multiple.split || ',') + } + return await this.props.onValueSet('', useV, await this.validate(useV)) + } + } + if (optionsConfig && (optionsConfig.from === 'manual' || optionsConfig.from === 'data')) { + props.treeData = temp + } + if (multiple === true || multiple?.type === 'array') { + if (Array.isArray(value)) { + props.value = (value as Array) + } else if (value !== undefined) { + props.value = undefined + console.warn('数组类型的树形选框的值需要是字符串或数值的数组。') + } + } else if (multiple?.type === 'split') { + if (typeof value === 'string' && value !== '') { + props.value = transformValueType(String(value).split(multiple.split || ','), multiple?.valueType) + } else if (value !== undefined) { + props.value = undefined + console.warn('字符串分隔类型的树形选框的值需要是字符串。') + } + } else { + props.value = Array.isArray(value) ? value : undefined + } + + if (mode === 'table') { + props.titleColumn = titleColumn + return this.renderTableComponent(props) + } else if (mode === 'tree') { + return this.renderTreeComponent(props) + } else { + return ( + + {this.renderComponent({ + value, + treeData: this.state.interfaceOptionsData, + onChange: async (value: TreeSelectValueType) => await this.props.onValueSet('', value, await this.validate(value)) + })} + + ) + } } } diff --git a/src/interface.ts b/src/interface.ts index 1f559664d9d0d57597e44744d185fff77e557cf3..60a20c5318db17d7005a4f6cb61eee4c5cdc2917 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -7,75 +7,84 @@ * - content: 内容 */ export interface RichStringConfig { - type: 'plain' | 'markdown' | 'html' - content: string + type: 'plain' | 'markdown' | 'html'; + content: string; } -export type ParamConfig = RecordParamConfig | DataParamConfig | StepParamConfig | SourceParamConfig | URLParamConfig | QueryParamConfig | HashParamConfig | InterfaceParamConfig | StaticParamConfig +export type ParamConfig = + | RecordParamConfig + | DataParamConfig + | StepParamConfig + | SourceParamConfig + | URLParamConfig + | QueryParamConfig + | HashParamConfig + | InterfaceParamConfig + | StaticParamConfig; -interface RecordParamConfig { - source: 'record' - field: string +export interface RecordParamConfig { + source: 'record'; + field: string; } -interface DataParamConfig { - source: 'data' - field: string +export interface DataParamConfig { + source: 'data'; + field: string; } -interface StepParamConfig { - source: 'step' - step: number - field: string +export interface StepParamConfig { + source: 'step'; + step: number; + field: string; } -interface SourceParamConfig { - source: 'source', - field: string +export interface SourceParamConfig { + source: 'source'; + field: string; } interface URLParamConfig { - source: 'url', - field: string + source: 'url'; + field: string; } interface QueryParamConfig { - source: 'query', - filed: any + source: 'query'; + filed: any; } interface HashParamConfig { - source: 'hash', - filed: any + source: 'hash'; + filed: any; } interface InterfaceParamConfig { - source: 'interface', + source: 'interface'; // api: { // url: string, // method: 'POST', // contentType: 'json', // withCredentials: true // }, - api: object, - apiResponse: string + api: object; + apiResponse: string; } interface StaticParamConfig { - source: 'static', - value: any + source: 'static'; + value: any; } /** * 表单/详情分栏配置定义 -* - * type: 分栏类型 -* - * - * span: 固定分栏 -* - * - * width: 宽度分栏 -* - * value: 分栏相关配置值 -* - * wrap: 分栏后是否换行 -* - * gap: 分栏边距 -*/ + * - * type: 分栏类型 + * - * - * span: 固定分栏 + * - * - * width: 宽度分栏 + * - * value: 分栏相关配置值 + * - * wrap: 分栏后是否换行 + * - * gap: 分栏边距 + */ export interface ColumnsConfig { - enable?: boolean - type?: 'span' | 'width' - value?: number | string, - wrap?: boolean - gap?: number | string - rowGap?: number | string + enable?: boolean; + type?: 'span' | 'width'; + value?: number | string; + wrap?: boolean; + gap?: number | string; + rowGap?: number | string; } diff --git a/src/steps/header/index.tsx b/src/steps/header/index.tsx index a7ec2d747c76be2b6eee07b26283931e10e340cf..d693a2ee9f33535a9ef3ed6652b1ee5620a75dfa 100644 --- a/src/steps/header/index.tsx +++ b/src/steps/header/index.tsx @@ -226,9 +226,9 @@ export default class HeaderStep extends Step { ref={(e: Step | null) => { e && e.stepPush() }} data={this.props.data} step={this.props.step} - onSubmit={async (data, unmountView) => {}} - onMount={async () => {}} - onUnmount={async (reload = false, data) => {}} + onSubmit={async (data, unmountView) => { }} + onMount={async () => { }} + onUnmount={async (reload = false, data) => { }} config={merge(config, defaultConfig)} baseRoute={this.props.baseRoute} checkPageAuth={this.props.checkPageAuth} @@ -252,7 +252,11 @@ export default class HeaderStep extends Step { }) case 'enumeration': if (statistic.options) { - EnumerationHelper.options(statistic.options, (config, source) => this.interfaceHelper.request(config, source, { data: this.props.data, step: this.props.step }, { loadDomain: this.props.loadDomain })).then((options) => { + EnumerationHelper.options( + statistic.options, + (config, source) => this.interfaceHelper.request(config, source, { data: this.props.data, step: this.props.step }, { loadDomain: this.props.loadDomain }), + { data: this.props.data, step: this.props.step } + ).then((options) => { if (!this.state || JSON.stringify(this.state[`statistic_options_${_position}_${index}`]) !== JSON.stringify(options)) { this.setState({ [`statistic_options_${_position}_${index}`]: options @@ -289,7 +293,7 @@ export default class HeaderStep extends Step { } } - render () { + render() { const props: IHeaderProps = {} if (this.props.config.breadcrumb && this.props.config.breadcrumb.enable) { diff --git a/src/util/enumeration.ts b/src/util/enumeration.ts index 0b3c41d09c39b7e18f7678360c86e0a0251de4a7..9ded388499ecc31c35114bc658ea34cc4b7dded8 100644 --- a/src/util/enumeration.ts +++ b/src/util/enumeration.ts @@ -1,7 +1,9 @@ -import { InterfaceConfig } from "./interface"; -import { getValue } from "./value"; +import { InterfaceConfig } from './interface' +import { getValue } from './value' +import { ParamConfig } from '../interface' +import ParamHelper from './param' -export type EnumerationOptionsConfig = ManualEnumerationOptionsConfig | InterfaceEnumerationOptionsConfig +export type EnumerationOptionsConfig = ManualEnumerationOptionsConfig | InterfaceEnumerationOptionsConfig | DataEnumerationOptionsConfig interface ManualEnumerationOptionsConfig { from: 'manual' @@ -18,11 +20,19 @@ interface InterfaceEnumerationOptionsConfig { format?: InterfaceEnumerationOptionsKVConfig | InterfaceEnumerationOptionsListConfig } -interface InterfaceEnumerationOptionsKVConfig { +interface DataEnumerationOptionsConfig { + from: 'data'; + sourceConfig?: ParamConfig; + format?: + | InterfaceEnumerationOptionsKVConfig + | InterfaceEnumerationOptionsListConfig; +} + +export interface InterfaceEnumerationOptionsKVConfig { type: 'kv' } -interface InterfaceEnumerationOptionsListConfig { +export interface InterfaceEnumerationOptionsListConfig { type: 'list' keyField: string labelField: string @@ -31,7 +41,18 @@ interface InterfaceEnumerationOptionsListConfig { export default class EnumerationHelper { static _instance: EnumerationHelper - public async options (config: EnumerationOptionsConfig, interfaceRequire: (config: InterfaceConfig, source: any) => Promise) { + optionsDataValue = (sourceConfig: ParamConfig, datas: { record?: object, data: object[], step: number }) => { + if (sourceConfig !== undefined) { + return ParamHelper(sourceConfig, datas) + } + return undefined + } + + public async options( + config: EnumerationOptionsConfig, + interfaceRequire: (config: InterfaceConfig, source: any) => Promise, + datas: { record?: object, data: object[], step: number } + ) { if (config) { if (config.from === 'manual') { if (config.data) { @@ -63,15 +84,41 @@ export default class EnumerationHelper { } } } + } else if (config.from === 'data') { + if (config.sourceConfig && config.sourceConfig.source) { + const data = ParamHelper(config.sourceConfig, datas) + if (config.format) { + if (config.format.type === 'kv') { + return Object.keys(data).map((key) => ({ + value: key, + label: data[key] + })) + } else if (config.format.type === 'list') { + if (Array.isArray(data)) { + return data.map((item: any) => { + return { + value: getValue(item, (config.format as InterfaceEnumerationOptionsListConfig).keyField), + label: getValue(item, (config.format as InterfaceEnumerationOptionsListConfig).labelField) + } + }) + } + } + } + } + return [] } } return [] } - static async options (config: EnumerationOptionsConfig, interfaceRequire: (config: InterfaceConfig, source: any) => Promise) { + static async options( + config: EnumerationOptionsConfig, + interfaceRequire: (config: InterfaceConfig, source: any) => Promise, + datas: { record?: object, data: object[], step: number } + ) { if (!EnumerationHelper._instance) { EnumerationHelper._instance = new EnumerationHelper() } - return await EnumerationHelper._instance.options(config, interfaceRequire) + return await EnumerationHelper._instance.options(config, interfaceRequire, datas) } -} \ No newline at end of file +} diff --git a/src/util/value.ts b/src/util/value.ts index 35a8463c108798c0d5b2015bbddbd7714fa1833a..10cd4ddee4947ec1c42c96c0168863e7789b58b8 100644 --- a/src/util/value.ts +++ b/src/util/value.ts @@ -114,10 +114,32 @@ export const listItemMove = (list: any[], currentIndex: number, sortType: 'up' | switch (sortType) { case 'up': currentIndex !== 0 && (list[currentIndex] = list.splice(currentIndex - 1, 1, list[currentIndex])[0]) - break; + break case 'down': currentIndex < list.length - 1 && (list[currentIndex] = list.splice(currentIndex + 1, 1, list[currentIndex])[0]) - break; + break } return list } + +/** + * 转化value数组中的值类型 + * @param list value数组 + * @param type 值类型 + * @returns value数组 + */ +export const transformValueType = (list: any[], type: 'string' | 'number' | 'boolean' | undefined) => { + switch (type) { + case 'string': + return list.map(v => String(v)) + + case 'number': + return list.map(v => +v) + + case 'boolean': + return list.map(v => Boolean(v)) + + default: + return list + } +}