From d22285bc951bc9948c9f23c0cdd213e0286b2026 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 24 Oct 2025 13:10:02 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A8=E5=8D=95CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ui/dropdown-menu.tsx | 199 ++++++ frontend/src/pages/Form/Category/service.ts | 46 ++ frontend/src/pages/Form/Category/types.ts | 40 ++ frontend/src/pages/Form/Data/Detail.tsx | 150 +++++ frontend/src/pages/Form/Data/index.tsx | 408 +++++++++++++ frontend/src/pages/Form/Data/service.ts | 53 ++ frontend/src/pages/Form/Data/types.ts | 70 +++ .../src/pages/Form/Definition/Designer.tsx | 263 ++++++++ frontend/src/pages/Form/Definition/index.tsx | 571 ++++++++++++++++++ frontend/src/pages/Form/Definition/service.ts | 58 ++ frontend/src/pages/Form/Definition/types.ts | 52 ++ frontend/src/router/index.tsx | 49 ++ 12 files changed, 1959 insertions(+) create mode 100644 frontend/src/components/ui/dropdown-menu.tsx create mode 100644 frontend/src/pages/Form/Category/service.ts create mode 100644 frontend/src/pages/Form/Category/types.ts create mode 100644 frontend/src/pages/Form/Data/Detail.tsx create mode 100644 frontend/src/pages/Form/Data/index.tsx create mode 100644 frontend/src/pages/Form/Data/service.ts create mode 100644 frontend/src/pages/Form/Data/types.ts create mode 100644 frontend/src/pages/Form/Definition/Designer.tsx create mode 100644 frontend/src/pages/Form/Definition/index.tsx create mode 100644 frontend/src/pages/Form/Definition/service.ts create mode 100644 frontend/src/pages/Form/Definition/types.ts diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..e804bca1 --- /dev/null +++ b/frontend/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,199 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + svg]:size-4 [&>svg]:shrink-0", + inset && "pl-8", + className + )} + {...props} + /> +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/frontend/src/pages/Form/Category/service.ts b/frontend/src/pages/Form/Category/service.ts new file mode 100644 index 00000000..4b77289b --- /dev/null +++ b/frontend/src/pages/Form/Category/service.ts @@ -0,0 +1,46 @@ +import request from '@/utils/request'; +import type { Page } from '@/types/base'; +import type { + FormCategoryQuery, + FormCategoryResponse, + FormCategoryRequest, +} from './types'; + +const BASE_URL = '/api/v1/forms/categories'; + +/** + * 分页查询分类 + */ +export const getCategories = (params?: FormCategoryQuery) => + request.get>(`${BASE_URL}/page`, { params }); + +/** + * 查询分类详情 + */ +export const getCategoryById = (id: number) => + request.get(`${BASE_URL}/${id}`); + +/** + * 查询所有启用的分类(常用) + */ +export const getEnabledCategories = () => + request.get(`${BASE_URL}/enabled`); + +/** + * 创建分类 + */ +export const createCategory = (data: FormCategoryRequest) => + request.post(BASE_URL, data); + +/** + * 更新分类 + */ +export const updateCategory = (id: number, data: FormCategoryRequest) => + request.put(`${BASE_URL}/${id}`, data); + +/** + * 删除分类 + */ +export const deleteCategory = (id: number) => + request.delete(`${BASE_URL}/${id}`); + diff --git a/frontend/src/pages/Form/Category/types.ts b/frontend/src/pages/Form/Category/types.ts new file mode 100644 index 00000000..7cb1b7c3 --- /dev/null +++ b/frontend/src/pages/Form/Category/types.ts @@ -0,0 +1,40 @@ +import { BaseQuery } from '@/types/base'; + +/** + * 表单分类查询参数 + */ +export interface FormCategoryQuery extends BaseQuery { + name?: string; + code?: string; + enabled?: boolean; +} + +/** + * 表单分类响应 + */ +export interface FormCategoryResponse { + id: number; + name: string; + code: string; + description?: string; + icon?: string; + sort: number; + enabled: boolean; + createBy?: string; + createTime?: string; + updateBy?: string; + updateTime?: string; +} + +/** + * 表单分类创建/更新请求 + */ +export interface FormCategoryRequest { + name: string; + code: string; + description?: string; + icon?: string; + sort?: number; + enabled?: boolean; +} + diff --git a/frontend/src/pages/Form/Data/Detail.tsx b/frontend/src/pages/Form/Data/Detail.tsx new file mode 100644 index 00000000..82d545d2 --- /dev/null +++ b/frontend/src/pages/Form/Data/Detail.tsx @@ -0,0 +1,150 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { FormRenderer } from '@/components/FormDesigner'; +import { ArrowLeft, Loader2 } from 'lucide-react'; +import { getFormDataById } from './service'; +import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types'; + +/** + * 表单数据详情页 + */ +const FormDataDetail: React.FC = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState(null); + + // 加载数据 + useEffect(() => { + if (id) { + loadData(Number(id)); + } + }, [id]); + + const loadData = async (dataId: number) => { + setLoading(true); + try { + const result = await getFormDataById(dataId); + setData(result); + } catch (error) { + console.error('加载表单数据失败:', error); + } finally { + setLoading(false); + } + }; + + // 返回列表 + const handleBack = () => { + navigate('/form/data'); + }; + + // 状态徽章 + const getStatusBadge = (status: FormDataStatus) => { + const statusMap: Record = { + DRAFT: { variant: 'outline', text: '草稿' }, + SUBMITTED: { variant: 'success', text: '已提交' }, + COMPLETED: { variant: 'default', text: '已完成' }, + }; + const statusInfo = statusMap[status]; + return {statusInfo.text}; + }; + + // 业务类型徽章 + const getBusinessTypeBadge = (type: FormDataBusinessType) => { + const typeMap: Record = { + STANDALONE: { variant: 'outline', text: '独立' }, + WORKFLOW: { variant: 'default', text: '工作流' }, + ORDER: { variant: 'secondary', text: '订单' }, + }; + const typeInfo = typeMap[type]; + return {typeInfo.text}; + }; + + // 描述项组件 + const DescriptionItem: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => ( +
+
{label}
+
{value}
+
+ ); + + if (loading) { + return ( +
+ + +
+ + 加载中... +
+
+
+
+ ); + } + + if (!data) { + return ( +
+ + +
数据不存在
+
+
+
+ ); + } + + return ( +
+ + +
+ 表单数据详情 + +
+
+ +
+ + + + + + + + + +
+
+
+ + + + 表单数据 + + + {/* 使用 FormRenderer 以只读模式展示数据 */} + + + +
+ ); +}; + +export default FormDataDetail; + diff --git a/frontend/src/pages/Form/Data/index.tsx b/frontend/src/pages/Form/Data/index.tsx new file mode 100644 index 00000000..daff70ba --- /dev/null +++ b/frontend/src/pages/Form/Data/index.tsx @@ -0,0 +1,408 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { DataTablePagination } from '@/components/ui/pagination'; +import { + Loader2, Search, Eye, Trash2, Download, Folder, + Activity, Clock, CheckCircle2, FileCheck, Database +} from 'lucide-react'; +import { getFormDataList, deleteFormData, exportFormData } from './service'; +import { getEnabledCategories } from '../Category/service'; +import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types'; +import type { FormCategoryResponse } from '../Category/types'; +import type { Page } from '@/types/base'; +import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page'; +import dayjs from 'dayjs'; + +/** + * 表单数据列表页 + */ +const FormDataList: React.FC = () => { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState | null>(null); + const [categories, setCategories] = useState([]); + const [query, setQuery] = useState({ + pageNum: DEFAULT_CURRENT - 1, + pageSize: DEFAULT_PAGE_SIZE, + formDefinitionId: searchParams.get('formDefinitionId') ? Number(searchParams.get('formDefinitionId')) : undefined, + businessKey: '', + categoryId: undefined as number | undefined, + status: undefined as FormDataStatus | undefined, + businessType: undefined as FormDataBusinessType | undefined, + }); + + // 加载分类列表 + const loadCategories = async () => { + try { + const result = await getEnabledCategories(); + setCategories(result || []); + } catch (error) { + console.error('加载分类失败:', error); + } + }; + + // 加载数据 + const loadData = async () => { + setLoading(true); + try { + const result = await getFormDataList(query); + setData(result); + } catch (error) { + console.error('加载表单数据失败:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadCategories(); + }, []); + + useEffect(() => { + loadData(); + }, [query]); + + // 搜索 + const handleSearch = () => { + setQuery(prev => ({ + ...prev, + pageNum: 0, + })); + }; + + // 重置搜索 + const handleReset = () => { + setQuery({ + pageNum: 0, + pageSize: DEFAULT_PAGE_SIZE, + formDefinitionId: undefined, + businessKey: '', + categoryId: undefined, + status: undefined, + businessType: undefined, + }); + }; + + // 根据分类 ID 获取分类信息 + const getCategoryInfo = (categoryId?: number) => { + return categories.find(cat => cat.id === categoryId); + }; + + // 查看详情 + const handleView = (record: FormDataResponse) => { + navigate(`/form/data/${record.id}`); + }; + + // 删除 + const handleDelete = async (record: FormDataResponse) => { + if (!confirm('确定要删除该数据吗?')) return; + try { + await deleteFormData(record.id); + loadData(); + } catch (error) { + console.error('删除数据失败:', error); + } + }; + + // 导出 + const handleExport = async () => { + try { + await exportFormData(query); + } catch (error) { + console.error('导出数据失败:', error); + } + }; + + // 状态徽章 + const getStatusBadge = (status: FormDataStatus) => { + const statusMap: Record = { + DRAFT: { variant: 'outline', text: '草稿', icon: Clock }, + SUBMITTED: { variant: 'success', text: '已提交', icon: CheckCircle2 }, + COMPLETED: { variant: 'default', text: '已完成', icon: FileCheck }, + }; + const statusInfo = statusMap[status]; + const Icon = statusInfo.icon; + return ( + + + {statusInfo.text} + + ); + }; + + // 业务类型徽章 + const getBusinessTypeBadge = (type: FormDataBusinessType) => { + const typeMap: Record = { + STANDALONE: { variant: 'outline', text: '独立' }, + WORKFLOW: { variant: 'default', text: '工作流' }, + ORDER: { variant: 'secondary', text: '订单' }, + }; + const typeInfo = typeMap[type]; + return {typeInfo.text}; + }; + + // 统计数据 + const stats = useMemo(() => { + const total = data?.totalElements || 0; + const draftCount = data?.content?.filter(d => d.status === 'DRAFT').length || 0; + const submittedCount = data?.content?.filter(d => d.status === 'SUBMITTED').length || 0; + const completedCount = data?.content?.filter(d => d.status === 'COMPLETED').length || 0; + return { total, draftCount, submittedCount, completedCount }; + }, [data]); + + const pageCount = data?.totalElements ? Math.ceil(data.totalElements / query.pageSize) : 0; + + return ( +
+
+

表单数据管理

+

+ 查看和管理用户提交的表单数据,支持按分类、状态、业务类型筛选。 +

+
+ + {/* 统计卡片 */} +
+ + + 总数据量 + + + +
{stats.total}
+

全部表单数据

+
+
+ + + 草稿 + + + +
{stats.draftCount}
+

暂存的数据

+
+
+ + + 已提交 + + + +
{stats.submittedCount}
+

待处理的数据

+
+
+ + + 已完成 + + + +
{stats.completedCount}
+

已处理完成

+
+
+
+ + + + 数据列表 + + + {/* 搜索栏 */} +
+
+ setQuery(prev => ({ ...prev, businessKey: e.target.value }))} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="h-9" + /> +
+ + + + + + +
+ + {/* 表格 */} +
+ + + + 表单标识 + 分类 + 业务类型 + 业务标识 + 提交人 + 提交时间 + 状态 + 操作 + + + + {loading ? ( + + +
+ + 加载中... +
+
+
+ ) : data?.content && data.content.length > 0 ? ( + data.content.map((record) => { + const categoryInfo = getCategoryInfo(record.categoryId); + return ( + + + + {record.formKey} + + + + {categoryInfo ? ( + + {categoryInfo.icon && } + {categoryInfo.name} + + ) : ( + 未分类 + )} + + {getBusinessTypeBadge(record.businessType)} + + {record.businessKey || '-'} + + + {record.submitter || '匿名'} + + + + {record.submitTime ? dayjs(record.submitTime).format('YYYY-MM-DD HH:mm:ss') : '-'} + + + {getStatusBadge(record.status)} + +
+ + +
+
+
+ ); + }) + ) : ( + + +
+ +
暂无表单数据
+
用户提交的表单数据将在此显示。
+
+
+
+ )} +
+
+
+ + {/* 分页 */} + {pageCount > 1 && ( + setQuery(prev => ({ + ...prev, + pageNum: page - 1 + }))} + /> + )} +
+
+
+ ); +}; + +export default FormDataList; + diff --git a/frontend/src/pages/Form/Data/service.ts b/frontend/src/pages/Form/Data/service.ts new file mode 100644 index 00000000..439877c1 --- /dev/null +++ b/frontend/src/pages/Form/Data/service.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request'; +import type { Page } from '@/types/base'; +import type { + FormDataQuery, + FormDataResponse, + FormDataSubmitRequest, + FormDataUpdateRequest, +} from './types'; + +const BASE_URL = '/api/v1/forms/data'; + +/** + * 分页查询表单数据列表 + */ +export const getFormDataList = (params?: FormDataQuery) => + request.get>(`${BASE_URL}/page`, { params }); + +/** + * 获取表单数据详情 + */ +export const getFormDataById = (id: number) => + request.get(`${BASE_URL}/${id}`); + +/** + * 提交表单数据 + */ +export const submitFormData = (data: FormDataSubmitRequest) => + request.post(BASE_URL, data); + +/** + * 更新表单数据 + */ +export const updateFormData = (id: number, data: FormDataUpdateRequest) => + request.put(`${BASE_URL}/${id}`, data); + +/** + * 删除表单数据 + */ +export const deleteFormData = (id: number) => + request.delete(`${BASE_URL}/${id}`); + +/** + * 批量删除表单数据 + */ +export const batchDeleteFormData = (ids: number[]) => + request.post(`${BASE_URL}/batch-delete`, { ids }); + +/** + * 导出表单数据 + */ +export const exportFormData = (params?: FormDataQuery) => + request.download(`${BASE_URL}/export`, undefined, { params }); + diff --git a/frontend/src/pages/Form/Data/types.ts b/frontend/src/pages/Form/Data/types.ts new file mode 100644 index 00000000..28323cad --- /dev/null +++ b/frontend/src/pages/Form/Data/types.ts @@ -0,0 +1,70 @@ +import { BaseQuery } from '@/types/base'; +import type { FormSchema } from '@/components/FormDesigner'; + +/** + * 业务类型 + */ +export type FormDataBusinessType = 'STANDALONE' | 'WORKFLOW' | 'ORDER'; + +/** + * 表单数据状态 + */ +export type FormDataStatus = 'DRAFT' | 'SUBMITTED' | 'COMPLETED'; + +/** + * 表单数据查询参数 + */ +export interface FormDataQuery extends BaseQuery { + formDefinitionId?: number; + formKey?: string; + categoryId?: number; // 分类ID(筛选) + businessType?: FormDataBusinessType; + businessKey?: string; + submitter?: string; + status?: FormDataStatus; + submitTimeStart?: string; + submitTimeEnd?: string; +} + +/** + * 表单数据响应 + */ +export interface FormDataResponse { + id: number; + formDefinitionId: number; + formKey: string; + formVersion: number; + categoryId?: number; // 分类ID + businessKey?: string; + businessType: FormDataBusinessType; + data: Record; + schemaSnapshot: FormSchema; + submitter?: string; + submitTime?: string; + status: FormDataStatus; + createBy?: string; + createTime?: string; + updateBy?: string; + updateTime?: string; +} + +/** + * 表单数据提交请求 + */ +export interface FormDataSubmitRequest { + formDefinitionId: number; + formKey: string; + businessType?: FormDataBusinessType; + businessKey?: string; + data: Record; + status?: FormDataStatus; +} + +/** + * 表单数据更新请求 + */ +export interface FormDataUpdateRequest { + data: Record; + status?: FormDataStatus; +} + diff --git a/frontend/src/pages/Form/Definition/Designer.tsx b/frontend/src/pages/Form/Definition/Designer.tsx new file mode 100644 index 00000000..2ea229b3 --- /dev/null +++ b/frontend/src/pages/Form/Definition/Designer.tsx @@ -0,0 +1,263 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { FormDesigner } from '@/components/FormDesigner'; +import type { FormSchema } from '@/components/FormDesigner'; +import { ArrowLeft, FileText, Tag, Folder, AlignLeft, Info } from 'lucide-react'; +import { Separator } from '@/components/ui/separator'; +import { getDefinitionById, createDefinition, updateDefinition } from './service'; +import { getEnabledCategories } from '../Category/service'; +import type { FormDefinitionRequest } from './types'; +import type { FormCategoryResponse } from '../Category/types'; + +/** + * 表单设计器页面 + */ +const FormDesignerPage: React.FC = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const isEdit = !!id; + + const [categories, setCategories] = useState([]); + const [formMeta, setFormMeta] = useState({ + name: '', + key: '', + categoryId: undefined as number | undefined, + description: '', + }); + const [formSchema, setFormSchema] = useState(null); + + // 加载分类列表 + const loadCategories = async () => { + try { + const result = await getEnabledCategories(); + setCategories(result || []); + } catch (error) { + console.error('加载分类失败:', error); + } + }; + + // 加载表单定义 + useEffect(() => { + loadCategories(); + if (isEdit && id) { + loadFormDefinition(Number(id)); + } + }, [id, isEdit]); + + const loadFormDefinition = async (definitionId: number) => { + try { + const result = await getDefinitionById(definitionId); + setFormMeta({ + name: result.name, + key: result.key, + categoryId: result.categoryId, + description: result.description || '', + }); + setFormSchema(result.schema); + } catch (error) { + console.error('加载表单定义失败:', error); + } + }; + + // 保存表单 + const handleSave = async (schema: FormSchema) => { + if (!formMeta.name.trim()) { + alert('请输入表单名称'); + return; + } + if (!formMeta.key.trim()) { + alert('请输入表单标识'); + return; + } + + const request: FormDefinitionRequest = { + name: formMeta.name, + key: formMeta.key, + categoryId: formMeta.categoryId, + description: formMeta.description, + schema, + status: 'PUBLISHED', + }; + + try { + if (isEdit && id) { + await updateDefinition(Number(id), request); + } else { + await createDefinition(request); + } + navigate('/form/definitions'); + } catch (error) { + console.error('保存表单失败:', error); + } + }; + + // 返回列表 + const handleBack = () => { + navigate('/form/definitions'); + }; + + return ( +
+ {/* 页面标题 */} +
+
+

+ {isEdit ? '编辑表单定义' : '创建表单定义'} +

+

+ {isEdit ? '修改表单的基本信息和字段配置' : '设计您的自定义表单,添加字段并配置验证规则'} +

+
+ +
+ + {/* 基本信息卡片 */} + + +
+
+ +
+
+ 基本信息 + + 设置表单的名称、标识和分类信息 + +
+
+
+ + +
+ {/* 第一行:表单名称 + 表单标识 */} +
+
+ + setFormMeta(prev => ({ ...prev, name: e.target.value }))} + className="h-10" + /> +

+ 将显示在表单列表和表单顶部 +

+
+ +
+ + setFormMeta(prev => ({ ...prev, key: e.target.value }))} + disabled={isEdit} + className="h-10 font-mono" + /> +

+ 英文字母、数字和中划线,用于 API 调用 +

+
+
+ + {/* 第二行:分类 */} +
+ + +

+ 帮助用户快速找到相关表单 +

+
+ + {/* 第三行:描述(跨整行) */} +
+ +