摘要

在上一篇中,我们已经把和页面相关的接口完成的差不多了。从创建页面,更新页面等等:

有了接口之后,我们就可以构建前端页面了。那这部分前端内容我们应该写在哪里呢?

有两种方式:

直接写在我们的XinBuilder项目里面,然后通过前端路由拆分成两个路由在创建一个项目,然后打包到后端服务中,也就是通过后端路由去控制

因为我不确定这个项目后面会有多少代码,虽然我们目前只是想实现页面的管理功能,但是后面我也不知道会增加到多少。

所以我准备使用两个React项目,和页面相关的这些功能我都会写在新的项目里,

1.创建项目

首先就是创建项目了,我们使用create-react-app创建一个项目:

> npx create-react-app app-builder --template typescript

然后再安装antD

npm install antd --save

然后把项目里没有用的文件删一删:

最后,因为我们要请求我们写好的接口,在安装一下axios。

npm install axios --save

2.路由的配置

对于这个项目,我们现在只准备完成和pageJson相关的。但是后面可能会有其他的页面,所以我们是需要路由的。

我们就先安装一下react-router-dom,然后使用路由来管理前端的页面。

npm install react-router-dom --save

对于路由,我们在src下新建一个routes用来管理所有的路由页面。

page文件夹就是代表和pageJson相关的路由。

现在我们回到index.tsx中,对page路由进行引入。

import React, { Suspense } from 'react';

import ReactDOM from 'react-dom/client';

import './index.css';

import Page from './routes/page';

import { HashRouter as Router, Routes , Route} from "react-router-dom";

const root = ReactDOM.createRoot(

document.getElementById('root') as HTMLElement

);

root.render(

}>

);

3.服务端的CORS配置

这时候,如果我们在项目里调用服务端的接口,会有跨域的问题。所以在XinBuilderServer中,我们修改一下main.ts文件:

import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'

import { NestExpressApplication } from '@nestjs/platform-express';

async function bootstrap() {

const app = await NestFactory.create(AppModule,{ cors: true });

const options = new DocumentBuilder()

.setTitle('API example')

.addBearerAuth()

.setVersion('1.0')

.build()

const document = SwaggerModule.createDocument(app, options)

SwaggerModule.setup('api-docs', app, document)

await app.listen(4000);

}

bootstrap();

通过修改CORS配置,来解决跨域的问题

的代码提交在github上: https://github.com/TeacherXin/XinBuilderServer2 commit: 第二节:修改CORS配置解决跨域问题

4.构建前端页面

那我们需要的效果就是:

至于这部分比较简单,我把代码的注释写一下,读者自己看就行。

不过编辑页面和预览页面,一会再细说。

import React, { useEffect, useState } from 'react'

import { Card, Col, Row, Button,Input, message, Modal,Divider, Select } from 'antd';

import {DeleteOutlined,DatabaseOutlined,FormOutlined,InsertRowBelowOutlined,UsergroupDeleteOutlined} from '@ant-design/icons';

import axios from 'axios'

import './index.css'

const { Search } = Input

interface PageJson {

pageName: string,

pageId: string,

pageJson: {

[key: string]: any

},

_id: string

}

