重构消息通知弹窗

This commit is contained in:
dengqichen 2025-11-15 11:26:01 +08:00
parent 88f5ec1d5c
commit 96517b809e
4 changed files with 245 additions and 82 deletions

View File

@ -95,6 +95,7 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
// 打开审批对话框
const handleOpenApproval = (task: PendingApprovalTask, result: ApprovalResult) => {
console.log('打开审批对话框,任务数据:', task); // 调试日志
setSelectedTask(task);
setApprovalResult(result);
setApprovalComment('');
@ -180,38 +181,41 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
{approvalList.map((task) => (
<div
key={task.taskId}
className="group relative p-4 border rounded-lg hover:border-primary/50 hover:shadow-md transition-all bg-card"
className="group relative p-4 border border-gray-200/80 rounded-xl hover:border-orange-300 hover:shadow-lg transition-all duration-300 bg-gradient-to-br from-white to-orange-50/30 overflow-hidden"
>
{/* 装饰性渐变背景 */}
<div className="absolute top-0 right-0 w-24 h-24 bg-gradient-to-br from-orange-100/40 to-transparent rounded-full blur-2xl -z-0" />
{/* 左侧内容区 */}
<div className="flex gap-4">
<div className="flex gap-3 relative z-10">
{/* 应用图标 */}
<div className="shrink-0">
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center">
<Package className="h-6 w-6 text-primary" />
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-orange-500 to-orange-600 flex items-center justify-center shadow-lg shadow-orange-500/30">
<Package className="h-6 w-6 text-white" />
</div>
</div>
{/* 主要信息 */}
<div className="flex-1 min-w-0 space-y-2">
<div className="flex-1 min-w-0 space-y-1.5">
{/* 标题行 */}
<div className="flex items-start justify-between gap-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-base font-bold text-foreground truncate">
<h3 className="text-base font-bold text-gray-900 truncate">
{task.applicationName}
</h3>
<span className="shrink-0 text-xs px-1.5 py-0.5 bg-orange-100 text-orange-700 rounded font-medium">
<span className="shrink-0 text-xs px-1.5 py-0.5 bg-gradient-to-r from-orange-500 to-orange-600 text-white rounded font-semibold shadow-sm">
#{task.deployRecordId}
</span>
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<code className="px-1.5 py-0.5 bg-muted rounded font-mono">
<div className="flex items-center gap-2 text-xs text-gray-600">
<code className="px-1.5 py-0.5 bg-gray-100 text-gray-700 rounded font-mono font-medium">
{task.applicationCode}
</code>
<span></span>
<span>{task.environmentName}</span>
<span></span>
<span>{task.teamName}</span>
<span className="text-gray-400"></span>
<span className="font-medium">{task.environmentName}</span>
<span className="text-gray-400"></span>
<span className="font-medium">{task.teamName}</span>
</div>
</div>
@ -219,7 +223,7 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
<div className="flex gap-2 shrink-0">
<Button
size="sm"
className="h-8 bg-green-600 hover:bg-green-700 text-white shadow-sm"
className="h-8 px-3 bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 text-white shadow-md shadow-green-500/30 hover:shadow-lg hover:shadow-green-500/40 transition-all duration-200 font-semibold text-xs"
onClick={() => handleOpenApproval(task, ApprovalResult.APPROVED)}
>
<CheckCircle className="h-3.5 w-3.5 mr-1" />
@ -228,7 +232,7 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
<Button
size="sm"
variant="outline"
className="h-8 border-red-200 text-red-600 hover:bg-red-50 hover:text-red-700 hover:border-red-300"
className="h-8 px-3 border-2 border-red-200 text-red-600 hover:bg-red-50 hover:text-red-700 hover:border-red-400 font-semibold transition-all duration-200 text-xs"
onClick={() => handleOpenApproval(task, ApprovalResult.REJECTED)}
>
<XCircle className="h-3.5 w-3.5 mr-1" />
@ -239,14 +243,15 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
{/* 审批内容 */}
{(task.approvalTitle || task.approvalContent) && (
<div className="p-2.5 bg-orange-50/60 border border-orange-100 rounded text-sm">
<div className="p-2.5 bg-gradient-to-r from-orange-50 to-amber-50 border border-orange-200/60 rounded-lg text-sm shadow-sm">
{task.approvalTitle && (
<div className="font-medium text-foreground mb-0.5">
<div className="font-semibold text-gray-900 mb-0.5 flex items-center gap-1.5 text-xs">
<div className="w-1 h-3 bg-orange-500 rounded-full" />
{task.approvalTitle}
</div>
)}
{task.approvalContent && (
<div className="text-xs text-muted-foreground">
<div className="text-xs text-gray-600 leading-relaxed pl-2.5">
{task.approvalContent}
</div>
)}
@ -255,26 +260,26 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
{/* 底部信息栏 */}
<div className="flex items-center justify-between pt-1">
<div className="flex items-center gap-3 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<User className="h-3 w-3" />
<span>{task.deployBy}</span>
<div className="flex items-center gap-3 text-xs">
<div className="flex items-center gap-1 text-gray-600">
<User className="h-3 w-3 text-blue-600" />
<span className="font-medium">{task.deployBy}</span>
</div>
<div className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
<span>{formatTime(task.deployStartTime)}</span>
<div className="flex items-center gap-1 text-gray-600">
<Calendar className="h-3 w-3 text-purple-600" />
<span className="font-medium">{formatTime(task.deployStartTime)}</span>
</div>
{task.pendingDuration && (
<div className="flex items-center gap-1 text-amber-600">
<Clock className="h-3 w-3" />
<span> {formatDuration(task.pendingDuration)}</span>
<div className="flex items-center gap-1 px-1.5 py-0.5 bg-amber-100 rounded">
<Clock className="h-3 w-3 text-amber-600" />
<span className="text-amber-700 font-semibold"> {formatDuration(task.pendingDuration)}</span>
</div>
)}
</div>
<Button
size="sm"
variant="ghost"
className="h-6 text-xs opacity-0 group-hover:opacity-100 transition-opacity"
className="h-6 px-2 text-xs text-orange-600 hover:text-orange-700 hover:bg-orange-50 opacity-0 group-hover:opacity-100 transition-all duration-200 font-medium"
onClick={() => handleViewDetail(task)}
>
@ -283,9 +288,9 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
{/* 备注(如果有) */}
{task.deployRemark && (
<div className="flex items-start gap-1.5 p-2 bg-muted/30 rounded text-xs">
<FileText className="h-3 w-3 text-muted-foreground shrink-0 mt-0.5" />
<span className="text-muted-foreground">{task.deployRemark}</span>
<div className="flex items-start gap-1.5 p-2 bg-gray-50 border border-gray-200 rounded text-xs">
<FileText className="h-3 w-3 text-gray-600 shrink-0 mt-0.5" />
<span className="text-gray-700 leading-relaxed">{task.deployRemark}</span>
</div>
)}
</div>
@ -300,77 +305,107 @@ export const PendingApprovalModal: React.FC<PendingApprovalModalProps> = ({
{/* 审批确认对话框 */}
<AlertDialog open={approvalDialogOpen} onOpenChange={setApprovalDialogOpen}>
<AlertDialogContent className="max-w-md">
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
{approvalResult === ApprovalResult.APPROVED ? (
<>
<CheckCircle className="h-5 w-5 text-green-600" />
</>
) : (
<>
<XCircle className="h-5 w-5 text-red-600" />
</>
)}
<AlertDialogContent className="max-w-lg">
<AlertDialogHeader className="space-y-3">
<div className="flex items-center justify-center">
<div className={
approvalResult === ApprovalResult.APPROVED
? "w-16 h-16 rounded-full bg-gradient-to-br from-green-500 to-green-600 flex items-center justify-center shadow-lg shadow-green-500/30"
: "w-16 h-16 rounded-full bg-gradient-to-br from-red-500 to-red-600 flex items-center justify-center shadow-lg shadow-red-500/30"
}>
{approvalResult === ApprovalResult.APPROVED ? (
<CheckCircle className="h-8 w-8 text-white" />
) : (
<XCircle className="h-8 w-8 text-white" />
)}
</div>
</div>
<AlertDialogTitle className="text-center text-xl font-bold">
{approvalResult === ApprovalResult.APPROVED ? '确认通过审批' : '确认拒绝审批'}
</AlertDialogTitle>
</AlertDialogHeader>
<div className="space-y-3 px-6 py-4">
<div className="space-y-4 px-6 py-2">
{selectedTask && (
<>
<div className="text-sm text-muted-foreground">
{approvalResult === ApprovalResult.APPROVED ? '通过' : '拒绝'}
<div className="text-center text-sm text-gray-600 px-4">
<span className={
approvalResult === ApprovalResult.APPROVED
? "font-semibold text-green-600 mx-1"
: "font-semibold text-red-600 mx-1"
}>
{approvalResult === ApprovalResult.APPROVED ? '通过' : '拒绝'}
</span>
</div>
<div className="p-3 bg-muted/50 rounded-md space-y-1.5 text-xs">
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[56px]">ID</span>
<span className="font-mono font-semibold">#{selectedTask.deployRecordId}</span>
<div className="p-4 bg-gradient-to-br from-gray-50 to-gray-100/50 border border-gray-200 rounded-xl space-y-3 text-sm shadow-sm">
<div className="flex items-center gap-3">
<span className="text-gray-500 min-w-[64px] font-medium">ID</span>
<span className="font-mono font-bold text-orange-600">#{selectedTask.deployRecordId}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[56px]"></span>
<span className="font-medium">{selectedTask.applicationName}</span>
<div className="h-px bg-gray-200" />
<div className="flex items-center gap-3">
<span className="text-gray-500 min-w-[64px] font-medium"></span>
<span className="font-semibold text-gray-900">{selectedTask.applicationName}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[56px]"></span>
<span className="font-medium">{selectedTask.environmentName}</span>
<div className="flex items-center gap-3">
<span className="text-gray-500 min-w-[64px] font-medium"></span>
<span className="font-semibold text-gray-900">{selectedTask.environmentName}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[56px]"></span>
<span className="font-medium">{selectedTask.deployBy}</span>
<div className="flex items-center gap-3">
<span className="text-gray-500 min-w-[64px] font-medium"></span>
<div className="flex items-center gap-2">
<div className="w-6 h-6 rounded-full bg-blue-100 flex items-center justify-center">
<User className="h-3.5 w-3.5 text-blue-600" />
</div>
<span className="font-semibold text-gray-900">{selectedTask.deployBy}</span>
</div>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground flex items-center gap-1">
<div className="space-y-2.5">
<label className="text-sm font-semibold text-gray-900 flex items-center gap-2">
<div className="w-1 h-4 bg-orange-500 rounded-full" />
{selectedTask.requireComment && (
<span className="text-destructive text-xs">*</span>
<span className="text-xs px-2 py-0.5 bg-red-100 text-red-700 rounded-md font-medium"></span>
)}
</label>
<Textarea
placeholder={selectedTask.requireComment ? "请填写审批意见(必填)" : "请填写审批意见(可选)"}
placeholder={selectedTask.requireComment ? "请填写审批意见..." : "请填写审批意见(可选)..."}
value={approvalComment}
onChange={(e) => setApprovalComment(e.target.value)}
rows={3}
className="resize-none"
rows={4}
className="resize-none border-gray-300 focus:border-orange-400 focus:ring-orange-400/20 rounded-lg"
/>
</div>
</>
)}
</div>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogFooter className="gap-3 sm:gap-3">
<AlertDialogCancel
onClick={() => setApprovalDialogOpen(false)}
className="flex-1 h-11 border-2 border-gray-300 hover:bg-gray-50 font-semibold"
>
</AlertDialogCancel>
<AlertDialogAction
onClick={handleSubmitApproval}
disabled={submitting}
className={
approvalResult === ApprovalResult.APPROVED
? "bg-green-600 hover:bg-green-700"
: "bg-red-600 hover:bg-red-700"
? "flex-1 h-11 bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 text-white shadow-lg shadow-green-500/30 font-semibold"
: "flex-1 h-11 bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg shadow-red-500/30 font-semibold"
}
>
{submitting ? '提交中...' : '确认'}
{submitting ? (
<span className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
...
</span>
) : (
'确认'
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@ -1,5 +1,11 @@
import request from '@/utils/request';
import type { DeployEnvironmentsResponse, StartDeploymentResponse, PendingApprovalTask, CompleteApprovalRequest } from './types';
import type {
DeployEnvironmentsResponse,
StartDeploymentResponse,
PendingApprovalTask,
PendingApprovalTaskResponse,
CompleteApprovalRequest
} from './types';
const DEPLOY_URL = '/api/v1/deploy';
@ -23,17 +29,61 @@ export const startDeployment = (deployData: Record<string, any>) =>
export const getDeployRecordFlowGraph = (deployRecordId: number) =>
request.get<import('./types').DeployRecordFlowGraph>(`${DEPLOY_URL}/records/${deployRecordId}/flow-graph`);
/**
*
*/
const transformApprovalTask = (response: PendingApprovalTaskResponse): PendingApprovalTask => {
return {
// 审批任务基本信息
taskId: response.approvalTask.taskId,
taskName: response.approvalTask.taskName,
taskDescription: response.approvalTask.taskDescription,
processInstanceId: response.approvalTask.processInstanceId,
processDefinitionId: response.approvalTask.processDefinitionId,
assignee: response.approvalTask.assignee || '',
createTime: response.approvalTask.createTime,
dueDate: response.approvalTask.dueDate || undefined,
approvalTitle: response.approvalTask.approvalTitle,
approvalContent: response.approvalTask.approvalContent,
approvalMode: response.approvalTask.approvalMode,
allowDelegate: response.approvalTask.allowDelegate,
allowAddSign: response.approvalTask.allowAddSign,
requireComment: response.approvalTask.requireComment,
pendingDuration: response.approvalTask.pendingDuration,
// 部署业务上下文信息
deployRecordId: response.deployRecord.id,
businessKey: response.deployRecord.businessKey,
deployRemark: response.deployRecord.remark,
deployStartTime: response.deployRecord.startTime,
teamId: response.team.id,
teamName: response.team.name,
applicationId: response.application.id,
applicationCode: response.application.code,
applicationName: response.application.name,
environmentId: response.environment.id,
environmentCode: response.environment.code,
environmentName: response.environment.name,
// 部署人信息 - 优先使用 nickname其次 username
deployBy: response.deployUser.nickname || response.deployUser.username,
};
};
/**
*
* @param workflowDefinitionKeys
* @param teamId ID
* @param environmentId ID
*/
export const getMyApprovalTasks = (
export const getMyApprovalTasks = async (
workflowDefinitionKeys?: string[],
teamId?: number,
environmentId?: number
) => {
): Promise<PendingApprovalTask[]> => {
const params: Record<string, any> = {};
if (workflowDefinitionKeys && workflowDefinitionKeys.length > 0) {
@ -48,7 +98,13 @@ export const getMyApprovalTasks = (
params.environmentId = environmentId;
}
return request.get<PendingApprovalTask[]>(`${DEPLOY_URL}/my-approval-tasks`, { params });
const response = await request.get<PendingApprovalTaskResponse[]>(
`${DEPLOY_URL}/my-approval-tasks`,
{ params }
);
// 将嵌套结构转换为扁平结构
return response.map(transformApprovalTask);
};
/**

View File

@ -120,7 +120,57 @@ export interface StartDeploymentResponse {
}
/**
*
*
*/
export interface PendingApprovalTaskResponse {
approvalTask: {
taskId: string;
taskName: string;
taskDescription?: string;
processInstanceId: string;
processDefinitionId: string;
assignee: string | null;
createTime: string;
dueDate?: string | null;
pendingDuration?: number;
approvalTitle?: string;
approvalContent?: string;
approvalMode: 'SINGLE' | 'MULTI' | 'OR' | 'ANY';
allowDelegate?: boolean;
allowAddSign?: boolean;
requireComment?: boolean;
};
deployRecord: {
id: number;
businessKey: string;
remark?: string;
startTime: string;
};
team: {
id: number;
name: string;
};
application: {
id: number;
code: string;
name: string;
};
environment: {
id: number;
code: string;
name: string;
};
deployUser: {
username: string;
nickname: string;
email: string;
phone: string;
departmentName: string;
};
}
/**
* 使
*/
export interface PendingApprovalTask {
// ============ 审批任务基本信息 ============
@ -134,7 +184,7 @@ export interface PendingApprovalTask {
dueDate?: string;
approvalTitle?: string;
approvalContent?: string;
approvalMode: 'SINGLE' | 'MULTI' | 'OR';
approvalMode: 'SINGLE' | 'MULTI' | 'OR' | 'ANY';
allowDelegate?: boolean;
allowAddSign?: boolean;
requireComment?: boolean;

View File

@ -1,4 +1,4 @@
import React, { useState, useMemo } from 'react';
import React, { useState, useMemo, useCallback } from 'react';
import { useReactFlow } from '@xyflow/react';
import { SmartStepEdge as BaseSmartEdge } from '@tisoap/react-flow-smart-edge';
import type { FlowNode } from '../types';
@ -7,9 +7,15 @@ import { convertToDisplayName } from '@/utils/workflow/variableConversion';
/**
* -
* 使 SmartStepEdge
*
* 便
* -
* -
* -
*/
const CustomEdge: React.FC<any> = (props) => {
const {
id,
style = {},
markerEnd,
label,
@ -18,7 +24,7 @@ const CustomEdge: React.FC<any> = (props) => {
} = props;
const [isHovered, setIsHovered] = useState(false);
const { getNodes } = useReactFlow();
const { getNodes, setEdges } = useReactFlow();
// 将 label 中的 UUID 转换为节点名
const displayLabel = useMemo(() => {
@ -61,10 +67,26 @@ const CustomEdge: React.FC<any> = (props) => {
const labelBgPadding = [4, 8] as [number, number];
const labelBgBorderRadius = 6;
/**
*
*
*/
const handleDoubleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
setEdges(eds => eds.map(ed => {
if (ed.id !== id) return ed;
// 清除可能存储的自定义路径数据
const { vertices, ...restData } = (ed.data || {}) as any;
return { ...ed, data: restData };
}));
}, [id, setEdges]);
return (
<g
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onDoubleClick={handleDoubleClick}
style={{ cursor: selected ? 'pointer' : 'default' }}
>
<BaseSmartEdge
{...restProps}