上篇文章实现了低代码基础功能,这一篇来实现低代码高级一点的东西,事件绑定
和组件联动
,有了这两个东西,低代码做出来的东西就不再是静态页面,可以拥有逻辑。
下面我们实现一下查看组件大纲树功能,使用antd
的Tree
组件。
封装一个组件树弹框组件
// src/editor/layouts/header/component-tree.tsx
import { Modal, Tree } from 'antd';
import { useComponets } from '../../stores/components';
interface ComponentTreeProps {
open: boolean,
onCancel: () => void,
}
const ComponentTree = ({ open, onCancel }: ComponentTreeProps) => {
const { components, setCurComponentId } = useComponets();
// 选择组件后,高亮当前组件,并关闭弹框
function componentSelect([selectedKey]: any[]) {
setCurComponentId(selectedKey);
onCancel && onCancel();
}
return (
<Modal
open={open}
title="组件树"
onCancel={onCancel}
destroyOnClose
footer={null}
>
<Tree
fieldNames={{ title: 'name', key: 'id' }}
treeData={components as any}
showLine
defaultExpandAll
onSelect={componentSelect}
/>
</Modal>
)
}
export default ComponentTree;
在头上工具栏加一个查看大纲按钮。
选择一个后
前端很多组件都会有事件,组件之间联动基本都是由事件发起的。
下面实现一个简单demo,点击按钮显示一个消息提示。
为了不让配置文件代码变得很多,把属性配置和事件配置拆成两个组件。
import { Segmented } from 'antd';
import type { SegmentedValue } from 'antd/es/segmented';
import { useState } from 'react';
import { useComponets } from '../../stores/components';
import ComponentAttr from './attr';
import ComponentEvent from './event';
const Setting: React.FC = () => {
const { curComponentId, curComponent } = useComponets();
const [key, setKey] = useState<SegmentedValue>('属性');
if (!curComponentId || !curComponent) return null;
return (
<div>
<Segmented value={key} onChange={setKey} block options={['属性', '事件']} />
<div className='pt-[20px]'>
{
key === '属性' && (
<ComponentAttr />
)
}
{
key === '事件' && (
<ComponentEvent />
)
}
</div>
</div>
)
}
export default Setting;
{
id: 1,
name: 'Button',
props: {
// 点击事件绑定显示消息动作
onClick: {
// 动作类型
type: 'ShowMessage',
// 动作配置
config: {
// 消息类型
type: 'success',
// 消息文本
text: '点击了按钮',
}
}
}
}
// src/editor/layouts/setting/event.tsx
import { Collapse, Input, Select } from 'antd';
import { ItemType } from '../../item-type';
import { useComponets } from '../../stores/components';
const componentEventMap = {
[ItemType.Button]: [{
name: 'onClick',
label: '点击事件',
}],
}
const ComponentEvent = () => {
const { curComponent, curComponentId, updateComponentProps } = useComponets();
// 事件类型改变
function typeChange(eventName: string, value: string) {
if (!curComponentId) return;
updateComponentProps(curComponentId, { [eventName]: { type: value, } })
}
// 消息类型改变
function messageTypeChange(eventName: string, value: string) {
if (!curComponentId) return;
updateComponentProps(curComponentId, {
[eventName]: {
...curComponent?.props?.[eventName],
config: {
...curComponent?.props?.[eventName]?.config,
type: value,
},
}
})
}
// 消息文本改变
function messageTextChange(eventName: string, value: string) {
if (!curComponentId) return;
updateComponentProps(curComponentId, {
[eventName]: {
...curComponent?.props?.[eventName],
config: {
...curComponent?.props?.[eventName]?.config,
text: value,
},
},
})
}
if (!curComponent) return null;
return (
<div className='px-[12px]'>
{(componentEventMap[curComponent.name] || []).map(setting => {
return (
<Collapse key={setting.name} defaultActiveKey={setting.name}>
<Collapse.Panel header={setting.label} key={setting.name}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div>动作:</div>
<div>
<Select
style={{ width: 160 }}
options={[
{ label: '显示提示', value: 'showMessage' },
]}
onChange={(value) => { typeChange(setting.name, value) }}
value={curComponent?.props?.[setting.name]?.type}
/>
</div>
</div>
{
curComponent?.props?.[setting.name]?.type === 'showMessage' && (
<div className='flex flex-col gap-[12px] mt-[12px]'>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div>类型:</div>
<div>
<Select
className='w-[160px]'
options={[
{ label: '成功', value: 'success' },
{ label: '失败', value: 'error' },
]}
onChange={(value) => { messageTypeChange(setting.name, value) }}
value={curComponent?.props?.[setting.name]?.config?.type}
/>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div>文本:</div>
<div>
<Input
className='w-[160px]'
onChange={(e) => { messageTextChange(setting.name, e.target.value) }}
value={curComponent?.props?.[setting.name]?.config?.text}
/>
</div>
</div>
</div>
)
}
</Collapse.Panel>
</Collapse>
)
})}
</div>
)
}
export default ComponentEvent;
预览时把上面数据结构改造成下面这样就行了
{
id: 1,
name: 'Button',
props: {
// 点击事件绑定显示消息动作
onClick: {
// 动作类型
type: 'ShowMessage',
// 动作配置
config: {
// 消息类型
type: 'success',
// 消息文本
text: '点击了按钮',
}
}
}
}
{
id: 1,
name: 'Button',
props: {
// 点击事件显示消息
onClick: () => {
message.success('点击了按钮');
}
}
}
渲染时处理事件
下面我们来实现一个简单的组件联动功能。
假设页面中有三个按钮,第一个是普通按钮,第二个按钮让第一个按钮loading,第三个按钮可以让第一个按钮结束loading。
想实现上面demo,有两种方式:
两个方案我们后面都会实现,这里先实现第二种方案。
封装一个自己的按钮组件,组件内部通过react的useImperativeHandle
api把想要暴露出去的方法暴露出去,然后在渲染组件的地方通过ref获取到组件实例,这时候我们就能调用组件里面暴露出来的方法了。
事件添加组件方法动作类型,当事件那里选的动作类型时组件方法的时候,动态显示组件树下拉框,选择一个组件后,动态显示这个组件暴露出来的方法下拉框,然后选择一个方法。
事件动作类型下拉框中天际一个组件方法类型
配置组件类型暴露出哪些方法
选择组件方法后,动态渲染组件树下拉框和组件方法下拉框
效果展示
import { Button as AntdButton } from 'antd';
import { forwardRef, useImperativeHandle, useState } from 'react';
const Button = (props: any, ref: any) => {
const [loading, setLoading] = useState(false);
// 暴露方法,父组件可以使用ref获取组件里暴露出去的方法
useImperativeHandle(ref, () => {
return {
startLoading: () => {
setLoading(true);
},
endLoading: () => {
setLoading(false);
},
}
}, []);
return (
<AntdButton loading={loading} {...props}>{props.children}</AntdButton>
)
}
export default forwardRef(Button);
先定义一个map,存放组件id和组件实例的映射。
动态渲染组件时,注入ref属性,拿到组件实例。
处理事件的地方添加处理组件方法的方法,通过组件id获取到组件实例,然后调用配置的方法。
效果展示
这一篇我们简单的实现了大纲、事件绑定、组件之间联动功能,虽说简单,但是万变不离其宗,后续可以在这基础上去拓展一些其他动作,让其可以实现更复杂的页面和逻辑。
阅读量:559
点赞量:0
收藏量:0