继画布区的实现之后,来到本系列的第四篇文章,如果你没有看过之前的文章,可以建议先看一下第一篇文章,里面介绍了要实现的项目,是否是你要学习的内容,再决定是否要学习这一些列的文章。
从零实现一套低代码(保姆级教程) — 【1】初始化项目,实现左侧组件列表
在上一篇中,我们实现了画布区的渲染,同时也支持了组件在画布区的随意拖拽布局。
所以我们实现出的低代码项目,在画布区的布局方式是自由布局。
同时在上一篇的结尾,也把Input组件的实现补充了(没有在文章体现,在GitHub上的提交记录可以看到)。
目前我们的项目是长这个样子的。
那如果我们只能拖拽组件,并不能对组件进行修改,那这个低代码就很无用了,所以这一篇。我们主要来实现右侧属性面板。
能够通过可视化的配置,来修改组件的属性。比如我想通过一个开关,来控制按钮的显示与隐藏,通过一个输入框,来控制按钮的文本。
OK,垃圾话就不说太多了,我们开始实现这一部分内容。
现在请读者打开rightPart下的index.tsx文件;
对于右侧的属性面板,我们可以参考左侧的面板,样式都一样,只不过定位后,属性面板的right值应该为0。
同时我们要思考一个问题,在项目的后期,我们可能不止可以修改组件的属性,也可以修改组件的样式,或者其他配置。所以我们可以在右侧的面板上,实现一个多页签。当然现在,我们只实现属性的面板。
import './index.css'
import { Tabs } from 'antd';
import type { TabsProps } from 'antd';
export default function RightCom() {
const getAttributePanel = () => {
return <div></div>
}
const items: TabsProps['items'] = [
{
key: 'attributePanel',
label: <div style={{fontSize:'18px',width:'100px',textAlign:'center'}}>属性</div>,
children: getAttributePanel(),
},
{
key: 'stylePanel',
label: <div style={{fontSize:'18px',width:'100px',textAlign:'center'}}>样式</div>,
children: 'Content of Tab Pane 2',
}
];
const onChange = () => {
}
return (
<div className='rightCom'>
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
</div>
)
}
.rightCom {
width: 20%;
position: absolute;
right: 0;
top: 10%;
background-color: white;
height: 90%;
}
我们实现出来的效果就是:
写到现在,是不是已经有点低代码的样子了,已经像回事了是吧。不要开心太早,后面还有很多的内容需要写。。。。 ̄□ ̄||
那我们想一下,对于右侧的属性面板。我们在什么时机去展示呢?
在网站的例子上,大家可以尝试一下,我在第一版实现的时候,是通过右键组件,然后选择设置属性,就会展示属性面板。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
XinBuilder 点击跳转
但是我觉得这种方式不是很友好,很多第一次打开的人不知道。所以在这个版本我准备换一种实现思路,点击组件的时候直接触发属性面板的显示。
OK,那我们现在就要回到mainPart中了,我的点击事件写在哪里呢?记得之前拖拽的事件写在哪了吗,我们就写在那里!!!!但是为了结构比较清晰,还是在外面包一层div用来转本展示选中的样式。
第二个问题是,在画布区我们怎么知道选中的是哪个组件呢?我们可以将组件的border设置为蓝色,这样我们就可以知道选中的是哪一个组件了!
这个时候,为了保证组件的唯一性,你就会发现,我们必须要给组件一个ID了,我们用时间戳来给组件一个comId。interface ComJson {
comType: string,
// 组件的唯一ID
comId: string,
style?: any
}
// 当前选中节点的comId
const [selectId, setSelectId] = useState<string>('')
const onDrop = (e: any) => {
// 其他代码
if(window.nowCom === 'renderCom' && dragCom && dragCom.style) {
// 其他代码
}else{
// 其他代码
comList.push({
comType: window.nowCom,
style,
// 通过时间戳生成comId
let comId = `comId_${Date.now()}`
comList.push({
comType: window.nowCom,
style,
comId
})
setSelectId(comId)
})
}
setComList([...comList])
}
const selectCom = (com: ComJson) => {
return () => {
// 点击事件设置选中节点的ID
setSelectId(com.comId)
}
}
return (
<div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>
{
comList.map(com => {
const Com = components[com.comType as keyof typeof components];
// 这里可以把key加上了
return <div key={com.comId} onClick={selectCom(com)} draggable onDragStart={onDragStart(com)}>
// 触发点击事件,新加的div
<div className={com.comId === selectId ? 'selectCom' : ''} style={com.style}>
<Com />
</div>
</div>
})
}
</div>
)
.selectCom{
border: 0.5px solid #1677ff;
}
这里一定要注意的是,我把Com的style提到了外层div上,因为这个style里面包含的内容主要是和位置相关的,我们没必要将其挂载在组件上。一定要修改一下,不然你的项目会有问题的,一拖拽就把其他的元素带起来
现在我们就实现了画布区节点选中的状态。
当我们在画布区选中某个节点的时候,要怎么通知右侧属性面板展示对应组件的属性配置呢?
这就涉及到跨组件传递数据的内容了。
其实从左侧的组件到画布区这一个过程,我就提到过,后面我们会用redux来进行状态管理,而全量的组件,正适合在redux中进行管理。但是和redux相关的内容,我会专门用一章节去实现,所以现在,我们依旧先挂载在window上。
const selectCom = (com: ComJson) => {
return () => {
setSelectId(com.comId);
// 挂载在window上,后面会使用redux进行替换
window.renderCom = com;
window.comList = comList;
window.setComList = setComList
}
}
我们先将当前组件,以及触发画布区重绘的方法挂载在window上。
现在我们回到rightPart下。我们在右侧面板先只展示一个输入框,那这个输入框用来干什么呢?用来更改按钮的文字!!!!!
const getAttributePanel = () => {
return <div>
<div className='attributeItem'>
<label>按钮文字:</label>
<div className='attributeItemValue'>
<Input onChange={changeComAttribute} />
</div>
</div>
</div>
}
const changeComAttribute = () => {
}
.attributeItem{
width: 200px;
height: 60px;
display: flex;
justify-content: space-between;
}
OK。现在我们在onchange方法里面,更改当前组件的按钮文字。这里我们起一个caption名称
const changeComAttribute = (e:any) => {
window.renderCom.caption = e.target.value;
window.setComList([...window.comList])
}
更改后,我们要在mainPart中,将这个属性传递给组件Com。
在mainPart下的index.tsx中:
<div onDrop={onDrop} onDragOver={onDragOver} onDragEnter={onDragEnter} className='mainCom'>
{
comList.map(com => {
const Com = components[com.comType as keyof typeof components];
return <div key={com.comId} onClick={selectCom(com)} draggable onDragStart={onDragStart(com)}>
<div className={com.comId === selectId ? 'selectCom' : ''} style={com.style}>
// 直接通过结构,将属性传给Com
<Com {...com}/>
</div>
</div>
})
}
</div>
最后我们来到组件:
import { Button as AntButton } from 'antd'
export default function Button(props: any) {
const {caption} = props
return (
<div>
<AntButton>{caption || '按钮'}</AntButton>
</div>
)
}
拿到caption后,去渲染按钮的文字。
这里要注意的是,因为刚刚我把style属性提到了外层的div上,所以在组件这里,就不要再接受style了!!!!!!!!!!!!!!!!
OK,到这里,右侧的属性面板,就可以更改按钮的文字了。
本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第四节:实现右侧属性面板
如果可以的话,可以给博主的GitHub点亮一颗小星星(?▽?)
本篇文章主要是为了帮助大家串通属性面板和组件之间的机制,主要还是清楚右侧改变的属性是如何映射到组件上的。
而在下一篇,我会主要实现右侧属性面板。比如,对于按钮来说,需要什么样的属性配置,对于Input框来说,需要什么样的属性配置。应该怎么去写代码,都会在下一章节继续完善。