自定义表单

31 阅读2分钟

我们在写自定义表单的时候,首选明确功能

  1. 表单子元素的所有类型
  2. 表单子元素的增删,赋值
  3. 表单子元素如果是一个符合元素(多选 单选)那么这个元素的增删改
  4. 表单是在展示状态还是在可以修改状态
  5. 还有一点需要注意的是渲染列表的时候需要绑定key并且key需要唯一 ,因为react的diff算法,不然删除元素的时候就会出现不论删除的是哪个元素出现的结果都是删除最后一个元素,

表单子元素的所有类型

目前表单有四种类型 头像上传、输入框、单选按钮、多选按钮

export enum ConfigType {
    IMG = 'IMG',
    INPUT = 'INPUT',
    SINGLE = 'SINGLE',
    MULE = 'MULE',
}

function RenderConfig({ type, index, title, arr, changeItemValue, display }: any) {

    const [editItem, setEditItem] = useState(false)
    const [img, setImg] = useState(nurseItemImg)
    const [input, setInput] = useState('')
    const [radioValue, setRadioValue] = useState()
    const [checkArr, setCheckArr] = useState<any>([])

    const imgFinish = (img: any) => {
        setImg(img)
        changeItemValue({ type: EditType.CHANGEVALUE, index, changeValue: img })
    }

    const onInput = (e: any) => {
        const value = e.target.value
        changeItemValue({ type: EditType.CHANGEVALUE, index, changeValue: value })
    }

    const onRadioChange = (e: any) => {
        console.log(e.target.value)
        const value = e.target.value
        setRadioValue(value)
        changeItemValue({
            type: EditType.CHANGEVALUE, index, changeValue: value
        })
    }

    let allCheckBoxArr: any = []

    if (arr && type == ConfigType.MULE) {
        allCheckBoxArr = arr.map((item: any) => item.value)
    }

    const checkAll = checkArr.length == allCheckBoxArr.length

    const onCheckAllChange: CheckboxProps['onChange'] = (e) => {
        const arr = e.target.checked ? allCheckBoxArr : []
        setCheckArr(arr);
        changeItemValue({
            type: EditType.CHANGEVALUE, index, changeValue: arr
        })
    };

    const itemChange = (e: any, value: any) => {
        console.log(e.target.checked)
        const checked = e.target.checked
        let arr = [...checkArr]
        if (checked) {
            arr.push(value)
        } else {
            arr = arr.filter((item) => item != value)
        }
        setCheckArr(arr)
        changeItemValue({
            type: EditType.CHANGEVALUE, index, changeValue: arr
        })
    }



    switch (type) {
        case ConfigType.IMG:
            return <Card display={display} changeItemValue={changeItemValue} type={type} index={index} title={`${index + 1}.${title}(上传图片)`}>
                <ImgUpload img={img} finish={(img: any) => {
                    imgFinish(img)
                }} />
            </Card>;
        case ConfigType.INPUT:
            return <Card display={display} changeItemValue={changeItemValue} type={type} index={index} title={`${index + 1}.${title}(输入)`}>
                <Input onChange={(e) => {
                    onInput(e)
                }} className='bg-[#F5F8FA]' placeholder='请输入文字' />
            </Card>;
        case ConfigType.SINGLE:
            if (display) {
                return <Card display={display} changeItemValue={changeItemValue} type={type} index={index} editItem={editItem} setEditItem={setEditItem} title={`${index + 1}.${title}(单选)`}>
                    <Radio.Group onChange={onRadioChange} value={radioValue}>
                        <Space className='w-full' direction="vertical">
                            {
                                arr ? arr.map((item: nurseSelectItemParam, itemIndex: number) => {
                                    return (
                                        <div className='relative flex'>
                                            <div className='flex'>
                                                <Radio className='pl-[5px]' value={item.value}>{item.value}</Radio>
                                            </div>
                                        </div>
                                    )

                                }) : ''
                            }
                        </Space>
                    </Radio.Group>
                </Card>;
            } else {
                return <Card changeItemValue={changeItemValue} type={type} index={index} editItem={editItem} setEditItem={setEditItem} title={`${index + 1}.${title}(单选)`}>
                    <Radio.Group onChange={onRadioChange} value={radioValue}>
                        <Space className='w-full' direction="vertical">

                            {
                                arr ? arr.map((item: nurseSelectItemParam, itemIndex: number) => {
                                    return (
                                        <div className='relative flex items-center justify-center'>
                                            <input
                                                key={item.id}
                                                value={item.value}
                                                onChange={(e) => {
                                                    const value = e.target.value
                                                    changeItemValue({ type: EditType.EDIT, value, index, itemIndex })
                                                }}
                                                className='w-full leading-5 text-base py-[8px] rounded-[10px] pl-[25px]' type="text" placeholder={`输入${nurseItemIncludeChild[0].text}选项`} />
                                            <div className='flex justify-between items-center'>
                                                <Radio className='absolute left-[5px]' value={item.value}></Radio>
                                                {editItem ? <div className='flex absolute right-[5px]'>
                                                    {itemIndex == arr.length - 1 ?
                                                        <div onClick={() => {
                                                            changeItemValue({ type: EditType.ADDITEM, index, itemIndex })
                                                        }} className='mr-[10px]'>
                                                            <img src={nurseItemAdd} className='w-[16px]' alt="" />
                                                        </div> : ''}
                                                    <div onClick={() => {
                                                        changeItemValue({ type: EditType.DELETEITEM, index, itemIndex })
                                                    }}>
                                                        <img src={nurseItemDelete} className='w-[16px]' alt="" />
                                                    </div>

                                                </div> : ''}
                                            </div>
                                        </div>
                                    )

                                }) : ''
                            }
                        </Space>
                    </Radio.Group>
                </Card>;
            }

        case ConfigType.MULE:
            if (display) {
                return <Card display={display} changeItemValue={changeItemValue} type={type} index={index} editItem={editItem} setEditItem={setEditItem} title={`${index + 1}.${title}(多选)`}>
                    <Space direction="vertical">
                        <Checkbox className='pl-[5px]' onChange={onCheckAllChange} checked={checkAll}>全选</Checkbox>
                        {
                            arr ? arr.map((item: nurseSelectItemParam, itemIndex: number) => {
                                return (
                                    <div className='relative'>
                                        <div className='flex pl-[5px]'>
                                            <Checkbox onChange={(e) => { itemChange(e, item.value) }} className=' left-[5px]' checked={checkArr.includes(item.value)}>{item.value}</Checkbox>
                                        </div>
                                    </div>
                                )
                            }) : ''
                        }
                    </Space>
                </Card>;
            } else {
                return <Card changeItemValue={changeItemValue} type={type} index={index} editItem={editItem} setEditItem={setEditItem} title={`${index + 1}.${title}(多选)`}>
                    <Space direction="vertical">
                        <Checkbox className='pl-[5px]' onChange={onCheckAllChange} checked={checkAll}>全选</Checkbox>
                        {
                            arr ? arr.map((item: nurseSelectItemParam, itemIndex: number) => {
                                return (
                                    <div className='relative flex items-center justify-center'>
                                        <input
                                            key={item.id}
                                            value={item.value}
                                            onChange={(e) => {
                                                const value = e.target.value
                                                changeItemValue({ type: EditType.EDIT, value, index, itemIndex })
                                            }}
                                            className='w-full leading-5 text-base py-[8px] rounded-[10px] pl-[25px]' type="text" placeholder={`输入${nurseItemIncludeChild[1].text}选项`} />
                                        <div className='flex justify-between items-center'>
                                            <Checkbox onChange={(e) => { itemChange(e, item.value) }} className='absolute left-[5px]' checked={checkArr.includes(item.value)}></Checkbox>
                                            {editItem ? <div className='flex absolute right-[5px]'>
                                                {itemIndex == arr.length - 1 ?
                                                    <div onClick={() => {
                                                        changeItemValue({ type: EditType.ADDITEM, index, itemIndex })
                                                    }} className='mr-[10px]'>
                                                        <img src={nurseItemAdd} className='w-[16px]' alt="" />
                                                    </div> : ''}
                                                <div onClick={() => {
                                                    changeItemValue({ type: EditType.DELETEITEM, index, itemIndex })
                                                }}>
                                                    <img src={nurseItemDelete} className='w-[16px]' alt="" />
                                                </div>

                                            </div> : ''}
                                        </div>
                                    </div>
                                )
                            }) : ''
                        }
                    </Space>
                </Card>;
            }
        default:
            return null;
    }
}

