From ecf47da81b03c79ab667cfe2aedacca3904e6576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E9=94=8B?= Date: Tue, 10 Feb 2026 21:51:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(connection-modal)?= =?UTF-8?q?:=20=E9=87=8D=E6=9E=84=E8=BF=9E=E6=8E=A5=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=8F=8D=E9=A6=88=E4=BA=A4=E4=BA=92=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=BC=B9=E7=AA=97=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将测试反馈统一收敛到底部状态区展示 - 失败原因改为独立弹窗查看,避免超长文案挤压主界面 - 调整 modal content/body/footer 弹性结构以适配高度变化 --- frontend/src/App.css | 15 ++ frontend/src/components/ConnectionModal.tsx | 164 ++++++++++++++++---- 2 files changed, 145 insertions(+), 34 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 913fa7b..e532c84 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -72,6 +72,21 @@ body[data-theme='dark'] { overflow: hidden !important; } +.connection-modal-wrap .ant-modal-content { + max-height: calc(100vh - 72px); + display: flex; + flex-direction: column; +} + +.connection-modal-wrap .ant-modal-body { + flex: 1 1 auto; + min-height: 0; +} + +.connection-modal-wrap .ant-modal-footer { + flex-shrink: 0; +} + /* Custom Title Bar Close Button Hover */ .titlebar-close-btn:hover { background-color: #ff4d4f !important; diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index 71cc32e..707feaa 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { Modal, Form, Input, InputNumber, Button, message, Checkbox, Divider, Select, Alert, Card, Row, Col, Typography, Collapse, Space, Table, Tag } from 'antd'; -import { DatabaseOutlined, ConsoleSqlOutlined, FileTextOutlined, CloudServerOutlined, AppstoreAddOutlined, CloudOutlined } from '@ant-design/icons'; +import { DatabaseOutlined, ConsoleSqlOutlined, FileTextOutlined, CloudServerOutlined, AppstoreAddOutlined, CloudOutlined, CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; import { useStore } from '../store'; import { DBGetDatabases, MongoDiscoverMembers, TestConnection, RedisConnect } from '../../wailsjs/go/app/App'; import { MongoMemberInfo, SavedConnection } from '../types'; @@ -38,6 +38,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal const [step, setStep] = useState(1); // 1: Select Type, 2: Configure const [activeGroup, setActiveGroup] = useState(0); // Active category index in step 1 const [testResult, setTestResult] = useState<{ type: 'success' | 'error', message: string } | null>(null); + const [testErrorLogOpen, setTestErrorLogOpen] = useState(false); const [dbList, setDbList] = useState([]); const [redisDbList, setRedisDbList] = useState([]); // Redis databases 0-15 const [mongoMembers, setMongoMembers] = useState([]); @@ -443,6 +444,7 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal useEffect(() => { if (open) { setTestResult(null); // Reset test result + setTestErrorLogOpen(false); setDbList([]); setRedisDbList([]); setMongoMembers([]); @@ -569,6 +571,12 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal }, 0); }; + const buildTestFailureMessage = (reason: unknown, fallback: string) => { + const text = String(reason ?? '').trim(); + const normalized = text && text !== 'undefined' && text !== 'null' ? text : fallback; + return `测试失败: ${normalized}`; + }; + const handleTest = async () => { if (testInFlightRef.current) return; testInFlightRef.current = true; @@ -598,10 +606,23 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal } } } else { - setTestResult({ type: 'error', message: "测试失败: " + res.message }); + const failMessage = buildTestFailureMessage( + res?.message, + '连接被拒绝或参数无效,请检查后重试' + ); + setTestResult({ type: 'error', message: failMessage }); } - } catch (e) { - // ignore + } catch (e: unknown) { + if (e && typeof e === 'object' && 'errorFields' in e) { + const failMessage = '测试失败: 请先完善必填项后再测试连接'; + setTestResult({ type: 'error', message: failMessage }); + return; + } + const reason = e instanceof Error + ? e.message + : (typeof e === 'string' ? e : '未知异常'); + const failMessage = buildTestFailureMessage(reason, '未知异常'); + setTestResult({ type: 'error', message: failMessage }); } finally { testInFlightRef.current = false; setLoading(false); @@ -900,7 +921,10 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal mongoReplicaPassword: '', }} onValuesChange={(changed) => { - if (testResult) setTestResult(null); // Clear result on change + if (testResult) { + setTestResult(null); // Clear result on change + setTestErrorLogOpen(false); + } if (changed.useSSH !== undefined) setUseSSH(changed.useSSH); // Type change handled by step 1, but keep sync if select changes (hidden now) if (changed.type !== undefined) setDbType(changed.type); @@ -1233,14 +1257,6 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal )} - {testResult && ( - - )} ); @@ -1250,12 +1266,59 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal ]; } - return [ - !initialValues && , - , - , - - ]; + const isTestSuccess = testResult?.type === 'success'; + const hasTestError = !!testResult && !isTestSuccess; + return ( +
+
+ {!initialValues && } + {testResult ? ( + + {isTestSuccess ? : } + {isTestSuccess ? '连接成功' : '连接失败'} + + ) : null} + {hasTestError && ( + + )} +
+ + + + + +
+ ); }; const getTitle = () => { @@ -1268,26 +1331,59 @@ const ConnectionModal: React.FC<{ open: boolean; onClose: () => void; initialVal ? { padding: '16px 24px', overflow: 'hidden' as const } : { padding: '16px 24px', - maxHeight: 'calc(100vh - 220px)', overflowY: 'auto' as const, overflowX: 'hidden' as const, }; return ( - - {step === 1 ? renderStep1() : renderStep2()} - + <> + + {step === 1 ? renderStep1() : renderStep2()} + + setTestErrorLogOpen(false)} + centered + width={760} + zIndex={10002} + destroyOnHidden + footer={[ + , + ]} + > +
+              {String(testResult?.message || '暂无失败日志')}
+          
+
+ ); };