在上一篇中,我们简单实现了右侧属性面板。可以通过更改按钮文字,重新渲染Button组件。
目前来到了本系列的第五章,本篇主要是实现Button组件的全部属性配置。
如果你是第一次看本系列中的文章,建议可以先看一下第一节:
从零实现一套低代码(保姆级教程) — 【1】初始化项目,实现左侧组件列表
在第一节中,说了本系列会实现出什么样的低代码项目。同时对技术栈等相关因素进行了分析。但是本系列因为是对线上例子的重构,所以实现的可能会有所差异。
关于属性面板的配置,可能是整个系列中比较重要的。因为低代码主要的想法就是通过可视化的配置来决定页面的结构和样式。那么如果属性这一章节可以通透起来,后面的样式,动作等配置都是手到擒来了。
OK,话不多说,咱们开始继续实现。
我们在上一节,只是简单实现了按钮文字的配置。那思考一个问题,对于不同的组件来说,属性面板中的配置一定也是不一样的。例如按钮组件有按钮文字,图标组件有图标类型。。。
所以,我们用一个utils文件,去管理不同组件的属性配置。我们在rightPart下新增一个文件夹staticUtils,用来存放相关的utils方法和变量。
在attributeMap里面,我们用来保存不同组件需要展示的属性配置。那么组件的属性配置在哪里可以看到呢?我们需要来到antD的官网:
https://ant-design.antgroup.com/components/button-cn#api
可以看到,在官网中,Button组件的属性配置都在这里了。
那我们希望的是,可以在我们的低代码平台中,通过属性面板中的配置,去改变按钮相关的属性。所以我们在attributeMap中,将对应的组件属性写下来。
interface AttributeMap {
[key: string]: ComAttribute[]
}
interface ComAttribute {
label: string,
value: string
}
const buttonAttribute: ComAttribute[] = [
{
label: '设置按钮文字',
value: 'caption'
},
{
label: '设置危险按钮',
value: 'danger'
},
{
label: '设置按钮禁用',
value: 'disabled'
},
{
label: '设置幽灵按钮',
value: 'ghost'
},
{
label: '设置按钮形状',
value: 'shape'
},
{
label: '设置按钮大小',
value: 'size'
}
]
const attributeMap: AttributeMap = {
Button: buttonAttribute
}
export {
attributeMap
}
OK,现在我们已经写完了对于Button组件所有的属性配置(当然不是antD中的所有,只选了一部分)。
现在我们需要再属性面板中,将这些属性展示出来。回到rightPart中的index.tsx中。
修改getAttributePanel方法:
import { attributeMap } from './staticUtils/attributeMap';
const getAttributePanel = () => {
const comType = window.renderCom?.comType;
// 拿到组件对应的属性列表
const comAttributeList = attributeMap[comType] || []
return <div>
{
comAttributeList.map((item,index) => {
return <div key={index} className='attributeItem'>
<label className='attributeLabel'>{item.label}</label>
<div className='attributeItemValue'>
<Input onChange={changeComAttribute(item.value)} />
</div>
</div>
})
}
</div>
}
const changeComAttribute = (value: string) => {
return (e: any) => {
window.renderCom[value] = e.target.value;
window.setComList([...window.comList])
}
}
然后我们再修改一下属性标题的样式。
.attributeLabel {
width: 200px;
margin-top: 5px;
margin-right: 15px;
white-space: nowrap; /* 防止文字换行 */
overflow: hidden; /* 隐藏超出部分 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
}
写完之后,你会发现。当我点击组件的时候,右侧的属性面板没有什么变化,这是为什么呢?因为目前我们实现的方案是通过windows来传递组件信息,所以不会引起组件的render。
所以我们可以手动触发一下组件的render方法,就在切换tab页签的时候,更新一下组件:
const [update, setUpdate] = useState({})
const onChange = (value: string) => {
setUpdate({a: 123})
}
这个时候,当你选中某一个组件时,再切换一下页签,就可以看到属性面板了!!!
这里我们注意下,因为之前我们在将组件拖拽到画布区后,没有在window上挂载组件,所以没有效果。所以这里必须点击按钮之后,renderCom才会挂载在window上。所以我们回到mainPart中修改一下。
const onDrop = (e: any) => {
// 修改的部分
const comNode = {
comType: window.nowCom,
style,
comId
}
comList.push(comNode)
window.renderCom = comNode;
window.comList = comList;
window.setComList = setComList
setSelectId(comId)
}
setComList([...comList])
}
OK,现在我们右侧属性面板已经展示了属性配置。但是呢,有一个问题是,比如控制按钮的显示和隐藏,我不需要使用文本框来控制,应该是一个开关来控制。
所以,对于不同的属性,我们要有不同的组件来控制它的值。
所以我们修改一下attributeMap文件:
interface AttributeMap {
[key: string]: ComAttribute[]
}
interface ComAttribute {
label: string,
value: string,
type: string,
options?: Array<any>,
defaultValue?: string
}
const buttonAttribute: ComAttribute[] = [
{
label: '设置按钮文字',
value: 'caption',
type: 'input'
},
{
label: '设置危险按钮',
value: 'danger',
type: 'switch'
},
{
label: '设置按钮禁用',
value: 'disabled',
type: 'switch'
},
{
label: '设置幽灵按钮',
value: 'ghost',
type: 'switch'
},
{
label: '设置按钮形状',
value: 'shape',
type: 'select',
options: [
{
value: 'default'
},
{
value: 'circle'
},
{
value: 'round'
}
],
defaultValue: 'default'
},
{
label: '设置按钮大小',
value: 'size',
type: 'select',
options: [
{
value: 'large'
},
{
value: 'middle'
},
{
value: 'small'
}
],
defaultValue: 'default'
}
]
const attributeMap: AttributeMap = {
Button: buttonAttribute
}
export {
attributeMap
}
在这里我们加一个type值,用来控制属性的配置组件。这里我们有三个值,input,select,switch分别代表输入框,下拉框和开关。
而options代表的是下拉框中的可选项,defaultValue代表默认值。
在getAttributePanel方法里,我们不能无脑的返回Input组件了,我们需要根据属性的type值,来返回不同的组件。
const getAttributePanel = () => {
const comType = window.renderCom?.comType;
const comAttributeList = attributeMap[comType] || []
return <div>
{
comAttributeList.map((item,index) => {
return <div key={index} className='attributeItem'>
<label className='attributeLabel'>{item.label}</label>
<div className='attributeItemValue'>
// 根据组件的type,返回不同的组件
<InputComponent {...item} onChange={changeComAttribute(item.value)}/>
</div>
</div>
})
}
</div>
}
我们创建一个staticComponet目录,用来保存公共的组件,在目录下新增InputComponent组件。
import { Input, Switch, Select} from "antd"
export default function InputComponent(props: any) {
const { onChange, type, defaultValue, options } = props
const getComponent = () => {
switch (type) {
case 'input': {
return <Input style={{width:'120px'}} defaultValue={defaultValue} onChange = {onChange}/>
}
case 'switch': {
return <Switch defaultValue={defaultValue} onChange = {onChange}/>
}
case 'select': {
return <Select style={{width:'120px'}} options={options} defaultValue={defaultValue} onChange={onChange}></Select>
}
}
}
return (
<div>
{getComponent()}
</div>
)
}
做完这些,我们右侧的属性面板就已经很像那么回事了!!!!
现在还有一个问题是,对于右侧,不管是Input还是Select,它们走的都是一个onChange。
有的组件是通过e.target,有的是直接拿即可。
所以我们要对onChange方法进行兼容,现在我们修改我们的changeComAttribute方法:
const changeComAttribute = (value: string) => {
return (e: any) => {
let attribute = e;
if(typeof e === 'object') {
attribute = e.target.value;
}
window.renderCom[value] = attribute;
window.setComList([...window.comList])
}
}
现在你可以通过更改右侧属性面板,然后在控制太查看renderCom的值是否发生了变化:
还记得我们之前是怎么通过更改按钮文字来让组件冲新渲染的嘛?一样的道理,现在我们来修改一下按钮的其他属性。
import { Button as AntButton } from 'antd'
export default function Button(props: any) {
const { caption, danger, disabled, ghost, shape, size} = props
return (
<div>
<AntButton
danger={danger}
disabled={disabled}
ghost={ghost}
shape={shape}
size={size}
>
{caption || '按钮'}
</AntButton>
</div>
)
}
现在你就可以通过属性面板的配置,促使组件的渲染了!!!!
到这里,这一篇讲的内容就完事了。
本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第五节:实现组件和属性面板的交互
如果可以的话,可以给博主的GitHub点亮一颗小星星(?▽?)
如果Button组件已经实现了的话,读者是否可以将Input的属性面板和组件也实现了呢?也是同样的道理,这部分内容,我会在github上进行提交,就不再重复讲解了。
本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第五节:实现Input组件的属性配置