From f59af01974029f6ee32dc271d0a5910ac293a3e1 Mon Sep 17 00:00:00 2001 From: lizhichao51 Date: Mon, 21 Apr 2025 18:39:04 +0800 Subject: [PATCH 1/5] =?UTF-8?q?[app-builder]=20=E6=96=B0=E5=A2=9E=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E9=85=8D=E7=BD=AE=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builtin/form/model-config-form/.gitignore | 4 + .../builtin/form/model-config-form/.npmrc | 3 + .../builtin/form/model-config-form/README.md | 174 +++++++++++ .../form/model-config-form/config.json | 48 +++ .../builtin/form/model-config-form/form.png | Bin 0 -> 51879 bytes .../form/model-config-form/package.json | 58 ++++ .../form/model-config-form/src/app.tsx | 64 ++++ .../src/assets/images/empty.png | Bin 0 -> 3755 bytes .../model-config-form/src/components/form.tsx | 288 ++++++++++++++++++ .../model-config-form/src/context/index.ts | 9 + .../form/model-config-form/src/index.tsx | 8 + .../model-config-form/src/styles/form.scss | 21 ++ .../form/model-config-form/src/utils/index.ts | 18 ++ .../form/model-config-form/webpack.common.js | 150 +++++++++ .../form/model-config-form/webpack.dev.js | 14 + .../form/model-config-form/webpack.prod.js | 15 + 16 files changed, 874 insertions(+) create mode 100644 app-builder/builtin/form/model-config-form/.gitignore create mode 100644 app-builder/builtin/form/model-config-form/.npmrc create mode 100644 app-builder/builtin/form/model-config-form/README.md create mode 100644 app-builder/builtin/form/model-config-form/config.json create mode 100644 app-builder/builtin/form/model-config-form/form.png create mode 100644 app-builder/builtin/form/model-config-form/package.json create mode 100644 app-builder/builtin/form/model-config-form/src/app.tsx create mode 100644 app-builder/builtin/form/model-config-form/src/assets/images/empty.png create mode 100644 app-builder/builtin/form/model-config-form/src/components/form.tsx create mode 100644 app-builder/builtin/form/model-config-form/src/context/index.ts create mode 100644 app-builder/builtin/form/model-config-form/src/index.tsx create mode 100644 app-builder/builtin/form/model-config-form/src/styles/form.scss create mode 100644 app-builder/builtin/form/model-config-form/src/utils/index.ts create mode 100644 app-builder/builtin/form/model-config-form/webpack.common.js create mode 100644 app-builder/builtin/form/model-config-form/webpack.dev.js create mode 100644 app-builder/builtin/form/model-config-form/webpack.prod.js diff --git a/app-builder/builtin/form/model-config-form/.gitignore b/app-builder/builtin/form/model-config-form/.gitignore new file mode 100644 index 00000000..f29eeccd --- /dev/null +++ b/app-builder/builtin/form/model-config-form/.gitignore @@ -0,0 +1,4 @@ +package-lock.json +index.html +node_modules/ +output/ \ No newline at end of file diff --git a/app-builder/builtin/form/model-config-form/.npmrc b/app-builder/builtin/form/model-config-form/.npmrc new file mode 100644 index 00000000..2480139d --- /dev/null +++ b/app-builder/builtin/form/model-config-form/.npmrc @@ -0,0 +1,3 @@ +strict-ssl=false +package-lock=true +registry=https://registry.npmjs.org/ \ No newline at end of file diff --git a/app-builder/builtin/form/model-config-form/README.md b/app-builder/builtin/form/model-config-form/README.md new file mode 100644 index 00000000..f7c7cb24 --- /dev/null +++ b/app-builder/builtin/form/model-config-form/README.md @@ -0,0 +1,174 @@ +# 自定义组件开发说明 +## 前提条件 +* 开发工具建议使用vscode, 基础安装要求建议node版本为18及以上, npm版本为10及以上。 +* React组件建议使用antd组件库,模板默认版本为4 .24.13。 + +## 操作步骤 +### 约束条件 +上传的组件包需要为zip压缩包,解压后大小不能超过5M,包含且只能包含三个部分: +* 表单代码打包的静态资源文件 build文件夹。 +* 表单的出入参配置文件 config.json。 +* 表单的预览图 form.jpg/form.png/form.jpeg 大小不能超过1M。 +## 开发组件代码 +### 创建文件 +* 用户在 /src/components文件夹下创建自己的组件文件,类型为.tsx。 +### 表单获取流程数据 +在流程中使用智能表单节点或结束节点选用表单时,可以将前序节点的输出作为表单初始化的数据使用。假设表单需要的数据结构为: +``` +{ + "a": "你好", + "b": "Demo1" +} +``` +在表单里使用如下代码: +data就是由流程传输给表单的,格式为{“a": "", "b": ""}的json数据,可以使用这个数据来初始化表单。 +注意:这里data的数据结构与config.json的入参配置一致。 +``` +const {data, terminateClick, resumingClick, restartClick} = useContext(DataContext); +``` +### 表单调用内置接口 +平台提供了三个内置接口:继续对话(resumingClick),重新对话(restartClick),终止对话(terminateClick)。使用这几个内置接口,可以与流程进行交互。 +在表单里使用如下代码,可以以调用方法的形式使用内置接口。 +``` +const {data, terminateClick, resumingClick, restartClick} = useContext(DataContext); +``` +1. 终止对话接口 +终止对话接口适用于配置在**智能表单**节点的表单,适用于用户希望在流程的中间过程中,想要终止本地对话的场景。具体的过程是: +``` +应用流程进行到智能表单节点,流程暂停 ---> 用户与表单进行交互,触发终止对话接口 ---> 流程终止,对话结束 +``` +使用示例: +``` +如果表单里有按钮用于触发终止对话: + + +调用终止对话接口: +const onTerminateClick = () => { + terminateClick({content: //字符串,终止对话后希望显示的文本}); +} +如果希望点击终止对话后,显示的文本是"终止对话" +terminateClick({content: "终止会话"}); +``` +**注意:在结束节点使用的表单如果调用终止对话接口,会出现错误。** +2. 继续对话接口 +继续对话接口适用于配置在**智能表单**节点的表单,适用于用户希望在流程的中间过程使用表单,进行一次人工交互,交互结束后流程继续的场景。具体的过程是: +``` +应用流程进行到智能表单节点,流程暂停 ---> 用户与表单进行交互,触发继续对话接口 ---> 流程继续进行到下一个节点 +``` +使用示例: +``` +如果表单里有按钮用于触发继续对话: + + +调用继续对话接口: +const onResumeClick = () => { + resumingClick({params: //这里是表单的出参数据}); +} +如果表单的出参有两个,String 类型的"a",Int 类型的"b": +resumingClick({params: {a: "hello", b: 1}}); +``` +**注意:在结束节点使用的表单如果调用继续对话接口,会出现错误。** +3. 重新对话接口 + 重新对话接口适用于配置在**结束**节点的表单,适用于用户希望在流程结束后,想使用相同的问题重新再发起一次对话。具体的过程是: +``` +应用流程进行到结束节点,流程结束 ---> 表单展示流程输出,用户与表单进行交互,触发重新对话接口 ---> 再次从头发起一次流程 +``` +使用示例: +``` +如果表单里有按钮用于触发重新对话: + + +调用重新对话接口: +const onRestartClick = () => { + restartClick({params: //这里是表单的出参数据}); +} +如果表单的出参有两个,String 类型的"a",Int 类型的"b": +restartClick({params: {a: "hello", b: 1}}); +``` +**注意:在智能表单节点使用的表单如果调用重新对话接口,需要先执行终止对话接口,将流程停止,再执行重新对话接口。** +### 表单调用外部接口 +* 如果想在表单中调用非平台内置的接口,需要保证接口支持跨域调用。 +### 表单使用图片 +* 表单使用图片文件时,需要将图片放置在/src/assets/images目录下 +* 表单路径需要写为"./src/assets/images/图片文件名 +示例: +``` + +``` +### 表单添加样式文件 +* 可以在/src/styles目录下添加样式文件,请使用.scss类型 +### 调试表单 +``` +执行 npm install +执行 npm start +启动表单项目,可以查看样式是否符合预期 +``` +* 模拟数据传输 +``` +可以通过设置app.tsx文件里的receiveData来模拟数据传输给表单 +示例 receiveData: { + data: { + "a": "你好", + "b": "Demo1" + }, // 真正的表单需要的输入 + uniqueId: 10, // 任意数字即可 + origin: http://127.0.0.1:3350, // 当前调试本地窗口origin + tenantId: fh47kl // 任意uuid即可 +} +``` + +### 打包表单代码 +``` +执行 npm run build 将组件打包在build文件夹里 +``` +## 表单出入参配置 +* 表单的出入参配置需要为json文件,名称为config.json。 +* 文件内容表示表单的出入参的类型、描述以及参数顺序等信息,需要符合[json schema规范](https://json-schema.apifox.cn/)。 +### 约束 +* 最外层parameters字段是入参,入参第一层必须type为object。 +* 必须包含name,支持中文、英文、数字、空格、中划线、下划线组合。 +* 可以包含description, 对参数进行描述。 +* 必须包含parameters。 +* 必须包含required, 内容不可以为properties下参数名之外的参数名。 +* 可以包含order, 若写必须为properties下所有参数名的列表;若不写,则默认按照properties下所有参数名的顺序。 +* 必须包含return,return字段是出参。 +### 示例 +``` +// 这是一个表单的出入参都为 字段名:"a", 类型:String; +{ + "schema": { + "parameters": { + "type": "object", + "required": [ + "a", + "b" + ], + "properties": { + "a": { + "type": "string", + "default": "haha" + }, + "b": { + "type": "string", + "default": "heihei" + } + } + }, + "return": { + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + } + } + } +} +``` +## 表单预览图 +* 表单预览图的类型支持为.jpg/.png/.jpeg, 名称为form,大小不超过1M。 +## 打包 +* 将build文件夹、config.json、form.png打成zip压缩包,压缩包名称支持大小写英文、中文和数字的字符串,可以包含中划线(-)和下划线(_),但不能以中划线(-)和下划线(_)开头或结尾。 \ No newline at end of file diff --git a/app-builder/builtin/form/model-config-form/config.json b/app-builder/builtin/form/model-config-form/config.json new file mode 100644 index 00000000..70424e55 --- /dev/null +++ b/app-builder/builtin/form/model-config-form/config.json @@ -0,0 +1,48 @@ +{ + "schema": { + "name": "模型管理表单", + "parameters": { + "type": "object", + "required": ["models"], + "properties": { + "models": { + "type": "array", + "items": { + "type": "object", + "properties": { + "createdAt": { "type": "string" }, + "modelName": { "type": "string" }, + "modelId": { "type": "string" }, + "baseUrl": { "type": "string" }, + "isDefault": { "type": "integer" }, + "userId": { "type": "string" } + }, + "required": ["modelId", "isDefault", "userId"] + } + } + } + }, + "return": { + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": ["add", "delete", "switch", "quit"] + }, + "info": { + "type": "object", + "properties": { + "modelName": { "type": "string" }, + "modelId": { "type": "string" }, + "baseUrl": { "type": "string" }, + "isDefault": { "type": "integer" }, + "userId": { "type": "string" }, + "apiKey": { "type": "string" } + }, + "required": ["modelName", "modelId", "baseUrl", "isDefault", "userId", "apiKey"] + } + }, + "required": ["action", "info"] + } + } +} diff --git a/app-builder/builtin/form/model-config-form/form.png b/app-builder/builtin/form/model-config-form/form.png new file mode 100644 index 0000000000000000000000000000000000000000..b39ef9e07310bb7432a480abeb9d65ce9f8f0c12 GIT binary patch literal 51879 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 + const uniqueId = getQueryParams(window.location.href); + + const handleMessage = (event: any) => { + setReceiveData(event.data); + }; + + const terminateClick = (params: any) => { + window.parent.postMessage({ type: 'app-engine-form-terminate', ...params, uniqueId }, receiveData.origin); + }; + + const resumingClick = (params: any) => { + window.parent.postMessage({ type: 'app-engine-form-resuming', ...params, uniqueId }, receiveData.origin); + }; + + const restartClick = (params: any) => { + window.parent.postMessage({ type: 'app-engine-form-restart', ...params, uniqueId }, receiveData.origin); + }; + + useEffect(() => { + if (inIframe()) { + window.addEventListener('message', handleMessage); + window.parent.postMessage({ + type: 'app-engine-form-ready', + uniqueId + }, '*'); + } + + const ro = new ResizeObserver(entries => { + entries.forEach(entry => { + const height = entry.contentRect.height > 900 ? 900 : entry.contentRect.height; + window.parent.postMessage({ type: 'app-engine-form-resize', height, uniqueId }, "*"); + }); + }); + ro.observe(document.querySelector('#custom-smart-form')); + + return () => { + if (inIframe()) { + window.removeEventListener('message', handleMessage); + } + + ro.unobserve(document.querySelector('#custom-smart-form')); + ro.disconnect(); + }; + }, []); + + return ( +

+ ); +} diff --git a/app-builder/builtin/form/model-config-form/src/assets/images/empty.png b/app-builder/builtin/form/model-config-form/src/assets/images/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..fd355f179e233a70d4b672f29a7d6f7fcb72fddf GIT binary patch literal 3755 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 V9HY0#kdy!b002ovPDHLkV1nFC>Vg0O literal 0 HcmV?d00001 diff --git a/app-builder/builtin/form/model-config-form/src/components/form.tsx b/app-builder/builtin/form/model-config-form/src/components/form.tsx new file mode 100644 index 00000000..764dbad0 --- /dev/null +++ b/app-builder/builtin/form/model-config-form/src/components/form.tsx @@ -0,0 +1,288 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Button, Table, Card, Typography, Space, Tooltip, Modal, Input, message, Dropdown, Menu } from 'antd'; +import { + PlusOutlined, + EllipsisOutlined, + ExclamationCircleOutlined, + LogoutOutlined +} from '@ant-design/icons'; +import { DataContext } from '../context'; +import '../styles/form.scss'; + +const { Title, Text } = Typography; +const { confirm } = Modal; + +const SmartForm: React.FC = () => { + const { data, resumingClick } = useContext(DataContext); + const [modelList, setModelList] = useState([]); + const [isModalVisible, setIsModalVisible] = useState(false); + const [newModel, setNewModel] = useState({ modelName: '', baseUrl: '', apiKey: '' }); + + const buildOutputInfo = (partial: Partial<{ + modelName: string; + modelId: string; + baseUrl: string; + userId: string; + isDefault: number; + apiKey: string; + }>) => ({ + modelName: partial.modelName || '', + modelId: partial.modelId || '', + baseUrl: partial.baseUrl || '', + userId: partial.userId || '', + isDefault: partial.isDefault ?? 0, + apiKey: partial.apiKey || '' + }); + + useEffect(() => { + if (Array.isArray(data?.models)) { + const enhancedModels = data.models.map((item, index) => ({ + ...item, + serial: index + 1, + })); + setModelList(enhancedModels); + } + }, [data]); + + const handleAdd = () => setIsModalVisible(true); + const handleAddCancel = () => setIsModalVisible(false); + + const handleAddConfirm = () => { + const { modelName, baseUrl, apiKey } = newModel; + if (!modelName.trim() || !baseUrl.trim() || !apiKey.trim()) { + message.warning('请填写完整模型信息'); + return; + } + + const userId = data?.models?.[0]?.userId || ''; + + const payload = buildOutputInfo({ + userId, + modelName, + baseUrl, + apiKey + }); + + resumingClick({ + params: { + action: 'add', + info: payload + } + }); + + setIsModalVisible(false); + setNewModel({ modelName: '', baseUrl: '', apiKey: '' }); + }; + + const handleDelete = (record: any) => { + confirm({ + title: '删除确认', + icon: , + content: ( + <> +

