mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
优化图片窗口的渲染;修复实时管道的问题;优化了图片密钥相关配置流程
This commit is contained in:
@@ -660,17 +660,14 @@ function createImageViewerWindow(imagePath: string, liveVideoPath?: string) {
|
||||
nodeIntegration: false,
|
||||
webSecurity: false // 允许加载本地文件
|
||||
},
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
color: '#00000000',
|
||||
symbolColor: '#ffffff',
|
||||
height: 40
|
||||
},
|
||||
frame: false,
|
||||
show: false,
|
||||
backgroundColor: '#000000',
|
||||
autoHideMenuBar: true
|
||||
})
|
||||
|
||||
setupCustomTitleBarWindow(win)
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
win.show()
|
||||
})
|
||||
|
||||
@@ -367,6 +367,8 @@ class ChatService {
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热 media 数据库列表缓存(后台异步执行)
|
||||
|
||||
@@ -190,8 +190,7 @@ export class WcdbCore {
|
||||
setTimeout(() => {
|
||||
if (!this.monitorCallback) return
|
||||
|
||||
this.monitorPipeClient = net.createConnection(this.monitorPipePath, () => {
|
||||
})
|
||||
this.monitorPipeClient = net.createConnection(this.monitorPipePath, () => {})
|
||||
|
||||
let buffer = ''
|
||||
this.monitorPipeClient.on('data', (data: Buffer) => {
|
||||
@@ -210,8 +209,7 @@ export class WcdbCore {
|
||||
}
|
||||
})
|
||||
|
||||
this.monitorPipeClient.on('error', () => {
|
||||
})
|
||||
this.monitorPipeClient.on('error', () => {})
|
||||
|
||||
this.monitorPipeClient.on('close', () => {
|
||||
this.monitorPipeClient = null
|
||||
|
||||
@@ -46,7 +46,6 @@ export function GlobalSessionMonitor() {
|
||||
return () => {
|
||||
removeListener()
|
||||
}
|
||||
} else {
|
||||
}
|
||||
return () => { }
|
||||
}, [])
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.title-sidebar-toggle {
|
||||
@@ -90,3 +92,55 @@
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.image-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-right: auto;
|
||||
padding-left: 16px;
|
||||
-webkit-app-region: no-drag;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.live-play-btn.active {
|
||||
background: rgba(var(--primary-rgb, 76, 132, 255), 0.16);
|
||||
color: var(--primary, #4c84ff);
|
||||
}
|
||||
}
|
||||
|
||||
.scale-text {
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
background: var(--border-color);
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,17 @@ interface TitleBarProps {
|
||||
sidebarCollapsed?: boolean
|
||||
onToggleSidebar?: () => void
|
||||
showWindowControls?: boolean
|
||||
customControls?: React.ReactNode
|
||||
showLogo?: boolean
|
||||
}
|
||||
|
||||
function TitleBar({
|
||||
title,
|
||||
sidebarCollapsed = false,
|
||||
onToggleSidebar,
|
||||
showWindowControls = true
|
||||
showWindowControls = true,
|
||||
customControls,
|
||||
showLogo = true
|
||||
}: TitleBarProps = {}) {
|
||||
const [isMaximized, setIsMaximized] = useState(false)
|
||||
|
||||
@@ -32,7 +36,7 @@ function TitleBar({
|
||||
return (
|
||||
<div className="title-bar">
|
||||
<div className="title-brand">
|
||||
<img src="./logo.png" alt="WeFlow" className="title-logo" />
|
||||
{showLogo && <img src="./logo.png" alt="WeFlow" className="title-logo" />}
|
||||
<span className="titles">{title || 'WeFlow'}</span>
|
||||
{onToggleSidebar ? (
|
||||
<button
|
||||
@@ -46,6 +50,7 @@ function TitleBar({
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{customControls}
|
||||
{showWindowControls ? (
|
||||
<div className="title-window-controls">
|
||||
<button
|
||||
|
||||
@@ -2981,8 +2981,8 @@ function ChatPage(props: ChatPageProps) {
|
||||
if (hasFoldedGroups && !visible.some(s => s.username.toLowerCase().includes('placeholder_foldgroup'))) {
|
||||
// 找到最新的折叠消息
|
||||
const latestFolded = foldedGroups.reduce((latest, current) => {
|
||||
const latestTime = latest.sortTimestamp || latest.lastTimestamp || latest.timestamp
|
||||
const currentTime = current.sortTimestamp || current.lastTimestamp || current.timestamp
|
||||
const latestTime = latest.sortTimestamp || latest.lastTimestamp
|
||||
const currentTime = current.sortTimestamp || current.lastTimestamp
|
||||
return currentTime > latestTime ? current : latest
|
||||
})
|
||||
|
||||
@@ -2990,19 +2990,19 @@ function ChatPage(props: ChatPageProps) {
|
||||
username: 'placeholder_foldgroup',
|
||||
displayName: '折叠的聊天',
|
||||
summary: `${latestFolded.displayName || latestFolded.username}: ${latestFolded.summary}`,
|
||||
timestamp: latestFolded.timestamp,
|
||||
sortTimestamp: latestFolded.sortTimestamp || latestFolded.lastTimestamp || latestFolded.timestamp,
|
||||
lastTimestamp: latestFolded.lastTimestamp || latestFolded.sortTimestamp || latestFolded.timestamp,
|
||||
type: 0,
|
||||
sortTimestamp: latestFolded.sortTimestamp || latestFolded.lastTimestamp,
|
||||
lastTimestamp: latestFolded.lastTimestamp || latestFolded.sortTimestamp,
|
||||
lastMsgType: 0,
|
||||
unreadCount: foldedGroups.reduce((sum, s) => sum + (s.unreadCount || 0), 0),
|
||||
isMuted: false,
|
||||
isFolded: false,
|
||||
isGroup: true
|
||||
isFolded: false
|
||||
}
|
||||
|
||||
// 按时间戳插入到正确位置
|
||||
const foldTime = foldEntry.sortTimestamp || foldEntry.lastTimestamp || foldEntry.timestamp
|
||||
const foldTime = foldEntry.sortTimestamp || foldEntry.lastTimestamp
|
||||
const insertIndex = visible.findIndex(s => {
|
||||
const sTime = s.sortTimestamp || s.lastTimestamp || s.timestamp
|
||||
const sTime = s.sortTimestamp || s.lastTimestamp
|
||||
return sTime < foldTime
|
||||
})
|
||||
if (insertIndex === -1) {
|
||||
|
||||
@@ -7,76 +7,6 @@
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
.title-bar {
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-right: 140px; // 为原生窗口控件留出空间
|
||||
|
||||
.window-drag-area {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.title-bar-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
-webkit-app-region: no-drag;
|
||||
margin-right: 16px;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.live-play-btn {
|
||||
&.active {
|
||||
background: rgba(var(--primary-rgb, 76, 132, 255), 0.16);
|
||||
color: var(--primary, #4c84ff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scale-text {
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
background: var(--border-color);
|
||||
margin: 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-viewport {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { ZoomIn, ZoomOut, RotateCw, RotateCcw } from 'lucide-react'
|
||||
import { LivePhotoIcon } from '../components/LivePhotoIcon'
|
||||
import TitleBar from '../components/TitleBar'
|
||||
import './ImageWindow.scss'
|
||||
|
||||
export default function ImageWindow() {
|
||||
@@ -207,31 +208,35 @@ export default function ImageWindow() {
|
||||
|
||||
return (
|
||||
<div className="image-window-container">
|
||||
<div className="title-bar">
|
||||
<div className="window-drag-area"></div>
|
||||
<div className="title-bar-controls">
|
||||
{hasLiveVideo && (
|
||||
<>
|
||||
<button
|
||||
onClick={handlePlayLiveVideo}
|
||||
title={isPlayingLive ? '正在播放实况' : '播放实况 (空格)'}
|
||||
className={`live-play-btn ${isPlayingLive ? 'active' : ''}`}
|
||||
disabled={isPlayingLive}
|
||||
>
|
||||
<LivePhotoIcon size={16} />
|
||||
<span style={{ fontSize: 13, marginLeft: 4 }}>Live</span>
|
||||
</button>
|
||||
<div className="divider"></div>
|
||||
</>
|
||||
)}
|
||||
<button onClick={handleZoomOut} title="缩小 (-)"><ZoomOut size={16} /></button>
|
||||
<span className="scale-text">{Math.round(displayScale * 100)}%</span>
|
||||
<button onClick={handleZoomIn} title="放大 (+)"><ZoomIn size={16} /></button>
|
||||
<div className="divider"></div>
|
||||
<button onClick={handleRotateCcw} title="逆时针旋转"><RotateCcw size={16} /></button>
|
||||
<button onClick={handleRotate} title="顺时针旋转 (R)"><RotateCw size={16} /></button>
|
||||
</div>
|
||||
</div>
|
||||
<TitleBar
|
||||
title="图片查看"
|
||||
showWindowControls={true}
|
||||
showLogo={false}
|
||||
customControls={
|
||||
<div className="image-controls">
|
||||
{hasLiveVideo && (
|
||||
<>
|
||||
<button
|
||||
onClick={handlePlayLiveVideo}
|
||||
title={isPlayingLive ? '正在播放实况' : '播放实况 (空格)'}
|
||||
className={`live-play-btn ${isPlayingLive ? 'active' : ''}`}
|
||||
disabled={isPlayingLive}
|
||||
>
|
||||
<LivePhotoIcon size={16} />
|
||||
<span style={{ fontSize: 13, marginLeft: 4 }}>Live</span>
|
||||
</button>
|
||||
<div className="divider"></div>
|
||||
</>
|
||||
)}
|
||||
<button onClick={handleZoomOut} title="缩小 (-)"><ZoomOut size={16} /></button>
|
||||
<span className="scale-text">{Math.round(displayScale * 100)}%</span>
|
||||
<button onClick={handleZoomIn} title="放大 (+)"><ZoomIn size={16} /></button>
|
||||
<div className="divider"></div>
|
||||
<button onClick={handleRotateCcw} title="逆时针旋转"><RotateCcw size={16} /></button>
|
||||
<button onClick={handleRotate} title="顺时针旋转 (R)"><RotateCw size={16} /></button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="image-viewport"
|
||||
|
||||
@@ -1379,15 +1379,12 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
scheduleConfigSave('keys', () => syncCurrentKeys({ imageAesKey: value, wxid }))
|
||||
}}
|
||||
/>
|
||||
<div className="form-hint" style={{ color: '#f59e0b', margin: '6px 0' }}>
|
||||
⚠️ 快速获取方案基于本地缓存计算,可能因账号信息不匹配而不准确。若图片无法解密,请使用「内存扫描」方案。
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px', marginTop: '4px' }}>
|
||||
<button className="btn btn-secondary btn-sm" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算(可能不准确)">
|
||||
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '快速获取(缓存计算)'}
|
||||
<button className="btn btn-primary btn-sm" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算">
|
||||
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '缓存计算(推荐)'}
|
||||
</button>
|
||||
<button className="btn btn-primary btn-sm" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存,准确率更高">
|
||||
{isFetchingImageKey ? '扫描中...' : '内存扫描(推荐)'}
|
||||
<button className="btn btn-secondary btn-sm" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存">
|
||||
{isFetchingImageKey ? '扫描中...' : '内存扫描'}
|
||||
</button>
|
||||
</div>
|
||||
{isFetchingImageKey ? (
|
||||
@@ -1399,7 +1396,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
) : (
|
||||
imageKeyStatus && <div className="form-hint status-text" style={{ marginTop: '8px' }}>{imageKeyStatus}</div>
|
||||
)}
|
||||
<span className="form-hint">内存扫描需要微信正在运行,并在微信中打开 2-3 张图片大图后再点击</span>
|
||||
<span className="form-hint">优先推荐缓存计算方案。若图片无法解密,可使用内存扫描(需微信运行并打开 2-3 张图片大图)</span>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
|
||||
@@ -780,9 +780,6 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
|
||||
|
||||
{currentStep.id === 'image' && (
|
||||
<div className="form-group">
|
||||
<div className="field-hint" style={{ color: '#f59e0b', marginBottom: '12px' }}>
|
||||
⚠️ 快速获取方案基于本地缓存计算,可能因账号信息不匹配而不准确。若图片无法解密,请使用下方「内存扫描」方案。
|
||||
</div>
|
||||
<div className="grid-2">
|
||||
<div>
|
||||
<label className="field-label">图片 XOR 密钥</label>
|
||||
@@ -795,11 +792,11 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
|
||||
<button className="btn btn-secondary btn-block" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算(可能不准确)">
|
||||
{isFetchingImageKey ? '获取中...' : '快速获取(缓存计算)'}
|
||||
<button className="btn btn-primary btn-block" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算">
|
||||
{isFetchingImageKey ? '获取中...' : '缓存计算(推荐)'}
|
||||
</button>
|
||||
<button className="btn btn-primary btn-block" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存,准确率更高,需要微信正在运行">
|
||||
{isFetchingImageKey ? '扫描中...' : '内存扫描(推荐)'}
|
||||
<button className="btn btn-secondary btn-block" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存">
|
||||
{isFetchingImageKey ? '扫描中...' : '内存扫描'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -813,7 +810,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
|
||||
imageKeyStatus && <div className="status-message" style={{ marginTop: '12px' }}>{imageKeyStatus}</div>
|
||||
)}
|
||||
|
||||
<div className="field-hint" style={{ marginTop: '8px' }}>内存扫描需要微信正在运行,并在微信中打开 2-3 张图片大图后再点击</div>
|
||||
<div className="field-hint" style={{ marginTop: '8px' }}>优先推荐缓存计算方案。若图片无法解密,可使用内存扫描(需微信运行并打开 2-3 张图片大图)</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user