|
|
@@ -25,6 +25,11 @@ function App(): JSX.Element {
|
|
|
const [selectedContactDetail, setSelectedContactDetail] = useState<UserContact | null>(null)
|
|
|
const [showSettings, setShowSettings] = useState(false)
|
|
|
const [showChatSearchModal, setShowChatSearchModal] = useState(false)
|
|
|
+ const [showForceUpdateModal, setShowForceUpdateModal] = useState(false)
|
|
|
+ const [updateStatus, setUpdateStatus] = useState<'idle' | 'checking' | 'update-available' | 'update-downloaded' | 'latest' | 'error'>('idle')
|
|
|
+ const [updateError, setUpdateError] = useState('')
|
|
|
+ const [updateDownloadProgress, setUpdateDownloadProgress] = useState<number | null>(null)
|
|
|
+ const [updateInfo, setUpdateInfo] = useState<{ version: string; releaseNotes: string } | null>(null)
|
|
|
const [inputValue, setInputValue] = useState('')
|
|
|
const [contextMenu, setContextMenu] = useState<{ x: number, y: number, msgId: number } | null>(null)
|
|
|
|
|
|
@@ -75,6 +80,49 @@ function App(): JSX.Element {
|
|
|
fetchContacts
|
|
|
})
|
|
|
|
|
|
+ // 监听自动更新事件(检测到更新则必须更新,支持后台下载并显示进度)
|
|
|
+ useEffect(() => {
|
|
|
+ if (!window.electron?.ipcRenderer) return
|
|
|
+ const onUpdateAvailable = (_: any, info: { version?: string; releaseNotes?: string | null }) => {
|
|
|
+ setUpdateStatus('update-available')
|
|
|
+ setUpdateDownloadProgress(0)
|
|
|
+ const notes = info?.releaseNotes != null ? String(info.releaseNotes).trim() : ''
|
|
|
+ setUpdateInfo({
|
|
|
+ version: info?.version ?? '',
|
|
|
+ releaseNotes: notes
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const onUpdateDownloaded = () => {
|
|
|
+ setUpdateStatus('update-downloaded')
|
|
|
+ setUpdateDownloadProgress(null)
|
|
|
+ setShowForceUpdateModal(true)
|
|
|
+ }
|
|
|
+ const onUpdateDownloadProgress = (_: any, info: { percent: number }) => {
|
|
|
+ setUpdateDownloadProgress(info.percent)
|
|
|
+ }
|
|
|
+ const onUpdateNotAvailable = () => {
|
|
|
+ setUpdateStatus('latest')
|
|
|
+ setUpdateDownloadProgress(null)
|
|
|
+ }
|
|
|
+ const onUpdateError = (_: any, message: string) => {
|
|
|
+ setUpdateStatus('error')
|
|
|
+ setUpdateError(message || '检查更新失败')
|
|
|
+ setUpdateDownloadProgress(null)
|
|
|
+ }
|
|
|
+ window.electron.ipcRenderer.on('update-available', onUpdateAvailable)
|
|
|
+ window.electron.ipcRenderer.on('update-downloaded', onUpdateDownloaded)
|
|
|
+ window.electron.ipcRenderer.on('update-download-progress', onUpdateDownloadProgress)
|
|
|
+ window.electron.ipcRenderer.on('update-not-available', onUpdateNotAvailable)
|
|
|
+ window.electron.ipcRenderer.on('update-error', onUpdateError)
|
|
|
+ return () => {
|
|
|
+ window.electron.ipcRenderer.removeAllListeners('update-available')
|
|
|
+ window.electron.ipcRenderer.removeAllListeners('update-downloaded')
|
|
|
+ window.electron.ipcRenderer.removeAllListeners('update-download-progress')
|
|
|
+ window.electron.ipcRenderer.removeAllListeners('update-not-available')
|
|
|
+ window.electron.ipcRenderer.removeAllListeners('update-error')
|
|
|
+ }
|
|
|
+ }, [])
|
|
|
+
|
|
|
// 监听切换联系人事件
|
|
|
useEffect(() => {
|
|
|
const handleSwitchContact = (_: any, contactId: number) => {
|
|
|
@@ -102,6 +150,13 @@ function App(): JSX.Element {
|
|
|
}
|
|
|
}, [activeTab, selectedContactDetail])
|
|
|
|
|
|
+ // 处理发送消息
|
|
|
+ const handleCheckForUpdates = useCallback(() => {
|
|
|
+ setUpdateError('')
|
|
|
+ setUpdateStatus('checking')
|
|
|
+ window.electron?.ipcRenderer?.send('check-for-updates')
|
|
|
+ }, [])
|
|
|
+
|
|
|
// 处理发送消息
|
|
|
const handleSend = useCallback(async () => {
|
|
|
if (!activeContactId) return
|
|
|
@@ -696,8 +751,81 @@ function App(): JSX.Element {
|
|
|
currentUserId={currentUserId}
|
|
|
currentUserName={currentUserName}
|
|
|
onLogout={handleLogout}
|
|
|
+ updateStatus={updateStatus}
|
|
|
+ updateError={updateError}
|
|
|
+ updateDownloadProgress={updateDownloadProgress}
|
|
|
+ onCheckForUpdates={handleCheckForUpdates}
|
|
|
/>
|
|
|
|
|
|
+ {/* 强制更新弹窗:检测到更新后必须重启安装,无取消选项 */}
|
|
|
+ {showForceUpdateModal && (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ position: 'fixed',
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ right: 0,
|
|
|
+ bottom: 0,
|
|
|
+ backgroundColor: 'rgba(0,0,0,0.6)',
|
|
|
+ display: 'flex',
|
|
|
+ alignItems: 'center',
|
|
|
+ justifyContent: 'center',
|
|
|
+ zIndex: 10001
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ backgroundColor: '#fff',
|
|
|
+ padding: '28px 32px',
|
|
|
+ borderRadius: '8px',
|
|
|
+ minWidth: '360px',
|
|
|
+ maxWidth: '420px',
|
|
|
+ boxShadow: '0 4px 20px rgba(0,0,0,0.2)',
|
|
|
+ textAlign: 'center'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div style={{ fontSize: '16px', color: '#333', marginBottom: '24px' }}>
|
|
|
+ 新版本已下载,请重启以完成更新。
|
|
|
+ </div>
|
|
|
+ {updateInfo?.releaseNotes ? (
|
|
|
+ <div
|
|
|
+ style={{
|
|
|
+ textAlign: 'left',
|
|
|
+ fontSize: '14px',
|
|
|
+ color: '#555',
|
|
|
+ marginBottom: '20px',
|
|
|
+ padding: '12px',
|
|
|
+ backgroundColor: '#f5f5f5',
|
|
|
+ borderRadius: '6px',
|
|
|
+ maxHeight: '160px',
|
|
|
+ overflowY: 'auto',
|
|
|
+ whiteSpace: 'pre-wrap',
|
|
|
+ wordBreak: 'break-word'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {updateInfo.releaseNotes}
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
+ <button
|
|
|
+ onClick={() => window.electron?.ipcRenderer?.send('quit-and-install')}
|
|
|
+ style={{
|
|
|
+ width: '100%',
|
|
|
+ padding: '12px 24px',
|
|
|
+ backgroundColor: '#1aad19',
|
|
|
+ color: '#fff',
|
|
|
+ border: 'none',
|
|
|
+ borderRadius: '6px',
|
|
|
+ cursor: 'pointer',
|
|
|
+ fontSize: '15px',
|
|
|
+ fontWeight: '500'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 立即重启并更新
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
{/* 历史搜索弹窗 - 简化版本,可以后续提取 */}
|
|
|
{showChatSearchModal && (
|
|
|
<div
|