Card组件

interface cardParam {
    children: any
    title: string
    changeItemValue: Function
    index: number
    type: string
    editItem?: boolean
    setEditItem?: Function
    display?: boolean
}

function Card(props: cardParam) {
    const { children, title, changeItemValue, type, index, editItem, setEditItem, display } = props

    const deleteItems = () => {
        changeItemValue({ index, type: EditType.DELETE })
    }
    const isMobile = useGetWindowSize()

    // 展示表单的页面
    if (props.hasOwnProperty("display") && display) {
        if (isMobile) {
            return <div className='p-[16px] pb-[15px] bg-[#fff] w-full m-auto rounded-[10px] mb-[16px]'>
                <CommonTitle name={title} type='rect' />
                <div className='flex flex-col ml-[9px] mb-[20px]'>
                    {children}
                </div>
            </div>
        } else {
            return <><CommonTitle name={title} type='rect' />
                <div className='flex flex-col ml-[9px] mb-[20px]'>
                    {children}
                </div>
            </>
        }
    }
    // 编辑的表单页面
    else {
        return <div className='p-[16px] bg-[#fff] w-full m-auto rounded-[10px] mb-[16px]'>
            <div className='flex items-center justify-between font-bold mb-[13px] text-base'>
                {title}
                <div className='flex text-base text-[#0072EF] font-normal'>
                    {props.hasOwnProperty("editItem") ? <div onClick={() => {
                        console.log(editItem)
                        setEditItem && setEditItem(!editItem)
                    }} className='mr-[15px]'>编辑</div> : ''}
                    <div className='flex items-center' onClick={() => { deleteItems() }}>
                        <img src={nurseSelectItemDelete} className='w-[20px]' alt="" /></div>
                </div>
            </div>
            <div className='flex flex-col'>
                {children}
            </div>
        </div>
    }


}

加载表单组件

interface nurseSelectItemParam {
    value: string
    id: number
    // changeValue?: any
}
interface nurseConfigParam {
    title: string
    type: ConfigType
    arr?: Array<nurseSelectItemParam>
    id: number
    changeValue: any
}
    interface preViewConfigParam {
    // deleteItem: Function
    display: boolean
    setNurseConfig: Function
    nurseConfig: Array<nurseConfigParam>
}
export function PreViewConfig(props: preViewConfigParam) {
    // 
    const changeItemValue = ({ type, index, itemIndex, value, changeValue }: any) => {
        switch (type) {
            case EditType.DELETE:
                {
                    const newConfig = [...nurseConfig].filter((item, itemIndex) => itemIndex != index);
                    setNurseConfig(newConfig);
                    return;
                }
            case EditType.ADDITEM:
                {
                    const newConfig = [...nurseConfig]
                    newConfig[index].arr?.push({ value: '', id: new Date().getTime() })
                    setNurseConfig(newConfig);
                    return;
                }
            case EditType.DELETEITEM:
                {
                    const newConfig = [...nurseConfig]
                    let changeItem = newConfig[index]
                    if (changeItem.arr) {
                        changeItem.arr = changeItem.arr.filter((item, changeIndex) => changeIndex != itemIndex)
                    }
                    newConfig[index] = changeItem
                    console.log(newConfig, changeItem, index, itemIndex)
                    setNurseConfig(newConfig);
                    return
                }
            case EditType.EDIT: {
                const newConfig = [...nurseConfig]
                const changeArr = newConfig[index].arr
                if (changeArr) {
                    changeArr[itemIndex].value = value
                    newConfig[index].arr = changeArr
                    setNurseConfig(newConfig);
                }
                return
            }
            case EditType.CHANGEVALUE: {
                const newConfig = [...nurseConfig]
                newConfig[index].changeValue = changeValue
                setNurseConfig(newConfig);
                return
            }
            case EditType.CHNAGESELETEVALUE: {
                return
            }
            default:
                return null
        }
    }
    const { display, setNurseConfig, nurseConfig } = props

    return (
        <div className={`${display ? '' : 'bg-[#f4f5f6]'} `}>
            {
                nurseConfig.map((item, index) => {
                    return <RenderConfig display={display} changeItemValue={changeItemValue} key={item.id} type={item.type} index={index} title={item.title} arr={item.arr} />
                })
            }
        </div>
    )
}

页面挂载

    <PreViewConfig display={false} nurseConfig={nurseConfig} setNurseConfig={setNurseConfig} />