增加审批组件
This commit is contained in:
parent
4599367d33
commit
0a64cfc339
@ -38,7 +38,7 @@ const FormDataDetail: React.FC = () => {
|
|||||||
|
|
||||||
// 返回列表
|
// 返回列表
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
navigate('/form/data');
|
navigate('/workflow/form/data');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 状态徽章
|
// 状态徽章
|
||||||
|
|||||||
@ -97,7 +97,7 @@ const FormDataList: React.FC = () => {
|
|||||||
|
|
||||||
// 查看详情
|
// 查看详情
|
||||||
const handleView = (record: FormDataResponse) => {
|
const handleView = (record: FormDataResponse) => {
|
||||||
navigate(`/form/data/${record.id}`);
|
navigate(`/workflow/form/data/${record.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
|
|||||||
@ -76,7 +76,7 @@ const FormDesignerPage: React.FC = () => {
|
|||||||
|
|
||||||
// 返回列表
|
// 返回列表
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
navigate('/form/definitions');
|
navigate('/workflow/form');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,248 +0,0 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
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 { Switch } from '@/components/ui/switch';
|
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
|
||||||
import { Loader2, FileText, Tag, Folder, AlignLeft, Copy } from 'lucide-react';
|
|
||||||
import { getEnabledCategories } from '../../Category/service';
|
|
||||||
import { updateDefinition } from '../service';
|
|
||||||
import type { FormCategoryResponse } from '../../Category/types';
|
|
||||||
import type { FormDefinitionRequest, FormDefinitionResponse } from '../types';
|
|
||||||
|
|
||||||
interface EditBasicInfoModalProps {
|
|
||||||
visible: boolean;
|
|
||||||
record: FormDefinitionResponse | null;
|
|
||||||
onClose: () => void;
|
|
||||||
onSuccess: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditBasicInfoModal: React.FC<EditBasicInfoModalProps> = ({ visible, record, onClose, onSuccess }) => {
|
|
||||||
const { toast } = useToast();
|
|
||||||
const [submitting, setSubmitting] = useState(false);
|
|
||||||
const [categories, setCategories] = useState<FormCategoryResponse[]>([]);
|
|
||||||
const [formData, setFormData] = useState({
|
|
||||||
name: '',
|
|
||||||
key: '',
|
|
||||||
categoryId: undefined as number | undefined,
|
|
||||||
description: '',
|
|
||||||
isTemplate: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadCategories = async () => {
|
|
||||||
try {
|
|
||||||
const result = await getEnabledCategories();
|
|
||||||
setCategories(result || []);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载分类失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (visible) {
|
|
||||||
loadCategories();
|
|
||||||
if (record) {
|
|
||||||
setFormData({
|
|
||||||
name: record.name,
|
|
||||||
key: record.key,
|
|
||||||
categoryId: record.categoryId,
|
|
||||||
description: record.description || '',
|
|
||||||
isTemplate: record.isTemplate || false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [visible, record]);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
if (!submitting) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!record) return;
|
|
||||||
|
|
||||||
if (!formData.name.trim()) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "验证失败",
|
|
||||||
description: "请输入表单名称",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!formData.key.trim()) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "验证失败",
|
|
||||||
description: "请输入表单标识",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!/^[a-zA-Z0-9-]+$/.test(formData.key)) {
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "验证失败",
|
|
||||||
description: "表单标识只能包含英文字母、数字和中划线",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSubmitting(true);
|
|
||||||
try {
|
|
||||||
const request: FormDefinitionRequest = {
|
|
||||||
name: formData.name,
|
|
||||||
key: formData.key,
|
|
||||||
categoryId: formData.categoryId,
|
|
||||||
description: formData.description,
|
|
||||||
schema: record.schema,
|
|
||||||
status: record.status,
|
|
||||||
isTemplate: formData.isTemplate,
|
|
||||||
version: record.version, // 乐观锁版本号
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateDefinition(record.id, request);
|
|
||||||
toast({
|
|
||||||
title: "更新成功",
|
|
||||||
description: `表单 "${formData.name}" 的基本信息已更新`,
|
|
||||||
});
|
|
||||||
onSuccess();
|
|
||||||
onClose();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新表单失败:', error);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "更新失败",
|
|
||||||
description: error instanceof Error ? error.message : '未知错误,请稍后重试',
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setSubmitting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={visible} onOpenChange={handleClose}>
|
|
||||||
<DialogContent className="sm:max-w-[600px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>编辑基本信息</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
修改表单的名称、标识、分类等基本信息
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="grid gap-6 py-4">
|
|
||||||
{/* 第一行:表单名称 + 表单标识 */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="edit-name" className="flex items-center gap-2">
|
|
||||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单名称 <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="edit-name"
|
|
||||||
placeholder="例如:员工请假申请表"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
|
||||||
className="h-10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="edit-key" className="flex items-center gap-2">
|
|
||||||
<Tag className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单标识 <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="edit-key"
|
|
||||||
placeholder="例如:employee-leave-form"
|
|
||||||
value={formData.key}
|
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, key: e.target.value }))}
|
|
||||||
className="h-10 font-mono"
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
英文字母、数字和中划线
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 第二行:分类 + 设为模板 */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="edit-category" className="flex items-center gap-2">
|
|
||||||
<Folder className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单分类
|
|
||||||
</Label>
|
|
||||||
<Select
|
|
||||||
value={formData.categoryId?.toString() || undefined}
|
|
||||||
onValueChange={(value) => setFormData(prev => ({ ...prev, categoryId: Number(value) }))}
|
|
||||||
>
|
|
||||||
<SelectTrigger id="edit-category" className="h-10">
|
|
||||||
<SelectValue placeholder="选择表单所属分类" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{categories.map(cat => (
|
|
||||||
<SelectItem key={cat.id} value={cat.id.toString()}>
|
|
||||||
{cat.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="edit-isTemplate" className="flex items-center gap-2">
|
|
||||||
<Copy className="h-4 w-4 text-muted-foreground" />
|
|
||||||
设为表单模板
|
|
||||||
</Label>
|
|
||||||
<div className="flex items-center h-10 rounded-lg border px-4 bg-muted/30">
|
|
||||||
<div className="flex-1">
|
|
||||||
<span className="text-sm">启用模板功能</span>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
id="edit-isTemplate"
|
|
||||||
checked={formData.isTemplate}
|
|
||||||
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, isTemplate: checked }))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 第三行:描述 */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="edit-description" className="flex items-center gap-2">
|
|
||||||
<AlignLeft className="h-4 w-4 text-muted-foreground" />
|
|
||||||
表单描述
|
|
||||||
</Label>
|
|
||||||
<Textarea
|
|
||||||
id="edit-description"
|
|
||||||
placeholder="简要说明此表单的用途和填写注意事项..."
|
|
||||||
value={formData.description}
|
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
|
||||||
className="min-h-[100px] resize-none"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={handleClose} disabled={submitting}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleSubmit} disabled={submitting}>
|
|
||||||
{submitting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
|
||||||
保存更改
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EditBasicInfoModal;
|
|
||||||
|
|
||||||
@ -10,21 +10,29 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { FileText, Tag, Folder, AlignLeft, Copy, Loader2 } from 'lucide-react';
|
import { FileText, Tag, Folder, AlignLeft, Copy, Loader2 } from 'lucide-react';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { createDefinition } from '../service';
|
import { createDefinition, updateDefinition } from '../service';
|
||||||
import { getEnabledCategories } from '../../Category/service';
|
import { getEnabledCategories } from '../../Category/service';
|
||||||
import type { FormDefinitionRequest } from '../types';
|
import type { FormDefinitionRequest, FormDefinitionResponse } from '../types';
|
||||||
import type { FormCategoryResponse } from '../../Category/types';
|
import type { FormCategoryResponse } from '../../Category/types';
|
||||||
|
|
||||||
interface CreateModalProps {
|
interface FormBasicInfoModalProps {
|
||||||
|
mode: 'create' | 'edit';
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
record?: FormDefinitionResponse | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: (id: number) => void;
|
onSuccess: (id?: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单定义创建弹窗(第一步:输入基本信息)
|
* 表单定义基本信息弹窗(统一的创建/编辑组件)
|
||||||
*/
|
*/
|
||||||
const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }) => {
|
const FormBasicInfoModal: React.FC<FormBasicInfoModalProps> = ({
|
||||||
|
mode,
|
||||||
|
visible,
|
||||||
|
record,
|
||||||
|
onClose,
|
||||||
|
onSuccess
|
||||||
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@ -37,12 +45,26 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
isTemplate: false,
|
isTemplate: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载分类列表
|
// 加载分类列表和初始化数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
loadCategories();
|
loadCategories();
|
||||||
|
if (mode === 'edit' && record) {
|
||||||
|
setFormData({
|
||||||
|
name: record.name,
|
||||||
|
key: record.key,
|
||||||
|
categoryId: record.categoryId,
|
||||||
|
description: record.description || '',
|
||||||
|
isTemplate: record.isTemplate || false,
|
||||||
|
});
|
||||||
|
} else if (mode === 'create') {
|
||||||
|
resetForm();
|
||||||
}
|
}
|
||||||
}, [visible]);
|
} else {
|
||||||
|
// 弹窗关闭时重置表单数据
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
}, [visible, mode, record?.id]); // 只依赖 record.id 而不是整个 record 对象
|
||||||
|
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
try {
|
try {
|
||||||
@ -65,9 +87,11 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
const handleClose = () => {
|
const handleClose = (open: boolean) => {
|
||||||
resetForm();
|
// 当 Dialog 的 open 状态变为 false 时才执行关闭逻辑
|
||||||
|
if (!open && !submitting) {
|
||||||
onClose();
|
onClose();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
@ -102,7 +126,8 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
|
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
// 创建表单定义(只保存基本信息,schema 为空)
|
if (mode === 'create') {
|
||||||
|
// 创建模式
|
||||||
const request: FormDefinitionRequest = {
|
const request: FormDefinitionRequest = {
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
key: formData.key,
|
key: formData.key,
|
||||||
@ -128,28 +153,54 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 跳转到设计器页面
|
// 跳转到设计器页面
|
||||||
navigate(`/form/definitions/${result.id}/design`);
|
navigate(`/workflow/form/${result.id}/design`);
|
||||||
handleClose();
|
onClose();
|
||||||
onSuccess(result.id);
|
onSuccess(result.id);
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error('创建表单失败:', error);
|
// 编辑模式
|
||||||
|
if (!record) return;
|
||||||
|
|
||||||
|
const request: FormDefinitionRequest = {
|
||||||
|
name: formData.name,
|
||||||
|
key: formData.key,
|
||||||
|
categoryId: formData.categoryId,
|
||||||
|
description: formData.description,
|
||||||
|
schema: record.schema,
|
||||||
|
status: record.status,
|
||||||
|
isTemplate: formData.isTemplate,
|
||||||
|
version: record.version, // 乐观锁版本号
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateDefinition(record.id, request);
|
||||||
toast({
|
toast({
|
||||||
title: '创建失败',
|
title: "更新成功",
|
||||||
description: error instanceof Error ? error.message : '创建表单失败',
|
description: `表单 "${formData.name}" 的基本信息已更新`,
|
||||||
variant: 'destructive'
|
|
||||||
});
|
});
|
||||||
|
// 只调用 onSuccess,让父组件管理关闭逻辑
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`${mode === 'create' ? '创建' : '更新'}表单失败:`, error);
|
||||||
|
// 错误提示已在 request.ts 中统一处理,这里只做日志记录和状态重置
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 根据模式调整文案
|
||||||
|
const title = mode === 'create' ? '创建表单定义' : '编辑基本信息';
|
||||||
|
const description = mode === 'create'
|
||||||
|
? '第一步:输入表单的基本信息,然后点击"下一步"进入表单设计器'
|
||||||
|
: '修改表单的名称、标识、分类等基本信息';
|
||||||
|
const submitButtonText = mode === 'create' ? '下一步:设计表单' : '保存更改';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={visible} onOpenChange={handleClose}>
|
<Dialog open={visible} onOpenChange={handleClose}>
|
||||||
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>创建表单定义</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
第一步:输入表单的基本信息,然后点击"下一步"进入表单设计器
|
{description}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
@ -159,13 +210,13 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
{/* 第一行:表单名称 + 表单标识 */}
|
{/* 第一行:表单名称 + 表单标识 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="create-name" className="flex items-center gap-2">
|
<Label htmlFor="form-name" className="flex items-center gap-2">
|
||||||
<FileText className="h-4 w-4 text-muted-foreground" />
|
<FileText className="h-4 w-4 text-muted-foreground" />
|
||||||
表单名称
|
表单名称
|
||||||
<span className="text-destructive">*</span>
|
<span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="create-name"
|
id="form-name"
|
||||||
placeholder="例如:员工请假申请表"
|
placeholder="例如:员工请假申请表"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
||||||
@ -177,13 +228,13 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="create-key" className="flex items-center gap-2">
|
<Label htmlFor="form-key" className="flex items-center gap-2">
|
||||||
<Tag className="h-4 w-4 text-muted-foreground" />
|
<Tag className="h-4 w-4 text-muted-foreground" />
|
||||||
表单标识
|
表单标识
|
||||||
<span className="text-destructive">*</span>
|
<span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="create-key"
|
id="form-key"
|
||||||
placeholder="例如:employee-leave-form"
|
placeholder="例如:employee-leave-form"
|
||||||
value={formData.key}
|
value={formData.key}
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, key: e.target.value }))}
|
onChange={(e) => setFormData(prev => ({ ...prev, key: e.target.value }))}
|
||||||
@ -198,7 +249,7 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
{/* 第二行:分类 + 设为模板 */}
|
{/* 第二行:分类 + 设为模板 */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="create-category" className="flex items-center gap-2">
|
<Label htmlFor="form-category" className="flex items-center gap-2">
|
||||||
<Folder className="h-4 w-4 text-muted-foreground" />
|
<Folder className="h-4 w-4 text-muted-foreground" />
|
||||||
表单分类
|
表单分类
|
||||||
</Label>
|
</Label>
|
||||||
@ -206,7 +257,7 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
value={formData.categoryId?.toString() || undefined}
|
value={formData.categoryId?.toString() || undefined}
|
||||||
onValueChange={(value) => setFormData(prev => ({ ...prev, categoryId: Number(value) }))}
|
onValueChange={(value) => setFormData(prev => ({ ...prev, categoryId: Number(value) }))}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="create-category" className="h-10">
|
<SelectTrigger id="form-category" className="h-10">
|
||||||
<SelectValue placeholder="选择表单所属分类" />
|
<SelectValue placeholder="选择表单所属分类" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -223,7 +274,7 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="create-isTemplate" className="flex items-center gap-2">
|
<Label htmlFor="form-isTemplate" className="flex items-center gap-2">
|
||||||
<Copy className="h-4 w-4 text-muted-foreground" />
|
<Copy className="h-4 w-4 text-muted-foreground" />
|
||||||
设为表单模板
|
设为表单模板
|
||||||
</Label>
|
</Label>
|
||||||
@ -232,7 +283,7 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
<span className="text-sm">启用模板功能</span>
|
<span className="text-sm">启用模板功能</span>
|
||||||
</div>
|
</div>
|
||||||
<Switch
|
<Switch
|
||||||
id="create-isTemplate"
|
id="form-isTemplate"
|
||||||
checked={formData.isTemplate}
|
checked={formData.isTemplate}
|
||||||
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, isTemplate: checked }))}
|
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, isTemplate: checked }))}
|
||||||
/>
|
/>
|
||||||
@ -245,12 +296,12 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
|
|
||||||
{/* 第三行:描述 */}
|
{/* 第三行:描述 */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="create-description" className="flex items-center gap-2">
|
<Label htmlFor="form-description" className="flex items-center gap-2">
|
||||||
<AlignLeft className="h-4 w-4 text-muted-foreground" />
|
<AlignLeft className="h-4 w-4 text-muted-foreground" />
|
||||||
表单描述
|
表单描述
|
||||||
</Label>
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="create-description"
|
id="form-description"
|
||||||
placeholder="简要说明此表单的用途和填写注意事项..."
|
placeholder="简要说明此表单的用途和填写注意事项..."
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
|
||||||
@ -263,12 +314,12 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={handleClose} disabled={submitting}>
|
<Button variant="outline" onClick={() => handleClose(false)} disabled={submitting}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSubmit} disabled={submitting}>
|
<Button onClick={handleSubmit} disabled={submitting}>
|
||||||
{submitting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
{submitting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||||
下一步:设计表单
|
{submitButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@ -276,5 +327,5 @@ const CreateModal: React.FC<CreateModalProps> = ({ visible, onClose, onSuccess }
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CreateModal;
|
export default FormBasicInfoModal;
|
||||||
|
|
||||||
@ -29,8 +29,7 @@ import type { FormDefinitionResponse, FormDefinitionStatus } from './types';
|
|||||||
import type { FormCategoryResponse } from '../Category/types';
|
import type { FormCategoryResponse } from '../Category/types';
|
||||||
import type { Page } from '@/types/base';
|
import type { Page } from '@/types/base';
|
||||||
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page';
|
||||||
import CreateModal from './components/CreateModal';
|
import FormBasicInfoModal from './components/FormBasicInfoModal';
|
||||||
import EditBasicInfoModal from './components/EditBasicInfoModal';
|
|
||||||
import CategoryManageDialog from './components/CategoryManageDialog';
|
import CategoryManageDialog from './components/CategoryManageDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,7 +135,7 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
|
|
||||||
// 编辑表单设计
|
// 编辑表单设计
|
||||||
const handleEdit = (record: FormDefinitionResponse) => {
|
const handleEdit = (record: FormDefinitionResponse) => {
|
||||||
navigate(`/form/definitions/${record.id}/design`);
|
navigate(`/workflow/form/${record.id}/design`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑基本信息
|
// 编辑基本信息
|
||||||
@ -195,7 +194,7 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
|
|
||||||
// 查看数据
|
// 查看数据
|
||||||
const handleViewData = (record: FormDefinitionResponse) => {
|
const handleViewData = (record: FormDefinitionResponse) => {
|
||||||
navigate(`/form/data?formDefinitionId=${record.id}`);
|
navigate(`/workflow/form/data?formDefinitionId=${record.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据分类 ID 获取分类信息
|
// 根据分类 ID 获取分类信息
|
||||||
@ -601,7 +600,8 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 创建表单弹窗 */}
|
{/* 创建表单弹窗 */}
|
||||||
<CreateModal
|
<FormBasicInfoModal
|
||||||
|
mode="create"
|
||||||
visible={createModalVisible}
|
visible={createModalVisible}
|
||||||
onClose={() => setCreateModalVisible(false)}
|
onClose={() => setCreateModalVisible(false)}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
@ -611,15 +611,26 @@ const FormDefinitionList: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 编辑基本信息弹窗 */}
|
{/* 编辑基本信息弹窗 */}
|
||||||
<EditBasicInfoModal
|
<FormBasicInfoModal
|
||||||
|
mode="edit"
|
||||||
visible={editBasicInfoVisible}
|
visible={editBasicInfoVisible}
|
||||||
record={editBasicInfoRecord}
|
record={editBasicInfoRecord}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
|
// 先关闭弹窗
|
||||||
setEditBasicInfoVisible(false);
|
setEditBasicInfoVisible(false);
|
||||||
|
// 延迟清空 record,等待 Dialog 关闭动画完成(避免遮罩层残留)
|
||||||
|
setTimeout(() => {
|
||||||
setEditBasicInfoRecord(null);
|
setEditBasicInfoRecord(null);
|
||||||
|
}, 300);
|
||||||
}}
|
}}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
|
// 先关闭弹窗
|
||||||
|
setEditBasicInfoVisible(false);
|
||||||
|
// 延迟清空 record 和刷新数据,等待 Dialog 关闭动画完成
|
||||||
|
setTimeout(() => {
|
||||||
|
setEditBasicInfoRecord(null);
|
||||||
loadData();
|
loadData();
|
||||||
|
}, 300);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -89,13 +89,18 @@ const router = createBrowserRouter([
|
|||||||
path: 'applications',
|
path: 'applications',
|
||||||
element: <Suspense fallback={<LoadingComponent/>}><ApplicationList/></Suspense>
|
element: <Suspense fallback={<LoadingComponent/>}><ApplicationList/></Suspense>
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'environments',
|
|
||||||
element: <Suspense fallback={<LoadingComponent/>}><EnvironmentList/></Suspense>
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'deployment',
|
path: 'deployment',
|
||||||
element: <Suspense fallback={<LoadingComponent/>}><DeploymentConfigList/></Suspense>
|
element: <Suspense fallback={<LoadingComponent/>}><DeploymentConfigList/></Suspense>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'resource',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'environments',
|
||||||
|
element: <Suspense fallback={<LoadingComponent/>}><EnvironmentList/></Suspense>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'jenkins-manager',
|
path: 'jenkins-manager',
|
||||||
@ -152,51 +157,6 @@ const router = createBrowserRouter([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'form',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'definitions',
|
|
||||||
element: (
|
|
||||||
<Suspense fallback={<LoadingComponent/>}>
|
|
||||||
<FormDefinitionList/>
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'definitions/create',
|
|
||||||
element: (
|
|
||||||
<Suspense fallback={<LoadingComponent/>}>
|
|
||||||
<FormDefinitionDesigner/>
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'definitions/:id/design',
|
|
||||||
element: (
|
|
||||||
<Suspense fallback={<LoadingComponent/>}>
|
|
||||||
<FormDefinitionDesigner/>
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'data',
|
|
||||||
element: (
|
|
||||||
<Suspense fallback={<LoadingComponent/>}>
|
|
||||||
<FormDataList/>
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'data/:id',
|
|
||||||
element: (
|
|
||||||
<Suspense fallback={<LoadingComponent/>}>
|
|
||||||
<FormDataDetail/>
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'workflow',
|
path: 'workflow',
|
||||||
children: [
|
children: [
|
||||||
@ -229,6 +189,51 @@ const router = createBrowserRouter([
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'form',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
index: true,
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<LoadingComponent/>}>
|
||||||
|
<FormDefinitionList/>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<LoadingComponent/>}>
|
||||||
|
<FormDefinitionDesigner/>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':id/design',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<LoadingComponent/>}>
|
||||||
|
<FormDefinitionDesigner/>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'data',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<LoadingComponent/>}>
|
||||||
|
<FormDataList/>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'data/:id',
|
||||||
|
element: (
|
||||||
|
<Suspense fallback={<LoadingComponent/>}>
|
||||||
|
<FormDataDetail/>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'node-design',
|
path: 'node-design',
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import axios, {AxiosRequestConfig, AxiosResponse} from 'axios';
|
import axios, {AxiosRequestConfig, AxiosResponse} from 'axios';
|
||||||
import {message} from 'antd';
|
import {toast} from '@/components/ui/use-toast';
|
||||||
|
|
||||||
export interface Response<T = any> {
|
export interface Response<T = any> {
|
||||||
code: number;
|
code: number;
|
||||||
@ -46,7 +46,11 @@ const responseHandler = (response: AxiosResponse<Response<any>>) => {
|
|||||||
return result.data;
|
return result.data;
|
||||||
} else {
|
} else {
|
||||||
if (result.message != undefined) {
|
if (result.message != undefined) {
|
||||||
message.error(result.message || defaultErrorMessage);
|
toast({
|
||||||
|
title: '操作失败',
|
||||||
|
description: result.message || defaultErrorMessage,
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
return Promise.reject(response);
|
return Promise.reject(response);
|
||||||
}
|
}
|
||||||
return Promise.reject(response);
|
return Promise.reject(response);
|
||||||
@ -56,7 +60,11 @@ const responseHandler = (response: AxiosResponse<Response<any>>) => {
|
|||||||
|
|
||||||
const errorHandler = (error: any) => {
|
const errorHandler = (error: any) => {
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
message.error('网络连接异常,请检查网络');
|
toast({
|
||||||
|
title: '网络错误',
|
||||||
|
description: '网络连接异常,请检查网络',
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +78,11 @@ const errorHandler = (error: any) => {
|
|||||||
case 401:
|
case 401:
|
||||||
// 登录已过期,清除所有本地存储并跳转到登录页
|
// 登录已过期,清除所有本地存储并跳转到登录页
|
||||||
errorMessage = '登录已过期,请重新登录';
|
errorMessage = '登录已过期,请重新登录';
|
||||||
message.error(errorMessage);
|
toast({
|
||||||
|
title: '登录已过期',
|
||||||
|
description: errorMessage,
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
|
|
||||||
// 清除本地存储的所有用户相关信息
|
// 清除本地存储的所有用户相关信息
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
@ -85,19 +97,35 @@ const errorHandler = (error: any) => {
|
|||||||
break;
|
break;
|
||||||
case 403:
|
case 403:
|
||||||
errorMessage = '拒绝访问';
|
errorMessage = '拒绝访问';
|
||||||
message.error(errorMessage);
|
toast({
|
||||||
|
title: '访问被拒绝',
|
||||||
|
description: errorMessage,
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case 404:
|
case 404:
|
||||||
errorMessage = '请求错误,未找到该资源';
|
errorMessage = '请求错误,未找到该资源';
|
||||||
message.error(errorMessage);
|
toast({
|
||||||
|
title: '请求错误',
|
||||||
|
description: errorMessage,
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case 500:
|
case 500:
|
||||||
errorMessage = '服务异常,请稍后再试';
|
errorMessage = '服务异常,请稍后再试';
|
||||||
message.error(errorMessage);
|
toast({
|
||||||
|
title: '服务器错误',
|
||||||
|
description: errorMessage,
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
errorMessage = '服务器异常,请稍后再试!';
|
errorMessage = '服务器异常,请稍后再试!';
|
||||||
message.error(errorMessage);
|
toast({
|
||||||
|
title: '请求失败',
|
||||||
|
description: errorMessage,
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user