TableForm组件`
import React, { useRef, useImperativeHandle, } from 'react'
// import zhCN from '@saas/saas-ui-pc-locale/es/zh_CN'
import {Row, Col, Form, Select, Button, DatePicker,} from 'antd'
import moment from 'moment'
import SearchSelect from './SearchSelect'
import NumberRange from './NumberRange'
// import Screen from './Screen'
import './index.less'
const { Option } = Select
const { RangePicker } = DatePicker
const defaultLayout = {
xxl: 6,
lg: 8,
md: 12,
}
export default React.forwardRef(function TableForm(props,ref,) {
const formRef = useRef()
const { fields = [], initialValues, submit } = props
/**
* 函数组件没有实例 通过forwardRef与useImperativeHandle钩子函数暴露出ref实例
*/
useImperativeHandle(ref, () => formRef.current)
const renderFormElement = (ele)=> {
// const Element = window.steamer.services[ele.type]
const { extraFields } = props || {}
switch (ele.type) {
case 'select':
return (
<Select
getPopupContainer={(triggerNode) => triggerNode.parentNode}
{...ele?.selectProps?.elementProps}
>
{ele?.selectProps?.list?.map((option, index) => {
return (
<Option
key={index}
value={option[ele?.selectProps?.optionValueField]}
>
{option[ele?.selectProps?.optionLabelField]}
</Option>
)
})}
</Select>
)
case 'searchSelect':
return <SearchSelect {...ele.selectProps}></SearchSelect>
case 'timerange':
return (
<RangePicker
style={{ width: '100%' }}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
// locale={zhCN.DatePicker}
showTime={{
defaultValue: [
moment('00:00:00', 'HH:mm:ss'),
moment('23:59:59', 'HH:mm:ss'),
],
}}
{...ele?.props}
/>
)
// case 'ChoiceBox':
// return <ChoiceBox {...ele?.props} ></ChoiceBox>
case 'NumberRange':
return <NumberRange {...ele?.props}></NumberRange>
case 'extra':
return extraFields?.[ele.field] || <></>
default:
return <></>
// return Element ? <Element {...ele.props}></Element> : <></>
}
}
const handleSubmit = () => {
formRef.current
.check()
.then((values) => {
submit?.(values)
})
.catch((e) => {
console.log(e)
})
}
return (
<div className="service-table-form">
<Form ref={formRef} initialValues={initialValues}>
{fields?.map((item, index) => {
return (
<div className="contioner" key={item.groupTitle}>
{item.groupTitle ? (
<div className="groupTitle">{item.groupTitle}</div>
) : null}
<div style={{ flex: 1 }}>
<Row gutter={24}>
{item.component.length &&
item.component.map((ele) => {
return (
<Col key={ele.field} {...(ele.layout || defaultLayout)}>
<Form.Item
{...ele?.formItemProps}
label={ele.label}
name={ele.field}
>
{renderFormElement(ele)}
</Form.Item>
</Col>
)
})}
{index === fields.length - 1 ? (
<Col xxl={6} lg={8} md={12}>
<Form.Item>
{props?.preBtns}
<Button type="primary" onClick={handleSubmit}>
{props?.searchText || '查询'}
</Button>
{props?.extraBtns}
</Form.Item>
</Col>
) : null}
</Row>
</div>
</div>
)
})}
</Form>
</div>
)
})
SearchSelect组件
import React, { useEffect, useState } from 'react'
import { get, debounce } from 'lodash'
import { Select,} from 'antd'
import axios from 'axios'
const { Option } = Select
export default function SearchSelect(props){
const {
elementProps,
optionLabelField,
optionValueField,
onChange,
needItem,
} = props
const [optionList, setOptionList] = useState([])
const [selectedValue, setSelectedValue] = useState(undefined)
/**
* 回显
*/
useEffect(()=>{
onSearch(props.value, true)
}, [])
/**
* 查询
* keyword 输入框的关键字
* isInit 是否初始化查询
*/
const onSearch = debounce(async (keyword,isInit)=> {
if (!keyword) {
return
}
const { req } = props
let reqParmas = {}
// 当配置项req.params是函数时,默认使用该函数组装请求参数(req.field字段自动失效)
if (typeof req.params === 'function') {
reqParmas = req.params(keyword)
} else {
reqParmas = Object.assign(
{
[req.field || 'keyword']: keyword,
},
req?.params,
)
}
const reqConfig = {
url: req?.url,
method: req?.method || 'GET',
params: reqParmas,
}
// 根据项目的请
const res = await axios(reqConfig)
if (typeof req.getter === 'function') {
setOptionList(req.getter(res) || [])
} else {
const list = get(res, req.getter) || []
if(isInit){
const curItem= list.find(i=>String(i[optionValueField]) === String(keyword))
setSelectedValue(JSON.stringify(curItem))
}
setOptionList(list)
}
}, 500)
const onSelected = (itemValue) => {
setSelectedValue(itemValue)
const { mode } = elementProps || {}
if (mode === 'multiple') {
const _itemValue = itemValue.map((item) => JSON.parse(item))
needItem
? onChange?.(_itemValue) // 多选且需要完整对象
: onChange?.(_itemValue.map((i) => i[optionValueField])) // 多选且不需要完整对象,只传递id
} else {
needItem
? onChange?.(itemValue ? JSON.parse(itemValue) : null) // 单选且需要完整对象
: onChange?.(
itemValue ? JSON.parse(itemValue)?.[optionValueField] : null,
) // 单选不需要完整对象,只传递id
}
}
/**
*
* @param option 渲染的每一项
* @returns 自定义渲染的返回格式
*/
const getSearchOptionLabel = (option) => {
// 自定义函数渲染
if (typeof optionLabelField === 'function') {
return optionLabelField?.(option)
} else if (
// 自定义模版渲染
optionLabelField?.includes('{{') &&
optionLabelField?.includes('}}')
) {
return handleTemplate(optionLabelField, option)
} else {
return option[optionLabelField]
}
}
/**
* 模板处理
* 例如template为 {{name}}-{{id}}
* 那name和id字段请传入params中
*/
const handleTemplate = (template, params) =>
template.replace(/\{\{(\w+)\}\}/g, ($1, $2) =>
$2 in params ? params[$2] : $1,
)
return (
<Select
{...elementProps}
showSearch
filterOption={false}
onSearch={onSearch}
value={selectedValue}
onChange={onSelected}
getPopupContainer={(triggerNode) => triggerNode.parentNode}
>
{optionList.map((option, index) => {
return (
<Option key={`${index}`} value={JSON.stringify(option)}>
{getSearchOptionLabel(option)}
</Option>
)
})}
</Select>
)
}
NumberRange 组件
import React, { Component } from 'react'
import {InputNumber} from 'antd'
const DEFAULT_FROM_FIELD = 'from'
const DEFAULT_TO_FIELD = 'to'
export default class NumberRange extends Component {
constructor(props) {
super(props)
const { fromField, toField, value, isCanEqual = true } = props
this.state = {
fromValue: value?.[fromField],
toValue: value?.[toField],
}
}
numChanged = (type, num) => {
const { fromValue, toValue } = this.state || {}
const { onChange, fromField, toField,isCanEqual } = this.props || {}
// 最小值改变
if (type === DEFAULT_FROM_FIELD) {
let fromValue
if(toValue){
isCanEqual?(fromValue = num > toValue ? '' : num):(fromValue = num >= toValue ? '' : num)
} else {
fromValue = num
}
this.setState({
fromValue,
})
onChange?.({ [fromField]: fromValue, [toField]: toValue })
}
// 最大值改变
if (type === DEFAULT_TO_FIELD) {
let toValue
if(fromValue){
isCanEqual?(toValue = num < fromValue ? '' : num):(toValue = num <= fromValue ? '' : num)
} else {
toValue = num
}
this.setState({
toValue,
})
onChange?.({ [fromField]: fromValue, [toField]: toValue })
}
}
render() {
const { fromProps, toProps, middleStr = '-' } = this.props
const { fromValue, toValue } = this.state
return (
<div className='input-number-continer'>
<InputNumber
{...fromProps}
value={fromValue}
onChange={(value) => this.numChanged(DEFAULT_FROM_FIELD, value)}
className='input-number'
/>
<span> {middleStr} </span>
<InputNumber
className='input-number'
{...toProps}
value={toValue}
onChange={(value) => this.numChanged(DEFAULT_TO_FIELD, value)}
/>
</div>
)
}
}
样式
.service-table-form{
.contioner{
display: flex;
.groupTitle{
width: 60px;
margin: 6px 20px 0 0;
}
.groupTitle::before{
content: '';
border-left: 4px solid #2e7bfd;
padding: 3px 5px;
}
}
}
.input-number-continer{
display: flex;
.input-number{
flex: 1;
}
}
使用示例
import React from 'react'
import {Button} from 'antd'
import TableForm from './App'
const bb=[{
id:'a',
name:'hah'
},
{
id:'aa',
name:'haha'
}]
const aa = [
{
groupTitle: '门店',
component: [
{
label: '门店名称/ID',
field: 'keyword',
type: 'searchSelect',
selectProps: {
elementProps: {
allowClear: true,
placeholder: '请输入门店名称/ID',
},
req: {
url: '/apiwg/bdop/sjst.m.bdop/BdopThriftService/queryPois',
method: 'GET',
field: 'keyword',
getter: 'poiItemTOs',
params: (keyword) => {
return {
queryPoiReq: {
queryPoiConditionTO: {
keyword,
sort: 'created_time',
scope: 'all',
},
pageTO: {
currentPage: 1,
pageSize: 10,
},
},
}
},
},
optionLabelField: '{{poiName}}({{poiId}})',
optionValueField: 'poiId',
},
},
{
type: 'extra',
field: 'org',
label: '组织',
},
],
},
{
groupTitle: '意向',
component: [
{
type: 'select',
label: '创建来源',
field: 'createType',
selectProps: {
elementProps: {
allowClear: true,
placeholder: '请选择',
},
list: bb,
optionValueField: 'id',
optionLabelField: 'name',
},
},
{
type: 'select',
label: '意向来源',
field: 'intentionSource',
selectProps: {
elementProps: {
allowClear: true,
placeholder: '请选择',
},
list: bb,
optionValueField: 'id',
optionLabelField: 'name',
},
},
{
type: 'timerange',
label: '创建时间',
field: 'createdTimeRange',
},
{
type: 'timerange',
label: '更新时间',
field: 'modifiedTimeRange',
},
{
type: 'timerange',
label: '进阶时间 (C→AB)',
field: 'saleStageAdvancedABTimeRange',
},
{
type: 'NumberRange',
label: '当前阶段停留天数',
props: {
fromProps: {
placeholder: '请输入整数',
min: 0,
precision: 0,
},
toProps: {
placeholder: '空代表无上限',
min: 0,
precision: 0,
},
fromField: 'low',
toField: 'high',
isCanEqual: true,
},
field: 'saleStageRetentionDayCntRange',
},
{
type: 'NumberRange',
label: '当前阶段拜访次数',
props: {
fromProps: {
placeholder: '请输入整数',
min: 0,
precision: 0,
},
toProps: {
placeholder: '空代表无上限',
min: 0,
precision: 0,
},
fromField: 'low',
toField: 'high',
isCanEqual: true,
},
field: 'saleStageActivityCntRange',
},
{
type: 'screenPlus',
field: 'saleChanceInfo',
label: '意向信息',
props: {
dataArr: [
{
childrenArr: [
{
renderType: 'RangePicker',
fieldProps: {
name: 'expectedDealTimeRange',
label: '预计成交日期',
},
componentProps: {
style: {
width: 520,
},
},
},
],
},
],
},
},
],
},
{
groupTitle: '点评',
component: [
{
type: 'searchSelect',
label: '点评人',
field: 'commentator',
selectProps: {
elementProps: {
allowClear: true,
placeholder: '请输入姓名/MIS',
},
req: {
url: '/apiwg/bdopthriftservice/queryEmpsByUsername',
method: 'GET',
field: 'username',
getter: 'employees',
},
optionLabelField: '{{userName}}({{login}})',
optionValueField: 'employeeId',
},
},
{
type: 'select',
label: '意向点评状态',
field: 'saleChanceCommentStatus',
selectProps: {
elementProps: {
allowClear: true,
placeholder: '请选择',
},
list:bb,
optionValueField: 'id',
optionLabelField: 'name',
},
},
{
type: 'select',
label: '当前阶段点评状态',
field: 'saleStageCommentStatus',
selectProps: {
elementProps: {
allowClear: true,
placeholder: '请选择',
},
list: bb,
optionValueField: 'id',
optionLabelField: 'name',
},
},
{
type: 'timerange',
label: '最近点评时间',
field: 'lastCommentTimeRange',
},
],
},
{
groupTitle: '待办',
component: [
{
type: 'select',
label: '待办状态',
field: 'taskStatus',
selectProps: {
elementProps: {
allowClear: true,
placeholder: '请选择',
},
list: bb,
optionValueField: 'id',
optionLabelField: 'name',
},
},
// {
// type: 'select',
// label: '待办类型',
// field: 'taskType',
// selectProps: {
// elementProps: {
// allowClear: true,
// placeholder: '请选择',
// },
// list: data.enumMap?.taskType,
// optionValueField: 'id',
// optionLabelField: 'name',
// },
// },
// {
// type: 'select',
// label: '检核目标',
// field: 'taskTargetType',
// selectProps: {
// elementProps: {
// allowClear: true,
// placeholder: '请选择',
// },
// list: data.enumMap?.taskTargetType,
// optionValueField: 'id',
// optionLabelField: 'name',
// },
// },
// {
// type: 'select',
// label: '检核目标结果',
// field: 'taskTargetResult',
// selectProps: {
// elementProps: {
// allowClear: true,
// placeholder: '请选择',
// },
// list: data.enumMap?.taskTargetStatus,
// optionValueField: 'id',
// optionLabelField: 'name',
// },
// },
{
type: 'timerange',
label: '有效期',
field: 'taskValidTimeRange',
},
{
type: 'timerange',
label: '创建时间',
field: 'taskCreateTimeRange',
},
{
type: 'timerange',
label: '处理时间',
field: 'taskHandleTimeRange',
},
],
},
]
export default function Test() {
return (
<TableForm
// ref={this.formRef}
fields={aa}
// submit={this.submit}
// initialValues={initialValues}
extraFields={{
// org: (
// <OrgTreeSelect
// mode={ORGTREESELECT_TYPE.ORG_EMP}
// inputProps={{
// onChangeCum: () => {},
// }}
// ORGType={17}
// multiple={false}
// isShowSearch={true}
// searchMode={3}
// onlySubordinate={true}
// />
// ),
}}
extraBtns={[
<Button
key="rest"
type="primary"
style={{
margin: '0 20px',
}}
// onClick={this.reset}
>
重置
</Button>,
]}
></TableForm>
)
}