在上一篇中,我们在项目中引入了redux。所以改动比较大,也是最复杂的一个章节。那如果你已经对上一节掌握了,那后面也不会有更难的内容了。
因为我们已经把一个整体的架子搭好了,后续只会在这个架子上缝缝补补。来到本系列的第七节内容,如果你是第一次看到这一篇文章, 建议先看一下第一节内容:
从零实现一套低代码(保姆级教程) — 【1】初始化项目,实现左侧组件列表
看到标题,我想读者可能不会理解,为什么实现一个Icon组件,要单独放一个章节呢?
如果你使用过antD,应该知道,对于antD组件,我们虽然用的是Icon组件,但其实是引入Icon下的不同图标组件。
那对于用户来讲,他并不知道,StepBackwardOutlined代表的是左箭头组件,所以在我们的低代码中,要支持用户以界面的形式,去选择图标。
之前我们实现了Icon组件,虽然只是一个文本,现在,我们来开始实现
那我们怎么知道Icon组件都需要配置什么属性呢?来到AntD的官网:
https://ant-design.antgroup.com/components/icon-cn
在API中看它都可以配置什么属性:
似乎也没有什么属性,我们就在comAttribute文件夹下,新增一个iconAttribute用来管理Icon组件的属性列表。
XinBuilder2\src\pages\builder\rightPart\staticUtils\comAttribute\buttonAttribute.ts
import { ComAttribute } from "../attributeMap"
const iconAttribute: ComAttribute[] = [
{
label: '图标旋转角度',
value: 'rotate',
type: 'number'
},
{
label: '是否有旋转动画',
value: 'spin',
type: 'switch'
}
]
export {
iconAttribute
}
XinBuilder2\src\pages\builder\rightPart\staticUtils\attributeMap.ts
import { iconAttribute } from './comAttribute/iconAttribute'
const attributeMap: AttributeMap = {
Button: buttonAttribute,
Input: inputAttribute,
Icon: iconAttribute
}
在配置的数组里你会发现,我又新增了一个type为number,也就是说当type是number的时候,我应该通过数字框来配置属性,现在我们到InputComponent组件下修改一下:
const getComponent = () => {
// 其他代码
// 返回数字框
case 'number': {
return <Input type="number" value={selectNode[value] || ''} style={{width:'120px'}} defaultValue={defaultValue} onChange = {onChange}/>
}
}
}
OK,针对于属性列表,我们已经完成了。现在我们来到组件里面,对属性进行兼容:
这里我先使用一个房子的Icon,来进行展示:
import { HomeOutlined } from '@ant-design/icons';
export default function Icon(props: any) {
const { rotate, spin } = props;
return (
<div>
<HomeOutlined rotate={rotate} spin={spin}/>
</div>
)
}
OK,现在页面效果就是这样的:
那我肯定希望,图标的类型是用户自定义的,那我们的组件,就不能使用HomeOutlined作为固定的图标
而是希望,我能从props里面,拿到一个type,值为HomeOutlined,我再根据这个HomeOutlined去渲染对应的图标
我们先不管怎么从props里面拿,先假设props里面有一个type,值就是图标类型,我们的组件应该怎么写?
我们可以通过require直接拿到所有的Icon组件,然后根据type返回对应的组件即可。
export default function Icon(props: any) {
const { rotate, spin, type } = props;
// 根据type来返回对应的Icon
const IconComponent = require('@ant-design/icons')[type || 'HomeOutlined']
return (
<div>
<IconComponent rotate={rotate} spin={spin}/>
</div>
)
}
OK,现在我们的组件已经写好了,只需要解决怎么将type传递给props了。我们要怎么做呢?
当然我们可以用一个输入框,让用户输入Icon的类型,但是用户需要去AntD中取查,图标对应的类型。
所以我们更希望做一个弹窗,让用户自己选择,类似于这样:
所以,在这里我们还要再加一个属性类型。
回到iconAttribute.ts文件下,我们新增一个弹窗类型:
XinBuilder2\src\pages\builder\rightPart\staticUtils\comAttribute\iconAttribute.ts
import { ComAttribute } from "../attributeMap"
const iconAttribute: ComAttribute[] = [
{
label: '图标旋转角度',
value: 'rotate',
type: 'number'
},
{
label: '是否有旋转动画',
value: 'spin',
type: 'switch'
},
// 新增弹窗类型, modalType是弹窗的类型(确定是哪个弹窗)
{
label: '选择图标',
value: 'type',
type: 'modal',
modalType: 'IconSelect'
}
]
export {
iconAttribute
}
因为我们后面肯定会有其他组件,需要其他的弹窗,所以我们新建一个文件夹来统一管理,就在pages下新建一个文件夹,用来管理所有的弹窗。
在IconSelect中,我们实现选择选择图标的弹窗。我们就先写一个弹窗,一会在实现。
XinBuilder2\src\pages\modal\IconSelect\index.tsx
import { Modal } from 'antd'
export default function IconSelect() {
return (
<div>
<Modal>
12345
</Modal>
</div>
)
}
XinBuilder2\src\pages\modal\index.ts
import IconSelect from "./IconSelect";
export default {
IconSelect
}
那我应该在哪里引入呢,一定是在InputComponent里面,对type === modal的类型,进行引入。
export default function InputComponent(props: any) {
const { onChange, type, defaultValue, options, selectNode, value, modalType,label } = props
// 获取组件的弹窗
const ModalComponent = require('../../../modal')[modalType || 'IconSelect'];
const showModal = () => {
}
const getComponent = () => {
switch (type) {
// 对于modal类型返回一个Button
case 'modal': {
return <Button onClick={showModal} style={{width:'120px'}}>{label}</Button>
}
}
}
return (
<div>
{getComponent()}
<ModalComponent />
</div>
)
}
在showModal方法中,我们只需要将这个弹窗进行展示即可:
const [openModal, setOpenModal] = useState(false)
const showModal = () => {
setOpenModal(true)
}
return (
<div>
{getComponent()}
<ModalComponent openModal={openModal} setOpenModal=setOpenModal{}/>
</div>
)
OK,现在我们来实现一下选择图标的弹窗,我们先将弹窗的基本样子写出来:
import { Modal } from 'antd'
export default function IconSelect(props: any) {
const { openModal, setOpenModal } = props;
const handleOk = () => {
setOpenModal(false)
}
const handleCancel = () => {
setOpenModal(false)
}
return (
<div>
<Modal open={openModal} onOk={handleOk} onCancel={handleCancel}>
12345
</Modal>
</div>
)
}
这样当你点击选择图标后,就可以看到弹窗弹出来了:
现在我们回到antD的官网上,我们先通过脚本将所有的图标爬下来。一个很简单的脚本,只需要到控制台去执行:
let arr = []
for(let i=0; i<document.getElementsByClassName('ant-badge').length; i++) {
arr.push(document.getElementsByClassName('ant-badge')[i].innerHTML)
}
然后把这个arr复制下来,粘贴到IconSelect下的一个json里面。(这里如果大家懒得去弄,就直接在我的github上复制即可,github在最下面)
到我们的弹窗里面,给他遍历一下:
import { Modal } from 'antd'
// 引入所有的组件类型
import IconList from './iconMap.json'
export default function IconSelect(props: any) {
return (
<div>
<Modal closable={false} open={openModal} onOk={handleOk} onCancel={handleCancel}>
<div className='iconList'>
{
IconList.map(item => {
const Icon = require('@ant-design/icons')[item];
return <div className='iconItem' key={item}>
<Icon />
</div>
})
}
</div>
</Modal>
</div>
)
}
现在我们的弹窗就展示了所有的Icon了:
现在我们来修改一下样式。
.iconList {
display: flex;
flex-wrap: wrap;
height: 400px;
overflow: auto;
}
.iconItem {
width:60px;
height: 60px;
font-size: 18px;
display: inline-block;
text-align: center;
line-height: 60px;
}
.iconItem:hover {
background-color: rgb(235, 232, 232);
}
现在我们的弹窗就展示没问题了:
OK,现在我们要实现第一个逻辑,点击图标的选中功能:
我们只需要记录一下,当前选中的图标类型,然后给选中的图标加一个背景样式就行了。
const [selectIcon, setSelectIcon] = useState('')
return (
<div>
<Modal closable={false} open={openModal} onOk={handleOk} onCancel={handleCancel}>
<div className='iconList'>
{
IconList.map(item => {
const Icon = require('@ant-design/icons')[item];
// 点击选中节点
return <div onClick={() => {setSelectIcon(item)}} className={selectIcon === item ? 'activeIcon':'iconItem'} key={item}>
<Icon />
</div>
})
}
</div>
</Modal>
</div>
)
选中节点的CSS样式
.activeIcon {
width:60px;
height: 60px;
font-size: 18px;
display: inline-block;
text-align: center;
line-height: 60px;
background-color: rgb(235, 232, 232);
}
最后当我们点击确定的时候,我们要更新对应的组件。怎么更新之前已经说过了,只需要从Store中拿到当前选中的节点,然后更新它的属性,在通过Store.dispatch方法更新Store。
import Store from '../../../store/index'
const { openModal, setOpenModal } = props;
const comList = JSON.parse(JSON.stringify(Store.getState().comList))
const selectCom = Store.getState().selectCom
const selectNode = comList.find((item: any) => item.comId === selectCom)
const [selectIcon, setSelectIcon] = useState('')
useEffect(() => {
setSelectIcon(selectNode.type)
},[openModal])
const handleOk = () => {
selectNode.type = selectIcon;
Store.dispatch({type: 'changeComList', value:comList})
setOpenModal(false)
setSelectIcon('')
}
const handleCancel = () => {
setOpenModal(false)
setSelectIcon('')
}
到此,我们就可以更改组件的图标了:
虽然这一篇只是实现了Icon组件,但其实内容也不少:
首先,我们增加了两个类型的属性,一种是number,一种是弹窗。
number很好理解,弹窗的话需要自己处理相关的属性更新。
同时,如果读者想要自己增加一个组件,过程也是一模一样的。后面我也只会挑一些特殊的组件来进行实现,一些普通的可能就只提交在github上了,就不会单独去写一篇文章了。
本章内容会提交在github上:
https://github.com/TeacherXin/XinBuilder2
commit: 第七节:实现Icon组件
如果可以的话,可以给博主的GitHub点亮一颗小星星(?▽?)
如果你已经实现了上面的内容,你可以看一下antD中的Button组件,它有一个属性是icon,也就是按钮图标。你可不可以用上面的弹窗,给按钮实现这一个属性的配置呢?
这一部分我就不会额外开一个章节,会在github上有一个提交记录:
https://github.com/TeacherXin/XinBuilder2
commit: 第七节:实现Button组件的icon属性