从零实现一套低代码(保姆级教程)【后端服务】 --- 【18】实现页面接口对应的前端

news2024/11/25 4:17:42

摘要

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

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

有两种方式:

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

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

所以我准备使用两个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(
  <Router>
    <Suspense>
    <Routes>
      <Route path={'/'} element={<Page />}></Route>
    </Routes>
    </Suspense>
  </Router>
);

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<NestExpressApplication>(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<PageJson []>()
  const [isModalOpen,setIsModalOpen] = useState<boolean>(false)
  const [pageName,setPageName] = useState<string>('')
  const [searchValue,setSearchValue] = useState<string>('')

  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 (
    <div className='PageList'>
      {contextHolder}
      <div className='pageLeft'>
        <div className='leftHeader'>XinBuilder</div>
        <div className='leftDiscribe'>轻量级的低代码平台</div>
        <Divider />
      </div>
      <div className='pageRight'>
        <div className='PageHeader'>
          <Search
            style={{ width: 304 }}
            onSearch={onSearch}
          />
          <Button className='pageButton' onClick={addNewPage}>新建页面</Button>
        </div>
        <Divider />
        <div className='PageBody'>
          <Row style={{width:'100%'}} gutter={16}>
            {
              (getSearchList(pageList) || []).map(item => {
                return <Col style={{marginTop:'10px'}} key={item._id} span={6}>
                  <Card
                    title={<div><span>{item.pageName || '匿名'}</span><DeleteOutlined onClick={deletePage(item.pageId)}style={{float:'right',cursor:'pointer'}} /></div>}
                    bordered={false}
                    headStyle={{fontSize:'14px'}}
                  >
                    <div style={{height:'50px'}}>
                      <Button type='text' onClick={toBuilderPage(item.pageId)}>编辑页面</Button>
                      <Button type='text'>预览页面</Button>
                    </div>
                  </Card>
                </Col>
              })
            }
          </Row>
        </div>
      </div>
      <Modal title="创建页面" open={isModalOpen} onOk={handleOk} onCancel={handleCancel} okText='创建' cancelText='取消'>
          <Input addonBefore="页面名称" value={pageName} onChange={changePageName} />
      </Modal>
    </div>
  )
}

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 (
    <div>
      <DesignTop />
      <LeftCom />
      <MainCom />
      <RightCom />
    </div>
  )
}

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 (
    <div className='designTop'>
      <span className='title'>XinBuilder</span>
      <Button onClick={savePage} type='primary' ghost>保存</Button>
    </div>
  )
}

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

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

博主补充

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1393848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

canvas绘制不同样式的五角星(图文示例)

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

十三、Qt 操作PDF文件放大缩小

一、在《十、Qt 操作PDF文件-CSDN博客》中我们用Poppler类库打开了PDF文件&#xff0c;在《十二、Qt 操作PDF文件(2)-CSDN博客》介绍了通过按钮进行上一页、下一页、跳转到某一页等功能&#xff0c;在本章节中&#xff0c;加入了 通过鼠标滚轮可以翻页。通过按住Ctrl鼠标滚轮可…

【十进制与二进制如何转换?推荐一个超好用的公式编辑器】

在计算机科学和电子工程中&#xff0c;二进制是一种非常重要的数字系统&#xff0c;因为它在数字处理和数据传输中被广泛使用。因此&#xff0c;理解如何将十进制数转换为二进制数是非常重要的。 可以使这个计算过程更加简单和快速。而且还可以用于其他数学方程式的编写和编辑。…

香港服务器带宽大点的(香港大带宽推荐)

在当今数字化时代&#xff0c;服务器带宽成为了企业发展的重要基石。对于需要处理大量数据和流量的企业来说&#xff0c;选择一款带宽充足、性能稳定的服务器是至关重要的。香港作为全球互联网的中心之一&#xff0c;其服务器带宽质量备受关注。本文将为您推荐几款香港大带宽服…

MySQL下对[库]的操作

目录 创建数据库 创建一个数据库案例&#xff1a; 字符集和校验规则&#xff1a; 默认字符集&#xff1a; 默认校验规则&#xff1a; 查看数据库支持的字符集&#xff1a; 查看数据库支持的字符集校验规则&#xff1a; 校验规则对数据库的影响&#xff1a; 操作数据…

黑马程序员JS基础笔记

js基础 软件第一天第二天 第三天第四天第五天 软件 截图软件snipaste esc取消 f1后再f1可取色&#xff0c;shift可换十六进制&#xff0c;c复制 画笔软件 思维导图软件 笔记软件 vscode报错插件Error Lens 第一天 组成&#xff1a;ECMAScriptWeb APIs(DOMBOM) 权威网站…

Tensorflow2.0笔记 - 基础数学运算

本笔记主要记录基于元素操作的,-,*,/,//,%,**,log,exp等运算&#xff0c;矩阵乘法运算&#xff0c;多维tensor乘法相关运算 import tensorflow as tf import numpy as nptf.__version__#element-wise运算&#xff0c;对应元素的,-,*,/,**,//,% tensor1 tf.fill([3,3], 4) ten…

