Index.js:
import React from 'react'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { Form, Button, Collapse, Col, Row } from 'antd'
import Header from './Header'
import useList from './useList'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import { Icon } from '../../../../components/light'
import { getComponentArr, getAttrFields } from './config'
import List from './List'
import BtnField from './BtnField'
const { Panel } = Collapse
function Index(props) {
const {
applicationTitle,
dataSource,
form,
formForAttr,
initValues,
initValuesForAttr,
tableId,
cardActiveId,
moveCard,
handleFinish,
handleFinishFailed,
handleAdd,
handleSave,
handleCardActiveId,
handleValuesChange,
handleDelete,
} = useList(props)
return (
<div className="m-admin-content">
<Header
applicationTitle={applicationTitle}
tableId={tableId}
onSave={handleSave}
></Header>
<div className="m-design-wrap">
<div className="m-design-sidebar">
<Collapse defaultActiveKey={['1', '2', '3']}>
<Panel header="通用字段" key="1">
<Row gutter={[2, 2]}>
<DndProvider backend={HTML5Backend}>
{getComponentArr().map((fieldInfo, index) => (
<BtnField key={index} fieldInfo={fieldInfo} onAdd={handleAdd} />
))}
</DndProvider>
</Row>
</Panel>
<Panel header="联系信息字段" key="2">
<Row gutter={[2, 2]}>
<Col span={8}>
<div className="m-component-item">
<div></div>
<div>敬请期待</div>
</div>
</Col>
</Row>
</Panel>
<Panel header="商品字段" key="3">
<Row gutter={[2, 2]}>
<Col span={8}>
<div className="m-component-item">
<div></div>
<div>敬请期待</div>
</div>
</Col>
</Row>
</Panel>
</Collapse>
</div>
<div className="m-design-content">
<Form
form={form}
labelCol={{ span: 4 }}
wrapperCol={{ span: 17 }}
initialValues={{ ...initValues }}
onFinish={handleFinish}
onFinishFailed={handleFinishFailed}
>
<DndProvider backend={HTML5Backend}>
<List
dataSource={dataSource}
cardActiveId={cardActiveId}
moveCard={moveCard}
handleCardActiveId={handleCardActiveId}
handleDelete={handleDelete}
/>
</DndProvider>
<Form.Item
wrapperCol={{ offset: 4, span: 17 }}
className="m-design-footer"
>
<Button type="primary" htmlType="submit" className="m-space">
<Icon name="submit" className="m-tool-btn-icon"></Icon>
提交
</Button>
<Button
className="m-space"
onClick={() => {
form.resetFields()
}}
>
<Icon name="reset" className="m-tool-btn-icon"></Icon>
重置
</Button>
</Form.Item>
</Form>
</div>
<div className="m-design-attr">
<Form
form={formForAttr}
labelCol={{ span: 8 }}
wrapperCol={{ span: 15 }}
initialValues={{ ...initValuesForAttr }}
scrollToFirstError={true}
onValuesChange={handleValuesChange}
id="m-set-application-modal-form"
className="m-set-application-modal-form"
>
{getAttrFields()}
</Form>
</div>
</div>
</div>
)
}
const mapStateToProps = (state) => {
return {}
}
const mapDispatchToProps = (dispatch) => {
return {
onSetState(key, value) {
dispatch({ type: 'SET_LIGHT_STATE', key, value })
},
onDispatch(action) {
dispatch(action)
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Index))
BtnField.js:
import { useDrag } from 'react-dnd'
import { ItemTypes } from './ItemTypes'
import { Icon } from '../../../../components/light'
import { Col } from 'antd'
export default function BtnField({ fieldInfo, onAdd }) {
const [{ isDragging }, drag] = useDrag(() => ({
type: ItemTypes.BTN_FIELD,
item: { ...fieldInfo },
end: (item, monitor) => {
const dropResult = monitor.getDropResult()
if (item && dropResult) {
console.log(`${item.title} 加入 ${dropResult.name}`)
onAdd({fieldInfo})
}
},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
handlerId: monitor.getHandlerId(),
}),
}))
const opacity = isDragging ? 0.4 : 1
return (
<Col span={8}>
<div
className="m-component-item"
ref={drag}
style={{ opacity }}
data-testid={`box-${fieldInfo.title}`}
onClick={() => onAdd({fieldInfo})}
>
<div>
<Icon name={fieldInfo.icon}></Icon>
</div>
<div>{fieldInfo.title}</div>
</div>
</Col>
)
}
config.js:
import { Form, Input, Button } from 'antd'
import { FieldRequired } from '../../../../components/light'
//表格列字段
const getColumns = (props) => {
return [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '字段名称',
dataIndex: 'title',
},
{
title: '英文名称',
dataIndex: 'dataIndex',
},
{
title: '表单组件名',
dataIndex: 'formComponentName',
render: (text) => {
return text ? text : '无'
},
},
{
title: '渲染函数名',
dataIndex: 'renderFunName',
render: (text) => {
return text ? text : '无'
},
},
{
title: '字段必填',
dataIndex: 'rules',
render: (text) => {
const result = Array.isArray(text) && text.length > 0 && text[0]
return result ? (result.required ? '是' : '否') : '否'
},
},
{
title: '表格展示',
dataIndex: 'isColumn',
render: (text) => {
return text ? '是' : '否'
},
},
// {
// title: '搜索',
// dataIndex: 'isSearch',
// render: (text) => {
// return text ? '是' : '否'
// },
// },
{
title: '添加/编辑',
dataIndex: 'isModalField',
render: (text) => {
return text ? '是' : '否'
},
},
{
title: '顺序号',
dataIndex: 'orderIndex',
render: (text) => {
return typeof text === 'number' ? text : '无'
},
},
{
title: '操作',
width: 220,
render: (record) => {
if (record.isSystem) {
return '系统字段'
} else {
return (
<div className="m-action">
<Button
className="m-action-btn"
size="small"
danger
onClick={() => props.onDelete(record)}
>
删除
</Button>
<Button
className="m-action-btn"
size="small"
onClick={() => props.onCheck(record)}
>
查看
</Button>
<Button
className="m-action-btn"
size="small"
onClick={() => props.onEdit(record)}
>
编辑
</Button>
</div>
)
}
},
},
]
}
//组件元素
const getComponentArr = () => {
return [
{
icon: 'input',
title: '单行文本',
formComponentName: "Input",
dataIndex: 'input',
renderFunName: "renderSpan"
},
{
icon: 'textarea',
title: '多行文本',
formComponentName: "TextArea",
dataIndex: 'textArea',
renderFunName: "renderSpan"
},
{
icon: 'number-input',
title: '数字',
formComponentName: "InputNumber",
dataIndex: 'inputNumber ',
renderFunName: "renderSpan"
},
]
}
//添加编辑查看对话框表单字段
const getAttrFields = () => {
return (
<>
<Form.Item
label="字段名称"
name="title"
rules={[
{
required: true,
message: '请输入字段名称!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="英文名称"
name="dataIndex"
rules={[
{
required: true,
message: '请输入字段名称!',
},
]}
>
<Input />
</Form.Item>
<Form.Item label="字段必填" name="rules">
<FieldRequired></FieldRequired>
</Form.Item>
</>
)
}
export { getColumns, getComponentArr, getAttrFields }
Header.js:
import React from 'react'
import { Button } from 'antd'
import { withRouter, Link } from 'react-router-dom'
import { Icon } from '../../../../components/light'
function Header(props) {
const { applicationTitle, tableId, onSave } = props
return (
<div className="m-design-header">
<div className="m-design-header-title">
<Icon
name="goback"
title="返回"
className="m-set-application-header-icon"
onClick={() => props.history.go(-1)}
></Icon>
<span title={applicationTitle}>{applicationTitle}</span>
</div>
<div className="m-design-header-middle"></div>
<div className="m-design-header-action">
<Button type="primary" onClick={onSave}>
保存
</Button>
<Link to={`/light/formview?id=${tableId}`} target="_blank" style={{display: 'inherit'}}>
<Button>预览</Button>
</Link>
</div>
</div>
)
}
export default withRouter(Header)
ItemTypes.js:
export const ItemTypes = {
LIST_ITEM: 'listItem',
BTN_FIELD: 'btnField' //'btnField',
}
List.js:
import { useDrop } from 'react-dnd'
import { ItemTypes } from './ItemTypes'
import ListItem from './ListItem'
export default function List({
dataSource,
cardActiveId,
moveCard,
handleCardActiveId,
handleDelete,
}) {
const [{ canDrop, isOver }, drop] = useDrop(() => ({
accept: ItemTypes.BTN_FIELD,
drop: () => ({ name: '容器' }),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
}))
const isActive = canDrop && isOver
return (
<div
ref={drop}
className={`m-center-list-wrap ${isActive ? 'active' : ''}`}
>
{dataSource.map((card, index) => (
<ListItem
key={card.id}
index={index}
cardActiveId={cardActiveId}
card={card}
moveCard={moveCard}
onCardActiveId={handleCardActiveId}
onDelete={handleDelete}
/>
))}
</div>
)
}
ListItem.js:
import { useRef } from 'react'
import { useDrag, useDrop } from 'react-dnd'
import { ItemTypes } from './ItemTypes'
import { Form, Input, Button } from 'antd'
import { getFormComponentArr } from '../../../../utils/tools'
export default function ListItem({
index,
cardActiveId,
card,
moveCard,
onCardActiveId,
onDelete,
}) {
const ref = useRef(null)
const [{ handlerId }, drop] = useDrop({
accept: ItemTypes.LIST_ITEM,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
}
},
hover(item, monitor) {
if (!ref.current) {
return
}
const dragIndex = item.index
const hoverIndex = index
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return
}
// Determine rectangle on screen
const hoverBoundingRect = ref.current?.getBoundingClientRect()
// Get vertical middle
const hoverMiddleY =
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
// Determine mouse position
const clientOffset = monitor.getClientOffset()
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return
}
// Time to actually perform the action
moveCard(dragIndex, hoverIndex)
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
item.index = hoverIndex
console.log(hoverIndex)
},
})
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.LIST_ITEM,
item: () => {
return { id: card.id, index }
},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
})
const opacity = isDragging ? 0 : 1
drag(drop(ref))
//console.log(card)
const renderDom = () => {
if (card.isModalField) {
const result = getFormComponentArr().find(
(componentItem) =>
componentItem.formComponentName === card.formComponentName
)
return (
<div
ref={ref}
style={{ opacity }}
data-handler-id={handlerId}
className={`m-design-card ${
cardActiveId === card.id ? 'active' : ''
}`}
onClick={() => onCardActiveId({ id: card.id })}
>
<div className="m-design-card-info">
<Form.Item
key={card.id}
label={card.title}
name={card.dataIndex}
rules={card.rules}
>
{result ? result.component : <Input></Input>}
</Form.Item>
</div>
<div className="m-design-card-action">
<Button
className="m-action-btn"
size="small"
danger
onClick={() => onDelete(card)}
>
删除
</Button>
</div>
</div>
)
} else {
return null
}
}
return <>{renderDom()}</>
}
useList.js:
import { useState, useEffect, useCallback } from 'react'
import Api from '../../../../api'
import { Modal, Form, message } from 'antd'
import update from 'immutability-helper'
import { getRouterSearchObj } from '../../../../utils/tools'
import { v4 as uuidv4 } from 'uuid'
const { confirm } = Modal
let currentDataSource = []
export default function useList(props) {
const [form] = Form.useForm()
const [formForAttr] = Form.useForm()
const [dataSource, setDataSource] = useState([])
const [applicationTitle, setApplicationTitle] = useState()
const [cardActiveId, setCardActiveId] = useState()
const [initValuesForAttr, setInitValuesForAttr] = useState({})
//获取路由参数
const routerSearchObj = getRouterSearchObj(props)
const tableId = routerSearchObj.id - 0
const addInitValues = {}
//搜索
const handleSearch = () => {
Api.light.fieldsSearch({ tableId }).then((res) => {
if (res.code === 200) {
let tempDataSource = res.data.fields.filter((item) => !item.isSystem)
setDataSource(tempDataSource)
setApplicationTitle(res.data.title)
if (Array.isArray(tempDataSource) && tempDataSource.length > 0) {
handleCardActiveId({
id: tempDataSource[0].id,
myDataSource: tempDataSource,
})
}
}
})
}
//拖动改变顺序
const moveCard = useCallback(
(dragIndex, hoverIndex) => {
const dragCard = dataSource[dragIndex]
setDataSource(
update(dataSource, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragCard],
],
})
)
},
[dataSource]
)
//添加新字段
const handleAdd = ({ fieldInfo }) => {
const orderIndexArr = currentDataSource.map((item) => item.orderIndex)
const orderIndex = Math.max.apply(Math, orderIndexArr) + 1
const id = uuidv4()
let tempValues = {
id,
dataIndex: `${fieldInfo.dataIndex}-${id}`,
isColumn: true,
isModalField: true,
orderIndex,
}
console.log({ ...fieldInfo, ...tempValues })
console.log(currentDataSource)
setDataSource([...currentDataSource, { ...fieldInfo, ...tempValues }])
}
//保存
const handleSave = () => {
console.log(dataSource)
const newDataSource = dataSource.map((item, index) => {
return { ...item, orderIndex: index + 1 }
})
console.log(newDataSource)
Api.light
.fieldsEditAll({ tableId, dataItem: newDataSource })
.then((res) => {
if (res.code === 200) {
message.success(res.message)
}
})
}
//删除
const handleDelete = (record) => {
console.log('删除, id:', record.id)
confirm({
title: '确认要删除吗?',
onOk() {
const newDataSource = dataSource.filter(item => item.id !== record.id)
setDataSource(newDataSource)
},
})
}
//添加或编辑
const handleFinish = (values) => {
console.log('Success:', values)
}
//校验失败
const handleFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo)
}
//设置当前card
const handleCardActiveId = ({ id, myDataSource = dataSource }) => {
setCardActiveId(id)
let currentItem = myDataSource.find((item) => item.id === id)
const rules =
Array.isArray(currentItem.rules) && currentItem.rules.length > 0
? currentItem.rules[0]
: {}
setInitValuesForAttr({ ...currentItem, rules })
}
//修改表单字段属性
const handleValuesChange = (changedValues, allValues) => {
const cardActiveIndex = dataSource.findIndex(
(item) => item.id === cardActiveId
)
let tempValues = {
rules: [allValues.rules],
}
dataSource[cardActiveIndex] = {
...dataSource[cardActiveIndex],
...allValues,
...tempValues,
}
setDataSource([...dataSource])
}
useEffect(() => {
formForAttr.resetFields()
// eslint-disable-next-line
}, [initValuesForAttr])
//挂载完
useEffect(() => {
handleSearch()
// eslint-disable-next-line
}, [])
//dataSource更新,同步更新currentDataSource,handleAdd函数中dataSource的值为空数组,这是一个bug
useEffect(() => {
currentDataSource = dataSource
}, [dataSource])
return {
form,
formForAttr,
initValuesForAttr,
dataSource,
applicationTitle,
addInitValues,
tableId,
cardActiveId,
handleSearch,
moveCard,
handleDelete,
handleFinish,
handleFinishFailed,
handleAdd,
handleSave,
handleCardActiveId,
handleValuesChange,
}
}