Merge branch 'refs/heads/master' into production

master
钟良源 3 months ago
commit ffdecae67d

@ -23,6 +23,7 @@
"@codemirror/lang-python": "^6.2.1",
"@loadable/component": "^5.13.2",
"@reduxjs/toolkit": "^2.9.0",
"@toast-ui/react-editor": "^3.2.3",
"@turf/turf": "^6.5.0",
"@uiw/codemirror-theme-github": "^4.25.2",
"@uiw/react-codemirror": "^4.21.25",

@ -35,6 +35,9 @@ importers:
'@reduxjs/toolkit':
specifier: ^2.9.0
version: 2.9.0(react-redux@7.2.9(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react@17.0.2)
'@toast-ui/react-editor':
specifier: ^3.2.3
version: 3.2.3(react@17.0.2)
'@turf/turf':
specifier: ^6.5.0
version: 6.5.0
@ -1140,6 +1143,14 @@ packages:
resolution: {integrity: sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==}
engines: {node: '>=10'}
'@toast-ui/editor@3.2.2':
resolution: {integrity: sha512-ASX7LFjN2ZYQJrwmkUajPs7DRr9FsM1+RQ82CfTO0Y5ZXorBk1VZS4C2Dpxinx9kl55V4F8/A2h2QF4QMDtRbA==}
'@toast-ui/react-editor@3.2.3':
resolution: {integrity: sha512-86QdgiOkBeSwRBEUWRKsTpnm6yu5j9HNJ3EfQN8EGcd7kI8k8AhExXyUJ3NNgNTzN7FfSKMw+1VaCDDC+aZ3dw==}
peerDependencies:
react: ^17.0.1
'@turf/along@6.5.0':
resolution: {integrity: sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==}
@ -2415,6 +2426,9 @@ packages:
domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
dompurify@2.5.8:
resolution: {integrity: sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==}
domutils@1.7.0:
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
@ -3547,6 +3561,9 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
orderedmap@2.1.1:
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
os-browserify@0.3.0:
resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==}
@ -3721,6 +3738,30 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
prosemirror-commands@1.7.1:
resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
prosemirror-history@1.4.1:
resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==}
prosemirror-inputrules@1.5.1:
resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==}
prosemirror-keymap@1.2.3:
resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
prosemirror-model@1.25.4:
resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
prosemirror-state@1.4.4:
resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
prosemirror-transform@1.10.4:
resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==}
prosemirror-view@1.41.3:
resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==}
prr@1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
@ -3991,6 +4032,9 @@ packages:
resolution: {integrity: sha512-a2S4Bh3bgrdO4BhKr2E4nZkjTvrJ2m2bWjMTzVYtoqSCn0HnuxosXnaJUHrMEziOWr3CzL9GjilQQKcyCQpJoA==}
hasBin: true
rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@ -5993,6 +6037,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@toast-ui/editor@3.2.2':
dependencies:
dompurify: 2.5.8
prosemirror-commands: 1.7.1
prosemirror-history: 1.4.1
prosemirror-inputrules: 1.5.1
prosemirror-keymap: 1.2.3
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-view: 1.41.3
'@toast-ui/react-editor@3.2.3(react@17.0.2)':
dependencies:
'@toast-ui/editor': 3.2.2
react: 17.0.2
'@turf/along@6.5.0':
dependencies:
'@turf/bearing': 6.5.0
@ -7956,6 +8016,8 @@ snapshots:
domelementtype@2.3.0: {}
dompurify@2.5.8: {}
domutils@1.7.0:
dependencies:
dom-serializer: 0.2.2
@ -9253,6 +9315,8 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
orderedmap@2.1.1: {}
os-browserify@0.3.0: {}
own-keys@1.0.1:
@ -9414,6 +9478,49 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
prosemirror-commands@1.7.1:
dependencies:
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-transform: 1.10.4
prosemirror-history@1.4.1:
dependencies:
prosemirror-state: 1.4.4
prosemirror-transform: 1.10.4
prosemirror-view: 1.41.3
rope-sequence: 1.3.4
prosemirror-inputrules@1.5.1:
dependencies:
prosemirror-state: 1.4.4
prosemirror-transform: 1.10.4
prosemirror-keymap@1.2.3:
dependencies:
prosemirror-state: 1.4.4
w3c-keyname: 2.2.8
prosemirror-model@1.25.4:
dependencies:
orderedmap: 2.1.1
prosemirror-state@1.4.4:
dependencies:
prosemirror-model: 1.25.4
prosemirror-transform: 1.10.4
prosemirror-view: 1.41.3
prosemirror-transform@1.10.4:
dependencies:
prosemirror-model: 1.25.4
prosemirror-view@1.41.3:
dependencies:
prosemirror-model: 1.25.4
prosemirror-state: 1.4.4
prosemirror-transform: 1.10.4
prr@1.0.1:
optional: true
@ -9720,6 +9827,8 @@ snapshots:
minimist: 1.2.8
source-map-support: 0.3.3
rope-sequence@1.3.4: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="16px" height="16px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M340 865.142857q0-57.142857-94.285714-57.142857-90.285714 0-90.285714 59.428571 0 57.714286 98.285714 57.714286 86.285714 0 86.285714-60zm-33.714286-431.428571q0-34.857143-17.142857-58.285714t-50.857143-23.428571q-70.857143 0-70.857143 82.857143 0 77.142857 70.857143 77.142857 68 0 68-78.285714zm153.714286-185.142857l0 115.428571q-20.571429 6.857143-45.142857 12.571429 9.142857 24.571429 9.142857 48 0 72.571429-41.714286 123.714286t-112.571429 64.285714q-22.857143 4.571429-34 15.428571t-11.142857 33.142857q0 17.714286 12.857143 29.428571t33.142857 18.285714 44.857143 12.571429 49.142857 14.571429 44.857143 21.428571 33.142857 36.571429 12.857143 56.285714q0 173.714286-207.428571 173.714286-39.428571 0-74.285714-7.142857t-66.285714-23.428571-50-46.857143-18.571429-72.857143q0-94.285714 104-128.571429l0-2.285714q-38.285714-23.428571-38.285714-72 0-62.285714 36-78.285714l0-2.285714q-41.142857-13.714286-68.285714-62t-27.142857-94.571429q0-79.428571 54.285714-132.285714t134.285714-52.857143q54.857143 0 101.714286 26.857143 56 0 124.571429-26.857143zm181.714286 503.428571l-126.857143 0q2.285714-25.714286 2.285714-76.571429l0-348q0-53.714286-2.285714-73.142857l126.857143 0q-2.285714 18.857143-2.285714 70.857143l0 350.285714q0 50.857143 2.285714 76.571429zm343.428571-126.857143l0 112q-40.571429 22.285714-99.428571 22.285714-35.428571 0-61.142857-11.428571t-40-28.571429-22.571429-44.571429-10.571429-52.571429-2.285714-58.857143l0-200.571429 1.142857 0 0-2.285714q-4 0-10.857143-0.571429t-10.285714-0.571429q-12 0-33.714286 3.428571l0-108.571429 54.857143 0 0-43.428571q0-30.857143-3.428571-50.857143l129.714286 0q-3.428571 23.428571-3.428571 94.285714l97.714286 0 0 108.571429q-8.571429 0-24.857143-1.142857t-24.285714-1.142857l-48.571429 0 0 208.571429q0 74.857143 49.714286 74.857143 34.857143 0 62.285714-18.857143zm-329.142857-541.142857q0 33.142857-22.285714 58t-54.857143 24.857143q-33.142857 0-56-24.857143t-22.857143-58q0-33.714286 22.571429-58.857143t56.285714-25.142857q33.142857 0 55.142857 25.428571t22 58.571429z" /></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="18px" height="18.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M689.3568 820.9408 333.6192 820.9408c-13.9264 0-25.1904-11.264-25.1904-25.1904L308.4288 465.152l-211.968 0c-10.1888 0-19.4048-6.144-23.296-15.5648C69.2224 440.2176 71.424 429.312 78.6432 422.144l415.0272-415.0784c9.472-9.472 26.1632-9.472 35.6352 0l411.392 411.3408c7.2704 4.4544 12.0832 12.4416 12.0832 21.5552 0 14.1312-11.52 24.576-25.7024 25.1904-0.1536 0-0.3072 0-0.512 0l-211.968 0 0 330.5472C714.5984 809.6256 703.2832 820.9408 689.3568 820.9408zM358.8096 770.5088l305.3568 0L664.1664 439.9616c0-13.9264 11.264-25.1904 25.1904-25.1904l176.3328 0L511.488 60.5184 157.2864 414.7712l176.3328 0c13.9264 0 25.1904 11.264 25.1904 25.1904L358.8096 770.5088zM96.4096 923.1872l830.1056 0L926.5152 1024 96.4096 1024 96.4096 923.1872 96.4096 923.1872zM96.4096 923.1872" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="18px" height="18.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M528 504a16 16 0 0 1 16 16v208h66.112c12.8 0 20.416 14.272 13.312 24.896l-98.112 147.136a16 16 0 0 1-26.624 0l-98.112-147.2a16 16 0 0 1 13.312-24.832H480v-208a16 16 0 0 1 16-16z m-16-384a256.384 256.384 0 0 1 254.08 224.384 240.32 240.32 0 0 1 225.92 240.768c-0.64 132.672-110.912 238.848-243.648 238.848h-28.352a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h28.864c98.496 0 180.8-80.64 179.136-179.072a176.192 176.192 0 0 0-176-172.928h-32a16 16 0 0 1-16-16v-12.8c0-105.088-83.2-193.152-188.288-195.2a192.192 192.192 0 0 0-195.712 192v16a16 16 0 0 1-16 16h-32A176.192 176.192 0 0 0 96 580.928c-1.664 98.496 80.64 179.072 179.136 179.072h28.864a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16h-28.352C142.912 824 32.64 717.824 32 585.152a240.32 240.32 0 0 1 225.92-240.768A256.384 256.384 0 0 1 512 120z" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,44 @@
import axios from 'axios';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 我的组件
export function getMyComponentList(params) {
return axios.get(`${urlPrefix}/componentBase/list`, { params });
}
// 协作组件
export function getCooperationComponentList(params) {
return axios.get(`${urlPrefix}/componentBase/page/collaborator`, { params });
}
// 获取标签列表
export function getTagList() {
return axios.get(`${urlPrefix}/componentBase/tags`);
}
// 校验项目标识
export function compProjectValidate(projectId) {
return axios.post(`${urlPrefix}/componentBase/validate?projectId=${projectId}`);
}
// 组件信息提交/更新
export function compSubmit(params) {
return axios.post(`${urlPrefix}/componentBase/submit`, params);
}
// 组件删除
export const remove = (ids) => {
return axios.post(`${urlPrefix}/componentBase/remove?ids=${ids}`);
};
// 组件导出
export const exportComponent = (id) => {
return axios.get(`${urlPrefix}/componentBase/export?id=${id}`);
};
// 复制代码和设计
export const copyAll = (params) => {
return axios.post(`${urlPrefix}/componentBase/copy`, params);
};

@ -0,0 +1,9 @@
import axios from 'axios';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 我的组件
export function getComponentClassify(classifyType: 'component' | 'language_type') {
return axios.get(`${urlPrefix}/componentClassify/list?classifyType=${classifyType}`);
}

@ -0,0 +1,24 @@
import axios from 'axios';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 获取组件设计
export function getComponentDesign(componentBaseId) {
return axios.get(`${urlPrefix}/componentDevelopProcess/design?componentBaseId=${componentBaseId}`);
}
// 更新组件设计
export function updateComponentDesign(params) {
return axios.post(`${urlPrefix}/componentDevelopProcess/design`, params);
}
// 获取组件基本信息
export function getComponentBaseInfo(componentBaseId) {
return axios.get(`${urlPrefix}/componentDevelopProcess/baseInfo?componentBaseId=${componentBaseId}`);
}
// 代码初始化
export function codeInit(componentBaseId) {
return axios.post(`${urlPrefix}/componentDevelopProcess/generate?componentBaseId=${componentBaseId}`);
}

@ -0,0 +1,20 @@
import axios from 'axios';
import { ComponentMarketParams, ReviewGroup } from '@/api/interface';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 组件审核列表
export function getReviewGroupByNew(params: ReviewGroup) {
return axios.get(`${urlPrefix}/componentMarket/reviewGroupByNew`, { params });
}
// 复制组件设计
export function copyDesign(params) {
return axios.post(`${urlPrefix}/componentBase/copyDesign`, params);
}
// 组件市场
export function getComponentMarket(params: ComponentMarketParams) {
return axios.get(`${urlPrefix}/componentMarket/list`, { params });
}

@ -0,0 +1,21 @@
import axios from 'axios';
import { AxiosPromise } from 'axios';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
export interface ComponentReleaseParams {
identifier: string;
version: string;
[key: string]: any;
}
// 组件发布/公开
export function componentRelease(params: ComponentReleaseParams): AxiosPromise<any> {
return axios.post(`${urlPrefix}/componentRelease/publish`, params);
}
// 组件撤销
export function componentRevoke(params: ComponentReleaseParams): AxiosPromise<any> {
return axios.post(`${urlPrefix}/componentRelease/revoke`, params);
}

@ -0,0 +1,9 @@
import axios from 'axios';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 我的组件
export function fileUpload(params) {
return axios.post(`${urlPrefix}/fileSystem/fileUpload`, params);
}

@ -1,4 +1,6 @@
// application
import { getReviewGroupByNew } from '@/api/componentMarket';
export interface CronModel {
appId: string;
cron: string;
@ -231,3 +233,42 @@ export interface GlobalParams {
defaultValue: any,
sceneId: string,
}
// component
export interface ComponentItem {
id: number;
name: string;
identifier: string;
projectId: string;
componentClassify: string;
codeLanguage: string;
desc: string;
componentStatus: string;
tags: string[];
version: string;
logoUrl: string;
localProjectPath: string;
publishStatus: number;
publicStatus: number;
publishTime: string;
updateTime: string;
onlineInstanceCount: number;
createUser: string;
permission: string;
repoCloneUrl: string;
createUserName: string;
}
export interface ReviewGroup {
queryType: string;
current?: number;
size?: number;
}
export interface ComponentMarketParams {
componentClassify: string;
componentClassifyLabel: string;
keyword: string;
current?: string | number;
size?: string | number;
}

@ -0,0 +1,69 @@
'use client';
import React, { useEffect, useRef, useState } from 'react';
import type { Editor as ToastEditor } from '@toast-ui/react-editor';
import '@toast-ui/editor/dist/toastui-editor.css';
import { isSSR } from '@/utils/is';
interface EditorSectionProps {
initialContent?: string;
}
// 创建一个 Viewer 组件用于服务端渲染
const EditorViewer: React.FC<{ content: string }> = ({ content }) => {
const viewerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isSSR && viewerRef.current) {
// 在客户端激活时,动态加载 Viewer 并渲染内容
import('@toast-ui/editor/dist/toastui-editor-viewer').then((module) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const { Viewer } = module;
new Viewer({
el: viewerRef.current!,
initialValue: content || ''
});
}).catch((error) => {
console.error('Failed to load Toast UI Viewer:', error);
});
}
}, [content]);
// 服务端直接渲染 HTML 内容
return <div ref={viewerRef} dangerouslySetInnerHTML={{ __html: content || '' }} />;
};
export default function EditorSection({ initialContent }: EditorSectionProps) {
const [isClient, setIsClient] = useState(false);
const editorRef = useRef<typeof ToastEditor | null>(null);
useEffect(() => {
if (!isSSR) {
setIsClient(true);
// 动态导入编辑器组件
import('@toast-ui/react-editor').then((module) => {
editorRef.current = module.Editor;
}).catch((error) => {
console.error('Failed to load Toast UI Editor:', error);
});
}
}, []);
// 在服务端或组件未加载完成时,使用 Viewer 模式显示内容
if (!isClient || !editorRef.current) {
return (
<div className="mt-8">
<EditorViewer content={initialContent || ''} />
</div>
);
}
const DynamicEditor = editorRef.current;
return (
<div className="mt-8">
<DynamicEditor initialValue={initialContent} initialEditType="markdown" />
</div>
);
}

@ -59,32 +59,42 @@
padding-left: 10px;
}
//.node-inputs,
//.node-outputs,
.node-inputs-api,
.node-outputs-api {
flex: 1;
}
.node-outputs-api {
.node-inputs {
margin-bottom: 5px;
.node-input-label {
font-size: 12px;
padding: 1px 0;
height: 20px;
line-height: 20px;
}
}
.node-inputs {
margin-bottom: 5px;
.node-data-type {
color: #adadad;
}
}
}
.node-outputs,
.node-outputs-api {
text-align: right;
}
.node-output-label {
font-size: 12px;
padding: 1px 0;
height: 20px;
line-height: 20px;
.node-data-type {
color: #adadad;
}
}
}
.node-content-api {

@ -76,6 +76,7 @@ const ParamsTable: React.FC<ParamsTableProps> = ({
) : (
<Input
value={record.id}
autoWidth={{ minWidth: 50, maxWidth: 100 }}
onChange={(value) => handleSave({ ...record, id: value })}
/>
)
@ -89,7 +90,7 @@ const ParamsTable: React.FC<ParamsTableProps> = ({
<span>{record.dataType === 'INTEGER' ? '整数' : record.dataType}</span>
) : (
<Select
autoWidth={{ minWidth: 200, maxWidth: 500 }}
autoWidth={{ minWidth: 150, maxWidth: 200 }}
options={dataTypeOptions}
value={record.dataType}
onChange={(value) => handleSave({ ...record, dataType: value })}
@ -104,14 +105,14 @@ const ParamsTable: React.FC<ParamsTableProps> = ({
render: (_: any, record: TableDataItem) => (
record.dataType === 'ARRAY' ? (
<Select
autoWidth={{ minWidth: 200, maxWidth: 500 }}
autoWidth={{ minWidth: 150, maxWidth: 200 }}
options={arrayTypeOptions}
value={record.arrayType}
onChange={(value) => handleSave({ ...record, arrayType: value })}
placeholder="请选择数组类型"
/>
) : (
<span></span>
<div style={{ minWidth: 70, maxWidth: 100 }}>-</div>
)
)
},
@ -151,9 +152,10 @@ const ParamsTable: React.FC<ParamsTableProps> = ({
allowClear
options={options}
style={{ width: '100%', minWidth: 100 }}
autoWidth={{ minWidth: 150, maxWidth: 200 }}
/>
<Input
style={{ width: '100%', minWidth: 100, marginTop: 10 }}
style={{ width: '100%', minWidth: 150, maxWidth: 200, marginTop: 10 }}
value={record.defaultValue}
onChange={(value) => handleSave({ ...record, defaultValue: value })}
placeholder="请输入默认值"
@ -166,6 +168,7 @@ const ParamsTable: React.FC<ParamsTableProps> = ({
if (record.id === 'maxTime') {
return (
<Input
style={{ width: '100%', minWidth: 150, maxWidth: 200, marginTop: 10 }}
type="number"
min="1"
step="1"
@ -177,6 +180,7 @@ const ParamsTable: React.FC<ParamsTableProps> = ({
return (
<Input
style={{ width: '100%', minWidth: 150, maxWidth: 200, marginTop: 10 }}
value={record.defaultValue}
onChange={(value) => handleSave({ ...record, defaultValue: value })}
placeholder="请输入默认值"
@ -189,7 +193,8 @@ const ParamsTable: React.FC<ParamsTableProps> = ({
title: '操作',
dataIndex: 'op',
render: (_: any, record: TableDataItem) => (
record.id !== 'maxTime' && <Button onClick={() => removeRow(record.key)} type="text" status="danger">
record.id !== 'maxTime' &&
<Button onClick={() => removeRow(record.key)} type="text" status="danger" style={{ maxWidth: 40 }}>
<IconDelete />
</Button>
)

@ -468,6 +468,7 @@ export const validateAllEdges = (edges: Edge[], nodes: any[]): ValidationResult
break;
case 'start':
case 'START':
// 开始节点应该有输出连接,但不需要输入连接
if (sourceEdges === 0) {
allErrors.push(`开始节点"${nodeName}"缺少输出连接`);
@ -475,6 +476,7 @@ export const validateAllEdges = (edges: Edge[], nodes: any[]): ValidationResult
break;
case 'end':
case 'END':
// 结束节点应该有输入连接,但不需要输出连接
if (targetEdges === 0) {
allErrors.push(`结束节点"${nodeName}"缺少输入连接`);

@ -0,0 +1,13 @@
export const globalOption = {
border: false,
align: 'left',
menuHeaderAlign: 'left',
size: 'mini',
refreshBtn: false,
filterBtn: false,
columnBtn: false,
searchShowBtn: false,
// labelPosition: "left"
};
export const TrimReg = /^[^\s]+$/;
export const patternTrim = { pattern: TrimReg, message: '不能输入空格' };

@ -0,0 +1,541 @@
import { globalOption, patternTrim } from '../globalOption';
// 组件公开状态码
export const publishStatusConstant = {
// 未公开
UNPUBLISHED: -1,
// 已公开
PUBLISHED: 1,
// 已撤销
REVOKED: 0,
// 审批中
APPROVAL: 2
};
export const publishStatusDict = [
{
label: '未公开',
value: publishStatusConstant.UNPUBLISHED
},
{
label: '未公开',
value: publishStatusConstant.REVOKED
},
{
label: '已公开',
value: publishStatusConstant.PUBLISHED
},
{
label: '审批中',
value: publishStatusConstant.APPROVAL
}
];
// 组价发布的状态码
export const publicStatus = {
// 未发布
UNPUBLISHED: -1,
UNPUBLISHED1: 0,
// 已发布
PUBLISHED: 1,
// 审核中
REVIEW: 2,
// 审核被拒
REJECTED: 3
};
export const publicStatusDict = [
{
label: '未发布',
value: publicStatus.UNPUBLISHED
},
{
label: '未发布',
value: publicStatus.UNPUBLISHED1
},
{
label: '已发布',
value: publicStatus.PUBLISHED
},
{
label: '审核中',
value: publicStatus.REVIEW
},
{
label: '审核被拒',
value: publicStatus.REJECTED
}
];
// 组件基本信息状态字段
export const componentStatusConstant = {
// 未设计
DEFAULT: 'default',
// 设计中
DESIGN: 'design',
// 编码中
CODING: 'coding',
// 已部署
DEPLOYED: 'deployed',
// 已公开
PUBLISHED: 'published'
};
// 组件所处阶段状态
export const componentStatusDict = [
{
label: '未设计',
value: componentStatusConstant.DEFAULT
},
{
label: '设计中',
value: componentStatusConstant.DESIGN
},
{
label: '编码中',
value: componentStatusConstant.CODING
},
{
label: '已部署',
value: componentStatusConstant.DEPLOYED
},
{
label: '已公开',
value: componentStatusConstant.PUBLISHED
}
];
// 组件所处阶段状态
export const componentStatusMap = componentStatusDict.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
// 组件类型字典映射
export const componentTypeDict = [
{
dictKey: '普通组件',
dictValue: 'normal'
},
{
dictKey: '监听组件',
dictValue: 'loop'
}
];
// 表格参数
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
tip: false,
searchShow: true,
searchMenuSpan: 6,
viewBtn: true,
delBtn: false,
selection: false,
dialogClickModal: false,
labelWidth: '100',
menuWidth: 500,
menuHeaderAlign: 'center',
header: true,
addBtn: true,
editBtn: false,
updateBtnText: '保 存',
addBtnText: '新增组件',
addBtnIcon: 'none',
column: [
{
label: '组件名称',
prop: 'name',
type: 'input',
width: 150,
placeholder: '请输入组件名称',
rules: [
{
required: true,
message: '组件名称不能为空',
trigger: 'blur'
},
{
pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_-]+$/,
message: '组件名称只能为中文、字母、数字、"-"、"_"组成'
},
patternTrim,
{ max: 50, message: '组件名称最长为50字符' }
]
},
{
label: '组件标识',
prop: 'projectId',
type: 'input',
width: 200,
overHidden: true,
placeholder: '请输入组件标识',
editDisabled: true,
rules: [
{ required: true, message: '组件标识不能为空' },
{
pattern: /^([a-z][a-z0-9]*[_-]?)+$/,
message: '组件标识由小写字母、数字、"-"、"_"组成,以小写字母开头,且“-”、"_"后第一个字符需为小写字母'
},
{ max: 30, message: '组件标识最长为30个字符' },
patternTrim
]
},
{
label: '分类',
prop: 'componentClassify',
type: 'select',
placeholder: '请选择分类',
width: 200,
dicUrl: '/api/blade-system/dict-biz/dictionary-tree?code=component_classify',
props: {
label: 'dictValue',
value: 'dictKey'
},
rules: [{ required: true, message: '分类不能为空' }]
},
{
label: '创建人',
prop: 'createUserName',
type: 'input',
display: false,
overHidden: true,
editDisabled: false,
width: 180,
hide: false
},
{
label: '代码语言',
prop: 'codeLanguage',
type: 'select',
dicUrl: '/api/blade-system/dict/dictionary?code=language_type',
value: 'Java',
editDisabled: true,
width: 150,
placeholder: '请选择代码语言',
props: {
label: 'dictValue',
value: 'dictKey'
},
rules: [{ required: true, message: '代码语言不能为空' }]
},
{
label: '标签',
prop: 'tags',
type: 'select',
remote: true,
multiple: true,
allowCreate: true,
filterable: true,
placeholder: '请选择标签',
width: 200,
dicUrl: '/api/blade-isdp/componentBase/tags',
hide: true
},
{
label: '组件类型',
prop: 'type',
type: 'select',
dicData: componentTypeDict,
value: 'normal',
editDisabled: true,
width: 150,
placeholder: '请选择组件类型',
props: {
label: 'dictKey',
value: 'dictValue'
},
rules: [{ required: true, message: '组件类型不能为空' }]
},
{
label: '图标',
prop: 'logoUrl',
type: 'upload',
hide: true,
listType: 'picture-card',
action: '/api/blade-isdp/fileSystem/fileUpload',
propsHttp: {
res: 'data',
url: 'link'
},
fileType: 'img',
limit: 1,
fileSize: 1024,
accept: 'image/png, image/jpeg, image/jpg',
tip: '图片尺寸不超过300*300像素大小限制为1mb以内'
},
{
label: '描述',
prop: 'desc',
type: 'textarea',
overHidden: true,
placeholder: '请输入描述',
formSlot: true,
hide: true,
minRows: 2,
maxLen: 100,
span: 24
},
{
label: '组件状态',
prop: 'componentStatus',
type: 'input',
display: false,
width: 100,
dicData: componentStatusDict
},
{
label: '创建部门',
prop: 'createDept',
type: 'input',
display: false,
hide: true
},
{
label: '创建时间',
prop: 'createTime',
type: 'input',
hide: true,
display: false
},
{
label: '发布状态',
prop: 'publicStatus',
type: 'input',
width: 90,
dicData: [
{
label: '未发布',
value: publicStatus.UNPUBLISHED
},
{
label: '未发布',
value: publicStatus.UNPUBLISHED1
},
{
label: '已发布',
value: publicStatus.PUBLISHED
},
{
label: '审核中',
value: publicStatus.REVIEW
},
{
label: '审核被拒',
value: publicStatus.REJECTED
}
],
display: false
},
{
label: '公开状态',
prop: 'publishStatus',
type: 'input',
width: 90,
dicData: [
{
label: '未公开',
value: publishStatusConstant.UNPUBLISHED
},
{
label: '未公开',
value: publishStatusConstant.REVOKED
},
{
label: '已公开',
value: publishStatusConstant.PUBLISHED
},
{
label: '审批中',
value: publishStatusConstant.APPROVAL
}
],
display: false
},
{
label: '修改人',
prop: 'updateUser',
type: 'input',
display: false,
hide: true
},
{
label: '修改时间',
prop: 'updateTime',
type: 'input',
width: 130,
display: false
},
{
label: '是否已删除',
prop: 'isDeleted',
type: 'input',
display: false,
hide: true
}
]
};
export const operateOption = {
...globalOption,
height: '400',
calcHeight: 30,
tip: false,
searchShow: false,
selection: false,
dialogClickModal: false,
header: false,
delBtn: true,
viewBtn: false,
dialogWidth: '55%',
menuWidth: 100,
saveBtn: false,
cancelBtn: false,
editBtn: true,
editBtnIcon: 'none',
delBtnIcon: 'none',
column: [
{
label: '接口名称',
prop: 'ident',
type: 'input',
placeholder: '请输入名称',
rules: [
{ required: true, message: '名称不能为空' },
{ pattern: /^[a-z][a-zA-Z0-9]*$/, message: '名称符需要为首字母小写的驼峰命名法' },
{ max: 20, message: '名称最长为20个字符' },
patternTrim
]
},
{
label: '描述',
prop: 'desc',
type: 'textarea',
placeholder: '请输入描述',
hide: true,
overHidden: true,
minRows: 2,
rules: [{ max: 200, message: '最长为200个字符' }, patternTrim],
span: 24
},
{
label: '输入参数',
prop: 'parameters',
type: 'input',
span: 24,
formSlot: true,
overHidden: true,
value: [],
slot: true
},
{
label: '输出参数',
prop: 'responses',
type: 'input',
span: 24,
formSlot: true,
overHidden: true,
value: [],
slot: true
}
]
};
export const baseConfigOption = {
...globalOption,
addBtn: false,
addRowBtn: true,
menu: true,
cellBtn: false,
editBtn: false,
delBtn: false,
dialogWidth: '40%',
menuWidth: '80',
column: [
{
label: '启动项名称',
prop: 'name',
placeholder: '启动项名称',
width: '150px',
rules: [{ required: true, message: '启动项名称不能为空' }, {
max: 50,
message: '启动项名称最长为50字符'
}, patternTrim],
cell: true,
span: 24
},
{
label: '参数名key',
prop: 'key',
input: 'input',
width: '200px',
rules: [{ required: true, message: '请输入参数名key' }, {
max: 50,
message: '参数名key最长为50字符'
}, patternTrim],
cell: true,
span: 24
},
{
label: '默认值',
prop: 'value',
width: '200px',
span: 24,
cell: true,
rules: [{ max: 200, message: '默认值最长为200字符' }, patternTrim]
},
{
label: '描述说明',
prop: 'desc',
overHidden: true,
span: 24,
cell: true,
rules: [{ max: 200, message: '描述说明最长为200字符' }, patternTrim]
}
]
};
export const baseDisplayToEntity = (row) => {
row.desc = row.desc || '';
row.tags = row.tags || [];
row.logoUrl = row.logoUrl || '';
return row;
};
export const baseEntityToDisplay = (row) => {
row.desc = row.desc || '';
row.logoUrl = row.logoUrl || '';
row.tags = row.tags || [];
return row;
};
export const toolbars = {
bold: true, // 粗体
italic: true, // 斜体
header: true, // 标题
underline: true, // 下划线
strikethrough: true, // 中划线
mark: true, // 标记
superscript: true, // 上角标
subscript: true, // 下角标
quote: true, // 引用
ol: true, // 有序列表
ul: true, // 无序列表
link: true, // 链接
imagelink: true, // 图片链接
code: true, // code
table: true, // 表格
fullscreen: true, // 全屏编辑
readmodel: true, // 沉浸式阅读
htmlcode: true, // 展示html源码
help: false, // 帮助
/* 1.3.5 */
undo: true, // 上一步
redo: true, // 下一步
trash: true, // 清空
save: false, // 保存触发events中的save事件
/* 1.4.2 */
navigation: true, // 导航目录
/* 2.1.8 */
alignleft: true, // 左对齐
aligncenter: true, // 居中
alignright: true, // 右对齐
/* 2.2.1 */
subfield: true, // 单双栏模式
preview: true // 预览
};

@ -0,0 +1,562 @@
import { globalOption, patternTrim } from '../globalOption';
/**
*
*/
export const runStatusConstant = {
HEALTHY: 'healthy',
UNKNOWN: 'unknown',
RUN: 'run',
ERROR: 'error',
STOP: 'stop',
};
export const runStatusDic = [
{
label: '可用',
value: runStatusConstant.HEALTHY,
},
{
label: '未知',
value: runStatusConstant.UNKNOWN,
},
{
// 部署 可停止
label: '运行',
value: runStatusConstant.RUN,
},
{
label: '异常',
value: runStatusConstant.ERROR,
},
{
// 部署 可启用
label: '停止',
value: runStatusConstant.STOP,
},
];
export const runStatusMap = runStatusDic.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
/**
*
*/
export const startStatusConstant = {
RUN: 'run',
STOP: 'stop',
};
export const startStatusDic = [
{
label: '启用中',
value: startStatusConstant.RUN,
},
{
label: '已下架',
value: startStatusConstant.STOP,
},
];
export const startStatusMap = startStatusDic.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
/**
*
* BUILT_DOING, BUILT_IGNORE,BUILT_DOCKER,BUILT_FAIL
*/
export const compileStatusConstant = {
BUILT_DOING: 'built-doing',
BUILT_IGNORE: 'built-ignore',
BUILT_DOCKER: 'built-docker',
BUILT_FAIL: 'built-fail',
// 前端单独使用
NOT_COMPILED: 'not-compiled',
get(type) {
return compileStatusConstant[type] || compileStatusConstant.NOT_COMPILED;
},
};
export const compileStatusDic = [
{
label: '编译中',
value: compileStatusConstant.BUILT_DOING,
},
{
label: '免编译',
value: compileStatusConstant.BUILT_IGNORE,
},
{
label: '已编译',
value: compileStatusConstant.BUILT_DOCKER,
},
{
label: '编译失败',
value: compileStatusConstant.BUILT_FAIL,
},
];
export const compileStatusMap = startStatusDic.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
/**
*
*/
export const runTypeConstant = {
LOCAL: 0,
ONLINE: 1,
};
export const runTypeDic = [
{
label: '本地运行',
value: runTypeConstant.LOCAL,
},
{
label: '线上运行',
value: runTypeConstant.ONLINE,
},
];
export const option = {
...globalOption,
height: '300',
calcHeight: 30,
tip: false,
searchShow: false,
header: false,
viewBtn: false,
delBtn: false,
index: true,
menuWidth: 300,
addBtn: false,
editBtn: false,
submitText: '修改',
emptyText: '暂无数据',
span: 24,
column: [
{
label: 'id',
prop: 'id',
hide: true,
display: false,
},
{
label: '标识',
prop: 'identifier',
type: 'input',
overHidden: true,
display: false,
},
{
label: '实例名称',
prop: 'name',
type: 'input',
overHidden: true,
rules: [
{ required: true, message: '必须填写实例名称' },
{ max: 50, message: '实例名称不能超过50字符' },
],
},
{
label: '运行类型',
prop: 'runType',
type: 'input',
width: 80,
display: false,
dicData: runTypeDic,
},
{
label: '运行状态',
prop: 'runStatus',
type: 'input',
width: 80,
dicData: runStatusDic,
display: false,
slot: true,
},
{
label: '创建时间',
prop: 'createTime',
type: 'input',
// width: 160,
display: false,
},
{
label: '实例描述',
prop: 'description',
type: 'textarea',
hide: true,
minRows: 3,
span: 24,
},
],
};
export const instanceOption = {
...globalOption,
calcHeight: 'auto',
// height: '300',
// calcHeight: 30,
tip: false,
searchShow: false,
header: false,
viewBtn: false,
delBtn: false,
index: true,
menuWidth: 350,
addBtn: false,
editBtn: false,
submitText: '修改',
emptyText: '取消',
span: 24,
column: [
...option.column,
{
label: '图标',
prop: 'logoUrl',
type: 'upload',
hide: true,
listType: 'picture-img',
action: '/api/blade-isdp/fileSystem/fileUpload',
propsHttp: {
res: 'data',
url: 'link',
},
fileType: 'img',
limit: 1,
fileSize: 1024 * 10,
accept: 'image/png, image/jpeg, image/jpg',
// tip: '图片尺寸不超过300*300像素大小限制为1mb以内',
// rules: [{
// required: true,
// message: "请输入图片地址",
// trigger: "blur"
// }]
},
],
};
export const envConfigOption = {
...globalOption,
height: 300,
calcHeight: 30,
tip: false,
searchShow: false,
addBtn: false,
addRowBtn: false,
cellBtn: false,
index: true,
menuWidth: 80,
editBtn: false,
delBtn: false,
column: [
{
label: '启动项名称',
prop: 'name',
placeholder: '启动项名称',
formSlot: true,
rules: [
{ required: true, message: '启动项名称不能为空' },
{ max: 50, message: '启动项名称最长为50字符' },
],
width: 120,
span: 24,
cell: true,
},
{
label: '参数名key',
prop: 'key',
formSlot: true,
rules: [
{ required: true, message: '请输入参数名key' },
{ max: 50, message: '参数名key最长为50字符' },
],
span: 24,
cell: true,
},
{
label: '配置值',
prop: 'value',
span: 24,
formSlot: true,
rules: [{ max: 200, message: '配置值最长为200字符' }],
cell: true,
},
{
label: '描述说明',
prop: 'desc',
type: 'input',
formSlot: true,
span: 24,
rules: [{ max: 200, message: '描述说明最长为200字符' }],
cell: true,
},
// {
// label: "配置类型",
// prop: "unmodifiable",
// value: false,
// width: 80,
// dicData: [
// {
// label: "自定义配置",
// value: false
// },
// {
// label: "必须配置",
// value: true
// }
// ]
// },
],
};
export const addInstanceOption = {
...globalOption,
submitBtn: false,
emptyBtn: false,
column: [
{
label: '部署环境',
prop: 'environment',
type: 'select',
value: 'docker-env-prod',
// disabled: true,
dicData: [],
rules: [{ required: true, message: '请选择环境变量' }],
overHidden: true,
},
{
label: '主机',
prop: 'deployEnvId',
type: 'select',
value: '',
props: {
label: 'name',
value: 'id',
desc: 'ip',
},
rules: [{ required: true, message: '请输入主机' }],
overHidden: true,
},
{
label: 'CPU核数',
prop: 'cpuCount',
type: 'slider',
step: 1,
min: 1,
max: 12,
showStops: true,
marks: [1, 4, 8].reduce((obj, pre) => {
obj[pre] = pre + '核';
return obj;
}, {}),
},
// {
// label: "容器名称",
// prop: "containerName",
// tip: "为空时自动生成",
// rules: [
// { min: 1, max: 63, message: "容器名称长度为1-63个字符" },
// patternTrim,
// ],
// },
{
label: '是否使用GPU',
prop: 'useGpu',
type: 'switch',
labelWidth: 100,
value: false,
dicData: [
{ label: '否', value: false },
{ label: '是', value: true },
],
},
{
label: '最大内存',
prop: 'memory',
type: 'slider',
step: 64,
min: 64,
max: 8192,
formSlot: true,
span: 24,
marks: {
64: '64MB',
1024: '1GB',
2048: '2GB',
4096: '4GB',
6144: '6GB',
},
showStops: true,
},
// 网络模式 选择框
{
label: '网络模式',
prop: 'networkType',
type: 'select',
span: 6,
value: 'bridge',
dicData: [
{
label: '桥接模式',
value: 'bridge',
},
{
label: '主机模式',
value: 'host',
},
{
label: '禁用网络',
value: 'null',
},
],
rules: [{ required: true, message: '请选择网络模式' }],
overHidden: true,
},
// 网卡
{
label: '网卡',
prop: 'networkMode',
type: 'select',
value: 'bridge',
span: 9,
overHidden: true,
rules: [{ required: true, message: '请选择网卡' }],
},
// ip
{
label: 'IP',
prop: 'ipv4Address',
type: 'input',
formSlot: true,
value: '',
overHidden: true,
span: 9,
},
{
label: '端口映射',
prop: 'ports',
type: 'dynamic',
span: 24,
children: {
height: 120,
column: [
{
label: '主机端口',
prop: 'hostPort',
type: 'number',
value: '80',
max: 65535,
rules: [{ required: true, message: '请输入主机端口' }],
},
{
label: '容器端口',
prop: 'containerPort',
type: 'number',
max: 65535,
rules: [{ required: true, message: '请输入容器端口' }],
},
{
label: '备注',
prop: 'remake',
type: 'input',
value: '',
},
],
},
},
{
label: '目录挂载',
prop: 'volumes',
type: 'dynamic',
span: 24,
children: {
height: 170,
column: [
{
label: '主机路径',
prop: 'hostPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入主机路径' }, patternTrim],
},
{
label: '容器路径',
prop: 'containerPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入容器路径' }, patternTrim],
},
{
label: '备注',
prop: 'remake',
type: 'input',
},
],
},
},
{
// 设备挂载
label: '设备挂载',
prop: 'devices',
type: 'dynamic',
span: 24,
children: {
height: 120,
column: [
{
label: '主机路径',
prop: 'hostPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入主机路径' }, patternTrim],
},
{
label: '容器路径',
prop: 'containerPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入容器路径' }, patternTrim],
},
{
label: '权限',
prop: 'permissions',
type: 'select',
value: 'rwm',
dicData: [
{ value: 'rwm', label: 'rwm' },
{ value: 'rw', label: 'rw' },
{ value: 'r', label: 'r' },
],
rules: [{ required: true, message: '请输入权限' }],
},
{
label: '备注',
prop: 'remake',
type: 'input',
value: '',
},
],
},
},
// 重启策略
{
label: '重启策略',
prop: 'restartPolicy',
type: 'select',
span: 6,
display: false,
hide: true,
value: 'always',
dicData: [
{
label: '始终',
value: 'always',
},
{
label: '失败',
value: 'on-failure',
},
{
label: '无',
value: 'no',
},
],
overHidden: true,
},
],
};

@ -0,0 +1,178 @@
import { globalOption, patternTrim } from '../globalOption';
export const releaseMaping = {
0: {
label: '未公开',
type: 'info'
},
'-1': {
label: '未公开',
type: 'info'
},
1: {
label: '已公开',
type: 'success'
}
};
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
tip: false,
searchShow: true,
searchSpan: 5,
searchMenuSpan: 4,
menuWidth: 120,
viewBtn: true,
delBtn: false,
selection: false,
header: false,
editBtn: false,
column: [
{
label: '组件名称',
prop: 'name',
type: 'input',
overHidden: true,
search: true,
placeholder: '请输入组件名称',
rules: [
{
required: true,
message: '组件名称不能为空',
trigger: 'blur'
},
patternTrim,
{ max: 20, message: '组件名称最长为20字符' }
]
},
{
label: '项目名',
prop: 'identifier',
type: 'input',
overHidden: true,
search: true,
placeholder: '请输入项目名',
rules: [
{ required: true, message: '项目名不能为空' },
{
pattern: /^([a-z][a-z0-9]*[_-]?)+$/,
message: '项目名由小写字母、数字、"-"、"_"组成,以小写字母开头,且“-”、"_"后第一个字符需为小写字母'
},
{ max: 30, message: '项目名最长为30个字符' },
patternTrim
]
},
{
label: '版本号',
prop: 'version',
type: 'input',
overHidden: true,
disabled: true,
value: 'v1',
width: 60,
span: 6,
rules: [
{
required: true,
message: '请输入组件版本号',
trigger: 'blur'
},
// {
// pattern: /^\d+(\.\d+){0,3}$/,
// message: "版本只能是由数字组成的x.x或x.x.x格式",
// trigger: "blur"
// },
{ max: 20, message: '版本号最长为20字符' }
]
},
{
label: '分类',
prop: 'componentClassify',
type: 'select',
placeholder: '请选择分类',
search: true,
dicUrl: '/api/blade-system/dict-biz/dictionary-tree?code=component_classify',
props: {
label: 'dictValue',
value: 'dictKey'
},
span: 9,
rules: [{ required: true, message: '分类不能为空' }]
// search: true,
},
{
label: '代码语言',
prop: 'codeLanguage',
type: 'select',
search: true,
dicUrl: '/api/blade-system/dict/dictionary?code=language_type',
value: 'Java',
width: 70,
span: 9,
placeholder: '请选择代码语言',
props: {
label: 'dictValue',
value: 'dictKey'
},
rules: [{ required: true, message: '代码语言不能为空' }]
},
{
label: '标签',
prop: 'tags',
type: 'select',
remote: true,
multiple: true,
allowCreate: true,
filterable: true,
placeholder: '请选择标签',
span: 24,
dicUrl: '/api/blade-isdp/componentBase/tags',
hide: true
},
{
label: '描述',
prop: 'desc',
type: 'textarea',
overHidden: true,
placeholder: '请输入描述',
minRows: 2,
maxLen: 100,
rules: [{ max: 200, message: '最长为200个字符' }],
span: 24
},
{
label: '图标',
prop: 'logoUrl',
type: 'upload',
hide: true,
listType: 'picture-img',
action: '/api/blade-isdp/fileSystem/fileUpload',
propsHttp: {
res: 'data',
url: 'link'
},
fileType: 'img',
limit: 1,
fileSize: 1024,
accept: 'image/png, image/jpeg, image/jpg',
tip: '图片尺寸不超过300*300像素大小限制为1mb以内'
},
{
label: '公开状态',
prop: 'publishStatus',
type: 'input',
slot: true,
display: false,
width: 90
},
{
label: '修改时间',
prop: 'updateTime',
type: 'input',
width: 130,
display: false
}
]
};

@ -0,0 +1,176 @@
import { globalOption, patternTrim } from '@/const/globalOption';
import { runTypeDic, runStatusDic, runStatusConstant } from '@/const/isdp/componentDeploy';
export const runStatusConstant2 = runStatusConstant;
export const componentTestOption = {
...globalOption,
height: '400',
calcHeight: 30,
tip: false,
searchShow: false,
selection: false,
dialogClickModal: false,
header: false,
delBtn: true,
viewBtn: true,
editBtn: false,
dialogWidth: '55%',
menuWidth: 230,
saveBtn: false,
cancelBtn: false,
menu: false,
emptyBtn: false,
submitBtn: false,
menuPosition: 'right',
column: [
{
label: '用例名称',
prop: 'testCaseName',
type: 'select',
allowCreate: true,
filterable: true,
placeholder: '请输入或选择测试用例',
rules: [{ required: true, message: '用例名称不能为空' }, { max: 20, message: '用例名称最长为20个字符' }, patternTrim],
dicData: [
{
label: '通过用例',
value: '通过用例',
},
{
label: '失败用例',
value: '失败用例',
},
{
label: '参数有误',
value: '参数有误',
},
{
label: '数据为空',
value: '数据为空',
},
{
label: '缺少参数',
value: '缺少参数',
},
{
label: '记录不存在',
value: '记录不存在',
},
],
span: 12,
},
{
type: 'none',
display: 'none',
},
{
label: '输入参数',
prop: 'expectDataIns',
type: 'dynamic',
span: 24,
children: {
height: 250,
addBtn: false,
delBtn: false,
column: [
{
label: '名称',
prop: 'id',
type: 'input',
width: 100,
},
{
label: '参数类型',
prop: 'type',
type: 'input',
width: 80,
},
{
label: '数组类型',
placeholder: '请选择数组类型',
prop: 'generic',
type: 'input',
width: 80,
},
{
label: '参数值',
prop: 'value',
type: 'input',
cell: true,
},
],
},
},
{
label: '输出参数',
prop: 'expectDataOuts',
type: 'dynamic',
span: 24,
children: {
height: 250,
addBtn: false,
delBtn: false,
column: [
{
label: '名称',
prop: 'id',
type: 'input',
width: 100,
},
{
label: '参数类型',
prop: 'type',
type: 'input',
width: 80,
},
{
label: '数组类型',
prop: 'generic',
type: 'input',
width: 80,
},
{
label: '参数值',
prop: 'value',
type: 'input',
cell: true,
},
],
},
},
],
};
export const option = {
...globalOption,
header: false,
editBtn: false,
delBtn: false,
menuWidth: 100,
column: [
{
label: '实例标识',
prop: 'identifier',
},
{
label: '实例名',
prop: 'name',
},
{
label: '运行类型',
prop: 'runType',
width: 80,
dicData: runTypeDic,
},
{
label: '实例状态',
prop: 'runStatus',
width: 80,
dicData: runStatusDic,
},
{
label: '实例测试时间',
width: 130,
prop: 'lastTestTime',
slot: true,
},
],
};

@ -0,0 +1,153 @@
import { globalOption, patternTrim } from '../globalOption';
export const archDict = [
{
label: 'x86_64',
value: 'x86_64',
},
{
label: 'aarch64',
value: 'aarch64',
},
];
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
searchShow: true,
searchMenuSpan: 6,
viewBtn: false,
delBtn: false,
selection: false,
header: true,
addBtn: false,
editBtn: false,
updateBtnText: '保 存',
menuHeaderAlign: 'center',
column: [
{
label: '环境IP',
prop: 'ip',
type: 'input',
overHidden: true,
span: 12,
rules: [
{
required: true,
message: '环境IP不能为空',
trigger: 'blur',
},
{
pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
message: '请输入正确的IP地址范围在0.0.0.0-255.255.255.255之间',
trigger: 'blur',
},
],
},
{
label: 'docker端口',
prop: 'dockerTcpPort',
type: 'number',
labelWidth: 100,
overHidden: true,
value: 2375,
span: 12,
rules: [
{
required: true,
message: 'docker端口不能为空',
trigger: 'blur',
},
{
pattern: /^(?:[1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/,
message: 'docker端口只能是1-65535之间',
trigger: 'blur',
},
],
},
{
label: '环境类型',
prop: 'type',
type: 'select',
overHidden: true,
search: true,
span: 12,
dicUrl: '/api/blade-system/dict/dictionary-tree?code=docker-env',
props: {
label: 'dictValue',
value: 'dictKey',
},
rules: [
{
required: true,
message: '环境类型不能为空',
trigger: 'blur',
},
],
},
{
label: '架构类型',
prop: 'arch',
type: 'select',
overHidden: true,
search: true,
span: 12,
dicData: archDict,
formSlot: true,
props: {
label: 'label',
value: 'value',
},
rules: [
{
required: true,
message: '架构类型不能为空',
trigger: 'blur',
},
],
},
{
label: '环境别名',
prop: 'name',
type: 'input',
search: true,
span: 24,
rules: [
{
required: true,
message: '环境IP不能为空',
trigger: 'blur',
},
patternTrim,
// 最大50
{
max: 50,
message: '环境别名最大长度不能超过50',
trigger: 'blur',
},
],
},
{
label: '备注',
prop: 'description',
type: 'textarea',
span: 24,
rules: [
{
max: 200,
message: '长度不超过200字符',
trigger: 'blur',
},
],
overHidden: true,
},
{
label: '实例数量',
labelWidth: 20,
span: 24,
prop: 'instanceCount',
display: false,
},
],
};

@ -0,0 +1,113 @@
import { globalOption } from '@/const/globalOption';
/**
* @
*/
// 数据显示的映射
export const typeMap = {
INTEGER: 'INT',
DOUBLE: 'DOU',
STRING: 'STR',
LONG: 'LONG',
FLOAT: 'FLOAT',
DATE: 'DATE',
TIMESTAMP: 'TS',
DATETIME: 'DT',
BOOLEAN: 'BOOL',
ARRAY: 'ARR',
OBJECT: 'OBJ',
};
export const dataTypeDict = [
{
label: '整数',
value: 'INTEGER',
},
{
label: '浮点数',
value: 'DOUBLE',
},
{
label: '字符串',
value: 'STRING',
},
{
label: '布尔值',
value: 'BOOLEAN',
},
{
label: '数组',
value: 'ARRAY',
},
{
label: '对象',
value: 'OBJECT',
},
{
label: '日期',
value: 'DATE',
},
{
label: '日期时间',
value: 'DATETIME',
},
{
label: '时间戳',
value: 'TIMESTAMP',
},
];
// 显示查看表单
export const showOption = {
...globalOption,
minHeight: '100',
calcHeight: 30,
tip: false,
searchShow: false,
searchMenuSpan: 24,
addBtn: false,
viewBtn: false,
selection: false,
editBtn: false,
delBtn: false,
dialogClickModal: false,
header: false,
dialogWidth: '50%',
menuWidth: 60,
// dialogFullscreen: true,
column: [
{
label: '名称',
prop: 'ident',
span: 12,
trigger: 'blur',
placeholder: '请输入名称',
rules: [
{ required: true, message: '名称不能为空' },
{ pattern: /^[a-z][a-zA-Z0-9]*$/, message: '名称符需要为首字母小写的驼峰命名法' },
{ max: 50, message: '名称最长为50个字符' },
],
},
{
label: '数据类型',
prop: 'type',
type: 'select',
placeholder: '请选择数据类型',
dicData: dataTypeDict,
trigger: 'blur',
rules: [{ required: true, message: '数据类型不能为空' }],
},
// 需要加上change
{
label: '数组类型',
prop: 'generic',
slot: true,
formSlot: true,
placeholder: '请选择数组类型',
dicData: dataTypeDict.filter((item) => item.value !== 'ARRAY'),
rules: [{ required: true, message: '数组类型不能为空' }],
},
{
label: '参数描述',
prop: 'desc',
},
],
};

@ -0,0 +1,72 @@
import { option as baseOption } from '../componentBase';
import { deepClone } from '@/utils/common';
export const permissionConstant = {
ADMIN: 'admin',
READ: 'read',
WRITE: 'write',
};
// 系统模块
export const permissionDic = [
{
label: '管理',
value: 'admin',
},
{
label: '读',
value: 'read',
},
{
label: '写',
value: 'write',
},
];
export const option = deepClone(baseOption);
option.header = false;
option.addBtn = false;
option.menuWidth = 200;
const hideArr = ['name', 'componentClassify', 'publicStatus'];
const columnArr = [];
option.column.forEach((element) => {
if (hideArr.includes(element.prop)) {
element.align = 'center';
columnArr.push(element);
}
});
option.column = [
...columnArr,
{
label: '组件标识',
prop: 'identifier',
type: 'input',
width: 150,
},
{
label: '组件版本',
prop: 'componentVersion',
type: 'input',
width: 120,
},
{
label: '组件描述',
prop: 'recommend',
type: 'input',
width: 150,
},
{
label: '组件评分',
prop: 'stars',
type: 'input',
width: 100,
},
{
label: '审核结果',
prop: 'reviewOpinion',
type: 'input',
width: 'auto',
},
];

@ -0,0 +1,59 @@
import { option as baseOption } from '../componentBase';
import { deepClone } from '@/util/util';
import { globalOption } from '@/const/globalOption';
export const permissionConstant = {
ADMIN: 'admin',
READ: 'read',
WRITE: 'write',
};
// 系统模块
export const permissionDic = [
{
label: '管理',
value: 'admin',
},
{
label: '读',
value: 'read',
},
{
label: '写',
value: 'write',
},
];
export const userOption = {
...globalOption,
addBtn: false,
editBtn: false,
viewBtn: false,
header: false,
delBtn: false,
height: 200,
menuWidth: 100,
column: [
{
label: '用户账号',
prop: 'collaboratorAccount',
},
{
label: '操作权限',
prop: 'permission',
slot: true,
width: '120px',
},
],
};
export const option = deepClone(baseOption);
option.header = false;
option.addBtn = false;
option.column.push({
label: '权限',
prop: 'permission',
type: 'select',
dicData: permissionDic,
display: false,
});

@ -0,0 +1,53 @@
import { globalOption } from '../globalOption';
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
tip: false,
dialogClickModal: false,
header: false,
menu: true,
editBtn: false,
delBtn: false,
menuWidth: '100',
column: [
{
label: '#',
prop: 'index',
fixed: true,
width: 40,
},
{
label: '名称',
prop: 'name',
overHidden: true,
width: 200,
},
{
label: '类型',
prop: 'type',
overHidden: true,
width: 100,
},
{
label: '格式',
prop: 'format',
overHidden: true,
width: 100,
},
{
label: '状态',
prop: 'status',
overHidden: true,
slot: true,
width: 150,
},
{
label: 'URL',
prop: 'url',
slot: true,
overHidden: true,
},
],
};

@ -39,10 +39,10 @@ import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle';
import { handelEventNodeList, updateEvent, upDatePublish } from '@/pages/flowEditor/utils/common';
import { Dispatch } from 'redux';
import { runMainFlow, stopApp } from '@/api/apps';
import { getAppListBySceneId, runMainFlow, stopApp } from '@/api/apps';
import store from '@/store';
import { updateAppEvent, updateAppEventChannel, updateAppFlowData } from '@/api/appEvent';
import { sleep } from '@/utils/common';
import { getUrlParams, sleep } from '@/utils/common';
import { queryEventItemBySceneIdOld, deleteEventSub, deleteEventPub } from '@/api/event';
export const useFlowCallbacks = (
@ -912,26 +912,10 @@ export const useFlowCallbacks = (
};
}
else if (nodeType === 'SUB') {
const flowSubMap = flowData[currentAppData.id]?.subMap || {};
const sameData: any = flowSubMap[newNode.data.compId];
if (sameData) {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId,
customDef: JSON.stringify({
dataIns: newNode.data.parameters.dataIns,
dataOuts: newNode.data.parameters.dataOuts,
subflowId: sameData,
name: newNode.data.title
})
};
}
else {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId
};
}
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId
};
}
else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
const { eventList } = store.getState().ideContainer;
@ -1067,26 +1051,10 @@ export const useFlowCallbacks = (
};
}
else if (nodeType === 'SUB') {
const flowSubMap = flowData[currentAppData.id]?.subMap || {};
const sameData: any = flowSubMap[newNode.data.compId];
if (sameData) {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId,
customDef: JSON.stringify({
dataIns: newNode.data.parameters.dataIns,
dataOuts: newNode.data.parameters.dataOuts,
subflowId: sameData,
name: newNode.data.title
})
};
}
else {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId
};
}
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId
};
}
else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
const { eventList } = store.getState().ideContainer;
@ -1181,22 +1149,15 @@ export const useFlowCallbacks = (
const res1: any = await queryEventItemBySceneIdOld(info.id);
if (res1.code === 200) dispatch(updateEventListOld(res1.data));
// 更新缓存数据 - 与主流程保持一致
dispatch(updateCanvasDataMap({
...canvasDataMap,
[currentAppData.id]: { nodes, edges }
}));
const appRes: any = await getAppInfoNew(currentAppData.id);
const appRes: any = await getAppInfoNew(currentAppData.parentAppId);
// 更新 flowData 中的数据
dispatch(updateFlowData({ [currentAppData.id]: appRes.data }));
dispatch(updateFlowData({ [currentAppData.parentAppId]: appRes.data }));
// 同步更新到 canvasDataMap
if (appRes.data.main?.components) {
const { nodes, edges } = convertFlowData(appRes.data.main.components, true);
setNodes(nodes);
setEdges(edges);
dispatch(updateCanvasDataMap({
...canvasDataMap,
[currentAppData.id]: { nodes, edges }
[currentAppData.parentAppId]: { nodes, edges }
}));
}
}
@ -1213,6 +1174,7 @@ export const useFlowCallbacks = (
appEventDefinition,
sceneId: info.id
};
const res: any = await setMainFlowNew(params, initialData.appId);
if (res.code === 200) {
Message.success('保存成功');
@ -1328,6 +1290,7 @@ export const useFlowCallbacks = (
}
}
}, [nodes, edges, initialData?.appId]);
// 运行处理函数
const handleRun = useCallback(async (running: boolean) => {
const { currentAppData, socketId, appRuntimeData } = store.getState().ideContainer;

@ -1,185 +1,119 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import styles from './style/index.module.less';
import { Button, Input, Select, Space, Cascader } from '@arco-design/web-react';
import { IconSearch } from '@arco-design/web-react/icon';
import { Button, Select, Space } from '@arco-design/web-react';
import { IconFullscreen, IconFullscreenExit } from '@arco-design/web-react/icon';
import { useSelector, useDispatch } from 'react-redux';
import { getMyComponentList } from '@/api/componentBase';
import { updateComponentCodingPath } from '@/store/ideContainer';
import { getComponentBaseInfo } from '@/api/componentDevelopProcess';
const Option = Select.Option;
const ComponentCoding = () => {
const [serverUrl, setServerUrl] = useState('https://arco.design/vue/component/button');
const [serverUrl, setServerUrl] = useState(''); // code-server 地址
const [optionsList, setOptionsList] = useState([]); // 下拉选择菜单
const [originList, setOriginList] = useState([]); // 原始数据-组件列表
const [currentComponent, setCurrentComponent] = useState({}); // 当前组件信息
const [isFullscreen, setIsFullscreen] = useState(false); // 全屏状态
const iframeRef = useRef(null);
const { componentCoding } = useSelector((state: any) => state.ideContainer);
const dispatch = useDispatch();
const getOptionsList = async () => {
const res: any = await getMyComponentList({
currPage: 1,
pageSize: 999
});
if (res.code === 200) {
setOriginList(res.data.list);
setOptionsList(res.data.list.map(item => {
return { label: item.name, value: item.localProjectPath, ...item };
}));
}
};
const getComponentInfo = async () => {
const res: any = await getComponentBaseInfo(componentCoding.id);
if (res.code === 200) {
setCurrentComponent(res.data);
}
};
useEffect(() => {
getOptionsList();
}, []);
useEffect(() => {
componentCoding.id && getComponentInfo();
const uri = process.env.NEXT_PUBLIC_DEV_CODE_SERVER_HOST;
const codeServerFolderPre = '/app/data';
const tempData = '/000000/admin_testcode1/master';
setServerUrl(`${uri}?folder=${codeServerFolderPre}${tempData}`);
});
const componentScreening = () => {
// 将数据结构修改为级联结构
const cascaderOptions = [
{
label: '设备数采与控制交互组件',
value: '2',
children: [
{
label: 'Java:8',
value: '2-1',
children: [
{
label: '数据采集模块',
value: '2-1-1'
},
{
label: '设备控制接口',
value: '2-1-2'
}
]
},
{
label: 'Python:3.10.12',
value: '2-2',
children: [
{
label: '数据采集模块',
value: '2-2-1'
},
{
label: '设备控制接口',
value: '2-2-2'
}
]
}
]
},
{
label: '视觉AI组件',
value: '3',
children: [
{
label: 'Java:8',
value: '3-1',
children: [
{
label: '图像识别',
value: '3-1-1'
},
{
label: '目标检测',
value: '3-1-2'
}
]
},
{
label: 'Python:3.10.12',
value: '3-2',
children: [
{
label: '图像识别',
value: '3-2-1'
},
{
label: '目标检测',
value: '3-2-2'
}
]
}
]
},
{
label: '运动规划组件',
value: '4',
children: [
{
label: 'Java:8',
value: '4-1',
children: [
{
label: '路径规划',
value: '4-1-1'
}
]
},
{
label: 'Python:3.10.12',
value: '4-2',
children: [
{
label: '路径规划',
value: '4-2-1'
}
]
}
]
},
{
label: '工艺知识服务组件',
value: '5',
children: [
{
label: 'Java:8',
value: '5-1',
children: [
{
label: '工艺参数优化',
value: '5-1-1'
}
]
},
{
label: 'Python:3.10.12',
value: '5-2',
children: [
{
label: '工艺参数优化',
value: '5-2-1'
}
]
}
]
},
{
label: '时序数据AI组件',
value: '6',
children: [
{
label: 'Java:8',
value: '6-1',
children: [
{
label: '时间序列预测',
value: '6-1-1'
}
]
},
{
label: 'Python:3.10.12',
value: '6-2',
children: [
{
label: '时间序列预测',
value: '6-2-1'
}
]
}
]
// 使用传入的localProjectPath或默认值
const path = componentCoding.localProjectPath || '/000000/admin_testcode1/master';
setServerUrl(`${uri}?folder=${codeServerFolderPre}${path}`);
}, [componentCoding]);
// 监听全屏状态变化
useEffect(() => {
const handleFullscreenChange = () => {
const isCurrentlyFullscreen =
document.fullscreenElement ||
(document as any).webkitFullscreenElement ||
(document as any).mozFullScreenElement ||
(document as any).msFullscreenElement;
setIsFullscreen(!!isCurrentlyFullscreen);
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
document.removeEventListener('mozfullscreenchange', handleFullscreenChange);
document.removeEventListener('MSFullscreenChange', handleFullscreenChange);
};
}, []);
// 切换全屏状态
const toggleFullscreen = () => {
const iframeContainer = document.querySelector(`.${styles['code-iframe']}`) as HTMLElement;
if (!isFullscreen) {
// 进入全屏
if (iframeContainer.requestFullscreen) {
iframeContainer.requestFullscreen();
}
else if ((iframeContainer as any).webkitRequestFullscreen) {
(iframeContainer as any).webkitRequestFullscreen();
}
else if ((iframeContainer as any).mozRequestFullScreen) {
(iframeContainer as any).mozRequestFullScreen();
}
else if ((iframeContainer as any).msRequestFullscreen) {
(iframeContainer as any).msRequestFullscreen();
}
}
else {
// 退出全屏
if (document.exitFullscreen) {
document.exitFullscreen();
}
];
return (
// 使用 Cascader 组件替换 Select 组件
<Cascader
placeholder="选择组件"
autoWidth={true}
showSearch
options={cascaderOptions}
/>
);
else if ((document as any).webkitExitFullscreen) {
(document as any).webkitExitFullscreen();
}
else if ((document as any).mozCancelFullScreen) {
(document as any).mozCancelFullScreen();
}
else if ((document as any).msExitFullscreen) {
(document as any).msExitFullscreen();
}
}
};
return (
<div className={styles['component-coding']}>
<div className={styles['header']}>
@ -187,9 +121,48 @@ const ComponentCoding = () => {
size={50}
style={{ marginTop: '20px', marginBottom: '30px' }}
>
<div className={styles['handleRow']}>
<span></span>
{componentScreening()}
<div className={styles['handle-row']}>
<Space size={40}>
<div className={styles['handle-row-item']}>
<span></span>
<span>{componentCoding.name || '未选择组件'}</span>
</div>
<div className={styles['handle-row-item']}>
<span></span>
<span>{componentCoding.projectId || '未选择组件'}</span>
</div>
<div className={styles['handle-row-item']}>
<span></span>
<Select
placeholder="选择组件"
style={{ width: 250 }}
showSearch
filterOption={(inputValue, option) => {
return option.props.children.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0;
}}
onChange={(value) => {
// 查找选中项的完整信息
const selectedComponent = originList.find(item => item.localProjectPath === value);
if (selectedComponent) {
// 更新Redux状态
dispatch(updateComponentCodingPath({
localProjectPath: selectedComponent.localProjectPath,
name: selectedComponent.name,
projectId: selectedComponent.projectId,
id: selectedComponent.id
}));
}
}}
value={componentCoding.localProjectPath || undefined}
>
{optionsList.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</div>
</Space>
</div>
</Space>
@ -200,17 +173,35 @@ const ComponentCoding = () => {
>
</Button>
<Input
prefix={<IconSearch />}
placeholder={'搜索组件关键词'}
style={{ width: 236 }}
/>
</Space>
</div>
<div className={styles['code-iframe']}>
<iframe width="100%" height="100%" frameBorder={0} src={serverUrl} />
<div className={styles['code-iframe-handle']}>
<Space size={10} className={styles['code-iframe-handle-left']}>
<span className={styles['align']}>
<img src={'/ideContainer/imgs/tongbu.svg'} style={{ width: 16, height: 16, marginRight: 5 }} />
</span>
<span className={styles['align']}>
<img src={'/ideContainer/imgs/git.svg'} style={{ width: 16, height: 16, marginRight: 5 }} />
git
</span>
<span className={styles['align']}>
<img src={'/ideContainer/imgs/tijiao.svg'} style={{ width: 16, height: 16, marginRight: 5 }} />
</span>
</Space>
<div className={styles['code-iframe-handle-right']} onClick={toggleFullscreen}>
{isFullscreen ? (
<IconFullscreenExit style={{ width: 18, height: 18, marginRight: 5, marginLeft: 5, color: '#ffffff' }} />
) : (
<IconFullscreen style={{ width: 18, height: 18, marginRight: 5, marginLeft: 5, color: '#ffffff' }} />
)}
</div>
</div>
<iframe width="100%" height="100%" frameBorder={0} src={serverUrl} ref={iframeRef} />
</div>
</div>
);

@ -6,9 +6,51 @@
.header {
display: flex;
justify-content: space-between;
.handle-row {
.handle-row-item {
display: flex;
align-items: center;
}
}
}
.code-iframe {
height: 90%;
.code-iframe-handle {
display: flex;
justify-content: space-between;
padding: 0 10px;
background-color: #2c2c2c;
border-top-left-radius: 7px;
border-top-right-radius: 7px;
.code-iframe-handle-left {
color: #ffffff;
.align {
display: flex;
align-items: center;
padding: 5px 8px;
&:hover {
cursor: pointer;
background-color: #474748;
}
}
}
.code-iframe-handle-right {
display: flex;
align-items: center;
justify-content: center;
&:hover {
cursor: pointer;
background-color: #474748;
}
}
}
}
}

@ -0,0 +1,172 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Message } from '@arco-design/web-react';
import EditableTable from '@/pages/componentDevelopment/componentList/editableTable';
import { updateComponentDesign } from '@/api/componentDevelopProcess';
const FormItem = Form.Item;
const TextArea = Input.TextArea;
const AddApiModal = ({
visible,
baseInfo,
componentDesignProgress,
componentDesignData,
onUpdateComponentDesign,
onCancel,
onOk
}) => {
const [form] = Form.useForm();
const [parametersData, setParametersData] = useState([]);
const [responsesData, setResponsesData] = useState([]);
// 当 visible 或 componentDesignProgress 变化时,设置表单初始值
useEffect(() => {
if (visible && componentDesignProgress) {
// 设置表单字段值
form.setFieldsValue({
ident: componentDesignProgress.ident || '',
desc: componentDesignProgress.desc || ''
});
// 设置参数表格数据
if (componentDesignProgress.parameters && Array.isArray(componentDesignProgress.parameters)) {
setParametersData(componentDesignProgress.parameters.map((param, index) => ({
key: param.id || index,
...param
})));
}
else {
setParametersData([]);
}
// 设置响应表格数据
if (componentDesignProgress.responses && Array.isArray(componentDesignProgress.responses)) {
setResponsesData(componentDesignProgress.responses.map((response, index) => ({
key: response.id || index,
...response
})));
}
else {
setResponsesData([]);
}
}
else if (visible) {
// 重置表单和表格数据
form.resetFields();
setParametersData([]);
setResponsesData([]);
}
}, [visible, componentDesignProgress, form]);
const submit = async () => {
try {
await form.validate();
const formData = form.getFields();
// 构造要提交的数据
const params = {
baseInfo
};
// 如果是编辑模式且有完整的组件设计数据,需要更新对应的记录
if (componentDesignProgress && componentDesignData && Array.isArray(componentDesignData)) {
// 更新对应的记录
const updatedOperates = componentDesignData.map(item => {
if (item.ident === componentDesignProgress.ident) {
// 更新记录
return {
...formData,
type: 'EVENT',
parameters: parametersData,
responses: responsesData
};
}
return item;
});
params['operates'] = updatedOperates;
}
else if (componentDesignData && Array.isArray(componentDesignData)) {
// 新增模式,将新记录添加到现有数据中
const newOperate = {
...formData,
type: 'EVENT',
parameters: parametersData,
responses: responsesData
};
params['operates'] = [...componentDesignData, newOperate];
}
else {
// 没有现有数据时,创建包含单个元素的数组
params['operates'] = [{
...formData,
type: 'EVENT',
parameters: parametersData,
responses: responsesData
}];
}
const res: any = await updateComponentDesign(params);
if (res.code === 200) {
Message.success(componentDesignProgress ? '更新成功' : '新增成功');
// 调用父组件传递的 onUpdateComponentDesign 回调,用于更新父组件中的数据
onUpdateComponentDesign && onUpdateComponentDesign(params);
}
else {
Message.error(res.message);
}
// 调用父组件传递的 onOk 回调,并传递数据
onOk && onOk(params);
} catch (error) {
console.error('表单验证失败:', error);
Message.error('请检查表单填写是否正确');
}
};
return (
<Modal
title={componentDesignProgress ? '编辑接口' : '新增接口'}
visible={visible}
onCancel={onCancel}
onOk={submit}
style={{ width: '50%' }}
okButtonProps={{ disabled: !visible }}
>
<Form form={form} layout="vertical">
<FormItem
label="接口名称"
field="ident"
required
rules={[
{
required: true,
message: '请输入接口名称'
}
]}
>
<Input placeholder="请输入接口名称" />
</FormItem>
<FormItem label="描述" field="desc">
<TextArea placeholder="请输入描述" />
</FormItem>
<FormItem label="输入参数" field="parameters">
<EditableTable
onDataUpdate={setParametersData}
initialData={componentDesignProgress?.parameters || []}
/>
</FormItem>
<FormItem label="输出参数" field="responses">
<EditableTable
onDataUpdate={setResponsesData}
initialData={componentDesignProgress?.responses || []}
/>
</FormItem>
</Form>
</Modal>
);
};
export default AddApiModal;

@ -0,0 +1,760 @@
import React, { useEffect, useState } from 'react';
import {
Modal,
Form,
Input,
Grid,
Space,
Divider,
Button,
Table,
TableColumnProps,
Select,
Message,
Upload,
Progress
} from '@arco-design/web-react';
import { IconPlus, IconEdit } from '@arco-design/web-react/icon';
import styles from './style/addComponentModal.module.less';
import EditorSection from '@/components/EditorSection';
import AddApiModal from '@/pages/componentDevelopment/componentList/addApiModal';
import CompReview from '@/pages/componentDevelopment/componentList/compReview';
import { getComponentClassify } from '@/api/componentClassify';
import {
compProjectValidate,
compSubmit,
getTagList,
copyAll,
getMyComponentList,
getCooperationComponentList
} from '@/api/componentBase';
import { copyDesign } from '@/api/componentMarket';
import { codeInit, getComponentDesign, updateComponentDesign } from '@/api/componentDevelopProcess';
const FormItem = Form.Item;
const Option = Select.Option;
// 定义组件模式类型
type ComponentMode = 'create' | 'edit' | 'copy';
const AddComponentModal = ({ visible, baseInfo, setVisible, onReFresh, mode = 'create' }) => {
const [selectedImage, setSelectedImage] = useState('');
const [description, setDescription] = useState('');
const [classifyList, setClassifyList] = useState([]);
const [showSaveBtn, setShowSaveBtn] = useState(false);
const [tagsList, setTagsList] = useState([]);
const [componentInfo, setComponentInfo] = useState(null); // 合并后的组件信息状态
const [componentDesignData, setComponentDesignData] = useState([]); // 新增状态用于存储接口设计数据
const [selectedApiData, setSelectedApiData] = useState(null); // 新增状态用于存储选中的API数据
const [showApiModal, setShowApiModal] = useState(false);
const [created, setCreated] = useState(false); // 是否提交了组件信息
const [codeInitLoading, setCodeInitLoading] = useState(false); // 代码初始化按钮 loading
const [file, setFile] = useState(null);
const [form] = Form.useForm();
// 组件语言选项
const codeLanguageOptions = [
{ label: 'Java:8', value: 'Java' },
{ label: 'Python:3.10.12', value: 'Python' }
];
// 组件类型选项
const componentTypeOptions = [
{ label: '普通组件', value: 'normal' },
{ label: '监听组件', value: 'loop' }
];
const cs = `arco-upload-list-item${file && file.status === 'error' ? ' is-error' : ''}`;
// 判断是否为复制模式
const isCopyMode = mode === 'copy';
// 判断是否为编辑模式
const isEditMode = mode === 'edit';
// 判断是否为创建模式
const isCreateMode = mode === 'create';
const columns: TableColumnProps[] = [
{
title: '接口名称',
dataIndex: 'ident'
},
{
title: '输入参数',
dataIndex: 'parameters',
render: (_, record) => {
if (!record.parameters || record.parameters.length === 0) {
return '-';
}
return record.parameters.map(param => param.ident).join(', ');
}
},
{
title: '输出参数',
dataIndex: 'responses',
render: (_, record) => {
if (!record.responses || record.responses.length === 0) {
return '-';
}
return record.responses.map(response => response.ident).join(', ');
}
},
{
title: '操作',
dataIndex: 'op',
width: 240,
render: (_, record) => (
<>
<Button
type="text"
onClick={() => {
// 设置选中的API数据用于编辑时回显
setSelectedApiData(record);
setShowApiModal(true);
}}
disabled={isCopyMode || (componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus))}
>
</Button>
<Button
type="text"
status="danger"
disabled={isCopyMode || (componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus))}
onClick={async () => {
// 显示删除确认框
Modal.confirm({
title: '确认删除',
content: `确定要删除接口 "${record.ident}" 吗?此操作不可恢复。`,
okButtonProps: { status: 'danger' },
onOk: async () => {
// 从 componentDesignData 中过滤掉要删除的记录
const updatedData = componentDesignData.filter(item => item.ident !== record.ident);
// 构造要提交的数据
const params = {
baseInfo: componentInfo,
operates: updatedData // 保持数组格式
};
// 调用接口更新数据
const res: any = await updateComponentDesign(params);
try {
if (res.code === 200) {
Message.success('删除成功');
// 更新本地状态
setComponentDesignData(updatedData);
}
else {
Message.error(res.message || '删除失败');
// 删除失败时恢复数据
setComponentDesignData(componentDesignData);
}
} catch (error) {
console.error('删除失败:', error);
Message.error('删除失败');
// 删除失败时恢复数据
setComponentDesignData(componentDesignData);
}
}
});
}}
>
</Button>
</>
)
}
];
const getComponentClassifyList = async () => {
const res: any = await getComponentClassify('component');
if (res.code === 200) {
const data = [];
res.data.forEach((item) => {
data.push({
label: item.classifyName,
value: item.id
});
});
setClassifyList(data);
}
};
const getTageList = async () => {
const res: any = await getTagList();
if (res.code === 200) setTagsList(res.data);
};
const validateProjectId = async (projectId: string) => {
if (!projectId) return;
try {
const res = await compProjectValidate(projectId);
if (res.data === false) {
// 项目标识已存在,设置表单字段错误
form.setFields({
projectId: {
value: projectId,
error: {
message: '项目标识已存在'
}
}
});
}
else {
// 项目标识可用,清除错误
form.setFields({
projectId: {
value: projectId,
error: null
}
});
}
} catch (error) {
// API调用出错
form.setFields({
projectId: {
value: projectId,
error: {
message: '验证项目标识时发生错误'
}
}
});
}
};
const onSubmit = async (showMessage = true) => {
try {
await form.validate();
const formData = form.getFields();
const params: any = {
...componentInfo,
name: formData.name,
projectId: formData.projectId,
logoUrl: selectedImage,
desc: description,
componentClassify: formData.componentClassify,
codeLanguage: formData.codeLanguage, // 直接使用value值
type: formData.type, // 直接使用value值
tags: formData.tags
};
// 如果是编辑模式componentInfo存在则传递id
if (componentInfo && componentInfo.id) {
params.id = componentInfo.id;
}
const res: any = await compSubmit(params);
if (res.code === 200) {
setComponentInfo(res.data);
setShowSaveBtn(true);
showMessage && Message.success(isEditMode ? '组件信息更新成功' : isCopyMode ? '组件复制成功' : '组件信息提交完成,可继续组件接口设计');
onReFresh();
// 最终保存成功后关闭弹窗
if (showSaveBtn && showMessage) {
setVisible(false);
return;
}
// 提交成功后获取组件设计数据
if (res.data && res.data.id) {
setCreated(true);
getComponentDesignData(res.data.id);
}
}
else {
showMessage && Message.error(isEditMode ? '组件信息更新失败' : isCopyMode ? '组件复制失败' : '组件信息提交失败');
}
} catch (error) {
console.error('提交/更新失败:', error);
}
};
const fetchComponentData = async (extraParams: any = {}) => {
try {
const params = {
currPage: 1,
pageSize: 99,
...extraParams
};
const res: any = await getMyComponentList(params);
if (res.code === 200 && res.data.list.length > 0) {
setComponentInfo(res.data.list[0]);
getComponentDesignData(res.data.list[0].id);
}
} catch (error) {
console.error('获取组件列表失败:', error);
Message.error('获取组件列表失败');
}
};
const getComponentDesignData = async (componentBaseId) => {
try {
const res = await getComponentDesign(componentBaseId);
if (res.data) {
// 处理返回的数据,使其符合表格显示格式
const processedData = res.data.operates.map(item => ({
...item,
key: item.ident,
name: item.ident // 使用ident作为名称显示
}));
setComponentDesignData(processedData);
}
} catch (error) {
console.error('获取组件设计数据失败:', error);
Message.error('获取组件设计数据失败');
}
};
// 仅复制设计
const handleCopyDesign = async () => {
try {
await form.validate();
const formData = form.getFields();
// 构造要提交的数据
const params = {
...componentInfo,
name: formData.name,
projectId: formData.projectId,
logoUrl: selectedImage,
desc: description,
componentClassify: formData.componentClassify,
codeLanguage: formData.codeLanguage, // 直接使用value值
type: formData.type, // 直例使用value值
tags: formData.tags
};
const res: any = await copyDesign(params);
if (res.code === 200) {
Message.success('仅复制设计成功');
onReFresh && onReFresh();
setVisible(false);
}
else {
Message.error(res.message || '复制失败');
}
} catch (error) {
console.error('复制失败:', error);
}
};
// 复制设计和代码
const handleCopyAll = async () => {
try {
await form.validate();
const formData = form.getFields();
// 构造要提交的数据
const params = {
...componentInfo,
name: formData.name,
projectId: formData.projectId,
logoUrl: selectedImage,
desc: description,
componentClassify: formData.componentClassify,
codeLanguage: formData.codeLanguage, // 直接使用value值
type: formData.type, // 直接使用value값
tags: formData.tags
};
const res: any = await copyAll(params);
if (res.code === 200) {
Message.success('复制设计和代码成功');
onReFresh && onReFresh();
setVisible(false);
}
else {
Message.error(res.message || '复制失败');
}
} catch (error) {
console.error('复制失败:', error);
}
};
const handleCodeInit = async () => {
setCodeInitLoading(true);
Message.warning('代码初始化中');
const res: any = await codeInit(componentInfo?.id);
setCodeInitLoading(false);
if (res.code === 200) {
Message.success('代码初始化完成');
fetchComponentData({ id: componentInfo?.id });
}
else Message.error(res.message || '代码初始化失败');
};
useEffect(() => {
getComponentClassifyList();
getTageList();
}, []);
// 当baseInfo或visible变化时设置表单初始值
useEffect(() => {
if (visible && (isEditMode || isCopyMode)) {
// 设置表单字段值
form.setFieldsValue({
name: baseInfo.name || '',
projectId: (isCopyMode ? '' : baseInfo.projectId) || '',
componentClassify: baseInfo.componentClassify || '',
codeLanguage: baseInfo.codeLanguage || '',
tags: baseInfo.tags || [],
type: baseInfo.type || ''
});
// 设置其他状态
setSelectedImage(baseInfo.logoUrl || '');
setDescription(baseInfo.desc || '');
setShowSaveBtn(true);
setComponentInfo(baseInfo);
// 获取组件设计数据
if (isEditMode || isCopyMode) {
getComponentDesignData(baseInfo.id);
}
// else {
// // 复制模式下清空接口设计数据
// setComponentDesignData([]);
// }
}
else if (visible && isCreateMode) {
// 重置表单
form.resetFields();
setSelectedImage('');
setDescription('');
setShowSaveBtn(false);
setComponentDesignData([]); // 清空接口设计数据
setComponentInfo(null);
}
}, [baseInfo, visible, form, isEditMode, isCopyMode, isCreateMode]);
const modalFooter = () => {
return (
<Space size={30}>
<Button size="small" type="outline" style={{ borderRadius: 5 }} onClick={() => setVisible(false)}></Button>
{isCopyMode ? (
<>
<Button
size="small"
type="primary"
style={{ borderRadius: 5 }}
onClick={handleCopyDesign}
>
</Button>
<Button
size="small"
type="primary"
style={{ borderRadius: 5 }}
onClick={handleCopyAll}
>
</Button>
</>
) : showSaveBtn ? (
<Button size="small" type="primary" style={{ borderRadius: 5 }} onClick={() => onSubmit()}>
{isEditMode ? '更新' : '保存'}
</Button>
) : (
<Button size="small" type="primary" style={{ borderRadius: 5 }} onClick={() => onSubmit()}>
{isEditMode ? '更新组件信息' : '提交组件信息'}
</Button>
)}
</Space>
);
};
const UploadImage = () => {
// 如果是编辑模式且有componentInfo.logoUrl则使用componentInfo中的logoUrl
const initialImageUrl = componentInfo && componentInfo.logoUrl ? componentInfo.logoUrl : null;
return (
<Upload
action="/api/v1/bpms-workbench/fileSystem/fileUpload"
fileList={file ? [file] : initialImageUrl ? [{ url: initialImageUrl, status: 'done' }] : []}
showUploadList={false}
onChange={(_, currentFile: any) => {
setFile({
...currentFile,
url: URL.createObjectURL(currentFile.originFile)
});
if (currentFile.status === 'done') setSelectedImage(currentFile.response.data.link);
}}
onProgress={(currentFile) => {
setFile(currentFile);
}}
>
<div className={cs}>
{(file && file.url) || initialImageUrl ? (
<div className="arco-upload-list-item-picture custom-upload-avatar">
<img src={file?.url || initialImageUrl} />
<div className="arco-upload-list-item-picture-mask">
<IconEdit />
</div>
{file && file.status === 'uploading' && file.percent < 100 && (
<Progress
percent={file.percent}
type="circle"
size="mini"
style={{
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translateX(-50%) translateY(-50%)'
}}
/>
)}
</div>
) : (
<div className="arco-upload-trigger-picture">
<div className="arco-upload-trigger-picture-text">
<IconPlus />
</div>
</div>
)}
</div>
</Upload>
);
};
return (
<Modal
className={styles['add-component-modal']}
title={isEditMode ? '编辑组件' : isCopyMode ? '复制组件' : '新增组件'}
visible={visible}
autoFocus={false}
focusLock={true}
style={{ width: '60%' }}
footer={modalFooter}
onCancel={() => setVisible(false)}
afterClose={() => {
// 关闭模态框后重置表单
form.resetFields();
setSelectedImage('');
setDescription('');
setShowSaveBtn(false);
setFile(null);
setCreated(false);
}}
>
<Form form={form} className={styles['add-component-container']}>
<div className={styles['first-half']}>
<div className={styles['component-preview']}>
<FormItem label="图标:" field="logoUrl">
<UploadImage />
</FormItem>
<FormItem label="组件预览:">
<div
style={{
width: '80%',
height: 200
}}
>
<CompReview componentDesignData={componentDesignData} />
</div>
</FormItem>
</div>
<div className={styles['component-info']}>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem label="名称:" field="name" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请输入名称');
}
return cb();
}
}
]}>
<Input style={{ width: '90%' }} allowClear placeholder="请输入名称" />
</FormItem>
</Grid.Col>
<Grid.Col span={12}>
<FormItem label="代码标识:" field="projectId" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请输入代码标识');
}
return cb();
}
}
]}>
<Input
style={{ width: '90%' }}
allowClear
placeholder="请输入代码标识"
disabled={created || isEditMode}
onChange={(e) => validateProjectId(e)}
/>
</FormItem>
</Grid.Col>
</Grid.Row>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem label="分类:" field="componentClassify" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请选择分类');
}
return cb();
}
}
]}>
<Select
placeholder="请选择分类"
style={{ width: '90%' }}
>
{classifyList.map((option, index) => (
<Option key={option.id} value={option.label}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={12}>
<FormItem label="组件语言:" field="codeLanguage" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请选择组件语言');
}
return cb();
}
}
]}>
<Select
placeholder="请选择组件语言"
style={{ width: '90%' }}
disabled={created || isEditMode || isCopyMode}
>
{codeLanguageOptions.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
</Grid.Row>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem label="标签:" field="tags">
<Select
placeholder="请选择标签"
allowCreate
mode="multiple"
style={{ width: '90%' }}
>
{tagsList.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={12}>
<FormItem label="组件类型:" field="type" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请选择组件类型');
}
return cb();
}
}
]}>
<Select
placeholder="请选择组件类型"
style={{ width: '90%' }}
disabled={created || isEditMode}
>
{componentTypeOptions.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
</Grid.Row>
<div className={styles['markdown-editor']}>
<div className={styles['markdown-label']}></div>
<EditorSection initialContent={description} />
</div>
</div>
</div>
{showSaveBtn && !isCopyMode && <div className={styles['last-half']}>
<div className={styles['last-half-header']}>
<p> </p>
<Space split={<Divider type="vertical" />}>
<Button
size="small"
type="secondary"
style={{ borderRadius: 5 }}
loading={codeInitLoading}
disabled={(!componentDesignData || componentDesignData.length === 0) || componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus)}
onClick={() => handleCodeInit()}
>
</Button>
<Button
size="mini"
type="primary"
style={{ borderRadius: 5 }}
onClick={() => {
setSelectedApiData(null); // 确保新增时清空选中的API数据
setShowApiModal(true);
}}
disabled={componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus)}
>
+
</Button>
</Space>
</div>
<Table columns={columns} data={componentDesignData} pagination={false} />
<AddApiModal
visible={showApiModal}
baseInfo={componentInfo}
componentDesignProgress={selectedApiData} // 添加这一行
componentDesignData={componentDesignData}
onUpdateComponentDesign={(params) => {
// 更新成功后重新获取组件设计数据
if (componentInfo && componentInfo.id) {
getComponentDesignData(componentInfo.id);
}
}}
onCancel={() => {
setShowApiModal(false);
setSelectedApiData(null); // 重置选中的API数据
}}
onOk={() => {
setShowApiModal(false);
setSelectedApiData(null); // 重置选中的API数据
}}
/>
</div>}
</Form>
</Modal>
);
};
export default AddComponentModal;

@ -0,0 +1,90 @@
import React from 'react';
import styles from './style/compReview.module.less';
const CompReview = ({ componentDesignData }) => {
// 确保数据是数组格式
const dataArray = Array.isArray(componentDesignData) ? componentDesignData :
componentDesignData ? [componentDesignData] : [];
return (
<div className={styles['comp-review-container']}>
<div className={styles['comp-review-header']}>
<div className={styles['comp-review-title']}></div>
</div>
<div className={styles['comp-review-body']}>
<div className={styles['comp-review-content']}>
{/* 上方内容 - 根据数组长度渲染 */}
<div className={styles['comp-review-top']}>
{dataArray.map((data, index) => (
<div key={data.key || data.id || index} className={styles['comp-review-item']}>
<div className={styles['comp-review-name']}>{data.ident}</div>
</div>
))}
</div>
{/* 输入输出部分 - 根据数组长度渲染 */}
<div className={styles['comp-review-io']}>
{/* 输入部分 */}
<div className={styles['comp-review-input']}>
<div className={styles['comp-review-io-label']}>input</div>
<div className={styles['comp-review-io-items']}>
{dataArray.map((data, index) => {
// 确保参数始终是数组格式
const parameters = Array.isArray(data.parameters) ? data.parameters :
(data.parameters ? [data.parameters] : []);
return (
<div key={data.key || data.id || index}>
{parameters.length > 0 ? (
parameters.map((param, paramIndex) => (
<div key={paramIndex} className={styles['comp-review-io-item']}>
{param.ident} {param.type}
</div>
))
) : (
<div className={styles['comp-review-io-placeholder']}>
</div>
)}
</div>
);
})}
</div>
</div>
{/* 输出部分 */}
<div className={styles['comp-review-output']}>
<div className={styles['comp-review-io-label']}>output</div>
<div className={styles['comp-review-io-items']}>
{dataArray.map((data, index) => {
// 确保响应始终是数组格式
const responses = Array.isArray(data.responses) ? data.responses :
(data.responses ? [data.responses] : []);
return (
<div key={data.key || data.id || index}>
{responses.length > 0 ? (
responses.map((response, responseIndex) => (
<div key={responseIndex} className={styles['comp-review-io-item']}>
{response.type} {response.ident}
</div>
))
) : (
<div className={styles['comp-review-io-placeholder']}>
</div>
)}
</div>
);
})}
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default CompReview;

@ -0,0 +1,209 @@
import React, { useState, useEffect } from 'react';
import { Button, Table, Input, Select, Modal, Message } from '@arco-design/web-react';
// 定义数据类型选项
const dataTypeOptions = [
{ label: 'STRING', value: 'STRING' },
{ label: 'INTEGER', value: 'INTEGER' },
{ label: 'BOOLEAN', value: 'BOOLEAN' },
{ label: 'ARRAY', value: 'ARRAY' },
{ label: 'OBJECT', value: 'OBJECT' },
{ label: 'JSON', value: 'JSON' },
{ label: 'DOUBLE', value: 'DOUBLE' }
];
// 定义数组类型选项
const arrayTypeOptions = [
{ label: 'STRING', value: 'STRING' },
{ label: 'INTEGER', value: 'INTEGER' },
{ label: 'DOUBLE', value: 'DOUBLE' },
{ label: 'BOOLEAN', value: 'BOOLEAN' },
{ label: 'JSON', value: 'JSON' },
{ label: 'OBJECT', value: 'OBJECT' }
];
function EditableCell({ value, onChange, columnType, record, dataIndex }) {
// 对于数组类型字段的特殊处理
if (dataIndex === 'generic') {
// 仅当数据类型为 ARRAY 时才可编辑
if (record.type === 'ARRAY') {
return (
<Select
value={value}
onChange={onChange}
options={arrayTypeOptions}
placeholder="请选择数组类型"
/>
);
}
else {
// 非 ARRAY 类型显示横杠
return <span>-</span>;
}
}
if (columnType === 'input') {
return (
<Input
value={value}
onChange={onChange}
placeholder="请输入"
/>
);
}
if (columnType === 'select') {
return (
<Select
value={value}
onChange={onChange}
options={dataTypeOptions}
placeholder="请选择数据类型"
/>
);
}
return <span>{value}</span>;
}
function EditableTable({ onDataUpdate, initialData = [] }) {
const [data, setData] = useState([]);
// 当初始数据变化时,更新表格数据
useEffect(() => {
if (initialData && Array.isArray(initialData) && initialData.length > 0) {
// 为每个数据项添加唯一的 key
const dataWithKeys = initialData.map((item, index) => ({
key: item.key || item.id || `key_${index}`,
...item
}));
setData(dataWithKeys);
}
}, [initialData]);
const handleValueChange = (key, dataIndex, value) => {
const newData = data.map(item => {
if (item.key === key) {
const updatedItem = { ...item, [dataIndex]: value };
// 如果更改的是 type 字段且不是 ARRAY则清空 generic 字段
if (dataIndex === 'type' && value !== 'ARRAY') {
updatedItem.generic = '';
}
return updatedItem;
}
return item;
});
setData(newData);
onDataUpdate && onDataUpdate(newData);
};
const columns = [
{
title: '名称',
dataIndex: 'ident',
render: (_, record) => (
<EditableCell
value={record.ident}
onChange={(value) => handleValueChange(record.key, 'ident', value)}
columnType="input"
record={record}
dataIndex="ident"
/>
)
},
{
title: '数据类型',
dataIndex: 'type',
render: (_, record) => (
<EditableCell
value={record.type}
onChange={(value) => handleValueChange(record.key, 'type', value)}
columnType="select"
record={record}
dataIndex="type"
/>
)
},
{
title: '数组类型',
dataIndex: 'generic',
render: (_, record) => (
<EditableCell
value={record.generic}
onChange={(value) => handleValueChange(record.key, 'generic', value)}
columnType="select"
record={record}
dataIndex="generic"
/>
)
},
{
title: '参数描述',
dataIndex: 'desc',
render: (_, record) => (
<EditableCell
value={record.desc}
onChange={(value) => handleValueChange(record.key, 'desc', value)}
columnType="input"
record={record}
dataIndex="desc"
/>
)
},
{
title: '操作',
dataIndex: 'op',
render: (_, record) => (
<Button
onClick={() => removeRow(record.key)}
type="text"
status="danger"
>
</Button>
)
}
];
const removeRow = (key) => {
const newData = data.filter(item => item.key !== key);
setData(newData);
onDataUpdate && onDataUpdate(newData);
};
const addRow = () => {
const newRow = {
key: `${Date.now()}`,
ident: '',
type: '',
generic: '',
desc: ''
};
const newData = [...data, newRow];
setData(newData);
onDataUpdate && onDataUpdate(newData);
};
return (
<>
<Button
style={{ marginBottom: 10 }}
type="primary"
onClick={addRow}
>
</Button>
<Table
data={data}
pagination={false}
columns={columns}
className="table-demo-editable-cell"
/>
</>
);
}
export default EditableTable;

@ -0,0 +1,166 @@
import React from 'react';
import { Button, Dropdown, Menu, Message } from '@arco-design/web-react';
import { ComponentItem } from '@/api/interface';
import {
componentStatusConstant,
publicStatus,
publishStatusConstant
} from '@/const/isdp/componentBase';
interface HandleButtonGroupProps {
row: ComponentItem;
index: number;
onHandlePublishComponent: (row: ComponentItem) => void;
onGoToReview: (row: ComponentItem) => void;
onPublishOrRevokeComponent: (action: 'publish' | 'revoke', identifier: string, version: string) => void;
onNavTo: (id: number, type: string) => void;
onSourceCodeView: (row: ComponentItem) => void;
onShowEdit: (row: ComponentItem, index: number) => void;
onCopyHandler: (row: ComponentItem) => void;
onShareCollaboration: (row: ComponentItem) => void;
onExportComponent: (id: number) => void;
onStopComponentShow: (row: ComponentItem) => void;
onRowDel: (row: ComponentItem) => void;
}
const HandleButtonGroup: React.FC<HandleButtonGroupProps> = ({
row,
index,
onHandlePublishComponent,
onGoToReview,
onPublishOrRevokeComponent,
onNavTo,
onSourceCodeView,
onShowEdit,
onCopyHandler,
onShareCollaboration,
onExportComponent,
onStopComponentShow,
onRowDel
}) => {
// 检查组件状态是否符合条件
const isEligible = (eligibleStatuses: string[], currentStatus: string) => {
return eligibleStatuses.includes(currentStatus.toLowerCase());
};
// 构建更多操作菜单
const renderDropdownMenu = () => {
const items = [];
// 组件复制
if (!isEligible([componentStatusConstant.DEFAULT, componentStatusConstant.DESIGN], row.componentStatus)) {
items.push(
<Menu.Item key="copy">
<Button type="text" onClick={() => onCopyHandler(row)}></Button>
</Menu.Item>
);
}
// 分享协作
if (!isEligible([componentStatusConstant.DESIGN, componentStatusConstant.DEFAULT], row.componentStatus)) {
items.push(
<Menu.Item key="share">
<Button type="text" onClick={() => onShareCollaboration(row)}></Button>
</Menu.Item>
);
}
// 导出组件
items.push(
<Menu.Item key="export">
<Button type="text" onClick={() => onExportComponent(row.id)}></Button>
</Menu.Item>
);
// 下架组件
if (isEligible([componentStatusConstant.DEPLOYED], row.componentStatus)) {
items.push(
<Menu.Item key="stop">
<Button type="text" onClick={() => onStopComponentShow(row)}></Button>
</Menu.Item>
);
}
// 删除组件
if (!isEligible([componentStatusConstant.DEPLOYED, componentStatusConstant.PUBLISHED], row.componentStatus)) {
items.push(
<Menu.Item key="delete">
<Button type="text" onClick={() => onRowDel(row)}></Button>
</Menu.Item>
);
}
return <Menu>{items}</Menu>;
};
return (
<>
{/* 发布组件/更新版本*/}
{row.publicStatus !== publicStatus.REVIEW && isEligible([componentStatusConstant.CODING, componentStatusConstant.DEPLOYED, componentStatusConstant.PUBLISHED], row.componentStatus) && (
<Button
type="text"
onClick={() => onHandlePublishComponent(row)}
>
{row.publicStatus === publicStatus.PUBLISHED ? '更新版本' : '发布组件'}
</Button>
)}
{(row.publicStatus === publicStatus.REVIEW || row.publicStatus === publicStatus.REJECTED) && (
<Button
type="text"
onClick={() => onGoToReview(row)}
>
</Button>
)}
{/* 公开组件/取消公开 */}
{row.publishStatus !== publishStatusConstant.PUBLISHED ? (
<Button
type="text"
onClick={() => onPublishOrRevokeComponent('publish', row.identifier, row.version)}
>
</Button>
) : (
<Button
type="text"
onClick={() => onPublishOrRevokeComponent('revoke', row.identifier, row.version)}
>
</Button>
)}
{/* 查看源码 */}
{isEligible(
[componentStatusConstant.CODING, componentStatusConstant.DEPLOYED, componentStatusConstant.PUBLISHED],
row.componentStatus
) ? (
<Button
type="text"
onClick={() => onSourceCodeView(row)}
>
</Button>
) : null}
{/* 编辑组件 */}
<Button
type="text"
onClick={() => onShowEdit(row, index)}
>
</Button>
{/* 更多操作 */}
<Dropdown droplist={renderDropdownMenu()} position="bl">
<Button type="text">
</Button>
</Dropdown>
</>
);
};
export default HandleButtonGroup;

@ -1,12 +1,41 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import styles from './style/index.module.less';
import { Button, Divider, Input, Space, Table, Radio } from '@arco-design/web-react';
import { Button, Divider, Input, Space, Table, Radio, Pagination, Modal, Message } from '@arco-design/web-react';
import { IconSearch } from '@arco-design/web-react/icon';
import { getMyComponentList, getCooperationComponentList, remove, exportComponent } from '@/api/componentBase';
import { getReviewGroupByNew } from '@/api/componentMarket';
import { componentRelease, componentRevoke } from '@/api/componentRelease';
import { ComponentItem } from '@/api/interface';
import AddComponentModal from '@/pages/componentDevelopment/componentList/addComponentModal';
import HandleButtonGroup from '@/pages/componentDevelopment/componentList/handleButtonGroup';
import {
componentStatusConstant,
componentStatusDict,
publicStatusDict,
publishStatusDict
} from '@/const/isdp/componentBase';
import dayjs from 'dayjs';
import { updateComponentCodingPath } from '@/store/ideContainer';
import { useDispatch } from 'react-redux';
const Group = Radio.Group;
const GlobalVarContainer = () => {
const [selectedItem, setSelectedItem] = useState('数字类型');
const [selectedItem, setSelectedItem] = useState('我的组件');
const [selectComponent, setSelectComponent] = useState(null);
const [componentData, setComponentData] = useState<ComponentItem[]>([]);
const [pagination, setPagination] = useState({
totalCount: 0,
pageSize: 10,
totalPage: 0,
currPage: 1
});
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const [mode, setMode] = useState<'create' | 'edit' | 'copy'>('create'); // 添加模式状态
const [searchValue, setSearchValue] = useState(''); // 添加搜索状态
const [componentStatus, setComponentStatus] = useState(''); // 添加组件状态筛选
const dispatch = useDispatch();
const menuItems = [
{
@ -29,70 +58,414 @@ const GlobalVarContainer = () => {
}
];
const columns = [
{
title: '组件名称',
dataIndex: 'name'
},
{
title: '组件标识',
dataIndex: 'projectId'
},
{
title: '分类',
dataIndex: 'componentClassify'
},
{
title: '创建人',
dataIndex: 'createUserName'
},
{
title: '代码语言',
dataIndex: 'codeLanguage'
},
{
title: '组件类型',
dataIndex: 'type',
render: (_, record) => {
return (
<span>{record.type === 'normal' ? '普通组件' : '监听组件'}</span>
);
}
},
{
title: '组件状态',
dataIndex: 'componentStatus',
render: (_, record) => {
const formattedData = componentStatusDict.find(item => item.value === componentStatusConstant[record.componentStatus]) || { label: '' };
return (
<span>{formattedData.label}</span>
);
}
},
{
title: '发布状态',
dataIndex: 'publicStatus',
render: (_, record) => {
const formattedData = publicStatusDict.find(item => item.value === record.publicStatus) || { label: '' };
return (
<span>{formattedData.label}</span>
);
}
},
{
title: '公开状态',
dataIndex: 'publishStatus',
render: (_, record) => {
const formattedData = publishStatusDict.find(item => item.value === record.publishStatus) || { label: '' };
return (
<span>{formattedData.label}</span>
);
}
},
{
title: '修改时间',
dataIndex: 'updateTime',
render: (_, record) => {
return (
<span>{dayjs(record.updateTime).format('YYYY-MM-DD HH:mm:ss')}</span>
);
}
},
{
title: '操作',
dataIndex: 'operations',
render: (_, record, index) => (
<HandleButtonGroup
row={record}
index={index}
onHandlePublishComponent={(row) => {
// TODO: 实现发布组件逻辑
console.log('Handle publish component', row);
}}
onGoToReview={(row) => {
// TODO: 实现查看审核逻辑
console.log('Go to review', row);
}}
onPublishOrRevokeComponent={async (action, identifier, version) => {
try {
if (action === 'publish') {
await componentRelease({ identifier, version });
Message.success('组件公开成功');
}
else if (action === 'revoke') {
await componentRevoke({ identifier, version });
Message.success('组件撤销成功');
}
// 重新获取数据以更新状态
fetchComponentData();
} catch (error) {
console.error('操作失败:', error);
Message.error(`组件${action === 'publish' ? '公开' : '撤销'}失败: ${error.message || ''}`);
}
}}
onNavTo={(id, type) => {
// TODO: 实现导航逻辑
console.log('Nav to', id, type);
}}
onSourceCodeView={(row) => {
dispatch(updateComponentCodingPath({
localProjectPath: row.localProjectPath,
name: row.name,
projectId: row.projectId,
id: row.id
}));
const event = new CustomEvent('navigateToTab', {
detail: {
path: 'componentCoding'
}
});
document.dispatchEvent(event);
}}
onShowEdit={(row, index) => {
setSelectComponent(row);
setVisible(true);
setMode('edit'); // 设置模式为复制
}}
onCopyHandler={(row) => {
setSelectComponent(row);
setVisible(true);
setMode('copy'); // 设置模式为复制
}}
onShareCollaboration={(row) => {
// TODO: 实现分享协作逻辑
console.log('Share collaboration', row);
}}
onExportComponent={(id) => {
// 实现导出组件逻辑
onExportComponent(id);
}}
onStopComponentShow={(row) => {
// TODO: 实现下架组件逻辑
console.log('Stop component show', row);
}}
onRowDel={(row) => {
// 显示确认框
Modal.confirm({
title: '确认删除',
content: `确定要删除组件 "${row.name}" 吗?此操作不可恢复。`,
okButtonProps: { status: 'danger' },
onOk: async () => {
try {
const res = await remove(row.id);
console.log('res:', res);
Message.success('组件删除成功');
// 重新获取数据
fetchComponentData();
} catch (error) {
console.error('删除组件失败:', error);
Message.error('组件删除失败');
}
}
});
}}
/>
)
}
];
useEffect(() => {
if (selectedItem === '我的组件' || selectedItem === '协同组件') {
// 当切换菜单或搜索条件变化时,重置到第一页
setPagination({
...pagination,
currPage: 1
});
// 延迟执行搜索,确保分页参数已更新
setTimeout(() => {
fetchComponentData();
}, 0);
}
else if (selectedItem === '组件审核') {
fetchComponentReview();
}
}, [selectedItem, searchValue]);
// 获取组件列表数据,支持传入额外参数
const fetchComponentData = async (extraParams: any = {}) => {
setLoading(true);
const apiMap = {
'我的组件': getMyComponentList,
'协同组件': getCooperationComponentList
};
try {
const params = {
currPage: pagination.currPage,
pageSize: pagination.pageSize,
...extraParams
};
// 如果有搜索关键词,则添加到参数中
if (searchValue.trim()) {
params.name = searchValue.trim();
}
// 如果选择了组件状态,则添加到参数中
if (componentStatus) {
params.componentStatus = componentStatus.toUpperCase();
}
const res: any = await apiMap[selectedItem](params);
setComponentData(res.data.list);
setPagination({
totalCount: res.data.totalCount,
pageSize: res.data.pageSize,
totalPage: res.data.totalPage,
currPage: res.data.currPage
});
} catch (error) {
console.error('获取组件列表失败:', error);
Message.error('获取组件列表失败');
} finally {
setLoading(false);
}
};
// 获取组件审核列表数据
const fetchComponentReview = async () => {
const res: any = await getReviewGroupByNew({
queryType: 'create',
current: pagination.currPage,
size: pagination.pageSize
});
console.log('组件审核列表:', res);
};
const handlePageChange = (page: number, pageSize?: number) => {
setPagination({
...pagination,
currPage: page,
pageSize: pageSize || pagination.pageSize
});
};
// 修改分页变化处理函数
useEffect(() => {
if (selectedItem === '我的组件' || selectedItem === '协同组件') {
fetchComponentData();
}
}, [pagination.currPage, pagination.pageSize, componentStatus]);
// 搜索处理函数
const searchHandle = () => {
// 重置到第一页并触发搜索
setPagination({
...pagination,
currPage: 1
});
fetchComponentData();
};
// 重置搜索
const resetSearch = () => {
setSearchValue('');
setComponentStatus(''); // 同时重置组件状态筛选
setPagination({
...pagination,
currPage: 1
});
fetchComponentData();
};
// 修改导出组件的回调函数
const onExportComponent = async (id) => {
try {
await exportComponent(id);
Message.success('组件导出成功');
} catch (error) {
console.error('导出组件失败:', error);
Message.error('组件导出失败');
}
};
// 组件状态筛选处理函数
const handleStatusChange = (value) => {
setComponentStatus(value);
};
return (
<div className={styles['comp-list-container']}>
{/*左侧菜单*/}
<div className={styles['comp-list-menu']}>
{menuItems.map((item, index) => (
<div
key={index}
className={selectedItem === item.label ? styles['selected'] : ''}
onClick={() => setSelectedItem(item.label)}
>
<img src={selectedItem === item.label ? item.activeIcon : item.icon} alt=""
style={{ width: 20, height: 20, marginRight: 12 }} />
<span>{item.label}</span>
</div>
))}
</div>
<div className={styles['comp-list-content']}>
{/*头部*/}
<div className={styles['comp-list-header']}>
<div className={styles['comp-list-search']}>
<Space>
<Input
prefix={<IconSearch />}
placeholder={'搜索'}
style={{ width: 236 }}
/>
<Button
type="primary"
style={{ marginLeft: 5, borderRadius: 4 }}
<>
<div className={styles['comp-list-container']}>
{/*左侧菜单*/}
<div className={styles['comp-list-menu']}>
{menuItems.map((item, index) => (
<div
key={index}
className={selectedItem === item.label ? styles['selected'] : ''}
onClick={() => setSelectedItem(item.label)}
>
<img src={selectedItem === item.label ? item.activeIcon : item.icon} alt=""
style={{ width: 20, height: 20, marginRight: 12 }} />
<span>{item.label}</span>
</div>
))}
</div>
<div className={styles['comp-list-content']}>
{/*头部*/}
<div className={styles['comp-list-header']}>
<div className={styles['comp-list-search']}>
<Space>
<Input
prefix={<IconSearch />}
placeholder={'输入组件名称搜索'}
style={{ width: 236 }}
value={searchValue}
onChange={(value) => setSearchValue(value)}
onPressEnter={searchHandle}
/>
<Button
type="primary"
style={{ marginLeft: 5, borderRadius: 4 }}
onClick={searchHandle}
>
</Button>
<Button
type="outline"
style={{ marginLeft: 5, borderRadius: 4 }}
onClick={resetSearch}
>
</Button>
</Space>
</div>
<div className={styles['comp-list-handle']}>
<Radio.Group
defaultValue={''}
name="button-radio-group"
value={componentStatus}
onChange={handleStatusChange}
style={{ marginRight: 30 }}
>
</Button>
</Space>
{[{ label: '全部', value: '' }, ...componentStatusDict].map((item) => {
return (
<Radio key={item.value} value={item.value}>
{({ checked }) => {
return (
<Button
tabIndex={-1}
key={item.value}
shape="round"
type={checked ? 'primary' : 'default'}
>
{item.label}
</Button>
);
}}
</Radio>
);
})}
</Radio.Group>
{selectedItem === '我的组件' && <Space split={<Divider type="vertical" />}>
<Button type="secondary" status="success" style={{ borderRadius: 4 }}></Button>
<Button type="outline" style={{ borderRadius: 4 }}></Button>
<Button type="primary" style={{ borderRadius: 4 }} onClick={() => {
setSelectComponent(null);
setVisible(true);
}}>+
</Button>
</Space>}
</div>
</div>
<div className={styles['comp-list-handle']}>
<Radio.Group defaultValue={'Beijing'} name="button-radio-group">
{['未设计', '编码中', '已部署'].map((item) => {
return (
<Radio key={item} value={item}>
{({ checked }) => {
return (
<Button tabIndex={-1} key={item} shape="round" type={checked ? 'primary' : 'default'}>
{item}
</Button>
);
}}
</Radio>
);
})}
</Radio.Group>
<Space split={<Divider type="vertical" />}>
<Button type="secondary" style={{ borderRadius: 4 }}></Button>
<Button type="outline" style={{ borderRadius: 4 }}></Button>
<Button type="primary" style={{ borderRadius: 4 }}>+ </Button>
</Space>
{/*数据列表*/}
<div className={styles['comp-list-list']}>
<Table
columns={columns}
data={componentData}
loading={loading}
pagination={false}
/>
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 16 }}>
<Pagination
current={pagination.currPage}
pageSize={pagination.pageSize}
total={pagination.totalCount}
onChange={handlePageChange}
/>
</div>
</div>
</div>
{/*数据列表*/}
<div className={styles['comp-list-list']}>
{/*<Table columns={columns} data={eventData} pagination={false} />*/}
</div>
</div>
</div>
{/*新增弹窗*/}
<AddComponentModal
visible={visible}
baseInfo={selectComponent}
setVisible={(visible) => {
setVisible(visible);
// 当关闭模态框时重置模式为create
if (!visible) {
setMode('create');
}
}}
onReFresh={fetchComponentData}
mode={mode} // 传递模式
/>
</>
);
};

@ -0,0 +1,60 @@
.add-component-modal {
:global(.arco-modal-content) {
padding-left: 0;
padding-right: 0;
background-color: #f7f8fa
}
}
.add-component-container {
.first-half {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 20px;
.component-preview {
overflow: hidden;
width: 30%;
background-color: #fff;
padding: 15px 5px;
border: 1px solid transparent;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.component-info {
width: 67%;
background-color: #fff;
padding: 15px 5px;
border: 1px solid transparent;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
.markdown-label {
font-size: 14px;
white-space: normal;
color: var(--color-text-2);
margin-bottom: 6px;
}
.markdown-editor {
padding-left: 50px;
padding-right: 40px;
}
}
}
.last-half {
box-sizing: border-box;
width: 100%;
background-color: #fff;
padding: 15px 20px;
.last-half-header {
display: flex;
justify-content: space-between;
}
}
}

@ -0,0 +1,94 @@
.comp-review-container {
width: 300px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.comp-review-header {
background-color: #FFA500;
color: white;
padding: 8px 12px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
}
.comp-review-icon {
font-size: 16px;
margin-right: 8px;
}
.comp-review-title {
font-weight: bold;
}
.comp-review-actions {
font-size: 12px;
cursor: pointer;
}
.comp-review-body {
background-color: white;
padding: 12px;
}
.comp-review-content {
border-radius: 4px;
padding: 8px;
min-height: 120px;
}
.comp-review-top {
margin-bottom: 12px;
border-bottom: 1px solid #ebeef5;
}
.comp-review-name {
font-weight: bold;
margin-bottom: 4px;
}
.comp-review-desc {
color: #666;
font-size: 12px;
}
.comp-review-io {
height: 100%;
}
.comp-review-input,
.comp-review-output {
flex: 1;
padding: 8px;
}
.comp-review-output {
border-right: none;
text-align: right;
}
.comp-review-io-label {
font-weight: bold;
margin-bottom: 4px;
font-size: 12px;
}
.comp-review-io-items {
margin-top: 4px;
}
.comp-review-io-item {
margin-bottom: 4px;
position: relative;
}
.comp-review-io-placeholder {
color: #999;
font-size: 12px;
text-align: center;
padding: 8px 0;
}

@ -1,14 +1,40 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import styles from './style/index.module.less';
import CustomCard from '@/components/CustomCard/index';
import CompCard from './compCard';
import { menu } from './test/data';
import { Tabs, Input, Select } from '@arco-design/web-react';
import { getComponentMarket } from '@/api/componentMarket';
import { getComponentClassify } from '@/api/componentClassify';
const TabPane = Tabs.TabPane;
const InputSearch = Input.Search;
function ComponentMarket() {
const getMarketData = async () => {
const params = {
componentClassify: '',
componentClassifyLabel: '全部',
keyword: '',
current: 1,
size: 15
};
const res: any = await getComponentMarket(params);
console.log('res:', res);
};
const getMenuList = async () => {
const res: any = await getComponentClassify('component');
console.log('menu:', res);
};
useEffect(() => {
getMarketData();
getMenuList();
}, []);
return (
<>
<div className={styles['comp-market-container']}>

@ -17,7 +17,7 @@ interface CompNodeProps {
const CompNode: React.FC<CompNodeProps> = ({ nodeData }) => {
return (
<div className={styles['comp-node-container']}>
<Card style={{ width: 450, margin: 10, border: '1px solid #dfe4ea' }}>
<Card style={{ maxWidth: 450, margin: 10, border: '1px solid #dfe4ea' }}>
{/*节点渲染*/}
<div className={styles['comp-node-box']}>
{/*节点标题*/}

@ -154,7 +154,8 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
newSet.delete(appId);
return newSet;
});
} else {
}
else {
// 隐藏节点 - 添加到隐藏节点集合
setHiddenNodes(prev => new Set(prev).add(appId));
}
@ -188,7 +189,8 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
newSet.delete(appId);
return newSet;
});
} else {
}
else {
// 隐藏节点 - 添加到隐藏节点集合
setHiddenNodes(prev => new Set(prev).add(appId));
}
@ -238,7 +240,14 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
return (
<div ref={reactFlowWrapper} style={{ width: '100%', height: '100%', position: 'relative' }}
onContextMenu={(e) => e.preventDefault()}>
onContextMenu={(e) => e.preventDefault()}
// 添加点击事件处理器,用于关闭添加节点菜单
onClick={() => {
if (edgeForNodeAdd || positionForNodeAdd) {
setEdgeForNodeAdd(null);
setPositionForNodeAdd(null);
}
}}>
<ReactFlow
id={reactFlowId}
nodes={nodes.map(node => {
@ -517,6 +526,8 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
zIndex: 1000,
transform: 'none'
}}
// 点击事件冒泡到父级处理,这里阻止默认行为
onClick={(e) => e.stopPropagation()}
>
<AddNodeMenu
onAddNode={(nodeType, node) => {

@ -1,7 +1,7 @@
import React from 'react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
import { Handle, Position, useStore } from '@xyflow/react';
import { deserializeValue, isJSON } from '@/utils/common';
import { deserializeValue, formatDataType, isJSON } from '@/utils/common';
import cronstrue from 'cronstrue/i18n';
interface NodeContentData {
@ -121,6 +121,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{apiIns.length > 0 && (
<div className={styles['node-inputs']}>
{apiIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.desc}
</div>
@ -131,7 +132,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{apiOuts.length > 0 && (
<div className={styles['node-outputs-api']}>
{apiOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
<div key={output.id || `output-${index}`} className={styles['node-output-label']}>
{output.desc}
</div>
))}
@ -155,7 +156,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.id || `输入${index + 1}`} {input.dataType}
<span
className={styles['node-data-type']}
>{input.id || `输入${index + 1}`} {formatDataType(input.dataType)}
</span>
</div>
))}
</div>
@ -164,8 +168,12 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{dataOuts.length > 0 && !isEndNode && (
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.dataType} {`${output.id}` || `输出${index + 1}`}
<div key={output.id || `output-${index}`} className={styles['node-output-label']}>
<span
className={styles['node-data-type']}
>{formatDataType(output.dataType)} {output.id || `输出${index + 1}`}
</span>
</div>
))}
</div>

@ -2,6 +2,7 @@ import React from 'react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
import { Handle, Position, useStore } from '@xyflow/react';
import { Image } from '@arco-design/web-react';
import { formatDataType } from '@/utils/common';
interface NodeContentData {
parameters?: {
@ -151,7 +152,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{`${input.desc} ${input.dataType}` || `输入${index + 1}`}
<span
className={styles['node-data-type']}
>{input.id || `输入${index + 1}`} {formatDataType(input.dataType)}
</span>
</div>
))}
</div>
@ -160,8 +164,11 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{dataOuts.length > 0 && !isEndNode && (
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.dataType} {output.id || `输出${index + 1}`}
<div key={output.id || `output-${index}`} className={styles['node-output-label']}>
<span
className={styles['node-data-type']}
>{formatDataType(output.dataType)} {output.id || `输出${index + 1}`}
</span>
</div>
))}
</div>

@ -1,7 +1,7 @@
import React from 'react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
import { Handle, Position, useStore } from '@xyflow/react';
import { deserializeValue } from '@/utils/common';
import { deserializeValue, formatDataType } from '@/utils/common';
import cronstrue from 'cronstrue/i18n';
interface NodeContentData {
@ -150,7 +150,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.id || `输入${index + 1}`} {input.dataType}
<span
className={styles['node-data-type']}
>{input.id || `输入${index + 1}`} {formatDataType(input.dataType)}
</span>
</div>
))}
</div>
@ -160,7 +163,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.dataType} {output.id || `输出${index + 1}`}
<span
className={styles['node-data-type']}
>{formatDataType(output.dataType)} {output.id || `输出${index + 1}`}
</span>
</div>
))}
</div>

@ -1,7 +1,7 @@
import React from 'react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
import { Handle, Position, useStore } from '@xyflow/react';
import { deserializeValue, isJSON } from '@/utils/common';
import { deserializeValue, formatDataType, isJSON } from '@/utils/common';
import cronstrue from 'cronstrue/i18n';
import { useSelector } from 'react-redux';
@ -70,7 +70,7 @@ const renderSpecialNodeHandles = (isStartNode: boolean, isEndNode: boolean, data
id={dataOuts[index].name || `output-${index}`}
style={{
...handleStyles.data,
top: `${70 + apiOuts.length * 20 + index * 20}px`
top: `${70 + apiIns.length * 20 + index * 20}px`
}}
/>
))}
@ -171,7 +171,7 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
id={dataOuts[index].name || dataOuts[index].id || `output-${index}`}
style={{
...handleStyles.data,
top: `${70 + (apiOuts.length + index) * 20}px`
top: `${70 + (apiIns.length + index) * 20}px`
}}
/>
))}
@ -252,7 +252,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{apiOuts.length > 0 && (
<div className={styles['node-outputs']}>
{apiOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
<div key={output.id || `output-${index}`} className={styles['node-output-label']}>
{output.desc}
</div>
))}
@ -275,7 +275,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.id || `输入${index + 1}`} {input.dataType}
<span
className={styles['node-data-type']}
>{input.id || `输入${index + 1}`} {formatDataType(input.dataType)}
</span>
</div>
))}
</div>
@ -283,8 +286,11 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{dataOuts.length > 0 && !isEndNode && (
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.dataType} {output.id || `输出${index + 1}`}
<div key={output.id || `output-${index}`} className={styles['node-output-label']}>
<span
className={styles['node-data-type']}
>{formatDataType(output.dataType)} {output.id || `输出${index + 1}`}
</span>
</div>
))}
</div>

@ -1,6 +1,7 @@
import React from 'react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
import { Handle, Position, useStore } from '@xyflow/react';
import { formatDataType } from '@/utils/common';
interface NodeContentData {
parameters?: {
@ -152,7 +153,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.id || `输入${index + 1}`} {input.dataType}
<span
className={styles['node-data-type']}
>{input.id || `输入${index + 1}`} {formatDataType(input.dataType)}
</span>
</div>
))}
</div>
@ -162,7 +166,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.dataType} {`${output.id}` || `输出${index + 1}`}
<span
className={styles['node-data-type']}
>{formatDataType(output.dataType)} {output.id || `输出${index + 1}`}
</span>
</div>
))}
</div>

@ -1,6 +1,7 @@
import React from 'react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
import { Handle, Position, useStore } from '@xyflow/react';
import { formatDataType } from '@/utils/common';
interface NodeContentData {
parameters?: {
@ -148,7 +149,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.id || `输入${index + 1}`} {input.dataType}
<span
className={styles['node-data-type']}
>{input.id || `输入${index + 1}`} {formatDataType(input.dataType)}
</span>
</div>
))}
</div>
@ -158,7 +162,10 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.dataType} {output.id || `输出${index + 1}`}
<span
className={styles['node-data-type']}
>{formatDataType(output.dataType)} {output.id || `输出${index + 1}`}
</span>
</div>
))}
</div>