你确定要删除这个模型吗?

+

模型名称:{record.modelName}

+ + ), + okText: '确认删除', + cancelText: '取消', + okType: 'danger', + onOk: () => { + const payload = buildOutputInfo({ + ...record, + apiKey: '' + }); + resumingClick({ + params: { + action: 'delete', + info: payload + } + }); + }, + }); + }; + + const handleSwitchDefault = (record: any) => { + confirm({ + title: '切换默认模型', + icon: , + content: `是否将 “${record.modelName}” 设置为默认模型?`, + okText: '确认', + cancelText: '取消', + onOk: () => { + const payload = buildOutputInfo({ + ...record, + apiKey: '' + }); + resumingClick({ + params: { + action: 'switch', + info: payload + } + }); + } + }); + }; + + const handleExit = () => { + const payload = buildOutputInfo({}) + resumingClick({ + params: { + action: 'quit', + info: payload + } + }); + }; + + const columns = [ + { + title: 'ID', + dataIndex: 'serial', + key: 'serial', + align: 'center', + width: 60, + }, + { + title: '模型名称', + dataIndex: 'modelName', + key: 'modelName', + align: 'center', + render: (text: string) => ( + +
+ {text} +
+
+ ), + }, + { + title: 'Base URL', + dataIndex: 'baseUrl', + key: 'baseUrl', + align: 'center', + render: (text: string) => ( + +
+ {text} +
+
+ ), + }, + { + title: '是否默认', + dataIndex: 'isDefault', + key: 'isDefault', + align: 'center', + render: (value: number) => (value === 1 ? '是' : '否'), + }, + { + title: '操作', + key: 'action', + align: 'center', + width: 80, + render: (_: any, record: any) => { + const menuItems = [ + record.isDefault !== 1 && { + key: 'switch', + label: '设为默认模型', + onClick: () => handleSwitchDefault(record) + }, + { + key: 'delete', + label: '删除模型', + onClick: () => handleDelete(record) + } + ].filter(Boolean); + + return ( + } trigger={['click']}> + + + + + + + +
+ + setNewModel({ ...newModel, modelName: e.target.value })} + placeholder="请输入模型名称" + /> +
+
+ + setNewModel({ ...newModel, apiKey: e.target.value })} + placeholder="请输入 API Key" + /> +
+
+ + setNewModel({ ...newModel, baseUrl: e.target.value })} + placeholder="请输入 Base URL" + /> +
+
+ + ); +}; + +export default SmartForm; diff --git a/app-builder/builtin/form/model-config-form/src/context/index.ts b/app-builder/builtin/form/model-config-form/src/context/index.ts new file mode 100644 index 00000000..ab7f51b4 --- /dev/null +++ b/app-builder/builtin/form/model-config-form/src/context/index.ts @@ -0,0 +1,9 @@ +/*************************************************请勿修改或删除该文件**************************************************/ +import { createContext } from 'react'; + +export const DataContext = createContext({ + data: {}, + terminateClick: (params) => {}, + resumingClick: (params) => {}, + restartClick: (params) => {}, +}); \ No newline at end of file diff --git a/app-builder/builtin/form/model-config-form/src/index.tsx b/app-builder/builtin/form/model-config-form/src/index.tsx new file mode 100644 index 00000000..df352562 --- /dev/null +++ b/app-builder/builtin/form/model-config-form/src/index.tsx @@ -0,0 +1,8 @@ +/*************************************************请勿修改或删除该文件**************************************************/ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './app'; +import 'antd/dist/antd.less'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(); diff --git a/app-builder/builtin/form/model-config-form/src/styles/form.scss b/app-builder/builtin/form/model-config-form/src/styles/form.scss new file mode 100644 index 00000000..1290b72f --- /dev/null +++ b/app-builder/builtin/form/model-config-form/src/styles/form.scss @@ -0,0 +1,21 @@ +.form-wrap { + height: 100%; + overflow-y: auto; + padding: 24px; + box-sizing: border-box; + + .ant-card { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + border-radius: 12px; + } + + .ant-table-thead > tr > th { + background: #fafafa; + font-weight: bold; + } + + .ant-btn[shape='circle']:hover { + background-color: #ffecec !important; + border-radius: 50%; + } +} diff --git a/app-builder/builtin/form/model-config-form/src/utils/index.ts b/app-builder/builtin/form/model-config-form/src/utils/index.ts new file mode 100644 index 00000000..6d7ea325 --- /dev/null +++ b/app-builder/builtin/form/model-config-form/src/utils/index.ts @@ -0,0 +1,18 @@ +/*************************************************请勿修改或删除getQueryParams方法**************************************************/ +export const getQueryParams = (url) => { + const regex = /uniqueId=([a-zA-Z0-9-]+)/; + const match = url.match(regex); + if (match && match.length > 1) { + return match[1]; + } else { + return null; + } +} + +export const inIframe = () => { + try { + return window.self !== window.top; + } catch (e) { + return true; + } +} diff --git a/app-builder/builtin/form/model-config-form/webpack.common.js b/app-builder/builtin/form/model-config-form/webpack.common.js new file mode 100644 index 00000000..9fd88e29 --- /dev/null +++ b/app-builder/builtin/form/model-config-form/webpack.common.js @@ -0,0 +1,150 @@ +const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const HappyPack = require("happypack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +module.exports = { + module: { + rules: [{ + test: /\.(js|jsx)$/, + exclude: /node_modules/, + include: [path.resolve(__dirname, "src")], + use: { + loader: "happypack/loader?id=babel", + }, + }, + { + test: /\.(png|jpg|gif)$/, + type: 'javascript/auto', + include: [path.resolve(__dirname, "src")], + exclude: /node_modules/, + use: [{ + loader: "url-loader", + options: { + esModule: false + } + }], + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.(woff|woff2|eot|otf|ttf)$/, + loader: "file-loader", + options: { + name: "[name]-[hash:5].min.[ext]" + } + }, + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + use: [{ + loader: 'babel-loader', + options: { + presets: [ + '@babel/preset-env', + '@babel/preset-react', + '@babel/preset-typescript', + ], + }, + }], + }, + { + test: /\.less$/, + use: [{ + loader: 'style-loader', + }, { + loader: 'css-loader', + }, { + loader: 'less-loader', + options: { + lessOptions: { + modifyVars: { + 'primary-color': '#2673e5', + 'border-radius-base': '4px' + }, + javascriptEnabled: true, + }, + }, + }], + }, + { + test: /\.s(c|a)ss$/, + include: [path.resolve(__dirname, "src")], + exclude: /node_modules/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + url: false, + sourceMap: true, + importLoaders: 2, + modules: { + auto: true, + exportLocalsConvention: 'dashesOnly', + localIdentName: '[local]__[hash:base64:5]', + }, + }, + }, + 'postcss-loader', + { + loader: 'sass-loader', + options: { + api: 'modern' + } + }, + ], + }, + ], + }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src') + }, + extensions: ['.ts', '.tsx', '.js', '.jsx'], + }, + plugins: [ + new HappyPack({ + id: "babel", + loaders: ["babel-loader?cacheDirectory"] + }), + new MiniCssExtractPlugin({ + filename: "[name].[hash:8].css", + chunkFilename: "[name].[hash:8].css", + }), + new HtmlWebpackPlugin({ + title: "module", + template: path.join(process.cwd(), './src/index.html'), + filename: "index.html", + }), + new CleanWebpackPlugin(), + new CopyWebpackPlugin({ + patterns: [ + { + from: 'config.json', + to: '../config.json' + }, + { + from: 'form.png', + to: '../form.png' + } + ] + }), + ], + optimization: { + splitChunks: { + chunks: 'all', + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all', + }, + }, + }, + }, +}; diff --git a/app-builder/builtin/form/model-config-form/webpack.dev.js b/app-builder/builtin/form/model-config-form/webpack.dev.js new file mode 100644 index 00000000..ace654aa --- /dev/null +++ b/app-builder/builtin/form/model-config-form/webpack.dev.js @@ -0,0 +1,14 @@ +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); +module.exports = merge(common, { + mode: 'development', + devtool: 'inline-source-map', + plugins: [], + devServer: { + historyApiFallback: true, + hot: true, + open: true, + https: false, + proxy: {} + }, +}); diff --git a/app-builder/builtin/form/model-config-form/webpack.prod.js b/app-builder/builtin/form/model-config-form/webpack.prod.js new file mode 100644 index 00000000..427dd3fa --- /dev/null +++ b/app-builder/builtin/form/model-config-form/webpack.prod.js @@ -0,0 +1,15 @@ + +const path = require("path"); +const { merge } = require("webpack-merge"); +const common = require("./webpack.common.js"); + +module.exports = merge(common, { + entry: { + index: "./src/index.tsx", + }, + output: { + filename: "[name].js", + path: path.resolve(__dirname, "output/build") + }, + plugins: [], +}); From da1be5059aa275fd41f340c4976ded53cdea32be Mon Sep 17 00:00:00 2001 From: lizhichao51 Date: Thu, 8 May 2025 10:01:06 +0800 Subject: [PATCH 2/5] =?UTF-8?q?[app-platform]=20=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=A1=A8=E5=8D=95=E4=BF=AE=E6=94=B9=E6=A3=80?= =?UTF-8?q?=E8=A7=86=E6=84=8F=E8=A7=81=EF=BC=8C=E6=8F=90=E5=8F=96=E8=A1=8C?= =?UTF-8?q?=E5=86=85=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/model-config-form/src/app.tsx | 31 ++++++++++++------- .../model-config-form/src/components/form.tsx | 6 ++-- .../model-config-form/src/styles/form.scss | 5 +++ 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/app-builder/builtin/form/model-config-form/src/app.tsx b/app-builder/builtin/form/model-config-form/src/app.tsx index da20dd57..60a8ee82 100644 --- a/app-builder/builtin/form/model-config-form/src/app.tsx +++ b/app-builder/builtin/form/model-config-form/src/app.tsx @@ -1,5 +1,5 @@ /*************************************************请勿修改或删除该文件**************************************************/ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { inIframe, getQueryParams } from './utils/index'; import { DataContext } from './context'; import Form from './components/form'; @@ -7,6 +7,7 @@ import Form from './components/form'; export default function App() { const [receiveData, setReceiveData] = useState({}); const uniqueId = getQueryParams(window.location.href); + const formRef = useRef(null); const handleMessage = (event: any) => { setReceiveData(event.data); @@ -35,30 +36,36 @@ export default function App() { const ro = new ResizeObserver(entries => { entries.forEach(entry => { - const height = entry.contentRect.height > 900 ? 900 : entry.contentRect.height; + const height = entry.contentRect.height; window.parent.postMessage({ type: 'app-engine-form-resize', height, uniqueId }, "*"); }); }); - ro.observe(document.querySelector('#custom-smart-form')); + + if (formRef.current) { + ro.observe(formRef.current); + } return () => { if (inIframe()) { window.removeEventListener('message', handleMessage); } - ro.unobserve(document.querySelector('#custom-smart-form')); + if (formRef.current) { + ro.unobserve(formRef.current); + } ro.disconnect(); }; }, []); return ( -
- - - -
+
+ + + +
); } diff --git a/app-builder/builtin/form/model-config-form/src/components/form.tsx b/app-builder/builtin/form/model-config-form/src/components/form.tsx index 764dbad0..7989a8e6 100644 --- a/app-builder/builtin/form/model-config-form/src/components/form.tsx +++ b/app-builder/builtin/form/model-config-form/src/components/form.tsx @@ -106,7 +106,7 @@ const SmartForm: React.FC = () => { confirm({ title: '切换默认模型', icon: , - content: `是否将 “${record.modelName}” 设置为默认模型?`, + content: `是否将 "${record.modelName}" 设置为默认模型?`, okText: '确认', cancelText: '取消', onOk: () => { @@ -220,7 +220,7 @@ const SmartForm: React.FC = () => { ]; return ( -
+
模型管理 @@ -234,7 +234,7 @@ const SmartForm: React.FC = () => { pagination={false} bordered size="middle" - locale={{ emptyText: '暂无模型数据,请点击“添加模型”' }}> + locale={{ emptyText: '暂无模型数据,请点击"添加模型"' }}>
diff --git a/app-builder/builtin/form/model-config-form/src/styles/form.scss b/app-builder/builtin/form/model-config-form/src/styles/form.scss index 1290b72f..4f4ea862 100644 --- a/app-builder/builtin/form/model-config-form/src/styles/form.scss +++ b/app-builder/builtin/form/model-config-form/src/styles/form.scss @@ -19,3 +19,8 @@ border-radius: 50%; } } + +.form-container { + padding: 24px; + background: #f5f5f5; +} \ No newline at end of file From 5f781296548dd5838b9f53468d63cc6190588766 Mon Sep 17 00:00:00 2001 From: lizhichao51 Date: Sat, 10 May 2025 11:01:34 +0800 Subject: [PATCH 3/5] =?UTF-8?q?[app-platform]=20=E4=BF=AE=E6=94=B9readme?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9A=84=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builtin/form/model-config-form/README.md | 251 +++++++++--------- 1 file changed, 125 insertions(+), 126 deletions(-) diff --git a/app-builder/builtin/form/model-config-form/README.md b/app-builder/builtin/form/model-config-form/README.md index f7c7cb24..ca15c8c9 100644 --- a/app-builder/builtin/form/model-config-form/README.md +++ b/app-builder/builtin/form/model-config-form/README.md @@ -1,174 +1,173 @@ -# 自定义组件开发说明 +# 画布 # 自定义组件开发说明 + ## 前提条件 -* 开发工具建议使用vscode, 基础安装要求建议node版本为18及以上, npm版本为10及以上。 -* React组件建议使用antd组件库,模板默认版本为4 .24.13。 + +* 开发工具建议使用 VSCode + +* 基础环境: Node.js 版本 >= 18, npm 版本 >= 10 + +* React 组件建议使用 Ant Design (版本: 4.24.13) ## 操作步骤 + ### 约束条件 -上传的组件包需要为zip压缩包,解压后大小不能超过5M,包含且只能包含三个部分: -* 表单代码打包的静态资源文件 build文件夹。 -* 表单的出入参配置文件 config.json。 -* 表单的预览图 form.jpg/form.png/form.jpeg 大小不能超过1M。 + +上传的组件包必须是 zip 压缩包,解压后文件大小不得超过 5M,且必须包含三部分: + +* build 文件夹: 表单代码打包后的静态资源 + +* config.json: 表单的输入输出参配置文件 + +* form.jpg/png/jpeg: 表单预览图,大小不得超过 1M + ## 开发组件代码 + ### 创建文件 -* 用户在 /src/components文件夹下创建自己的组件文件,类型为.tsx。 + +* 在 `/src/components` 目录下创建 `.tsx` 类型的组件文件 + ### 表单获取流程数据 -在流程中使用智能表单节点或结束节点选用表单时,可以将前序节点的输出作为表单初始化的数据使用。假设表单需要的数据结构为: -``` -{ - "a": "你好", - "b": "Demo1" -} -``` -在表单里使用如下代码: -data就是由流程传输给表单的,格式为{“a": "", "b": ""}的json数据,可以使用这个数据来初始化表单。 -注意:这里data的数据结构与config.json的入参配置一致。 -``` -const {data, terminateClick, resumingClick, restartClick} = useContext(DataContext); + +用于初始化表单数据: + +```tsx +const { data, terminateClick, resumingClick, restartClick } = useContext(DataContext); ``` + +`data` 为 json 数据,结构与 config.json 的输入参配置一致 + ### 表单调用内置接口 -平台提供了三个内置接口:继续对话(resumingClick),重新对话(restartClick),终止对话(terminateClick)。使用这几个内置接口,可以与流程进行交互。 -在表单里使用如下代码,可以以调用方法的形式使用内置接口。 -``` -const {data, terminateClick, resumingClick, restartClick} = useContext(DataContext); -``` -1. 终止对话接口 -终止对话接口适用于配置在**智能表单**节点的表单,适用于用户希望在流程的中间过程中,想要终止本地对话的场景。具体的过程是: -``` -应用流程进行到智能表单节点,流程暂停 ---> 用户与表单进行交互,触发终止对话接口 ---> 流程终止,对话结束 -``` -使用示例: -``` -如果表单里有按钮用于触发终止对话: + +**1. 终止对话 terminateClick()** + +```tsx -调用终止对话接口: const onTerminateClick = () => { - terminateClick({content: //字符串,终止对话后希望显示的文本}); + terminateClick({ content: "终止会话" }); } -如果希望点击终止对话后,显示的文本是"终止对话" -terminateClick({content: "终止会话"}); ``` -**注意:在结束节点使用的表单如果调用终止对话接口,会出现错误。** -2. 继续对话接口 -继续对话接口适用于配置在**智能表单**节点的表单,适用于用户希望在流程的中间过程使用表单,进行一次人工交互,交互结束后流程继续的场景。具体的过程是: -``` -应用流程进行到智能表单节点,流程暂停 ---> 用户与表单进行交互,触发继续对话接口 ---> 流程继续进行到下一个节点 -``` -使用示例: -``` -如果表单里有按钮用于触发继续对话: + +**注意:结束节点不能调用 terminateClick** + +**2. 继续对话 resumingClick()** + +```tsx -调用继续对话接口: const onResumeClick = () => { - resumingClick({params: //这里是表单的出参数据}); + resumingClick({ params: { a: "hello", b: 1 } }); } -如果表单的出参有两个,String 类型的"a",Int 类型的"b": -resumingClick({params: {a: "hello", b: 1}}); ``` -**注意:在结束节点使用的表单如果调用继续对话接口,会出现错误。** -3. 重新对话接口 - 重新对话接口适用于配置在**结束**节点的表单,适用于用户希望在流程结束后,想使用相同的问题重新再发起一次对话。具体的过程是: -``` -应用流程进行到结束节点,流程结束 ---> 表单展示流程输出,用户与表单进行交互,触发重新对话接口 ---> 再次从头发起一次流程 -``` -使用示例: -``` -如果表单里有按钮用于触发重新对话: + +**注意:结束节点不能调用 resumingClick** + +**3. 重新对话 restartClick()** + +```tsx -调用重新对话接口: const onRestartClick = () => { - restartClick({params: //这里是表单的出参数据}); + restartClick({ params: { a: "hello", b: 1 } }); } -如果表单的出参有两个,String 类型的"a",Int 类型的"b": -restartClick({params: {a: "hello", b: 1}}); -``` -**注意:在智能表单节点使用的表单如果调用重新对话接口,需要先执行终止对话接口,将流程停止,再执行重新对话接口。** -### 表单调用外部接口 -* 如果想在表单中调用非平台内置的接口,需要保证接口支持跨域调用。 -### 表单使用图片 -* 表单使用图片文件时,需要将图片放置在/src/assets/images目录下 -* 表单路径需要写为"./src/assets/images/图片文件名 -示例: ``` + +**注意:如果在智能表单节点使用,需先调 terminateClick 再 restartClick** + +### 调用外部接口 + +* 要求接口支持跨域 + +### 使用图片 + +* 图片文件放在 `/src/assets/images` + +* 路径: `./src/assets/images/xxx.png` + +```tsx ``` -### 表单添加样式文件 -* 可以在/src/styles目录下添加样式文件,请使用.scss类型 + +### 表单样式文件 + +* 可以在 `/src/styles` 目录下添加 `.scss` 样式文件 + ### 调试表单 + +```bash +npm install +npm start ``` -执行 npm install -执行 npm start -启动表单项目,可以查看样式是否符合预期 -``` -* 模拟数据传输 -``` -可以通过设置app.tsx文件里的receiveData来模拟数据传输给表单 -示例 receiveData: { - data: { - "a": "你好", - "b": "Demo1" - }, // 真正的表单需要的输入 - uniqueId: 10, // 任意数字即可 - origin: http://127.0.0.1:3350, // 当前调试本地窗口origin - tenantId: fh47kl // 任意uuid即可 + +* 模拟数据 app.tsx: + +```ts +receiveData: { + data: { a: "你好", b: "Demo1" }, + uniqueId: 10, + origin: "http://127.0.0.1:3350", + tenantId: "fh47kl" } ``` -### 打包表单代码 -``` -执行 npm run build 将组件打包在build文件夹里 -``` -## 表单出入参配置 -* 表单的出入参配置需要为json文件,名称为config.json。 -* 文件内容表示表单的出入参的类型、描述以及参数顺序等信息,需要符合[json schema规范](https://json-schema.apifox.cn/)。 -### 约束 -* 最外层parameters字段是入参,入参第一层必须type为object。 -* 必须包含name,支持中文、英文、数字、空格、中划线、下划线组合。 -* 可以包含description, 对参数进行描述。 -* 必须包含parameters。 -* 必须包含required, 内容不可以为properties下参数名之外的参数名。 -* 可以包含order, 若写必须为properties下所有参数名的列表;若不写,则默认按照properties下所有参数名的顺序。 -* 必须包含return,return字段是出参。 -### 示例 +### 打包 + +```bash +npm run build ``` -// 这是一个表单的出入参都为 字段名:"a", 类型:String; + +## 表单输入输出参 config.json + +### 基础规范 + +* 格式需符合[json schema规范](https://json-schema.apifox.cn/) + +* 格式示例: + +```json { "schema": { "parameters": { "type": "object", - "required": [ - "a", - "b" - ], + "required": ["a", "b"], "properties": { - "a": { - "type": "string", - "default": "haha" - }, - "b": { - "type": "string", - "default": "heihei" - } + "a": { "type": "string", "default": "haha" }, + "b": { "type": "string", "default": "heihei" } } }, "return": { "type": "object", "properties": { - "a": { - "type": "string" - }, - "b": { - "type": "string" - } + "a": { "type": "string" }, + "b": { "type": "string" } } } } } ``` + +* 最外层parameters字段是入参,入参第一层必须type为object。 + +* 必须包含name,支持中文、英文、数字、空格、中划线、下划线组合。 + +* 可以包含description, 对参数进行描述。 + +* 必须包含parameters。 + +* 必须包含required, 内容不可以为properties下参数名之外的参数名。 + +* 可以包含order, 若写必须为properties下所有参数名的列表;若不写,则默认按照properties下所有参数名的顺序。 + +* 必须包含return,return字段是出参。 + ## 表单预览图 -* 表单预览图的类型支持为.jpg/.png/.jpeg, 名称为form,大小不超过1M。 -## 打包 -* 将build文件夹、config.json、form.png打成zip压缩包,压缩包名称支持大小写英文、中文和数字的字符串,可以包含中划线(-)和下划线(_),但不能以中划线(-)和下划线(_)开头或结尾。 \ No newline at end of file + +* 名称: form.jpg/png/jpeg + +* 大小: 不超过 1M + +## 打包规则 + +* 包含 build/、config.json、form.png + +* 将build文件夹、config.json、form.png打成zip压缩包,压缩包名称支持大小写英文、中文和数字的字符串,可以包含中划线(-)和下划线(_),但不能以中划线(-)和下划线(_)开头或结尾。 From 990ea0f25acdf8218ff6ae9d99afb49bb9c60b76 Mon Sep 17 00:00:00 2001 From: lizhichao51 Date: Sat, 10 May 2025 16:37:56 +0800 Subject: [PATCH 4/5] =?UTF-8?q?[app-platform]=20=E4=BF=AE=E6=94=B9readme?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9A=84=E8=A1=8C=E5=86=85=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../builtin/form/model-config-form/README.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app-builder/builtin/form/model-config-form/README.md b/app-builder/builtin/form/model-config-form/README.md index ca15c8c9..1ab62543 100644 --- a/app-builder/builtin/form/model-config-form/README.md +++ b/app-builder/builtin/form/model-config-form/README.md @@ -38,7 +38,7 @@ const { data, terminateClick, resumingClick, restartClick } = useContext(DataCon ### 表单调用内置接口 -**1. 终止对话 terminateClick()** +**1. 终止对话 `terminateClick()`** ```tsx @@ -48,9 +48,9 @@ const onTerminateClick = () => { } ``` -**注意:结束节点不能调用 terminateClick** +**注意:结束节点不能调用 `terminateClick`** -**2. 继续对话 resumingClick()** +**2. 继续对话 `resumingClick()`** ```tsx @@ -60,9 +60,9 @@ const onResumeClick = () => { } ``` -**注意:结束节点不能调用 resumingClick** +**注意:结束节点不能调用 `resumingClick`** -**3. 重新对话 restartClick()** +**3. 重新对话 `restartClick()`** ```tsx @@ -72,7 +72,7 @@ const onRestartClick = () => { } ``` -**注意:如果在智能表单节点使用,需先调 terminateClick 再 restartClick** +**注意:如果在智能表单节点使用,需先调 `terminateClick` 再 `restartClick`** ### 调用外部接口 @@ -99,7 +99,7 @@ npm install npm start ``` -* 模拟数据 app.tsx: +* 模拟数据 `app.tsx`: ```ts receiveData: { @@ -146,19 +146,19 @@ npm run build } ``` -* 最外层parameters字段是入参,入参第一层必须type为object。 +* 最外层 `parameters` 字段是入参,入参第一层必须 `type` 为 `object`。 -* 必须包含name,支持中文、英文、数字、空格、中划线、下划线组合。 +* 必须包含 `name`,支持中文、英文、数字、空格、中划线、下划线组合。 -* 可以包含description, 对参数进行描述。 +* 可以包含 `description`,对参数进行描述。 -* 必须包含parameters。 +* 必须包含 `parameters`。 -* 必须包含required, 内容不可以为properties下参数名之外的参数名。 +* 必须包含 `required`,内容不可以为 `properties` 下参数名之外的参数名。 -* 可以包含order, 若写必须为properties下所有参数名的列表;若不写,则默认按照properties下所有参数名的顺序。 +* 可以包含 `order`,若写必须为 `properties` 下所有参数名的列表;若不写,则默认按照 `properties` 下所有参数名的顺序。 -* 必须包含return,return字段是出参。 +* 必须包含 `return`,`return` 字段是出参。 ## 表单预览图 From 4b93a832a8bee54f8ef40131b6162bb7ef388549 Mon Sep 17 00:00:00 2001 From: lizhichao51 Date: Sat, 10 May 2025 16:46:52 +0800 Subject: [PATCH 5/5] =?UTF-8?q?[app-platform]=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8D=E5=BA=94=E8=AF=A5=E5=BF=BD=E7=95=A5index.html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app-builder/builtin/form/model-config-form/.gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/app-builder/builtin/form/model-config-form/.gitignore b/app-builder/builtin/form/model-config-form/.gitignore index f29eeccd..59305424 100644 --- a/app-builder/builtin/form/model-config-form/.gitignore +++ b/app-builder/builtin/form/model-config-form/.gitignore @@ -1,4 +1,3 @@ package-lock.json -index.html node_modules/ output/ \ No newline at end of file