diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8ffb507
--- /dev/null
+++ b/README.md
@@ -0,0 +1,45 @@
+
+## 安装并开始
+
+```dotnetcli
+yarn
+yarn setup
+yarn start
+```
+
+本项目需要在 [低代码系统](https://github.com/react-low-code/vitis-system) 使用
+
+## 写在后面
+
+2020年初,为了加速重构遗留系统,我在任职的公司落地了低代码,当时的低代码及其简陋,不足为道,但派上了用场,我们只有5个前端工程师,在一个季度内,搭建了200+个页面。回忆起当时的方案,大概存在以下5个问题:
+1. 搭建完成的应用无法独立部署
+2. 无渲染沙箱,当处于编辑态时,画布无纯净的运行环境
+3. 无组件市场,低代码设计器能使用的组件全部写死在项目内
+4. 用来描述低代码应用的Schema无版本管理,无法查看以前保存的版本。
+5. 开发人员无法对搭建完成的应用二次开发。
+2021 年底,机械工业出版社的图书编辑邀请我写一本与开发低代码相关的图书,今日,业已成书。本书一一解决了上述 5 个问题,同时涉及业务场景的需求分析,从开发技术层面来讲,读者将了解到下面这 5 个方面的内容:
+
+1)JSON Schema保存到Git仓库中,它不影响线上运行的低代码应用,只用于APP各版本的预览和重新编辑。
+
+2)线上运行的应用与JSON Schema脱钩,即便低代码平台停止服务,线上的APP依然能正常运行。
+
+3)引入渲染沙箱,设计器和渲染器位于不同的Frame,此时画布拥有纯净的运行环境。
+
+4)设计组件规范,开发脚手架,其用于开发、调试和上传低代码组件,这使设计器能使用丰富的组件去开发应用,同时让低代码组件和低代码平台解耦。
+
+5)开发低代码平台所需的基础设施,包括GitLab CI/CD、npm私有库,LDAP账号管理系统等。
+
+本书将分为4大部分,其中第3部分介绍开发低代码平台涉及的各个方面,这部分难度最大。如果你是一名经验丰富的软件工程师并且对低代码已有了解,建议从第4章开始;但是,如果你对低代码了解得不多,请一定从第一章的基础理论知识开始学习。
+
+第一部分是基础篇,只包含一章,它介绍后续章节使用的理论知识,涉及的知识点有React Context API、React Hooks、React Ref、Mobx和MongoDB等,要想在本地运行本图书介绍的低代码平台,你需要在自己电脑上下载MongoDB。
+
+第二部分为需求分析篇,包含两章,它介绍低代码平台开发的应用要满足哪些需求,同时也介绍低代码平台的功能。
+
+第三部分为实战篇,包含五章,是本图书的重点,介绍如何开发低代码平台,其中展示了大量的代码示例,涉及的内容有低代码架构策略、低代码组件、设计器、渲染器和代码生成器。
+
+第四部分为基础设施篇,只包含一章。低代码平台用于创建应用程序,它本身也是应用程序,值得一提的是,它对研发体系的要求相当高。如果你手上没有一套完善的研发体系,涵盖代码托管、CI/CD、CDN,npm私有库等部分,那么妄谈开发低代码平台。基础设施篇涉及的内容有,如何使用GitLab CI/CD建立持续部署 pipeline、如何搭建npm私有库,如何搭建LDAP账号管理系统等。
+本图书提供了7个开源项目,全部源文件可以从https://github.com/react-low-code下载。
+
+对《低代码平台开发实践:基于React》感兴趣的朋友可在[京东购买](https://item.jd.com/14012127.html),也可通过我的微信微信公众号——前端知识小站联系到我。
+
+
diff --git a/WechatIMG78.jpg b/WechatIMG78.jpg
new file mode 100644
index 0000000..9b1f1af
Binary files /dev/null and b/WechatIMG78.jpg differ
diff --git a/packages/default-ext/src/plugin/ComponentsPane/component.tsx b/packages/default-ext/src/plugin/ComponentsPane/component.tsx
index 20f34d4..2af870d 100644
--- a/packages/default-ext/src/plugin/ComponentsPane/component.tsx
+++ b/packages/default-ext/src/plugin/ComponentsPane/component.tsx
@@ -148,7 +148,7 @@ export default class ComponentsPane extends React.Component<{},State>{
onOpenChange={this.onOpenChange}
open={this.state.active}
>
-
+
}
diff --git a/packages/default-ext/src/plugin/ComponentsPane/icon.tsx b/packages/default-ext/src/plugin/ComponentsPane/icon.tsx
deleted file mode 100644
index 9355c90..0000000
--- a/packages/default-ext/src/plugin/ComponentsPane/icon.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react';
-
-export default function SvgComponent({ className, onClick }: {className?: string, onClick?: () => void}) {
- return (
-
- );
-}
diff --git a/packages/default-ext/src/plugin/LifeCyclesPane/component.tsx b/packages/default-ext/src/plugin/LifeCyclesPane/component.tsx
index a5b6d4e..980d477 100644
--- a/packages/default-ext/src/plugin/LifeCyclesPane/component.tsx
+++ b/packages/default-ext/src/plugin/LifeCyclesPane/component.tsx
@@ -86,7 +86,7 @@ export default function () {
onOpenChange={onOpenChange}
open={active}
>
-
+
)
diff --git a/packages/default-ext/src/plugin/NetworkInterceptorsPane/component.tsx b/packages/default-ext/src/plugin/NetworkInterceptorsPane/component.tsx
index 98f1da6..1122eb2 100644
--- a/packages/default-ext/src/plugin/NetworkInterceptorsPane/component.tsx
+++ b/packages/default-ext/src/plugin/NetworkInterceptorsPane/component.tsx
@@ -120,7 +120,7 @@ export default function () {
onOpenChange={onOpenChange}
open={active}
>
-
+
)
diff --git a/packages/default-ext/src/plugin/NetworkInterceptorsPane/index.less b/packages/default-ext/src/plugin/NetworkInterceptorsPane/index.less
index 543684e..e2c19a3 100644
--- a/packages/default-ext/src/plugin/NetworkInterceptorsPane/index.less
+++ b/packages/default-ext/src/plugin/NetworkInterceptorsPane/index.less
@@ -1,6 +1,5 @@
.NetworkInterceptorsPane {
.icon {
- color: rgba(0, 0, 0, 0.26);
font-size: 22px;
cursor: pointer;
diff --git a/packages/default-ext/src/plugin/PromptPane/component.tsx b/packages/default-ext/src/plugin/PromptPane/component.tsx
new file mode 100644
index 0000000..33fa50c
--- /dev/null
+++ b/packages/default-ext/src/plugin/PromptPane/component.tsx
@@ -0,0 +1,74 @@
+import { Popover, Input, Button } from 'antd';
+import React, { useState,useLayoutEffect, useEffect } from "react"
+import { OpenAIOutlined } from '@ant-design/icons';
+
+const BASE_URL = 'http://127.0.0.1:3001'
+
+export default function PromptPane() {
+ const [active, setActive] = useState(false);
+ const [height, setHeight] = useState(0);
+ const [keyword,setKeyword] = useState('')
+ const [loading, setLoading] = useState(false);
+
+ useLayoutEffect(() => {
+ setHeight(document.body.clientHeight - 130)
+ },[])
+
+ const onOpenChange = () => {
+ setActive(!active)
+ }
+
+ const onChange = (e: React.ChangeEvent) => {
+ setKeyword(e.target.value);
+ };
+
+ const onOK = () => {
+ if (!keyword) {
+ return ;
+ }
+ setLoading(true);
+ fetch(BASE_URL+'/prompt/generate/schema', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ prompt: keyword
+ })
+ })
+ .then(async (response) => {
+ if (!response.ok) {
+ throw new Error('网络请求失败: ' + response.status);
+ }
+ const res: {
+ data: any;
+ code: string;
+ msg: string;
+ } = await response.json()
+ console.log(window.VitisLowCodeEngine.project,'window.VitisLowCodeEngine.project')
+ if (window.VitisLowCodeEngine.project) {
+ window.VitisLowCodeEngine.project.insertSchema(res.data);
+ }
+ })
+ .finally(() => {
+ setLoading(false);
+ })
+ }
+
+ return (
+
+
+
+
+ }
+ onOpenChange={onOpenChange}
+ open={active}
+ >
+
+
+ )
+}
diff --git a/packages/default-ext/src/plugin/PromptPane/index.tsx b/packages/default-ext/src/plugin/PromptPane/index.tsx
new file mode 100644
index 0000000..254ed73
--- /dev/null
+++ b/packages/default-ext/src/plugin/PromptPane/index.tsx
@@ -0,0 +1,18 @@
+import { PluginContext } from 'vitis-lowcode-types'
+import Pane from './component'
+
+function PromptPane(ctx: PluginContext) {
+ return {
+ init() {
+ ctx.skeleton.add({
+ name: "PromptPane",
+ content: Pane,
+ area: "left"
+ })
+ }
+ }
+}
+
+PromptPane.pluginName = 'PromptPane'
+
+export default PromptPane
\ No newline at end of file
diff --git a/packages/default-ext/src/plugin/SchemaPane/component.tsx b/packages/default-ext/src/plugin/SchemaPane/component.tsx
index 21638ce..f2ca84d 100644
--- a/packages/default-ext/src/plugin/SchemaPane/component.tsx
+++ b/packages/default-ext/src/plugin/SchemaPane/component.tsx
@@ -50,7 +50,7 @@ export default function() {
onOpenChange={onOpenChange}
open={active}
>
-
+
diff --git a/packages/default-ext/src/plugin/index.ts b/packages/default-ext/src/plugin/index.ts
index 73404db..2ad3a4d 100644
--- a/packages/default-ext/src/plugin/index.ts
+++ b/packages/default-ext/src/plugin/index.ts
@@ -2,10 +2,12 @@ import ComponentsPanePlugin from './ComponentsPane'
import LifeCyclesPane from './LifeCyclesPane'
import NetworkInterceptorsPane from './NetworkInterceptorsPane'
import SchemaPane from './SchemaPane'
+import PromptPane from './PromptPane';
export const defaultPlugins = [
ComponentsPanePlugin,
LifeCyclesPane,
NetworkInterceptorsPane,
- SchemaPane
+ SchemaPane,
+ PromptPane,
]
\ No newline at end of file
diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts
index d6b44b7..b38313b 100644
--- a/packages/engine/src/index.ts
+++ b/packages/engine/src/index.ts
@@ -2,6 +2,7 @@ import { createElement } from 'react'
import { render } from 'react-dom'
import { defaultPlugins, defaultSetters } from 'vitis-lowcode-default-ext'
import { PageSchema } from 'vitis-lowcode-types'
+import { fetchSchema } from './servers/ai';
import { plugins, setters, material } from './shell'
import Workbench from './layout/workbench'
@@ -43,6 +44,10 @@ export function init(container?: HTMLElement, options: EngineOptions = {}) {
if (options.pageSchema) {
observableProject.setSchema(options.pageSchema)
}
+
+ // fetchSchema().then((schema) => {
+ // observableProject.documentModel.insertSchema(schema);
+ // })
render(createElement(Root),container)
}
\ No newline at end of file
diff --git a/packages/engine/src/node/index.ts b/packages/engine/src/node/index.ts
index ca461a1..98477dd 100644
--- a/packages/engine/src/node/index.ts
+++ b/packages/engine/src/node/index.ts
@@ -59,7 +59,7 @@ export default class Node {
this.isFormControl = !!initSchema.isFormControl
this.owner = owner
- this.children = initSchema.children.map(child => this.owner.createNode(child, this))
+ this.children = initSchema.children?.map(child => this.owner.createNode(child, this)) || []
this.props = new Props(this, initSchema.props)
this.extraProps = new Props(this, initSchema.extraProps)
}
diff --git a/packages/engine/src/project/documentModel.ts b/packages/engine/src/project/documentModel.ts
index 9956e13..c45dbb0 100644
--- a/packages/engine/src/project/documentModel.ts
+++ b/packages/engine/src/project/documentModel.ts
@@ -1,4 +1,4 @@
-import { PageSchema, NodeSchema, LifeCycles, JSFunction, Interceptors } from 'vitis-lowcode-types'
+import { PageSchema, NodeSchema, LifeCycles, JSFunction, Interceptors, ContainerSchema } from 'vitis-lowcode-types'
import { makeAutoObservable } from 'mobx'
import Node from '../node'
import type Project from './index'
@@ -72,6 +72,18 @@ export default class DocumentModel {
this.hoveredNodeId = id
}
+ insertSchema(schemas: NodeSchema[], parentNode: Node = this.rootNode) {
+ const insert = (schemas: NodeSchema[], parentNode: Node) => {
+ schemas.forEach((schema, index) => {
+ const newNode = this.createNode(schema, parentNode);
+ parentNode.inertChildAtIndex(newNode, parentNode.childrenSize + index);
+ });
+ }
+
+ insert(schemas, parentNode)
+ this.project.renderer?.rerender();
+ }
+
updateLifeCycles(name: keyof LifeCycles, value: JSFunction) {
this.lifeCycles[name] = value
}
diff --git a/packages/engine/src/project/host.ts b/packages/engine/src/project/host.ts
index fcfb8d8..97032e1 100644
--- a/packages/engine/src/project/host.ts
+++ b/packages/engine/src/project/host.ts
@@ -14,7 +14,6 @@ export default class Host implements HostSpec {
constructor(project: Project) {
this.project = project
-
}
onAssetUpdated = async (additionalPackageNames: string[]) => {
@@ -62,6 +61,7 @@ export default class Host implements HostSpec {
this.renderer = await this.createSimulator()
+ this.project.setRenderer(this.renderer);
material.off(ASSET_UPDATED, this.onAssetUpdated).on(ASSET_UPDATED, this.onAssetUpdated)
this.setupEvent()
diff --git a/packages/engine/src/project/index.ts b/packages/engine/src/project/index.ts
index fd39a1e..346a2ca 100644
--- a/packages/engine/src/project/index.ts
+++ b/packages/engine/src/project/index.ts
@@ -1,7 +1,7 @@
import { makeAutoObservable } from 'mobx'
import Designer from './designer';
import DocumentModel from './documentModel'
-import { PageSchema, ObservableProjectSpec, LifeCycles, JSFunction, Interceptors } from 'vitis-lowcode-types'
+import { PageSchema, ObservableProjectSpec, LifeCycles, JSFunction, Interceptors, SimulatorSpec } from 'vitis-lowcode-types'
const defaultPageSchema: PageSchema = {
componentName: 'Page',
@@ -75,6 +75,7 @@ const defaultPageSchema: PageSchema = {
export default class Project implements ObservableProjectSpec{
readonly designer = new Designer(this)
readonly documentModel: DocumentModel
+ renderer: SimulatorSpec
get schema() {
return this.documentModel.schema as PageSchema
@@ -97,6 +98,10 @@ export default class Project implements ObservableProjectSpec{
this.documentModel.open(schema)
}
+ setRenderer(renderer: SimulatorSpec) {
+ this.renderer = renderer;
+ }
+
updateLifeCycles = (name: keyof LifeCycles, value: JSFunction) => {
this.documentModel.updateLifeCycles(name, value)
}
diff --git a/packages/engine/src/servers/ai.ts b/packages/engine/src/servers/ai.ts
new file mode 100644
index 0000000..0389522
--- /dev/null
+++ b/packages/engine/src/servers/ai.ts
@@ -0,0 +1,26 @@
+import { BASE_URL } from './config';
+
+export async function fetchSchema () {
+ return fetch(BASE_URL+'/prompt/generate/schema', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ // 在这里添加你要发送的请求数据
+ prompt: '生成一个包含用户名和密码的表单'
+ })
+ })
+ .then(async (response) => {
+ if (!response.ok) {
+ throw new Error('网络请求失败: ' + response.status);
+ }
+ const res: {
+ data: any;
+ code: string;
+ msg: string;
+ } = await response.json()
+
+ return res.data;
+ })
+}
\ No newline at end of file
diff --git a/packages/engine/src/servers/config.ts b/packages/engine/src/servers/config.ts
new file mode 100644
index 0000000..ed750e4
--- /dev/null
+++ b/packages/engine/src/servers/config.ts
@@ -0,0 +1 @@
+export const BASE_URL = 'http://127.0.0.1:3001'
\ No newline at end of file
diff --git a/packages/engine/src/shell/project.ts b/packages/engine/src/shell/project.ts
index 78a5d1e..65e4baf 100644
--- a/packages/engine/src/shell/project.ts
+++ b/packages/engine/src/shell/project.ts
@@ -1,6 +1,6 @@
import { EventEmitter } from 'eventemitter3';
import type InterProject from '../project';
-import { ProjectSpec, LifeCycles, JSFunction, Interceptors } from 'vitis-lowcode-types'
+import { ProjectSpec, LifeCycles, JSFunction, Interceptors, NodeSchema } from 'vitis-lowcode-types'
export default class Project extends EventEmitter implements ProjectSpec {
private readonly project: InterProject
@@ -28,4 +28,8 @@ export default class Project extends EventEmitter implements ProjectSpec {
getSchema() {
return this.project.schema
}
+
+ insertSchema(schemas: NodeSchema[]) {
+ this.project.documentModel.insertSchema(schemas);
+ }
}
\ No newline at end of file
diff --git a/packages/types/src/project.ts b/packages/types/src/project.ts
index 632e4e7..272c66a 100644
--- a/packages/types/src/project.ts
+++ b/packages/types/src/project.ts
@@ -1,6 +1,7 @@
import type EventEmitter from 'eventemitter3';
import { ElementType } from 'react'
import { PageSchema, LifeCycles, JSFunction, Interceptors } from './schema'
+import { NodeSchema } from 'vitis-lowcode-types'
export interface ProjectSpec extends EventEmitter {
updateLifeCycles(name: keyof LifeCycles, value: JSFunction): void
@@ -8,6 +9,7 @@ export interface ProjectSpec extends EventEmitter {
getInterceptors(): Interceptors | undefined
updateInterceptors(name: keyof Interceptors, value: JSFunction): void
getSchema(): PageSchema
+ insertSchema(schemas: NodeSchema[]): void;
}
export interface ObservableProjectSpec {