@ -1,6 +1,7 @@
import React, { useEffect } from 'react';
import { Menu } from '@arco-design/web-react';
import { Menu, Message } from '@arco-design/web-react';
import { Node } from '@xyflow/react';
import { isJSON } from '@/utils/common';
interface NodeContextMenuProps {
node: Node;
@ -36,6 +37,10 @@ const NodeContextMenu: React.FC<NodeContextMenuProps> = ({
// 判断节点类型如果是SUB类型则打开新标签页
if (node.type === 'SUB') {
// 创建自定义事件来通知打开新标签页
const customDef = isJSON((node as any).data.component.customDef) ? JSON.parse((node as any).data.component.customDef) : {};
if (Object.keys(customDef).length === 0) {
Message.warning('新导入的复合组件请保存后再进行编辑!');
}
const openTabEvent = new CustomEvent('openSubNodeTab', {
detail: { node }
});

@ -87,8 +87,8 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({
boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
minWidth: 300,
width: 'max-content',
maxWidth: '80vw',
height: '95%',
maxWidth: '80%',
height: '90%',
bottom: 0,
borderRadius: 10
}}

@ -244,8 +244,10 @@ export const upDatePublish = async (revertedData) => {
publishAppId: []
};
revertedData.forEach(item => {
if (item?.component && item.component.type === 'SUB' && !item.component.customDef) params.publishAppId.push(item.component.compId); // 复合组件的这个compId实际上就是flowHousVO里面的id
// 复合组件的这个compId实际上就是flowHousVO里面的id
if (item?.component && item.component.type === 'SUB' && !item.component.customDef) params.publishAppId.push(`${item.component.compId}/${item.nodeId}`);
});
if (params.publishAppId.length > 0) {
const res: any = await refPublish(params);
if (res.code === 200) return res.data;

@ -0,0 +1,101 @@
import React, { useEffect, useState } from 'react';
import dayjs from 'dayjs';
import { Tabs, Table, TableColumnProps, Input } from '@arco-design/web-react';
import { IconCheckCircleFill, IconLoading, IconCloseCircleFill } from '@arco-design/web-react/icon';
import { formatSeconds } from '@/utils/common';
const TabPane = Tabs.TabPane;
const columns: TableColumnProps[] = [
{
title: '节点',
dataIndex: 'name'
},
{
title: '时间',
render: (_: any, record) => (
<>
<span>{dayjs(record.startTime).format('YYYY-MM-DD HH:mm:ss')}</span>
<br></br>
{record.endTime > 0 && <span>{dayjs(record.endTime).format('YYYY-MM-DD HH:mm:ss')}</span>}
</>
)
},
{
title: '耗时',
dataIndex: 'duration',
render: (_: any, record) => (
record.duration > 1000 ?
<span>{formatSeconds((record.duration / 1000).toString())}</span> :
<span>{record.duration}</span>
)
},
{
title: '状态',
dataIndex: 'state',
render: (_: any, record) => (
<>
{record.state === 1 && <IconCheckCircleFill fontSize={30} style={{ color: 'rgb(var(--green-6))' }} />}
{record.state === 0 && <IconLoading fontSize={30} style={{ color: 'rgb(var(--arcoblue-4))' }} />}
{record.state === -1 && <IconCloseCircleFill fontSize={30} style={{ color: 'rgb(var(--red-6))' }} />}
</>
)
},
{
title: '输入参数',
dataIndex: 'input',
render: (_: any, record) => (
<span>{JSON.stringify(record.input)}</span>
)
},
{
title: '输出参数',
dataIndex: 'output',
render: (_: any, record) => (
<span>{JSON.stringify(record.output)}</span>
)
}
];
const RunTimeData = ({ logData }) => {
const [currentTab, setCurrentTab] = useState('0');
const [tabList, setTabList] = useState([]);
const [keyMap, setKeyMap] = useState({});
const [data, setData] = useState([]);
const onTabChange = (key) => {
setCurrentTab(key);
setData(keyMap[key]);
};
useEffect(() => {
const tempMap = {};
const list = logData.map((item, index) => {
tempMap[index] = item.nodes;
return {
key: index,
title: item.parent
};
});
setKeyMap(tempMap);
setTabList(list);
}, [logData]);
useEffect(() => {
if (tabList.length > 0) {
setData(keyMap[0]);
onTabChange(0); // 默认选中第一个
}
}, [tabList]);
return (
<>
<Tabs type="card" onChange={onTabChange} activeTab={currentTab}>
{tabList.map((item) => (<TabPane key={item.key} title={item.title}></TabPane>))}
</Tabs>
<Table columns={columns} data={data} />
</>
);
};
export default RunTimeData;

@ -120,13 +120,14 @@ function IDEContainer() {
// 监听自定义事件,处理打开子节点标签页的逻辑
useEffect(() => {
const handleOpenSubNodeTab = (event: CustomEvent) => {
const handleOpenSubNodeTab = async (event: CustomEvent) => {
const { node } = event.detail;
const subCompList = flowData[currentAppData.id].subs;
const customDef = isJSON(node.data.component.customDef) ? JSON.parse(node.data.component.customDef) : {};
const currentSubComp = subCompList.find((item) => item.flowId === customDef.subflowId);
// 根据节点信息创建新的标签页
if (currentSubComp && Object.keys(currentSubComp).length > 0) {
await getAppList();
const newNodeKey = currentSubComp.flowId;
// 查找菜单项

@ -3,7 +3,8 @@ import { ResizeBox, Tabs } from '@arco-design/web-react';
import styles from './style/logBar.module.less';
import { updateLogBarStatus } from '@/store/ideContainer';
import { useSelector, useDispatch } from 'react-redux';
import { getNodeData } from '@/api/appIns'; // 添加导入
import { getNodeData } from '@/api/appIns';
import RunTimeData from './components/runTimeData';
const TabPane = Tabs.TabPane;
@ -50,15 +51,14 @@ const LogBar: React.FC<LogBarProps> = () => {
const [tabs] = useState(data);
const [activeTab, setActiveTab] = useState('1');
const resizeBoxRef = useRef<HTMLDivElement>(null); // 引用 ResizeBox 容器
const { logBarStatus, appRuntimeData } = useSelector((state: any) => state.ideContainer);
const dispatch = useDispatch();
const [validationLogs, setValidationLogs] = useState<LogMessage[]>([]);
const [runtimeLogs, setRuntimeLogs] = useState<LogMessage[]>([]); // 添加运行时日志状态
const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态
const { currentAppData } = useSelector((state: any) => state.ideContainer);
// 添加运行数据状态
const [runtimeData, setRuntimeData] = useState<RuntimeData>({});
const [runtimeData, setRuntimeData] = useState<RuntimeData>({}); // 添加运行数据状态
const [loading, setLoading] = useState(false);
const { logBarStatus, appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer);
const dispatch = useDispatch();
// 处理 Tab 点击事件
const handleTabClick = (key: string) => {
@ -113,7 +113,7 @@ const LogBar: React.FC<LogBarProps> = () => {
setValidationLogs(prev => [...prev, newLog]);
// 自动切换到校验日志tab并展开logBar
setActiveTab('2');
// setActiveTab('2');
dispatch(updateLogBarStatus(true));
}
// 如果是运行时日志
@ -128,7 +128,6 @@ const LogBar: React.FC<LogBarProps> = () => {
setRuntimeLogs(prev => [...prev, newLog]);
// 自动切换到运行日志tab并展开logBar
setActiveTab('1');
dispatch(updateLogBarStatus(true));
// 同时将日志添加到当前应用的运行日志中
@ -159,11 +158,11 @@ const LogBar: React.FC<LogBarProps> = () => {
let intervalId: NodeJS.Timeout | null = null;
// 只有在当前tab是运行数据且有当前应用时才开始轮询
if (activeTab === '3' && currentAppData?.id && logBarStatus) {
if (activeTab === '3' && currentAppData?.id && logBarStatus && appRuntimeData[currentAppData.id]?.runId) {
const fetchRuntimeData = async () => {
try {
setLoading(true);
const response = await getNodeData(currentAppData.id);
const response = await getNodeData(appRuntimeData[currentAppData.id].runId);
setRuntimeData(prev => ({
...prev,
[currentAppData.id]: response.data
@ -245,17 +244,10 @@ const LogBar: React.FC<LogBarProps> = () => {
return (
<div style={{ padding: '10px', height: 'calc(100% - 40px)', overflowY: 'auto' }}>
{loading ? (
<p>...</p>
) : !currentAppData?.id ? (
<p></p>
) : !currentAppDataContent ? (
{!currentAppDataContent ? (
<p></p>
) : (
<div>
<h3>: {currentAppData.name}</h3>
<pre>{JSON.stringify(currentAppDataContent, null, 2)}</pre>
</div>
<RunTimeData logData={currentAppDataContent} />
)}
</div>
);

@ -585,12 +585,42 @@ const SideBar: React.FC<SideBarProps> = ({
}
};
// 监听导航到Tab的事件
const handleNavigateToTab = (event: CustomEvent) => {
const { path } = event.detail;
// 查找对应的菜单项
const menuItems = menuData[identity];
if (!menuItems) return;
const findMenuItem = (items: any[]): any => {
for (const item of items) {
if (item.path === path) {
return item;
}
if (item.children) {
const found = findMenuItem(item.children);
if (found) return found;
}
}
return null;
};
const menuItem = findMenuItem(menuItems);
if (menuItem) {
// 触发菜单选择
onMenuSelect?.({ ...menuItem });
}
};
document.addEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener);
document.addEventListener('navigateToTab', handleNavigateToTab as EventListener);
return () => {
document.removeEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener);
document.removeEventListener('navigateToTab', handleNavigateToTab as EventListener);
};
}, []);
}, [menuData, identity, onMenuSelect]);
// 渲染节点的额外操作按钮
const renderNodeExtra = (node) => {

@ -23,7 +23,7 @@
flex-direction: column;
position: relative;
flex: 1;
overflow: auto;
overflow: hidden;
}
}

@ -5,10 +5,11 @@ import { getImageUrl, imageList } from '@/utils/pubUse';
interface CoverProps {
defaultImage?: string;
imgWidth?: number | string;
onImageChange?: (image: string) => void;
}
const Cover: React.FC<CoverProps> = ({ defaultImage, onImageChange }) => {
const Cover: React.FC<CoverProps> = ({ defaultImage, onImageChange, imgWidth = 200 }) => {
const [visible, setVisible] = useState(false);
const [currentImage, setCurrentImage] = useState('');
@ -35,7 +36,7 @@ const Cover: React.FC<CoverProps> = ({ defaultImage, onImageChange }) => {
return (
<div className={styles['cover-container']}>
<div className={styles['cover-image']}>
<Image width={200} src={currentImage} preview={false} onClick={() => setVisible(true)}></Image>
<Image width={imgWidth} src={currentImage} preview={false} onClick={() => setVisible(true)}></Image>
</div>
<Modal
style={{ width: '60%' }}
@ -52,7 +53,7 @@ const Cover: React.FC<CoverProps> = ({ defaultImage, onImageChange }) => {
render={(item, index) => {
return (
<List.Item key={index}>
<Image width={200} src={item.src} preview={false} onClick={() => {
<Image width={imgWidth} src={item.src} preview={false} onClick={() => {
handleImageSelect(item.src);
}}></Image>
</List.Item>

@ -25,6 +25,13 @@ interface IDEContainerState {
eventlisteneList: any[] // [{nodeID:topic}]
}>;
gloBalVarList: any;
// 组件开发相关状态
componentCoding: {
localProjectPath: string; // code-server路径
name: string; // 组件名称
projectId: string; // 组件标识
id: string,// 组件id
};
}
// 初始状态
@ -43,7 +50,13 @@ const initialState: IDEContainerState = {
nodeStatusMap: {}, // 初始化节点状态映射
isRunning: false, // 默认未运行
appRuntimeData: {}, // 按应用ID隔离的应用运行状态和日志数据
gloBalVarList: [] // 工程级全局变量列表
gloBalVarList: [], // 工程级全局变量列表
componentCoding: {
localProjectPath: '',
name: '',
projectId: '',
id: ''
}
};
// 创建切片
@ -196,6 +209,10 @@ const ideContainerSlice = createSlice({
if (state.appRuntimeData[appId]) {
state.appRuntimeData[appId].logs = [];
}
},
// 更新组件编码路径
updateComponentCodingPath(state, action) {
state.componentCoding = { ...action.payload };
}
}
});
@ -220,7 +237,8 @@ export const {
updateRuntimeId,
updateEventNodeList,
addRuntimeLog,
clearRuntimeLogs
clearRuntimeLogs,
updateComponentCodingPath
} = ideContainerSlice.actions;
// 默认导出 reducer

@ -8,6 +8,25 @@ export interface OpenWindowOptions {
[key: string]: any;
}
export const getObjType = (obj) => {
const toString = Object.prototype.toString;
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object',
};
if (obj instanceof Element) {
return 'element';
}
return map[toString.call(obj)];
};
// 新窗口打开处理函数
export function openWindow(url: string, opts?: OpenWindowOptions) {
const { target = '_blank', ...others } = opts || {};
@ -231,6 +250,32 @@ export const deserializeValue = (value: any): any => {
}
};
/**
*
*/
export const deepClone = (data) => {
const type = getObjType(data);
let obj;
if (type === 'array') {
obj = [];
} else if (type === 'object') {
obj = {};
} else {
//不再具有下一层次
return data;
}
if (type === 'array') {
for (let i = 0, len = data.length; i < len; i++) {
obj.push(deepClone(data[i]));
}
} else if (type === 'object') {
for (const key in data) {
obj[key] = deepClone(data[key]);
}
}
return obj;
};
export const sleep = (ms: number): Promise<void> => {
return new Promise(resolve => setTimeout(resolve, ms));
};

@ -516,7 +516,7 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
* React Flow nodes edges convertFlowData
* @param nodes - React Flow
* @param edges - React Flow
* @param complexKV - 使id {IDsub_ID}
* @param complexKV - 使id {ID/nodeIdsub_ID}
* @returns convertFlowData
*/
export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: any) => {
@ -537,13 +537,28 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an
// 处理 component 信息
if (node.type === 'SUB' && !node.data.component.customDef) {
let subflowId = '';
if (complexKV && node.data.compId) {
// 遍历complexKV找到匹配的条目使用节点ID进行匹配
for (const key in complexKV) {
if (key.includes('/')) {
const [numericId, nodeId] = key.split('/');
// 使用节点ID进行匹配
if (nodeId === node.id) {
subflowId = complexKV[key];
break;
}
}
}
}
nodeConfig.component = {
type: 'SUB',
compId: node.data.compId,
customDef: JSON.stringify({
dataIns: node.data.parameters.dataIns,
dataOuts: node.data.parameters.dataOuts,
subflowId: complexKV[node.data.compId],
subflowId: subflowId,
name: node.data.title
})
};
@ -552,7 +567,9 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an
nodeConfig.component = { ...node.data.component };
}
else {
nodeConfig.component = null;
nodeConfig.component = {
type: node.type
};
}
// 处理参数信息

Loading…
Cancel
Save