前端面试题-CSS3新增特性

增加了transition过渡和animation动画 transition过渡,可以实现元素状态的渐变效果&#xff0c;即当元素的状态发生变化时&#xff0c;元素会平滑的过渡到新的状态。要使用transition属性&#xff0c;需要指定要执行过渡效果的属性&#xff0c;过渡效果的持续时间&#xff0c;…

空间数据结构(四叉树、八叉树、BVH树、BSP树、k-d树)

一. 前言 在游戏程序中&#xff0c;利用空间数据结构加速计算往往是非常重要的优化思想&#xff0c;空间数据结构可以应用于场景管理、渲染、物理、游戏逻辑等方面。 二、多叉树 2.1 四叉树 四叉树是很常见的一种 2D 碰撞检测方法&#xff0c;实现手段也五花八门。不过在具体…

多语言客服系统:企业全球化战略的关键

随着全球化的发展&#xff0c;越来越多的企业开始将目光投向国际市场。然而&#xff0c;随之而来的挑战是如何有效地与不同语言和文化背景的客户进行沟通和交流。在这个多元化的时代&#xff0c;多语言客服系统成为了企业全球化战略的关键。 一、多语言客服系统的定义 多语言客…

【Elsevier】“有史以来最快的一次投稿”,1个月零4天录用,录用率60%,国人友好!

发表说 截图来源&#xff1a;LetPub 01 期刊概况 Optical Materials 【出版社】Elsevier 【ISSN】0925-3467 【检索情况】SCI&EI双检 【WOS收录年份】1994年 【出刊频率】月刊&#xff0c;最新一期Volume 148 &#xff0c;In progress (February 2024) 【期刊官网】 …

[AutoSar]BSW_OS 02 Autosar OS_STACK

目录 关键词平台说明一、 task stack1.1 Task stack 的共享1.2 task stack 的实际使用大小 二、ISR stack2.1 ISR stack 的共享 三、Single-stack&#xff08;单一栈&#xff09;和multi-stack &#xff08;多栈&#xff09;策略3.1 Single-stack3.2 multi-stack 四、Stack Che…

从 `go build` 到 `go test`:Go 语言命令行工具全解析

从 go build 到 go test&#xff1a;Go 语言命令行工具全解析 引言go build 命令详解go test 的运用其他重要的 Go 命令结论 引言 在当今的软件开发领域&#xff0c;Go 语言以其简洁、高效和强大的并发处理能力受到广泛的欢迎。作为一门现代编程语言&#xff0c;Go 不仅拥有丰…

2024Navicat最新下载安装教程

下载 下载链接&#xff1a;链接 选择自己想下载的&#xff0c;我下载的是Navicat Premium 16 点击直接下载 安装 下载好后双击打开安装&#xff0c;下一步 同意&#xff0c;下一步 选择安装位置&#xff0c;下一步 安装 安装完成&#xff0c;安装完成后先不要打开 …

OSI七层协议和五层协议

【 1 】互联网协议交互的基础 硬件设备 光缆 【 2 】OSI七层协议 物理层&#xff08;Physical Layer&#xff09;&#xff1a;负责传输比特流&#xff08;0和1&#xff09;以及物理连接的建立和维护。数据链路层&#xff08;Data Link Layer&#xff09;&#xff1a;提供可…

eNSP学习—— 终端跨二层交换机连接三层网关设备进行通信

组网需求&#xff1a; HOSTA 和 HOSTB、HOSTC 和 HOSTD 分属研发部和质量部&#xff0c;两部门通过一台二层交换 机互联&#xff0c;两部门有业务往来&#xff0c;需要二层隔离&#xff0c;三层通信。 配置思路 &#xff1a; 1.将 SwitchB 连接终端 HOST 的接口加入 vlan&…

vue2+webpack升级vue3+vite,报错Cannot read properties of null (reading ‘isCE‘)

同学们可以私信我加入学习群&#xff01; 正文开始 前言问题分析解决总结 前言 系列文章&#xff1a;vue2webpack升级vue3vite&#xff0c;修改插件兼容性bug 前面的文章主要是介绍&#xff0c;在升级初始阶段遇到的一些显而易见的兼容性问题和bug。随着项目迭代的不断深入&a…

220v变5vic-220v变直流5v小封装220MA电流

220v变5v ic-220v变直流5v小封装220MA电流&#xff0c;交流或者直流输入都可以&#xff0c;交流输入的时候输入端需要先整流&#xff08;半波、全波都可以&#xff09;&#xff0c;40v~265v输入&#xff0c;经过220v变5v ic芯片电路&#xff0c;稳定输出5v200MA电流&#xff0c…

配置https---Nginx认证ssl证书

nginx作为前端的负载均衡服务器已经很熟悉了,项目需要使用https安全的时候就需要认证证书了 dockerweb管理工具 Portainer 如果对docker不那么熟悉可以使用docker 第三方管理端 docker run -d --restartalways --name portainer -p 9000:9000 -v /var/run/docker.sock:/var/…

【机组】通用寄存器单元实验的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《机组 | 模块单元实验》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 ​ 目录 &#x1f33a;一、 实验目…