增加审批组件

This commit is contained in:
dengqichen 2025-10-25 12:20:43 +08:00
parent f05e6a55fd
commit a4ab7a6016
7 changed files with 160 additions and 153 deletions

View File

@ -206,7 +206,6 @@ const FormRenderer = forwardRef<FormRendererRef, FormRendererProps>((props, ref)
const handleReset = () => { const handleReset = () => {
form.resetFields(); form.resetFields();
setFormData({}); setFormData({});
message.info('表单已重置');
}; };
// 监听表单值变化 // 监听表单值变化

View File

@ -14,7 +14,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport <ToastPrimitives.Viewport
ref={ref} ref={ref}
className={cn( className={cn(
"fixed top-0 right-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:flex-col md:max-w-[420px]", "fixed top-0 right-0 z-[9999] flex max-h-screen w-full flex-col-reverse p-4 sm:flex-col md:max-w-[420px]",
className className
)} )}
{...props} {...props}

View File

@ -3,7 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { FormRenderer } from '@/components/FormDesigner'; import Editor from '@/components/Editor';
import { ArrowLeft, Loader2 } from 'lucide-react'; import { ArrowLeft, Loader2 } from 'lucide-react';
import { getFormDataById } from './service'; import { getFormDataById } from './service';
import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types'; import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types';
@ -100,48 +100,59 @@ const FormDataDetail: React.FC = () => {
return ( return (
<div className="p-6"> <div className="p-6">
<Card className="mb-4"> <div className="flex items-center justify-between mb-4">
<CardHeader> <h2 className="text-2xl font-bold"></h2>
<div className="flex items-center justify-between"> <Button variant="outline" onClick={handleBack}>
<CardTitle></CardTitle> <ArrowLeft className="h-4 w-4 mr-2" />
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="h-4 w-4 mr-2" /> </Button>
</div>
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-0">
<DescriptionItem label="表单标识" value={data.formKey} />
<DescriptionItem label="表单版本" value={`v${data.formVersion}`} />
<DescriptionItem label="业务类型" value={getBusinessTypeBadge(data.businessType)} />
<DescriptionItem label="业务标识" value={data.businessKey || '-'} />
<DescriptionItem label="提交人" value={data.submitter || '-'} />
<DescriptionItem label="提交时间" value={data.submitTime || '-'} />
<DescriptionItem label="状态" value={getStatusBadge(data.status)} />
<DescriptionItem label="创建时间" value={data.createTime || '-'} />
<DescriptionItem label="更新时间" value={data.updateTime || '-'} />
</div>
</CardContent>
</Card>
<Card> <div className="grid grid-cols-2 gap-4">
<CardHeader> <Card>
<CardTitle></CardTitle> <CardHeader>
</CardHeader> <CardTitle></CardTitle>
<CardContent> </CardHeader>
{/* 使用 FormRenderer 以只读模式展示数据 */} <CardContent>
<FormRenderer <div className="space-y-0">
schema={data.schemaSnapshot} <DescriptionItem label="表单标识" value={data.formKey} />
value={data.data} <DescriptionItem label="表单版本" value={`v${data.formVersion}`} />
readonly={true} <DescriptionItem label="业务类型" value={getBusinessTypeBadge(data.businessType)} />
showSubmit={false} <DescriptionItem label="业务标识" value={data.businessKey || '-'} />
showCancel={true} <DescriptionItem label="提交人" value={data.submitter || '-'} />
cancelText="返回" <DescriptionItem label="提交时间" value={data.submitTime || '-'} />
onCancel={handleBack} <DescriptionItem label="状态" value={getStatusBadge(data.status)} />
/> <DescriptionItem label="创建时间" value={data.createTime || '-'} />
</CardContent> <DescriptionItem label="更新时间" value={data.updateTime || '-'} />
</Card> </div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="border rounded-md overflow-hidden">
<Editor
height="600px"
language="json"
value={JSON.stringify(data.data, null, 2)}
options={{
readOnly: true,
minimap: { enabled: false },
fontSize: 14,
lineNumbers: 'on',
scrollBeyondLastLine: false,
automaticLayout: true,
wordWrap: 'on',
theme: 'vs-light'
}}
/>
</div>
</CardContent>
</Card>
</div>
</div> </div>
); );
}; };

View File

