重构前端逻辑
This commit is contained in:
parent
ccc8bc591e
commit
3b77cf7539
@ -50,7 +50,6 @@ interface CustomNodeData {
|
||||
endTime?: string | null;
|
||||
duration?: number | null;
|
||||
errorMessage?: string | null;
|
||||
isUnreachable?: boolean;
|
||||
processInstanceId?: string;
|
||||
onViewLog?: (nodeId: string, nodeName: string) => void;
|
||||
}
|
||||
@ -59,7 +58,7 @@ interface CustomNodeData {
|
||||
* 自定义流程节点组件
|
||||
*/
|
||||
const CustomFlowNode: React.FC<any> = ({ data }) => {
|
||||
const { nodeName, nodeType, status, startTime, endTime, duration, errorMessage, isUnreachable } = data as CustomNodeData;
|
||||
const { nodeName, nodeType, status, startTime, endTime, duration, errorMessage } = data as CustomNodeData;
|
||||
const statusColor = getNodeStatusColor(status);
|
||||
const isNotStarted = status === 'NOT_STARTED';
|
||||
const isRunning = status === 'RUNNING';
|
||||
@ -89,7 +88,6 @@ const CustomFlowNode: React.FC<any> = ({ data }) => {
|
||||
isNotStarted && 'border-2 border-dashed',
|
||||
!isNotStarted && 'border-2 border-solid shadow-sm',
|
||||
isRunning && 'animate-pulse',
|
||||
isUnreachable && 'opacity-40', // 不可达节点半透明
|
||||
canViewLog && 'cursor-pointer hover:shadow-md hover:scale-[1.02]' // 可查看日志的节点增加交互效果
|
||||
)}
|
||||
style={{
|
||||
@ -461,123 +459,95 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
|
||||
return map;
|
||||
}, [flowData]);
|
||||
|
||||
// 计算可达节点(从已执行节点出发能到达的节点)
|
||||
const reachableNodes = useMemo(() => {
|
||||
if (!flowData?.graph?.edges || !flowData?.nodeInstances) return new Set<string>();
|
||||
// 智能过滤:只显示实际执行路径 + 到终点的路径
|
||||
const visibleNodeIds = useMemo(() => {
|
||||
if (!flowData?.graph?.nodes || !flowData?.graph?.edges || !flowData?.nodeInstances) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
const executedNodeIds = flowData.nodeInstances
|
||||
.filter(ni => ni.status !== 'NOT_STARTED')
|
||||
.map(ni => ni.nodeId);
|
||||
const visible = new Set<string>();
|
||||
|
||||
if (executedNodeIds.length === 0) return new Set<string>();
|
||||
// 1. 收集所有已执行的节点(id !== null 表示后端创建了实例)
|
||||
const executedNodes = flowData.nodeInstances.filter(ni => ni.id !== null);
|
||||
executedNodes.forEach(ni => visible.add(ni.nodeId));
|
||||
|
||||
const reachable = new Set<string>(executedNodeIds);
|
||||
const queue = [...executedNodeIds];
|
||||
// 如果没有执行任何节点,显示所有节点(初始状态)
|
||||
if (executedNodes.length === 0) {
|
||||
flowData.graph.nodes.forEach(n => visible.add(n.id));
|
||||
return visible;
|
||||
}
|
||||
|
||||
// 2. 找出最后执行的节点(按时间排序)
|
||||
const lastExecutedNode = [...executedNodes].sort((a, b) => {
|
||||
const timeA = a.startTime ? new Date(a.startTime.replace(' ', 'T')).getTime() : 0;
|
||||
const timeB = b.startTime ? new Date(b.startTime.replace(' ', 'T')).getTime() : 0;
|
||||
return timeB - timeA; // 降序
|
||||
})[0];
|
||||
|
||||
// 3. 找出所有终点节点(没有出边的节点,通常是 END_EVENT)
|
||||
const allNodeIds = new Set(flowData.graph.nodes.map(n => n.id));
|
||||
const nodesWithOutgoingEdges = new Set(flowData.graph.edges.map(e => e.from));
|
||||
const endNodes = flowData.graph.nodes.filter(n => !nodesWithOutgoingEdges.has(n.id));
|
||||
|
||||
// 4. BFS 从最后执行的节点到所有终点节点的路径
|
||||
if (lastExecutedNode && endNodes.length > 0) {
|
||||
const queue = [lastExecutedNode.nodeId];
|
||||
const visited = new Set<string>([lastExecutedNode.nodeId]);
|
||||
|
||||
// BFS 遍历所有可达节点
|
||||
while (queue.length > 0) {
|
||||
const currentId = queue.shift()!;
|
||||
const outgoingEdges = flowData.graph.edges.filter(e => e.from === currentId);
|
||||
|
||||
for (const edge of outgoingEdges) {
|
||||
if (!reachable.has(edge.to)) {
|
||||
reachable.add(edge.to);
|
||||
if (!visited.has(edge.to)) {
|
||||
visited.add(edge.to);
|
||||
visible.add(edge.to); // 添加到可见集合
|
||||
queue.push(edge.to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reachable;
|
||||
return visible;
|
||||
}, [flowData]);
|
||||
|
||||
// 转换为 React Flow 节点(显示所有节点,但对不可达节点置灰)
|
||||
// 转换为 React Flow 节点(只显示可见节点)
|
||||
const flowNodes: Node[] = useMemo(() => {
|
||||
if (!flowData?.graph?.nodes || !flowData?.nodeInstances) return [];
|
||||
|
||||
const isRunning = flowData.runningNodeCount > 0;
|
||||
|
||||
// 按执行顺序排序已执行的节点
|
||||
const executedInstances = flowData.nodeInstances
|
||||
.filter(ni => ni.status !== 'NOT_STARTED')
|
||||
.sort((a, b) => {
|
||||
const timeA = a.startTime ? new Date(a.startTime.replace(' ', 'T')).getTime() : 0;
|
||||
const timeB = b.startTime ? new Date(b.startTime.replace(' ', 'T')).getTime() : 0;
|
||||
return timeA - timeB;
|
||||
});
|
||||
|
||||
// 线性布局:从左到右排列
|
||||
const nodePositionMap = new Map<string, { x: number; y: number }>();
|
||||
const horizontalSpacing = 300; // 水平间距
|
||||
const verticalSpacing = 150; // 垂直间距(用于分支)
|
||||
const startX = 100; // 起始X坐标
|
||||
const startY = 150; // 起始Y坐标
|
||||
|
||||
// 已执行的节点:从左到右线性排列
|
||||
executedInstances.forEach((instance, index) => {
|
||||
nodePositionMap.set(instance.nodeId, {
|
||||
x: startX + index * horizontalSpacing,
|
||||
y: startY,
|
||||
});
|
||||
});
|
||||
|
||||
// 未执行的节点:根据在图中的位置和层级布局
|
||||
const notStartedNodes = flowData.graph.nodes.filter(
|
||||
node => !nodePositionMap.has(node.id)
|
||||
);
|
||||
|
||||
// 简单布局:按节点在edges中的出现顺序,从左到右、上到下排列
|
||||
let currentX = startX + executedInstances.length * horizontalSpacing;
|
||||
let currentRow = 0;
|
||||
const nodesPerRow = 3;
|
||||
|
||||
notStartedNodes.forEach((node, index) => {
|
||||
const row = Math.floor(index / nodesPerRow);
|
||||
const col = index % nodesPerRow;
|
||||
nodePositionMap.set(node.id, {
|
||||
x: currentX + col * horizontalSpacing,
|
||||
y: startY + row * verticalSpacing,
|
||||
});
|
||||
});
|
||||
|
||||
// 生成所有节点
|
||||
return flowData.graph.nodes.map((node) => {
|
||||
// 过滤并转换为 React Flow 节点
|
||||
return flowData.graph.nodes
|
||||
.filter(node => visibleNodeIds.has(node.id)) // 只显示可见节点
|
||||
.map((node) => {
|
||||
const instance = nodeInstanceMap.get(node.id);
|
||||
const position = nodePositionMap.get(node.id) || { x: 0, y: 0 };
|
||||
const isReachable = reachableNodes.has(node.id);
|
||||
const isNotStarted = !instance || instance.status === 'NOT_STARTED';
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
type: 'custom',
|
||||
position,
|
||||
position: node.position || { x: 0, y: 0 }, // 使用设计器中保存的坐标
|
||||
data: {
|
||||
nodeName: node.nodeName,
|
||||
nodeType: node.nodeType,
|
||||
nodeType: instance?.nodeType || node.nodeType, // 优先使用运行时的 nodeType
|
||||
nodeId: node.id,
|
||||
status: instance?.status || 'NOT_STARTED',
|
||||
startTime: instance?.startTime,
|
||||
endTime: instance?.endTime,
|
||||
duration: instance?.duration,
|
||||
errorMessage: instance?.errorMessage,
|
||||
// 新增:不可达且未执行的节点标记为置灰
|
||||
isUnreachable: isRunning && isNotStarted && !isReachable,
|
||||
processInstanceId: flowData.processInstanceId,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [flowData, nodeInstanceMap, reachableNodes]);
|
||||
}, [flowData, nodeInstanceMap, visibleNodeIds]);
|
||||
|
||||
// 转换为 React Flow 边
|
||||
// 转换为 React Flow 边(只显示连接可见节点的边)
|
||||
const flowEdges: Edge[] = useMemo(() => {
|
||||
if (!flowData?.graph?.edges) return [];
|
||||
|
||||
const isRunning = flowData.runningNodeCount > 0;
|
||||
const displayedNodeIds = new Set(flowNodes.map(n => n.id));
|
||||
|
||||
return flowData.graph.edges
|
||||
.filter(edge => {
|
||||
// 只显示连接已显示节点的边
|
||||
return displayedNodeIds.has(edge.from) && displayedNodeIds.has(edge.to);
|
||||
// 只显示连接可见节点的边
|
||||
return visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to);
|
||||
})
|
||||
.map((edge, index) => {
|
||||
const source = edge.from;
|
||||
@ -587,20 +557,21 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
|
||||
const sourceStatus = sourceInstance?.status || 'NOT_STARTED';
|
||||
const targetStatus = targetInstance?.status || 'NOT_STARTED';
|
||||
|
||||
// 判断这条边是否在可达路径上
|
||||
const isReachableEdge = reachableNodes.has(source) && reachableNodes.has(target);
|
||||
|
||||
// 根据节点状态确定边的样式
|
||||
let strokeColor = '#d1d5db'; // 默认灰色
|
||||
let strokeWidth = 2;
|
||||
let animated = false;
|
||||
let opacity = 1;
|
||||
let strokeDasharray: string | undefined = undefined;
|
||||
|
||||
// 源节点已完成 + 目标节点也已完成/运行中 = 绿色实线
|
||||
if (sourceStatus === 'COMPLETED' && (targetStatus === 'COMPLETED' || targetStatus === 'RUNNING')) {
|
||||
strokeColor = '#10b981'; // 绿色
|
||||
}
|
||||
// 源节点失败 = 红色
|
||||
// 源节点 TERMINATED = 橙色实线
|
||||
else if (sourceStatus === 'TERMINATED') {
|
||||
strokeColor = '#f59e0b'; // 橙色
|
||||
}
|
||||
// 源节点失败 = 红色实线
|
||||
else if (sourceStatus === 'FAILED') {
|
||||
strokeColor = '#ef4444'; // 红色
|
||||
}
|
||||
@ -609,31 +580,22 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
|
||||
strokeColor = '#3b82f6'; // 蓝色
|
||||
animated = true;
|
||||
}
|
||||
// 源节点完成 + 目标节点未开始
|
||||
else if (sourceStatus === 'COMPLETED' && targetStatus === 'NOT_STARTED') {
|
||||
if (isReachableEdge && isRunning) {
|
||||
strokeColor = '#9ca3af'; // 浅灰色(即将执行)
|
||||
} else {
|
||||
strokeColor = '#d1d5db'; // 默认灰色
|
||||
opacity = 0.3; // 不可达路径半透明
|
||||
}
|
||||
}
|
||||
// 两端都未执行
|
||||
else if (sourceStatus === 'NOT_STARTED' && targetStatus === 'NOT_STARTED') {
|
||||
opacity = isRunning && !isReachableEdge ? 0.3 : 0.5;
|
||||
// 源节点完成 + 目标节点未开始 = 虚线(即将执行的路径)
|
||||
else if ((sourceStatus === 'COMPLETED' || sourceStatus === 'TERMINATED') && targetStatus === 'NOT_STARTED') {
|
||||
strokeColor = '#9ca3af'; // 浅灰色
|
||||
strokeDasharray = '5,5';
|
||||
}
|
||||
|
||||
return {
|
||||
id: edge.id || `edge-${source}-${target}-${index}`,
|
||||
source,
|
||||
target,
|
||||
type: 'smoothstep',
|
||||
type: 'straight', // 使用直线类型
|
||||
animated,
|
||||
style: {
|
||||
stroke: strokeColor,
|
||||
strokeWidth,
|
||||
opacity,
|
||||
strokeDasharray: (sourceStatus === 'COMPLETED' && targetStatus === 'NOT_STARTED' && isReachableEdge) ? '5,5' : undefined,
|
||||
strokeDasharray,
|
||||
},
|
||||
markerEnd: {
|
||||
type: 'arrowclosed',
|
||||
@ -643,7 +605,7 @@ export const DeployFlowGraphModal: React.FC<DeployFlowGraphModalProps> = ({
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [flowData, nodeInstanceMap, flowNodes, reachableNodes]);
|
||||
}, [flowData, nodeInstanceMap, visibleNodeIds]);
|
||||
|
||||
// 获取部署状态信息
|
||||
const deployStatusInfo = flowData
|
||||
|
||||
@ -151,35 +151,49 @@ const DeployNodeLogDialog: React.FC<DeployNodeLogDialogProps> = ({
|
||||
<ScrollArea className="flex-1 border rounded-md bg-gray-50" ref={scrollAreaRef}>
|
||||
<div className="p-2 font-mono text-xs">
|
||||
{logData?.logs && logData.logs.length > 0 ? (
|
||||
logData.logs.map((log, index) => (
|
||||
logData.logs.map((log, index) => {
|
||||
// 计算行号宽度
|
||||
const lineNumWidth = Math.max(4, String(logData.logs.length).length + 1);
|
||||
|
||||
// 格式化时间戳为可读格式:2025-11-07 16:27:41.494
|
||||
const timestamp = dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||
|
||||
return (
|
||||
<div
|
||||
key={log.sequenceId}
|
||||
className="flex items-start hover:bg-gray-200 px-2 py-0.5 whitespace-nowrap"
|
||||
className="flex items-start hover:bg-gray-100 px-2 py-0.5 whitespace-nowrap"
|
||||
>
|
||||
{/* 行号 - 根据总行数动态调整宽度 */}
|
||||
{/* 行号 - 动态宽度,右对齐 */}
|
||||
<span
|
||||
className="text-muted-foreground flex-shrink-0 text-right pr-3 select-none"
|
||||
style={{ width: `${Math.max(3, String(logData.logs.length).length)}ch` }}
|
||||
className="text-gray-400 flex-shrink-0 text-right select-none"
|
||||
style={{ width: `${lineNumWidth}ch`, marginRight: '1ch' }}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
{/* 时间 - 18个字符宽度 */}
|
||||
<span className="text-muted-foreground flex-shrink-0 pr-2" style={{ width: '18ch' }}>
|
||||
{dayjs(log.timestamp).format('MM-DD HH:mm:ss.SSS')}
|
||||
</span>
|
||||
{/* 级别 - 5个字符宽度 */}
|
||||
|
||||
{/* 时间戳 - 可读格式,23个字符 (2025-11-07 16:27:41.494) */}
|
||||
<span
|
||||
className={cn('flex-shrink-0 font-semibold pr-2', getLevelClass(log.level))}
|
||||
style={{ width: '5ch' }}
|
||||
className="text-gray-600 flex-shrink-0"
|
||||
style={{ width: '23ch', marginRight: '2ch' }}
|
||||
>
|
||||
{timestamp}
|
||||
</span>
|
||||
|
||||
{/* 日志级别 - 5个字符,右对齐 */}
|
||||
<span
|
||||
className={cn('flex-shrink-0 font-semibold text-right', getLevelClass(log.level))}
|
||||
style={{ width: '5ch', marginRight: '2ch' }}
|
||||
>
|
||||
{log.level}
|
||||
</span>
|
||||
{/* 日志内容 - 不换行,支持水平滚动 */}
|
||||
|
||||
{/* 日志消息 - 占据剩余空间,不换行 */}
|
||||
<span className="flex-1 text-gray-800 whitespace-nowrap overflow-x-auto">
|
||||
{log.message}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
|
||||
<Clock className="h-12 w-12 mb-4 text-muted-foreground/30" />
|
||||
|
||||
@ -214,7 +214,7 @@ const ApplicationList: React.FC = () => {
|
||||
{
|
||||
accessorKey: 'appCode',
|
||||
header: '应用编码',
|
||||
size: 150,
|
||||
size: 200,
|
||||
},
|
||||
{
|
||||
accessorKey: 'appName',
|
||||
@ -249,7 +249,7 @@ const ApplicationList: React.FC = () => {
|
||||
{
|
||||
id: 'repository',
|
||||
header: '代码仓库',
|
||||
size: 200,
|
||||
size: 280,
|
||||
cell: ({ row }) => {
|
||||
const project = row.original.repositoryProject;
|
||||
return project ? (
|
||||
|
||||
@ -39,6 +39,7 @@ interface TeamApplicationDialogProps {
|
||||
applications: Application[]; // 可选择的应用列表
|
||||
jenkinsSystems: any[];
|
||||
workflowDefinitions: WorkflowDefinition[];
|
||||
existingApplicationIds?: number[]; // 已添加的应用ID列表(用于过滤)
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSave: (data: {
|
||||
id?: number;
|
||||
@ -62,6 +63,7 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
applications,
|
||||
jenkinsSystems,
|
||||
workflowDefinitions,
|
||||
existingApplicationIds = [],
|
||||
onOpenChange,
|
||||
onSave,
|
||||
onLoadBranches,
|
||||
@ -209,11 +211,7 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
});
|
||||
onOpenChange(false);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: mode === 'edit' ? '保存失败' : '添加失败',
|
||||
description: error.response?.data?.message || error.message,
|
||||
});
|
||||
// 错误已经在 request.ts 中通过 toast 显示了,这里不需要再显示
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@ -254,17 +252,24 @@ const TeamApplicationDialog: React.FC<TeamApplicationDialogProps> = ({
|
||||
<SelectValue placeholder="选择应用" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{applications.length === 0 ? (
|
||||
{(() => {
|
||||
// 在新建模式下,过滤掉已添加的应用
|
||||
const availableApps = mode === 'create'
|
||||
? applications.filter(app => !existingApplicationIds.includes(app.id))
|
||||
: applications;
|
||||
|
||||
return availableApps.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
暂无应用
|
||||
{mode === 'create' ? '暂无可添加的应用' : '暂无应用'}
|
||||
</div>
|
||||
) : (
|
||||
applications.map((app) => (
|
||||
availableApps.map((app) => (
|
||||
<SelectItem key={app.id} value={app.id.toString()}>
|
||||
{app.appName}({app.appCode})
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{mode === 'edit' && (
|
||||
|
||||
@ -246,7 +246,9 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
{teamApplications.map((app) => (
|
||||
<TableRow key={app.id}>
|
||||
<TableCell className="font-medium">
|
||||
{app.applicationName || `应用 ${app.applicationId}`}
|
||||
{app.applicationName && app.applicationCode
|
||||
? `${app.applicationName}(${app.applicationCode})`
|
||||
: app.applicationName || app.applicationCode || `应用 ${app.applicationId}`}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{getEnvironmentName(app.environmentId)}
|
||||
@ -257,10 +259,7 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
</TableCell>
|
||||
<TableCell>{app.deployJob || '-'}</TableCell>
|
||||
<TableCell>
|
||||
{app.workflowDefinitionName ||
|
||||
(app.workflowDefinitionId
|
||||
? `工作流 ${app.workflowDefinitionId}`
|
||||
: '-')}
|
||||
{app.workflowDefinitionName || '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
@ -309,6 +308,12 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
applications={applications}
|
||||
jenkinsSystems={jenkinsSystems}
|
||||
workflowDefinitions={workflowDefinitions}
|
||||
existingApplicationIds={
|
||||
// 传递当前环境已添加的应用ID列表
|
||||
teamApplications
|
||||
.filter(app => app.environmentId === editingEnvironment.id)
|
||||
.map(app => app.applicationId)
|
||||
}
|
||||
onSave={handleSaveApplication}
|
||||
onLoadBranches={handleLoadBranches}
|
||||
onLoadJenkinsJobs={handleLoadJenkinsJobs}
|
||||
@ -320,7 +325,11 @@ export const TeamApplicationManageDialog: React.FC<
|
||||
open={deleteDialogOpen}
|
||||
onOpenChange={setDeleteDialogOpen}
|
||||
title="确认删除"
|
||||
description={`确定要删除应用配置 "${deletingApp?.applicationName || `应用 ${deletingApp?.applicationId}`}" 吗?此操作无法撤销。`}
|
||||
description={`确定要删除应用配置 "${
|
||||
deletingApp?.applicationName && deletingApp?.applicationCode
|
||||
? `${deletingApp.applicationName}(${deletingApp.applicationCode})`
|
||||
: deletingApp?.applicationName || deletingApp?.applicationCode || `应用 ${deletingApp?.applicationId}`
|
||||
}" 吗?此操作无法撤销。`}
|
||||
item={deletingApp}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onSuccess={() => {
|
||||
|
||||
@ -63,7 +63,7 @@ const formSchema = z.object({
|
||||
environmentId: z.number().min(1, '请选择环境'),
|
||||
approvalRequired: z.boolean().default(false),
|
||||
approverUserIds: z.array(z.number()).default([]),
|
||||
notificationChannelId: z.number().optional(),
|
||||
notificationChannelId: z.number().nullish(),
|
||||
notificationEnabled: z.boolean().default(false),
|
||||
requireCodeReview: z.boolean().default(false),
|
||||
remark: z.string().max(100, '备注最多100个字符').optional(),
|
||||
@ -253,6 +253,7 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
};
|
||||
|
||||
const handleSubmit = async (data: FormData) => {
|
||||
console.log('表单提交开始', data);
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const payload = {
|
||||
@ -266,6 +267,8 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
remark: data.remark,
|
||||
};
|
||||
|
||||
console.log('提交数据', payload);
|
||||
|
||||
if (configId) {
|
||||
// 更新已有配置
|
||||
await updateTeamEnvironmentConfig(configId, payload);
|
||||
@ -285,6 +288,7 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
onOpenChange(false);
|
||||
onSuccess?.();
|
||||
} catch (error: any) {
|
||||
console.error('保存失败', error);
|
||||
toast({
|
||||
title: '保存失败',
|
||||
description: error.message || '保存环境配置失败',
|
||||
@ -295,6 +299,13 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveClick = () => {
|
||||
console.log('保存按钮被点击');
|
||||
console.log('表单值', form.getValues());
|
||||
console.log('表单错误', form.formState.errors);
|
||||
form.handleSubmit(handleSubmit)();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
@ -304,17 +315,16 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody>
|
||||
{loading ? (
|
||||
<DialogBody>
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
</DialogBody>
|
||||
) : (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(handleSubmit)}
|
||||
className="space-y-6"
|
||||
>
|
||||
<DialogBody>
|
||||
<div className="space-y-6">
|
||||
{/* 环境选择 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
@ -383,6 +393,7 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
@ -409,7 +420,8 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
field.onChange(field.value?.filter(id => id !== user.id) || []);
|
||||
}}
|
||||
>
|
||||
@ -571,13 +583,12 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={submitting}
|
||||
@ -585,15 +596,17 @@ export const TeamEnvironmentConfigDialog: React.FC<
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={form.handleSubmit(handleSubmit)}
|
||||
disabled={submitting}
|
||||
type="button"
|
||||
onClick={handleSaveClick}
|
||||
disabled={submitting || loading}
|
||||
>
|
||||
{submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
保存
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ const FormDataDetail: React.FC = () => {
|
||||
|
||||
// 返回列表
|
||||
const handleBack = () => {
|
||||
navigate('/workflow/form/data');
|
||||
navigate(-1); // 返回上一页,避免硬编码路径
|
||||
};
|
||||
|
||||
// 状态徽章
|
||||
|
||||
@ -81,7 +81,7 @@ const FormDesignerPage: React.FC = () => {
|
||||
|
||||
// 返回列表
|
||||
const handleBack = () => {
|
||||
navigate('/workflow/form');
|
||||
navigate(-1); // 返回上一页,避免硬编码路径
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -180,7 +180,7 @@ const WorkflowDesignInner: React.FC = () => {
|
||||
}, [getNodes, getEdges, saveWorkflow, currentWorkflowId, workflowTitle, workflowDefinition]);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
navigate('/workflow/definition');
|
||||
navigate(-1); // 返回上一页,避免硬编码路径
|
||||
}, [navigate]);
|
||||
|
||||
// 预览表单
|
||||
|
||||
Loading…
Reference in New Issue
Block a user