From 12e218c6f85618d324497165b363972f70df851b Mon Sep 17 00:00:00 2001 From: dengqichen Date: Sat, 1 Nov 2025 15:41:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=A3=E7=A0=81=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E8=A1=A8=E5=8D=95=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CodeEditorField.tsx | 156 ++++++++++++++++++ .../components/CodeEditorPropertyConfig.tsx | 140 ++++++++++++++++ .../src/components/FormDesigner/config.ts | 20 +++ frontend/src/components/FormDesigner/types.ts | 8 + frontend/src/router/index.tsx | 14 +- 5 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/FormDesigner/components/CodeEditorField.tsx create mode 100644 frontend/src/components/FormDesigner/components/CodeEditorPropertyConfig.tsx diff --git a/frontend/src/components/FormDesigner/components/CodeEditorField.tsx b/frontend/src/components/FormDesigner/components/CodeEditorField.tsx new file mode 100644 index 00000000..57e5050b --- /dev/null +++ b/frontend/src/components/FormDesigner/components/CodeEditorField.tsx @@ -0,0 +1,156 @@ +/** + * 代码编辑器字段渲染组件 + * 基于 Monaco Editor 实现 + */ + +import React, { useState, useRef } from 'react'; +import { Button, Space, Tooltip } from 'antd'; +import { FullscreenOutlined, FullscreenExitOutlined, FormatPainterOutlined } from '@ant-design/icons'; +import type { FieldRendererProps } from '../config'; +import Editor from '@/components/Editor'; +import type { editor } from 'monaco-editor'; + +const CodeEditorField: React.FC = ({ field, value, onChange, isPreview }) => { + const [isFullscreen, setIsFullscreen] = useState(false); + const editorRef = useRef(null); + + const language = field.language || 'javascript'; + const height = field.height || 300; + const theme = field.theme || 'vs-dark'; + const readOnly = field.readOnly || false; + const showMinimap = field.showMinimap !== false; // 默认显示 + + const toggleFullscreen = () => { + setIsFullscreen(!isFullscreen); + }; + + const handleFormat = () => { + if (editorRef.current) { + editorRef.current.getAction('editor.action.formatDocument')?.run(); + } + }; + + const handleEditorMount = (editor: editor.IStandaloneCodeEditor) => { + editorRef.current = editor; + }; + + const editorHeight = isFullscreen ? 'calc(100vh - 160px)' : `${height - 40}px`; // 减去工具栏高度 + + return ( + <> + {/* 全屏遮罩 */} + {isFullscreen && ( +
+ )} + + {/* 编辑器主容器 */} +
+ {/* 顶部工具栏 */} +
+ + +
+ + {/* 编辑器容器 */} +
+ { + if (!readOnly && onChange) { + onChange(newValue); + } + }} + onMount={handleEditorMount} + options={{ + readOnly: readOnly || !isPreview, + minimap: { + enabled: showMinimap, + scale: 1, + showSlider: 'always', + }, + scrollBeyondLastLine: false, + fontSize: 14, + lineNumbers: 'on', + formatOnPaste: true, + formatOnType: false, + automaticLayout: true, + wordWrap: 'on', + tabSize: 2, + scrollbar: { + vertical: 'visible', + horizontal: 'visible', + }, + }} + /> +
+
+ + ); +}; + +export default CodeEditorField; diff --git a/frontend/src/components/FormDesigner/components/CodeEditorPropertyConfig.tsx b/frontend/src/components/FormDesigner/components/CodeEditorPropertyConfig.tsx new file mode 100644 index 00000000..6560693f --- /dev/null +++ b/frontend/src/components/FormDesigner/components/CodeEditorPropertyConfig.tsx @@ -0,0 +1,140 @@ +/** + * 代码编辑器字段属性配置组件 + */ + +import React from 'react'; +import { Form, Input, InputNumber, Select, Switch } from 'antd'; +import type { PropertyConfigProps } from '../config'; + +const CodeEditorPropertyConfig: React.FC = ({ field, onChange }) => { + const handleChange = (key: string, value: any) => { + onChange({ + ...field, + [key]: value, + }); + }; + + return ( + <> + {/* 基础配置 */} + + handleChange('label', e.target.value)} + placeholder="请输入字段标签" + /> + + + + handleChange('name', e.target.value)} + placeholder="请输入字段名称(英文)" + /> + + + {/* 编辑器配置 */} + {/* 编程语言 */} + + handleChange('theme', value)} + options={[ + { label: '浅色主题', value: 'vs' }, + { label: '深色主题', value: 'vs-dark' }, + { label: '高对比度', value: 'hc-black' }, + ]} + /> + + + {/* 占位符文本 */} + + handleChange('placeholder', e.target.value)} + placeholder="请输入占位提示" + /> + + + {/* 基本属性 */} + + handleChange('required', checked)} + /> + + + + handleChange('disabled', checked)} + /> + + + {/* 编辑器特性 */} + {/* 显示小地图 */} + + handleChange('showMinimap', checked)} + /> + + + {/* 显示语言标签 */} + + handleChange('showLanguage', checked)} + /> + + + {/* 只读模式 */} + + handleChange('readOnly', checked)} + /> + + + ); +}; + +export default CodeEditorPropertyConfig; + diff --git a/frontend/src/components/FormDesigner/config.ts b/frontend/src/components/FormDesigner/config.ts index 2dd46bef..0bf9bee6 100644 --- a/frontend/src/components/FormDesigner/config.ts +++ b/frontend/src/components/FormDesigner/config.ts @@ -21,8 +21,11 @@ import { MinusOutlined, FileTextOutlined, BorderOutlined, + CodeOutlined, } from '@ant-design/icons'; import type { FieldType, FieldConfig } from './types'; +import CodeEditorField from './components/CodeEditorField'; +import CodeEditorPropertyConfig from './components/CodeEditorPropertyConfig'; // 🔌 属性配置组件 Props export interface PropertyConfigProps { @@ -254,6 +257,23 @@ export const COMPONENT_LIST: ComponentMeta[] = [ ], }, }, + { + type: 'code-editor', + label: '代码编辑器', + icon: CodeOutlined, + category: '高级字段', + defaultConfig: { + placeholder: '请输入代码', + language: 'javascript', + height: 300, + theme: 'vs-dark', + readOnly: false, + showMinimap: true, + showLanguage: true, + }, + PropertyConfigComponent: CodeEditorPropertyConfig, + FieldRendererComponent: CodeEditorField, + }, ]; // 根据分类分组组件 diff --git a/frontend/src/components/FormDesigner/types.ts b/frontend/src/components/FormDesigner/types.ts index 51d71836..b9932e15 100644 --- a/frontend/src/components/FormDesigner/types.ts +++ b/frontend/src/components/FormDesigner/types.ts @@ -18,6 +18,7 @@ export type FieldType = | 'rate' // 评分 | 'upload' // 文件上传 | 'cascader' // 级联选择 + | 'code-editor' // 代码编辑器 | 'text' // 纯文本 | 'grid' // 栅格布局 | 'divider' // 分割线 @@ -143,6 +144,13 @@ export interface FieldConfig { columnSpans?: number[]; // 栅格每列宽度(用于 grid 组件,如 [2, 18, 2],总和不超过24) gutter?: number; // 栅格间距(用于 grid 组件) children?: FieldConfig[][]; // 子字段(用于容器组件,二维数组,每个子数组代表一列) + // 代码编辑器专用属性 + language?: string; // 编程语言(用于 code-editor 组件,如 'javascript', 'python', 'json', 'groovy' 等) + height?: number; // 编辑器高度(用于 code-editor 组件,单位: px) + theme?: 'vs' | 'vs-dark' | 'hc-black'; // 编辑器主题(用于 code-editor 组件) + readOnly?: boolean; // 是否只读(用于 code-editor 组件) + showMinimap?: boolean; // 是否显示小地图(用于 code-editor 组件) + showLanguage?: boolean; // 是否显示语言类型标签(用于 code-editor 组件) // ========== 审批人选择器专用属性(工作流扩展) ========== // 审批配置 diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index a84259f1..aaff7961 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -1,5 +1,5 @@ import { createBrowserRouter, Navigate, RouteObject } from 'react-router-dom'; -import { Suspense } from 'react'; +import { Suspense, lazy } from 'react'; import { Spin } from 'antd'; import Login from '../pages/Login'; import BasicLayout from '../layouts/BasicLayout'; @@ -7,6 +7,9 @@ import { getRouteComponent } from './routeMap'; import type { MenuResponse } from '@/pages/System/Menu/List/types'; import store from '../store'; +// 表单设计器测试页面(写死的路由) +const FormDesignerTest = lazy(() => import('../pages/FormDesigner')); + // 加载组件 const LoadingComponent = () => (
@@ -78,6 +81,15 @@ const createDynamicRouter = () => { path: '', element: }, + // 写死的测试路由:表单设计器测试页面 + { + path: 'workflow/form-designer', + element: ( + }> + + + ) + }, // 动态生成的路由 ...dynamicRoutes, // 404 路由