实现完美的浮动通知系统
- 添加自定义通知组件替代阻塞式alert - 浮动定位不影响页面布局,宽度自适应内容 - 支持成功/错误两种样式,渐变背景+阴影效果 - 实现完整的淡入淡出动画,原地显示隐藏 - 重启提示显示4秒,普通操作反馈2-3秒 - 智能定时器管理,支持动画完成后清理 用户体验:切换供应商后优雅提示"请重启Claude Code终端以生效"
This commit is contained in:
@@ -89,3 +89,63 @@
|
||||
.browse-btn:hover {
|
||||
background: #2980b9;
|
||||
}
|
||||
|
||||
/* 供应商列表区域 - 相对定位容器 */
|
||||
.provider-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 浮动通知 - 绝对定位,不占据空间 */
|
||||
.notification-floating {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
|
||||
width: fit-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
animation: fadeOut 0.3s ease-out;
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
|
||||
}
|
||||
|
||||
.notification-error {
|
||||
background: linear-gradient(135deg, #e74c3c 0%, #ec7063 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.3);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { Provider } from '../shared/types'
|
||||
import ProviderList from './components/ProviderList'
|
||||
import AddProviderModal from './components/AddProviderModal'
|
||||
@@ -11,6 +11,31 @@ function App() {
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false)
|
||||
const [configPath, setConfigPath] = useState<string>('')
|
||||
const [editingProviderId, setEditingProviderId] = useState<string | null>(null)
|
||||
const [notification, setNotification] = useState<{ message: string; type: 'success' | 'error' } | null>(null)
|
||||
const [isNotificationVisible, setIsNotificationVisible] = useState(false)
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
// 设置通知的辅助函数
|
||||
const showNotification = (message: string, type: 'success' | 'error', duration = 3000) => {
|
||||
// 清除之前的定时器
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
|
||||
// 立即显示通知
|
||||
setNotification({ message, type })
|
||||
setIsNotificationVisible(true)
|
||||
|
||||
// 设置淡出定时器
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setIsNotificationVisible(false)
|
||||
// 等待淡出动画完成后清除通知
|
||||
setTimeout(() => {
|
||||
setNotification(null)
|
||||
timeoutRef.current = null
|
||||
}, 300) // 与CSS动画时间匹配
|
||||
}, duration)
|
||||
}
|
||||
|
||||
// 加载供应商列表
|
||||
useEffect(() => {
|
||||
@@ -18,6 +43,15 @@ function App() {
|
||||
loadConfigPath()
|
||||
}, [])
|
||||
|
||||
// 清理定时器
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
const loadProviders = async () => {
|
||||
const loadedProviders = await window.electronAPI.getProviders()
|
||||
@@ -58,10 +92,10 @@ function App() {
|
||||
const success = await window.electronAPI.switchProvider(id)
|
||||
if (success) {
|
||||
setCurrentProviderId(id)
|
||||
// 移除阻塞式alert
|
||||
console.log('供应商切换成功')
|
||||
// 显示重启提示,时间更长
|
||||
showNotification('切换成功!请重启 Claude Code 终端以生效', 'success', 4000)
|
||||
} else {
|
||||
console.error('切换失败,请检查配置')
|
||||
showNotification('切换失败,请检查配置', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,17 +104,12 @@ function App() {
|
||||
await window.electronAPI.updateProvider(provider)
|
||||
await loadProviders()
|
||||
setEditingProviderId(null)
|
||||
// 移除阻塞式alert,避免焦点管理问题
|
||||
setTimeout(() => {
|
||||
console.log('供应商更新成功')
|
||||
}, 100)
|
||||
// 显示编辑成功提示,时间较短
|
||||
showNotification('供应商配置已保存', 'success', 2000)
|
||||
} catch (error) {
|
||||
console.error('更新供应商失败:', error)
|
||||
setEditingProviderId(null)
|
||||
// 错误情况下也避免alert
|
||||
setTimeout(() => {
|
||||
console.error('保存失败,请重试')
|
||||
}, 100)
|
||||
showNotification('保存失败,请重试', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +135,22 @@ function App() {
|
||||
</header>
|
||||
|
||||
<main className="app-main">
|
||||
<ProviderList
|
||||
<div className="provider-section">
|
||||
{/* 浮动通知组件 */}
|
||||
{notification && (
|
||||
<div className={`notification-floating ${notification.type === 'error' ? 'notification-error' : 'notification-success'} ${isNotificationVisible ? 'fade-in' : 'fade-out'}`}>
|
||||
{notification.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProviderList
|
||||
providers={providers}
|
||||
currentProviderId={currentProviderId}
|
||||
onSwitch={handleSwitchProvider}
|
||||
onDelete={handleDeleteProvider}
|
||||
onEdit={setEditingProviderId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{configPath && (
|
||||
<div className="config-path">
|
||||
|
||||
Reference in New Issue
Block a user