(null);
const [showOneDriveForm, setShowOneDriveForm] = useState(false);
// OneDrive Form State (for adding new account)
@@ -322,16 +324,23 @@ export const SettingsPage = ({ storageStats, onSignedOut, onOpenTasksForAccount,
return data;
};
+ useEffect(() => {
+ if (!probeFeedback) return;
+ const timer = window.setTimeout(() => setProbeFeedback(null), 4_000);
+ return () => window.clearTimeout(timer);
+ }, [probeFeedback?.sequence]);
+
const handleProbeAccount = async (account: StorageAccount) => {
if (probingAccountId) return;
setProbingAccountId(account.id);
+ setProbeFeedback(null);
try {
await fileApi.probeStorageAccount(account.id);
await reloadStorageConfig();
- await showNotice(`“${account.name}”连接测试成功`);
+ setProbeFeedback(previous => ({ accountId: account.id, tone: 'success', message: '连接测试成功', sequence: (previous?.sequence ?? 0) + 1 }));
} catch (error: any) {
await reloadStorageConfig().catch(() => undefined);
- await showNotice(error?.message || '存储账户连接测试失败', '连接测试失败');
+ setProbeFeedback(previous => ({ accountId: account.id, tone: 'error', message: error?.message || '连接测试失败,请稍后重试', sequence: (previous?.sequence ?? 0) + 1 }));
} finally {
setProbingAccountId(null);
}
@@ -1234,7 +1243,7 @@ export const SettingsPage = ({ storageStats, onSignedOut, onOpenTasksForAccount,
{account.name || "未命名账户"}
{account.id}
-
void handleProbeAccount(account)} />
+ void handleProbeAccount(account)} />
@@ -1415,7 +1424,7 @@ export const SettingsPage = ({ storageStats, onSignedOut, onOpenTasksForAccount,
{account.name || "未命名账户"}
{account.id}
-
void handleProbeAccount(account)} />
+ void handleProbeAccount(account)} />
@@ -1600,7 +1609,7 @@ export const SettingsPage = ({ storageStats, onSignedOut, onOpenTasksForAccount,
{account.name || "未命名账户"}
{account.id}
-
void handleProbeAccount(account)} />
+ void handleProbeAccount(account)} />
@@ -1786,7 +1795,7 @@ export const SettingsPage = ({ storageStats, onSignedOut, onOpenTasksForAccount,
{account.name || "未命名账户"}
{account.id}
-
void handleProbeAccount(account)} />
+ void handleProbeAccount(account)} />
@@ -1994,7 +2003,7 @@ export const SettingsPage = ({ storageStats, onSignedOut, onOpenTasksForAccount,
{account.name || "未命名账户"}
{account.id}
-
void handleProbeAccount(account)} />
+ void handleProbeAccount(account)} />
diff --git a/frontend/src/components/pages/frontendInteractionPolish.test.ts b/frontend/src/components/pages/frontendInteractionPolish.test.ts
index d31a4a3..b5b21b2 100644
--- a/frontend/src/components/pages/frontendInteractionPolish.test.ts
+++ b/frontend/src/components/pages/frontendInteractionPolish.test.ts
@@ -25,6 +25,16 @@ test('the global header owns the theme switch on every page', () => {
assert.doesNotMatch(settings, /label=\{t\("settings\.general\.theme"\)\}/);
});
+test('storage connection test feedback stays beside the account that triggered it', () => {
+ assert.match(settings, /interface ProbeFeedbackState \{ accountId: string; tone: 'success' \| 'error'; message: string; sequence: number; \}/);
+ assert.match(settings, /probeFeedback\?\.accountId === account\.id \? probeFeedback : null/);
+ assert.match(settings, /role="status" aria-live="polite"/);
+ assert.match(settings, /setProbeFeedback\(previous => \(\{ accountId: account\.id, tone: 'success', message: '连接测试成功', sequence: \(previous\?\.sequence \?\? 0\) \+ 1 \}\)\)/);
+ assert.match(settings, /tone: 'error'/);
+ assert.doesNotMatch(settings, /showNotice\(`“\$\{account\.name\}”连接测试成功`\)/);
+ assert.doesNotMatch(settings, /showNotice\(error\?\.message \|\| '存储账户连接测试失败', '连接测试失败'\)/);
+});
+
test('settings uses non-modal transient feedback for notices but keeps confirmations and prompts modal', () => {
assert.match(settings, /interface ActionNoticeState/);
assert.match(settings, /role="status"/);
@@ -33,7 +43,7 @@ test('settings uses non-modal transient feedback for notices but keeps confirmat
assert.match(settings, /setActionNotice\(\{ title, message, tone:[^\n]*\}\);/);
assert.match(settings, /return Promise\.resolve\(\);/);
assert.doesNotMatch(settings, /window\.location\.reload\(\)/);
- assert.doesNotMatch(settings, /interface ActionNoticeState[\s\S]*resolve\?:/);
+ assert.doesNotMatch(settings, /interface ActionNoticeState\s*\{[^}]*resolve\?:/);
assert.doesNotMatch(settings, /mode:\s*'notice'\s*\|\s*'confirm'\s*\|\s*'prompt'/);
assert.doesNotMatch(settings, /state\.mode === 'notice' \? '知道了'/);
});