外观
第77章—低代码编辑器:拖拽优化、Table组件
2131字约7分钟
2024-09-19
在 amis 编辑器里,物料拖动到画布区后,还可以拖动改变位置:
现在我们的编辑器没有支持拖动改变位置:
我们来实现下:
其实这个也很简单,就是给物料也加上 useDrag 就可以了。
比如给 Button 加一下:
const [_, drag] = useDrag({
type: 'Button',
item: {
type: 'Button'
}
});
现在是能拖动了,但是和从物料区拖过来的 drop 逻辑一样,都是新增组件。
我们得区分下两者。
加上 dragType 属性,然后带上当前拖拽的组件 id:
在 useDrop 的时候判断下 dragTag,如果是 move,那就先 delete 再 add
import { useDrop } from "react-dnd";
import { useComponentConfigStore } from "../stores/component-config";
import { getComponentById, useComponetsStore } from "../stores/components";
export interface ItemType {
type: string;
dragType?: 'move' | 'add',
id: number
}
export function useMaterailDrop(accept: string[], id: number) {
const { addComponent, deleteComponent, components } = useComponetsStore();
const { componentConfig } = useComponentConfigStore();
const [{ canDrop }, drop] = useDrop(() => ({
accept,
drop: (item: ItemType, monitor) => {
const didDrop = monitor.didDrop()
if (didDrop) {
return;
}
if(item.dragType === 'move') {
const component = getComponentById(item.id, components)!;
deleteComponent(item.id);
addComponent(component, id)
} else {
const config = componentConfig[item.type];
addComponent({
id: new Date().getTime(),
name: item.type,
desc: config.desc,
props: config.defaultProps
}, id)
}
},
collect: (monitor) => ({
canDrop: monitor.canDrop(),
}),
}));
return { canDrop, drop }
}
测试下:
这样就实现了拖拽改变位置。
在 Container 组件也加上 useDrag:
这里因为要同时给 div 绑定 drag、drop 的处理,所以用 useRef 拿到 ref 之后再绑定。
import { useDrag } from 'react-dnd';
import { useMaterailDrop } from '../../hooks/useMaterailDrop';
import { CommonComponentProps } from '../../interface';
import { useEffect, useRef } from 'react';
const Container = ({ id, name, children, styles }: CommonComponentProps) => {
const {canDrop, drop } = useMaterailDrop(['Button', 'Container'], id);
const divRef = useRef<HTMLDivElement>(null);
const [_, drag] = useDrag({
type: name,
item: {
type: name,
dragType: 'move',
id: id
}
});
useEffect(() => {
drop(divRef);
drag(divRef);
}, []);
return (
<div
data-component-id={id}
ref={divRef}
style={styles}
className={`min-h-[100px] p-[20px] ${ canDrop ? 'border-[2px] border-[blue]' : 'border-[1px] border-[#000]'}`}
>{children}</div>
)
}
export default Container;
接下来我们加一下 Table 的物料组件:
materials/Table/dev.tsx
import { Table as AntdTable } from 'antd';
import React, { useEffect, useMemo, useRef } from 'react';
import { CommonComponentProps } from '../../interface';
import { useMaterailDrop } from '../../hooks/useMaterailDrop';
import { useDrag } from 'react-dnd';
function Table({ id, name, children, styles }: CommonComponentProps) {
const {canDrop, drop } = useMaterailDrop(['TableColumn'], id);
const divRef = useRef<HTMLDivElement>(null);
const [_, drag] = useDrag({
type: name,
item: {
type: name,
dragType: 'move',
id: id
}
});
useEffect(() => {
drop(divRef);
drag(divRef);
}, []);
const columns = useMemo(() => {
return React.Children.map(children, (item: any) => {
return {
title: <div className='m-[-16px] p-[16px]' data-component-id={item.props?.id}>{item.props?.title}</div>,
dataIndex: item.props?.dataIndex,
key: item
}
})
}, [children]);
return (
<div
className={`w-[100%] ${canDrop ? 'border-[2px] border-[blue]' : 'border-[1px] border-[#000]'}`}
ref={divRef}
data-component-id={id}
style={styles}
>
<AntdTable
columns={columns}
dataSource={[]}
pagination={false}
/>
</div>
);
}
export default Table;
添加 drop、drag 的处理,用 antd 的 table 来渲染。
这里 columns 的处理比较巧妙:
我们拖拽 TableColumn 组件过来的时候,用 React.Children 遍历,把它变为 columns 配置。
当然,这个 TableColumn 组件还没写。
在 componentConfig 添加 Table 组件的配置:
Table: {
name: 'Table',
defaultProps: {},
desc: '表格',
setter: [
{
name: 'url',
label: 'url',
type: 'input',
},
],
dev: TableDev,
prod: TableDev
}
然后在 Page、Modal、Container 组件里支持下 Table 的 drop:
试一下:
没啥问题。
然后再实现下 TableColumn 组件:
materials/TableColumn/dev.tsx
const TableColumn = () => {
return <></>
}
export default TableColumn;
materials/TableColumn/prod.tsx
const TableColumn = () => {
return <></>
}
export default TableColumn;
这只是我们做 column 配置用的,不需要渲染内容。
在 ColumnConfig 加一下配置:
TableColumn: {
name: 'TableColumn',
desc: '表格列',
defaultProps: {
dataIndex:`col_${new Date().getTime()}`,
title: '列名'
},
setter: [
{
name: 'type',
label: '类型',
type: 'select',
options: [
{
label: '文本',
value: 'text',
},
{
label: '日期',
value: 'date',
},
],
},
{
name: 'title',
label: '标题',
type: 'input',
},
{
name: 'dataIndex',
label: '字段',
type: 'input',
},
],
dev: TableColumnDev,
prod: TableColumnProd,
}
试下效果:
我们用 TableColumn 组件来配置字段。
然后再来实现 Table 组件的 prod 版本:
materials/Table/prod.tsx
import { Table as AntdTable } from 'antd';
import dayjs from 'dayjs';
import React, { useEffect, useMemo, useState } from 'react';
import axios from 'axios';
import { CommonComponentProps } from '../../interface';
const Table = ({ url, children }: CommonComponentProps) => {
const [data, setData] = useState<Array<Record<string, any>>>([]);
const [loading, setLoading] = useState(false);
const getData = async () => {
if (url) {
setLoading(true);
const { data } = await axios.get(url);
setData(data);
setLoading(false);
}
}
useEffect(() => {
getData();
}, []);
const columns = useMemo(() => {
return React.Children.map(children, (item: any) => {
if (item?.props?.type === 'date') {
return {
title: item.props?.title,
dataIndex: item.props?.dataIndex,
render: (value: any) => value ? dayjs(value).format('YYYY-MM-DD') : null,
}
} else {
return {
title: item.props?.title,
dataIndex: item.props?.dataIndex,
}
}
})
}, [children]);
return (
<AntdTable
columns={columns}
dataSource={data}
pagination={false}
rowKey="id"
loading={loading}
/>
);
}
export default Table;
生产环境的 Table 需要请求 url,拿到数据后设置到 table。
并且渲染列的时候,如果是 date,要用 dayjs 做下格式化。
安装下用到的包:
npm install --save axios
npm install --save dayjs
改下 componentConfig 里的组件:
试一下:
可以看到,确实发请求了。
只不过现在没这个接口。
我们用 nest 创建一个后端服务:
npx @nestjs/cli new lowcode-demo-backend
改下 AppController,加一个接口:
@Get('data')
data() {
return [
{ name: '光光', sex: '男', birthday: new Date('1994-07-07').getTime() },
{ name: '东东', sex: '男', birthday: new Date('1995-06-06').getTime() },
{ name: '小红', sex: '女', birthday: new Date('1996-08-08').getTime() }
]
}
在 main.ts 开启跨域:
把服务跑起来:
npm run start:dev
浏览器访问下:
这样接口就有了。
我们再来试下 Table 组件:
添加三个 TableColumn,配置下字段。
然后在 Table 配置下 url:
再点击预览:
这样,Table 组件就会请求 url,然后根据配置渲染表格
案例代码上传了小册仓库,可以切换到这个 commit 查看:
git reset --hard 3df08cf3e09d69817f1bc75bf1b0f9f5e8cb41c4
总结
这节我们实现了物料组件拖拽改变位置,并实现了 Table 组件。
拖拽改变位置只要在物料组件上加上 useDrag 就可以了,要注意区分 add 和 move 的情况,加上标识,分别做处理。
Table 组件可以配置 url,然后拖拽 TableColumn 进来,TableColumn 可以配置字段信息。
Preview 渲染的时候,根据 url 请求接口,然后根据 columns 的配置来渲染数据。
这样,Table 的物料组件就完成了。