diff --git a/frontend/package.json b/frontend/package.json
index 7b8a04e2..e31a5495 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,6 +12,9 @@
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@ant-design/pro-components": "^2.8.2",
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "^3.9.1",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.12",
diff --git a/frontend/src/pages/FormDesigner/components/ComponentPanel.tsx b/frontend/src/pages/FormDesigner/components/ComponentPanel.tsx
new file mode 100644
index 00000000..64ea9461
--- /dev/null
+++ b/frontend/src/pages/FormDesigner/components/ComponentPanel.tsx
@@ -0,0 +1,78 @@
+/**
+ * 左侧组件面板
+ * 显示可拖拽的表单组件列表
+ */
+
+import React from 'react';
+import { useDraggable } from '@dnd-kit/core';
+import { CSS } from '@dnd-kit/utilities';
+import { Collapse, Typography } from 'antd';
+import type { ComponentMeta } from '../config';
+import { getComponentsByCategory } from '../config';
+import '../styles.css';
+
+const { Panel } = Collapse;
+const { Text } = Typography;
+
+// 可拖拽组件项
+const DraggableComponent: React.FC<{ component: ComponentMeta }> = ({ component }) => {
+ const { attributes, listeners, setNodeRef, transform } = useDraggable({
+ id: `new-${component.type}`,
+ data: {
+ type: component.type,
+ isNew: true,
+ },
+ });
+
+ const style = {
+ transform: CSS.Translate.toString(transform),
+ cursor: 'move',
+ };
+
+ const Icon = component.icon;
+
+ return (
+
+
+ {component.label}
+
+ );
+};
+
+// 组件面板
+const ComponentPanel: React.FC = () => {
+ const componentsByCategory = getComponentsByCategory();
+
+ return (
+
+
+ 组件列表
+
+
+
+ {Object.entries(componentsByCategory).map(([category, components]) => (
+
+
+ {components.map((component) => (
+
+ ))}
+
+
+ ))}
+
+
+ );
+};
+
+export default ComponentPanel;
+
diff --git a/frontend/src/pages/FormDesigner/components/DesignCanvas.tsx b/frontend/src/pages/FormDesigner/components/DesignCanvas.tsx
new file mode 100644
index 00000000..082d7005
--- /dev/null
+++ b/frontend/src/pages/FormDesigner/components/DesignCanvas.tsx
@@ -0,0 +1,80 @@
+/**
+ * 中间设计画布
+ * 可拖放字段的区域
+ */
+
+import React from 'react';
+import { useDroppable } from '@dnd-kit/core';
+import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
+import { Empty, Typography } from 'antd';
+import FieldItem from './FieldItem';
+import type { FieldConfig } from '../types';
+import '../styles.css';
+
+const { Text } = Typography;
+
+interface DesignCanvasProps {
+ fields: FieldConfig[];
+ selectedFieldId?: string;
+ onSelectField: (fieldId: string) => void;
+ onDeleteField: (fieldId: string) => void;
+ labelAlign?: 'left' | 'right' | 'top';
+}
+
+const DesignCanvas: React.FC = ({
+ fields,
+ selectedFieldId,
+ onSelectField,
+ onDeleteField,
+ labelAlign = 'right',
+}) => {
+ const { setNodeRef } = useDroppable({
+ id: 'canvas',
+ });
+
+ return (
+
+
+ 表单设计
+
+
+
+ {fields.length === 0 ? (
+
+
+
+ ) : (
+
f.id)}
+ strategy={verticalListSortingStrategy}
+ >
+
+ {fields.map((field) => (
+ onSelectField(field.id)}
+ onDelete={() => onDeleteField(field.id)}
+ selectedFieldId={selectedFieldId}
+ onSelectField={onSelectField}
+ onDeleteField={onDeleteField}
+ labelAlign={labelAlign}
+ />
+ ))}
+
+
+ )}
+
+
+ );
+};
+
+export default DesignCanvas;
+
diff --git a/frontend/src/pages/FormDesigner/components/FieldItem.tsx b/frontend/src/pages/FormDesigner/components/FieldItem.tsx
new file mode 100644
index 00000000..8647eff0
--- /dev/null
+++ b/frontend/src/pages/FormDesigner/components/FieldItem.tsx
@@ -0,0 +1,102 @@
+/**
+ * 画布中的字段项
+ * 支持拖拽排序、选中、删除
+ */
+
+import React from 'react';
+import { useSortable } from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+import { Button, Space } from 'antd';
+import { HolderOutlined, DeleteOutlined, CopyOutlined } from '@ant-design/icons';
+import type { FieldConfig } from '../types';
+import FieldRenderer from './FieldRenderer';
+import '../styles.css';
+
+interface FieldItemProps {
+ field: FieldConfig;
+ isSelected: boolean;
+ onSelect: () => void;
+ onDelete: () => void;
+ selectedFieldId?: string; // 传递给栅格内部字段
+ onSelectField?: (fieldId: string) => void;
+ onDeleteField?: (fieldId: string) => void;
+ labelAlign?: 'left' | 'right' | 'top'; // 标签对齐方式
+}
+
+const FieldItem: React.FC = ({
+ field,
+ isSelected,
+ onSelect,
+ onDelete,
+ selectedFieldId,
+ onSelectField,
+ onDeleteField,
+ labelAlign = 'right',
+}) => {
+ const {
+ attributes,
+ listeners,
+ setNodeRef,
+ transform,
+ transition,
+ isDragging,
+ } = useSortable({
+ id: field.id,
+ });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ opacity: isDragging ? 0.5 : 1,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ }
+ onClick={(e) => {
+ e.stopPropagation();
+ // TODO: 复制功能
+ }}
+ />
+ }
+ onClick={(e) => {
+ e.stopPropagation();
+ onDelete();
+ }}
+ />
+
+
+
+ );
+};
+
+export default FieldItem;
+
diff --git a/frontend/src/pages/FormDesigner/components/FieldRenderer.tsx b/frontend/src/pages/FormDesigner/components/FieldRenderer.tsx
new file mode 100644
index 00000000..fd6379ec
--- /dev/null
+++ b/frontend/src/pages/FormDesigner/components/FieldRenderer.tsx
@@ -0,0 +1,264 @@
+/**
+ * 字段渲染器
+ * 根据字段类型渲染对应的表单组件
+ */
+
+import React from 'react';
+import {
+ Form,
+ Input,
+ InputNumber,
+ Select,
+ Radio,
+ Checkbox,
+ DatePicker,
+ TimePicker,
+ Switch,
+ Slider,
+ Rate,
+ Upload,
+ Cascader,
+ Divider,
+ Button,
+ Typography,
+} from 'antd';
+import { UploadOutlined } from '@ant-design/icons';
+import type { FieldConfig } from '../types';
+import GridField from './GridField';
+
+const { Text } = Typography;
+
+const { TextArea } = Input;
+
+interface FieldRendererProps {
+ field: FieldConfig;
+ value?: any;
+ onChange?: (value: any) => void;
+ isPreview?: boolean; // 是否为预览模式
+ selectedFieldId?: string; // 当前选中的字段ID
+ onSelectField?: (fieldId: string) => void;
+ onDeleteField?: (fieldId: string) => void;
+ labelAlign?: 'left' | 'right' | 'top'; // 标签对齐方式
+}
+
+const FieldRenderer: React.FC = ({
+ field,
+ value,
+ onChange,
+ isPreview = false,
+ selectedFieldId,
+ onSelectField,
+ onDeleteField,
+ labelAlign = 'right',
+}) => {
+ // 布局组件特殊处理
+ if (field.type === 'divider') {
+ return {field.label};
+ }
+
+ if (field.type === 'text') {
+ return (
+
+ {field.content || '这是一段文字'}
+
+ );
+ }
+
+ if (field.type === 'grid') {
+ return (
+
+ );
+ }
+
+ const renderField = () => {
+ switch (field.type) {
+ case 'input':
+ return (
+ onChange?.(e.target.value)}
+ />
+ );
+
+ case 'textarea':
+ return (
+