@ -294,13 +294,13 @@ const FormDataList: React.FC = () => {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead className="w-[180px]"></TableHead> <TableHead className="w-[240px]"></TableHead>
<TableHead className="w-[120px]"></TableHead> <TableHead className="w-[120px]"></TableHead>
<TableHead className="w-[100px]"></TableHead> <TableHead className="w-[100px]"></TableHead>
<TableHead className="w-[150px]"></TableHead> <TableHead className="w-[150px]"></TableHead>
<TableHead className="w-[100px]"></TableHead> <TableHead className="w-[100px]"></TableHead>
<TableHead className="w-[180px]"></TableHead> <TableHead className="w-[180px]"></TableHead>
<TableHead className="w-[100px]"></TableHead> <TableHead className="w-[140px]"></TableHead>
<TableHead className="text-right"></TableHead> <TableHead className="text-right"></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo, useRef } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Modal, Button as AntButton } from 'antd';
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/card'; import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/card';
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
@ -8,7 +9,7 @@ import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
import { DataTablePagination } from '@/components/ui/pagination'; import { DataTablePagination } from '@/components/ui/pagination';
import { FormRenderer } from '@/components/FormDesigner'; import { FormRenderer, type FormRendererRef } from '@/components/FormDesigner';
import { import {
Loader2, Plus, Search, Eye, Edit, FileText, Ban, Trash2, Loader2, Plus, Search, Eye, Edit, FileText, Ban, Trash2,
Database, MoreHorizontal, CheckCircle2, XCircle, Clock, AlertCircle, Database, MoreHorizontal, CheckCircle2, XCircle, Clock, AlertCircle,
@ -61,6 +62,7 @@ const FormDefinitionList: React.FC = () => {
// 预览弹窗 // 预览弹窗
const [previewVisible, setPreviewVisible] = useState(false); const [previewVisible, setPreviewVisible] = useState(false);
const [previewForm, setPreviewForm] = useState<FormDefinitionResponse | null>(null); const [previewForm, setPreviewForm] = useState<FormDefinitionResponse | null>(null);
const previewFormRef = useRef<FormRendererRef>(null);
// 删除确认弹窗 // 删除确认弹窗
const [deleteVisible, setDeleteVisible] = useState(false); const [deleteVisible, setDeleteVisible] = useState(false);
@ -522,26 +524,30 @@ const FormDefinitionList: React.FC = () => {
{/* 预览弹窗 */} {/* 预览弹窗 */}
{previewForm && ( {previewForm && (
<Dialog open={previewVisible} onOpenChange={setPreviewVisible}> <Modal
<DialogContent className="max-w-4xl max-h-[85vh] overflow-y-auto"> title={`预览表单 - ${previewForm.name}`}
<DialogHeader> open={previewVisible}
<DialogTitle></DialogTitle> onCancel={() => setPreviewVisible(false)}
<DialogDescription> width={800}
{previewForm.name} - {previewForm.key} (v{previewForm.formVersion}) footer={[
</DialogDescription> <AntButton
</DialogHeader> key="close"
<Separator className="my-4" /> onClick={() => setPreviewVisible(false)}
<div className="py-4"> >
<FormRenderer
schema={previewForm.schema} </AntButton>,
value={{}} ]}
readonly={true} >
showSubmit={false} <div className="text-sm text-muted-foreground mb-4">
onCancel={() => setPreviewVisible(false)} {previewForm.key} (v{previewForm.formVersion})
/> </div>
</div> <FormRenderer
</DialogContent> ref={previewFormRef}
</Dialog> schema={previewForm.schema}
value={{}}
readonly={true}
/>
</Modal>
)} )}
{/* 删除确认弹窗 */} {/* 删除确认弹窗 */}

View File

@ -859,7 +859,6 @@ const handleSubmit = async () => {
</Button>, </Button>,
]} ]}
destroyOnClose
> >
<FormRenderer <FormRenderer
ref={modalFormRef} ref={modalFormRef}

View File

@ -4,21 +4,9 @@
*/ */
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { Modal } from 'antd'; import { Modal, Button } from 'antd';
import { Button } from '@/components/ui/button';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogPortal,
AlertDialogOverlay,
} from '@/components/ui/alert-dialog';
import { FormRenderer, type FormRendererRef } from '@/components/FormDesigner'; import { FormRenderer, type FormRendererRef } from '@/components/FormDesigner';
import { useToast } from '@/components/ui/use-toast';
import type { FormDefinitionResponse } from '@/pages/Form/Definition/types'; import type { FormDefinitionResponse } from '@/pages/Form/Definition/types';
import type { WorkflowDefinition } from '../types'; import type { WorkflowDefinition } from '../types';
@ -44,7 +32,7 @@ const StartWorkflowModal: React.FC<StartWorkflowModalProps> = ({
}) => { }) => {
const formRef = useRef<FormRendererRef>(null); const formRef = useRef<FormRendererRef>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showCancelConfirm, setShowCancelConfirm] = useState(false); const { toast } = useToast();
// 处理提交 // 处理提交
const handleSubmit = async (formData: Record<string, any>) => { const handleSubmit = async (formData: Record<string, any>) => {
@ -69,16 +57,18 @@ const StartWorkflowModal: React.FC<StartWorkflowModalProps> = ({
}; };
}; };
// 处理取消 // 处理取消 - 直接关闭弹窗
const handleCancel = () => { const handleCancel = () => {
setShowCancelConfirm(true); onClose();
}; };
// 确认取消 // 处理重置
const confirmCancel = () => { const handleReset = () => {
formRef.current?.reset(); formRef.current?.reset();
setShowCancelConfirm(false); toast({
onClose(); title: "表单已重置",
description: "所有字段已恢复为初始状态",
});
}; };
// 处理表单提交触发 // 处理表单提交触发
@ -100,72 +90,74 @@ const StartWorkflowModal: React.FC<StartWorkflowModalProps> = ({
const { schema } = formDefinition; const { schema } = formDefinition;
return ( return (
<> <Modal
<Modal title={
title={formDefinition.name || `启动工作流:${workflowDefinition.name}`} <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', paddingRight: '40px' }}>
open={open} <span>{formDefinition.name || `启动工作流:${workflowDefinition.name}`}</span>
onCancel={handleCancel} <div style={{ display: 'flex', gap: '8px', fontSize: '12px' }}>
width={schema.formConfig.formWidth || 600} <span style={{
footer={ background: '#f5f5f5',
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}> padding: '2px 10px',
<Button variant="outline" onClick={() => formRef.current?.reset()}> borderRadius: '12px',
color: '#666',
</Button> fontWeight: 'normal'
<Button variant="outline" onClick={handleCancel}> }}>
v{workflowDefinition.flowVersion}
</Button> </span>
<Button <span style={{
onClick={handleTriggerSubmit} background: '#f5f5f5',
disabled={loading} padding: '2px 10px',
> borderRadius: '12px',
{loading ? '启动中...' : '启动工作流'} color: '#666',
</Button> fontWeight: 'normal'
}}>
v{formDefinition.formVersion}
</span>
</div> </div>
}
destroyOnClose
>
<FormRenderer
ref={formRef}
fields={schema.fields}
formConfig={schema.formConfig}
onSubmit={handleSubmit}
/>
{/* 表单元信息 */}
<div style={{
marginTop: 16,
padding: 12,
background: '#f5f5f5',
borderRadius: 4,
fontSize: 12,
color: '#666'
}}>
<div><strong></strong>{workflowDefinition.key}</div>
<div><strong></strong>v{workflowDefinition.flowVersion}</div>
<div><strong></strong>{formDefinition.key}</div>
<div><strong></strong>v{formDefinition.formVersion}</div>
</div> </div>
</Modal> }
open={open}
{/* 取消确认对话框 - 设置更高的 z-index 确保在 Modal 之上 */} onCancel={handleCancel}
<AlertDialog open={showCancelConfirm} onOpenChange={setShowCancelConfirm}> width={schema.formConfig.formWidth || 600}
<AlertDialogPortal> centered
<AlertDialogOverlay style={{ zIndex: 1050 }} /> footer={
<AlertDialogContent style={{ zIndex: 1051 }}> <div style={{ display: 'flex', justifyContent: 'center', gap: '12px' }}>
<AlertDialogHeader> <Button
<AlertDialogTitle></AlertDialogTitle> key="reset"
<AlertDialogDescription> onClick={handleReset}
style={{ minWidth: '88px' }}
</AlertDialogDescription> >
</AlertDialogHeader>
<AlertDialogFooter> </Button>
<AlertDialogCancel onClick={() => setShowCancelConfirm(false)}></AlertDialogCancel> <Button
<AlertDialogAction onClick={confirmCancel}></AlertDialogAction> key="cancel"
</AlertDialogFooter> onClick={handleCancel}
</AlertDialogContent> style={{ minWidth: '88px' }}
</AlertDialogPortal> >
</AlertDialog>
</> </Button>
<Button
key="submit"
type="primary"
loading={loading}
onClick={handleTriggerSubmit}
style={{
minWidth: '120px',
backgroundColor: '#000',
borderColor: '#000'
}}
>
</Button>
</div>
}
>
<FormRenderer
ref={formRef}
schema={schema}
onSubmit={handleSubmit}
/>
</Modal>
); );
}; };