摘要
经过之前的十篇文章,我们已经完成了后端服务。从这一篇开始,我们着重于实现运行时的功能。回顾上一篇的内容,我们实现了一个可以存储数据的Form表单。
而这个Form表单有一个submit的事件,我们思考一下,像这种组件的事件,应该在设计态的时候触发吗?
应该是不允许的,因为我们在设计页面的时候,很容易不小心点击到按钮的点击事件等。所以我们需要将这些事件的触发,放在另一个地方。
我们再想一个问题,我们设计好的页面,应该以页面的方式呈现出来,而不应该有那些左边的组件,右边的属性面板。所以需要一个纯展示组件的页面。
这里提供一个正常的思路,我们创建一个项目叫Render,然后这个项目只负责处理接口返回的页面数据,进行展示。但是由于我们这个项目只作为教程,这里我们就不新建一个项目了,只做一个路由来表示运行时的页面。
然后我们的事件,只会在运行时的页面触发,在设计态不允许触发。
1.创建运行时路由并实现
在这里我们使用react-router作为路由管理。 安装:
npm install react-router-dom
安装好后我们创建路由页面,修改App.tsx
import Builder from './pages/builder';
import Render from './pages/render';
import { BrowserRouter, Routes ,Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" Component={Builder} />
</Routes>
<Routes>
<Route path="/render" Component={Render} />
</Routes>
</BrowserRouter>
);
}
export default App;
同时在pages文件夹下,创建render文件夹之后来做render组件的实现。
我们之前在mainPart下做了组件是如何显示的。所以到render组件里面我们只需要复用即可。
import React, { useEffect } from 'react';
import { message } from 'antd';
import Store from '../../store'
import axios from 'axios';
import { ComJson } from '../builder/mainPart';
import * as components from '../builder/leftPart/component'
import { subscribeHook } from '../../store/subscribe';
interface IRenderProps {
}
const Render: React.FunctionComponent<IRenderProps> = (props) => {
const comList = JSON.parse(JSON.stringify(Store.getState().comList))
useEffect(() => {
const search = window.location.search || '';
const pageId = search.replace('?pageId=', '');
axios.post('http://localhost:4000/page-json/findPageByID', {
pageId
})
.then(res => {
if(res.data.data) {
Store.dispatch({type: 'changeComList', value: res.data.data.pageJson || []})
}else{
message.error('获取页面详情失败')
}
})
}, [])
subscribeHook()
const getComponent = (com: any) => {
const Com = components[com.comType as keyof typeof components];
return <div key={com.comId}>
<div style={com.style}>
<Com {...com} >
{
com.childList && com.childList.map((item: any) => {
return getComponent(item)
})
}
</Com>
</div>
</div>
}
return (
<div className='mainCom'>
{
comList.map((com: ComJson) => {
return getComponent(com)
})
}
</div>
)
};
export default Render;
注意,在render里面,我们不需要drag相关的事件,因为它是在设计态需要的。在运行态,我们纯展示即可。
这里记得之前在AppBuilder项目中,我们留了一个口子,就是预览页面。现在我们可以访问这个路由页面,达到预览的效果了。
2.修改Form组件的submit事件
现在,我们需要一个方法,用来判断当前页面是不是运行时页面。
const isRender = () => {
if(window.location.pathname === '/render') {
return true;
}else {
return false;
}
}
OK,有了这个方法之后,我们去修改Form组件的提交事件,让它只在运行时生效。
const submit = () => {
if(!isRender()) {
return;
}
// 其他代码
3.添加动作面板
对于组件来说,右侧有属性面板和样式面板。现在我们想增加一个动作面板,用来绑定动作。比如按钮组件可以绑定点击事件onClick。Input组件可以绑定onChange事件。
至于展示的方式我们只需要以按钮的方式即可。类似于下面这样:
所以现在我们回到rightPart下: src/pages/builder/rightPart/staticUtils
在staticUtils中增加一个comAction,用来表示每个组件可以绑定的事件类型。
export interface ActionMap {
[key: string]: string[]
}
const actionMap = {
Button: ['onClick', 'onMousedown', 'onMouseup']
}
export {
actionMap
}
有了这个之后,我们修改rightPart组件: src/pages/builder/rightPart/index.tsx
增加一个获取动作面板的方法:
const getActionPanel = () => {
const comType = selectNode?.comType || '';
return <div className='actionPanel'>
{
(actionMap?.[comType as keyof typeof actionMap] || []).map((item, index) => {
return <Button key={index}>{item}</Button>
})
}
</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: getStylePanel(),
},
{
key: 'actionPanel',
label: <div style={{fontSize:'18px',width:'100px',textAlign:'center'}}>动作</div>,
children: getActionPanel(),
}
];
博主补充
这篇就写到这里吧,可能读者会奇怪,动作面板为什么是一堆按钮呢?因为如果我们想给组件添加事件,那不可避免的就要写代码。所以我们希望,点击动作面板的按钮,可以出现一个给用户写代码的弹窗。
在弹窗里写和动作相关的逻辑。这里我们留到下一篇章去实现。
这部分代码提交在github上
github.com/TeacherXin/…
commit: fix: 第二十一节:实现运行时路由页面和动作面板