export default function Page() {

const [messageApi, contextHolder] = message.useMessage();

const [pageList, setPageList] = useState()

const [isModalOpen,setIsModalOpen] = useState(false)

const [pageName,setPageName] = useState('')

const [searchValue,setSearchValue] = useState('')

useEffect(() => {

getPageList()

}, [])

/**

* 获取全部List的接口

*/

const getPageList = () => {

axios.post(`http://localhost:4000/page-json/findAllPage`)

.then(res => {

setPageList(res.data.data)

})

.catch(err => {

messageApi.open({

type: 'error',

content: '获取页面列表失败',

});

})

}

/**

* 更改搜索框的内容

* @param value 搜索框的内容

*/

const onSearch = (value: string) => {

setSearchValue(value)

}

/**

* 新建页面的弹窗

*/

const addNewPage = () => {

setIsModalOpen(true);

setPageName('')

}

/**

* 搜索内容的过滤

* @param list 页面列表

* @returns 过滤后的页面列表

*/

const getSearchList = (list: PageJson [] | undefined) => {

return (list || []).filter(item => {

return item.pageName.indexOf(searchValue) > -1

})

}

/**

* 根据页面ID进行删除

* @param pageId 页面的ID

* @returns

*/

const deletePage = (pageId: string) => {

return () => {

axios.post(`http://localhost:4000/page-json/deletePage`,{

pageId

})

.then(res => {

messageApi.open({

type: 'success',

content: '删除成功',

});

getPageList()

})

.catch(err => {

messageApi.open({

type: 'error',

content: '删除失败',

});

})

}

}

/**

* 新增页面掉的接口

*/

const handleOk = () => {

const user = JSON.parse(localStorage.getItem('user') || '{}');

axios.post(`http://localhost:4000/page-json/addPage`,{

pageName: pageName,

pageId:'pageInfo_' + new Date().getTime(),

pageJson: {},

})

.then(res => {

messageApi.open({

type: 'success',

content: '新建页面成功',

});

getPageList()

setIsModalOpen(false)

})

.catch(err => {

messageApi.open({

type: 'error',

content: '新建页面失败',

});

})

}

/**

* 新建页面弹窗的取消回调

*/

const handleCancel = () => {

setIsModalOpen(false)

}

/**

* 更改输入的页面名称

* @param e 页面名称

*/

const changePageName = (e: any) => {

setPageName(e.target.value)

}

const toBuilderPage = (pageId: string) => {

return () => {

}

}

return (

{contextHolder}

XinBuilder

轻量级的低代码平台

style={{ width: 304 }}

onSearch={onSearch}

/>

{

(getSearchList(pageList) || []).map(item => {

return

title={

{item.pageName || '匿名'}
}

bordered={false}

headStyle={{fontSize:'14px'}}

>

})

}

)

}

5.跳转页面详情

当我点击编辑页面的时候,应该跳转到对应页面的编辑状态。也就是我们之前实现的项目。 那我在我们的设计器项目怎么知道当前的页面ID呢?

所以我们需要再跳转的时候,将pageId带过去,怎么带呢,只能通过URL上面的参数实现,所以我们现在可以实现一下toBuilderPage方法。

/**

* 根据页面ID跳转到详情页

* @param pageId 页面ID

* @returns

*/

const toBuilderPage = (pageId: string) => {

return () => {

window.open(`http://localhost:3000?pageId=${pageId}`)

}

}

6.修改XinBuilder项目

OK,现在我们现在回到我们的低代码项目里,在builder目录下的index.tsx中,我们要根据URL上的pageId,调取接口来获取到页面详情

获取到之后,我们再通过Store去更新redux。

import { useEffect } from 'react'

import DesignTop from './designTop'

import LeftCom from './leftPart'

import MainCom from './mainPart'

import RightCom from './rightPart'

import axios from 'axios'

import Store from '../../store'

import { message } from 'antd'

export default function Builder() {

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('获取页面详情失败')

}

})

}, [])

return (

)

}

OK,现在我们还需要就是给设计器增加保存的功能,我们来到designTop中,给它添加一个保存的按钮。

import { Button, message } from 'antd'

import './index.css'

import Store from '../../../store'

import axios from 'axios'

export default function DesignTop() {

const savePage = () => {

const search = window.location.search || '';

const pageId = search.replace('?pageId=', '');

const comList = Store.getState().comList;

axios.post('http://localhost:4000/page-json/updatePage', {

pageId,

pageJson: comList

})

.then(res => {

if(res.data.code == 200) {

message.success('保存成功')

}

})

}

return (

XinBuilder

)

}

到此为止,在上一篇中实现的所有接口,我们就实现完对它的调用了。

和XinBuilder相关的代码提交在github上: https://github.com/TeacherXin/XinBuilder2 commit: 第十七节:实现页面的保存以及加载

博主补充

本篇相关的代码提交在github上: https://github.com/TeacherXin/AppBuilder commit: 第一节:初始化项目,实现页面的创建等操作

目前我们已经有三个项目了:

AppBuilder 最外层的壳子,提供创建页面等操作XinBuilder 设计器项目,负责对页面进行配置XinBuilderServer 后端服务,负责数据的存储

后面还会有一个运行时的项目。。。。。

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: