增加审批组件

This commit is contained in:
dengqichen 2025-10-24 23:03:54 +08:00
parent 164a39bd42
commit 114d549a5c
3 changed files with 166 additions and 81 deletions

View File

@ -11,10 +11,12 @@ 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 { Loader2, FileText, Tag, AlignLeft, Folder } from 'lucide-react';
import { Loader2 } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
import type { WorkflowDefinition, WorkflowCategoryResponse } from '../types';
import type { WorkflowDefinition, WorkflowCategoryResponse, WorkflowDefinitionRequest } from '../types';
import { saveDefinition, updateDefinition, getWorkflowCategoryList } from '../service';
import { getDefinitions as getFormDefinitions } from '@/pages/Form/Definition/service';
import type { FormDefinitionResponse } from '@/pages/Form/Definition/types';
interface EditModalProps {
visible: boolean;
@ -28,32 +30,34 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
const isEdit = !!record;
const [submitting, setSubmitting] = useState(false);
const [categories, setCategories] = useState<WorkflowCategoryResponse[]>([]);
const [formDefinitions, setFormDefinitions] = useState<FormDefinitionResponse[]>([]);
const [formData, setFormData] = useState({
name: '',
key: '',
categoryId: undefined as number | undefined,
formDefinitionId: undefined as number | undefined,
description: '',
triggers: [] as string[],
});
useEffect(() => {
if (visible) {
loadCategories();
loadFormDefinitions();
if (record) {
setFormData({
name: record.name,
key: record.key,
categoryId: record.categoryId,
formDefinitionId: record.formDefinitionId,
description: record.description || '',
triggers: record.triggers || [],
});
} else {
setFormData({
name: '',
key: '',
categoryId: undefined,
formDefinitionId: undefined,
description: '',
triggers: [],
});
}
}
@ -73,6 +77,17 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
}
};
const loadFormDefinitions = async () => {
try {
const result = await getFormDefinitions({ status: 'PUBLISHED' });
if (result?.content) {
setFormDefinitions(result.content);
}
} catch (error) {
console.error('加载表单定义失败:', error);
}
};
const handleClose = () => {
if (!submitting) {
onClose();
@ -124,22 +139,39 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
setSubmitting(true);
try {
const submitData: WorkflowDefinition = {
...formData,
id: record?.id || 0,
flowVersion: isEdit ? record.flowVersion : 1,
status: isEdit ? record.status : 'DRAFT',
graph: record?.graph || { nodes: [], edges: [] },
formConfig: record?.formConfig || { formItems: [] },
} as WorkflowDefinition;
if (isEdit && record) {
// 更新时需要传递完整的数据和版本号
const submitData: WorkflowDefinitionRequest = {
name: formData.name,
key: formData.key,
categoryId: formData.categoryId,
formDefinitionId: formData.formDefinitionId,
flowVersion: record.flowVersion, // 流程版本
description: formData.description,
graph: record.graph,
bpmnXml: record.bpmnXml,
status: record.status,
version: record.version, // 乐观锁版本号
};
await updateDefinition(record.id, submitData);
toast({
title: '更新成功',
description: `工作流 "${formData.name}" 已更新`,
});
} else {
// 新建时初始化基本数据
const submitData: WorkflowDefinitionRequest = {
name: formData.name,
key: formData.key,
categoryId: formData.categoryId,
formDefinitionId: formData.formDefinitionId,
flowVersion: 1, // 新建时流程版本为1
description: formData.description,
graph: { nodes: [], edges: [] },
status: 'DRAFT',
};
await saveDefinition(submitData);
toast({
title: '创建成功',
@ -161,8 +193,6 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
}
};
const selectedCategory = categories.find(c => c.id === formData.categoryId);
return (
<Dialog open={visible} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[600px]">
@ -172,8 +202,7 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
<div className="grid gap-6 py-4">
{/* 流程分类 */}
<div className="space-y-2">
<Label htmlFor="categoryId" className="flex items-center gap-2">
<Folder className="h-4 w-4 text-muted-foreground" />
<Label htmlFor="categoryId">
<span className="text-destructive">*</span>
</Label>
<Select
@ -182,7 +211,6 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
setFormData(prev => ({
...prev,
categoryId: Number(value),
triggers: [], // 切换分类时清空触发器
}));
}}
disabled={isEdit}
@ -205,27 +233,56 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
)}
</div>
{/* 流程名称 */}
{/* 启动表单 */}
<div className="space-y-2">
<Label htmlFor="name" className="flex items-center gap-2">
<FileText className="h-4 w-4 text-muted-foreground" />
<span className="text-destructive">*</span>
<Label htmlFor="formDefinitionId">
{isEdit && <span className="text-xs text-muted-foreground ml-1">()</span>}
</Label>
<Input
id="name"
placeholder="例如Jenkins 构建流程"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
className="h-10"
/>
<div className="flex gap-2">
<Select
value={formData.formDefinitionId?.toString() || ''}
onValueChange={(value) => {
setFormData(prev => ({
...prev,
formDefinitionId: value ? Number(value) : undefined,
}));
}}
disabled={isEdit}
>
<SelectTrigger id="formDefinitionId" className="h-10 flex-1">
<SelectValue placeholder="请选择启动表单(可选)" />
</SelectTrigger>
<SelectContent>
{formDefinitions.map(form => (
<SelectItem key={form.id} value={form.id.toString()}>
{form.name} ({form.key})
</SelectItem>
))}
</SelectContent>
</Select>
{formData.formDefinitionId && !isEdit && (
<Button
type="button"
variant="outline"
size="icon"
className="h-10 w-10 shrink-0"
onClick={() => setFormData(prev => ({ ...prev, formDefinitionId: undefined }))}
>
×
</Button>
)}
</div>
<p className="text-xs text-muted-foreground">
</p>
</div>
{/* 流程标识 */}
<div className="space-y-2">
<Label htmlFor="key" className="flex items-center gap-2">
<Tag className="h-4 w-4 text-muted-foreground" />
<Label htmlFor="key">
<span className="text-destructive">*</span>
{isEdit && <span className="text-xs text-muted-foreground">()</span>}
{isEdit && <span className="text-xs text-muted-foreground ml-1">()</span>}
</Label>
<Input
id="key"
@ -240,10 +297,25 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
</p>
</div>
{/* 流程名称 */}
<div className="space-y-2">
<Label htmlFor="name">
<span className="text-destructive">*</span>
{isEdit && <span className="text-xs text-muted-foreground ml-1">()</span>}
</Label>
<Input
id="name"
placeholder="例如Jenkins 构建流程"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
disabled={isEdit}
className="h-10"
/>
</div>
{/* 描述 */}
<div className="space-y-2">
<Label htmlFor="description" className="flex items-center gap-2">
<AlignLeft className="h-4 w-4 text-muted-foreground" />
<Label htmlFor="description">
</Label>
<Textarea
@ -255,17 +327,6 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
/>
</div>
{/* 提示:触发方式在设计阶段配置 */}
{selectedCategory && selectedCategory.supportedTriggers && selectedCategory.supportedTriggers.length > 0 && (
<div className="rounded-lg border p-4 bg-muted/30">
<p className="text-sm text-muted-foreground">
<strong></strong> {selectedCategory.supportedTriggers.join(', ')}
</p>
<p className="text-xs text-muted-foreground mt-1">
</p>
</div>
)}
</div>
<DialogFooter>

View File

@ -2,6 +2,7 @@ import request from '@/utils/request';
import {
WorkflowDefinition,
WorkflowDefinitionQuery,
WorkflowDefinitionRequest,
WorkflowCategoryResponse,
WorkflowCategoryQuery,
WorkflowCategoryRequest
@ -27,10 +28,10 @@ export const deployDefinition = (id: number) =>
export const deleteDefinition = (id: number) =>
request.delete<void>(`${DEFINITION_URL}/${id}`);
export const saveDefinition = (data: WorkflowDefinition) =>
export const saveDefinition = (data: WorkflowDefinitionRequest) =>
request.post<WorkflowDefinition>(`${DEFINITION_URL}`, data);
export const updateDefinition = (id: number, data: WorkflowDefinition) =>
export const updateDefinition = (id: number, data: WorkflowDefinitionRequest) =>
request.put<WorkflowDefinition>(`${DEFINITION_URL}/${id}`, data);
/**

View File

@ -1,41 +1,45 @@
import {BaseResponse, BaseQuery} from '@/types/base';
/**
*
*/
export type WorkflowDefinitionStatus = 'DRAFT' | 'PUBLISHED' | 'DISABLED';
/**
*
*/
export interface WorkflowDefinition extends BaseResponse {
id: number;
name: string;
key: string;
description?: string;
flowVersion?: number;
status?: string;
categoryId?: number; // 分类ID
triggers: string[];
graph: {
nodes: WorkflowDefinitionNode[];
edges: any[];
};
formConfig: {
formItems: any[];
};
formVariablesSchema?: {
type: string;
required?: string[];
properties: {
[key: string]: {
type: string;
title: string;
description?: string;
dataSource?: {
url: string;
type: string;
params?: Record<string, any>;
dependsOn?: string[];
labelField?: string;
valueField?: string;
};
};
};
};
bpmnXml?: string;
formDefinitionId?: number; // 启动表单ID
processDefinitionId?: string; // 流程定义ID
flowVersion: number; // 流程版本
bpmnXml?: string; // BPMN XML内容
graph?: WorkflowDefinitionGraph; // 流程图数据
status: WorkflowDefinitionStatus; // 流程状态
description?: string; // 流程描述
}
/**
*
*/
export interface WorkflowDefinitionGraph {
nodes: WorkflowDefinitionNode[];
edges: WorkflowDefinitionEdge[];
}
/**
*
*/
export interface WorkflowDefinitionEdge {
id: string;
source: string;
target: string;
sourceHandle?: string;
targetHandle?: string;
type?: string;
label?: string;
}
export interface WorkflowDefinitionNode {
@ -57,11 +61,30 @@ export interface WorkflowDefinitionNode {
}>;
}
/**
*
*/
export interface WorkflowDefinitionQuery extends BaseQuery {
name?: string;
key?: string;
categoryId?: number; // 分类ID筛选
status?: string;
status?: WorkflowDefinitionStatus;
}
/**
* /
*/
export interface WorkflowDefinitionRequest {
name: string;
key: string;
categoryId?: number;
formDefinitionId?: number;
flowVersion?: number; // 流程版本新建时传1更新时传当前版本
description?: string;
graph?: WorkflowDefinitionGraph;
bpmnXml?: string;
status?: WorkflowDefinitionStatus;
version?: number; // 乐观锁版本号(更新时必传)
}
/**