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,
|
nodeIntegration: false,
|
||||||
webSecurity: false // 允许加载本地文件
|
webSecurity: false // 允许加载本地文件
|
||||||
},
|
},
|
||||||
titleBarStyle: 'hidden',
|
frame: false,
|
||||||
titleBarOverlay: {
|
|
||||||
color: '#00000000',
|
|
||||||
symbolColor: '#ffffff',
|
|
||||||
height: 40
|
|
||||||
},
|
|
||||||
show: false,
|
show: false,
|
||||||
backgroundColor: '#000000',
|
backgroundColor: '#000000',
|
||||||
autoHideMenuBar: true
|
autoHideMenuBar: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setupCustomTitleBarWindow(win)
|
||||||
|
|
||||||
win.once('ready-to-show', () => {
|
win.once('ready-to-show', () => {
|
||||||
win.show()
|
win.show()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -367,6 +367,8 @@ class ChatService {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预热 media 数据库列表缓存(后台异步执行)
|
* 预热 media 数据库列表缓存(后台异步执行)
|
||||||
|
|||||||
@@ -190,8 +190,7 @@ export class WcdbCore {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.monitorCallback) return
|
if (!this.monitorCallback) return
|
||||||
|
|
||||||
this.monitorPipeClient = net.createConnection(this.monitorPipePath, () => {
|
this.monitorPipeClient = net.createConnection(this.monitorPipePath, () => {})
|
||||||
})
|
|
||||||
|
|
||||||
let buffer = ''
|
let buffer = ''
|
||||||
this.monitorPipeClient.on('data', (data: 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.on('close', () => {
|
||||||
this.monitorPipeClient = null
|
this.monitorPipeClient = null
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export function GlobalSessionMonitor() {
|
|||||||
return () => {
|
return () => {
|
||||||
removeListener()
|
removeListener()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
return () => { }
|
return () => { }
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-sidebar-toggle {
|
.title-sidebar-toggle {
|
||||||
@@ -90,3 +92,55 @@
|
|||||||
color: #fff;
|
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
|
sidebarCollapsed?: boolean
|
||||||
onToggleSidebar?: () => void
|
onToggleSidebar?: () => void
|
||||||
showWindowControls?: boolean
|
showWindowControls?: boolean
|
||||||
|
customControls?: React.ReactNode
|
||||||
|
showLogo?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function TitleBar({
|
function TitleBar({
|
||||||
title,
|
title,
|
||||||
sidebarCollapsed = false,
|
sidebarCollapsed = false,
|
||||||
onToggleSidebar,
|
onToggleSidebar,
|
||||||
showWindowControls = true
|
showWindowControls = true,
|
||||||
|
customControls,
|
||||||
|
showLogo = true
|
||||||
}: TitleBarProps = {}) {
|
}: TitleBarProps = {}) {
|
||||||
const [isMaximized, setIsMaximized] = useState(false)
|
const [isMaximized, setIsMaximized] = useState(false)
|
||||||
|
|
||||||
@@ -32,7 +36,7 @@ function TitleBar({
|
|||||||
return (
|
return (
|
||||||
<div className="title-bar">
|
<div className="title-bar">
|
||||||
<div className="title-brand">
|
<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>
|
<span className="titles">{title || 'WeFlow'}</span>
|
||||||
{onToggleSidebar ? (
|
{onToggleSidebar ? (
|
||||||
<button
|
<button
|
||||||
@@ -46,6 +50,7 @@ function TitleBar({
|
|||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{customControls}
|
||||||
{showWindowControls ? (
|
{showWindowControls ? (
|
||||||
<div className="title-window-controls">
|
<div className="title-window-controls">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -2981,8 +2981,8 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
if (hasFoldedGroups && !visible.some(s => s.username.toLowerCase().includes('placeholder_foldgroup'))) {
|
if (hasFoldedGroups && !visible.some(s => s.username.toLowerCase().includes('placeholder_foldgroup'))) {
|
||||||
// 找到最新的折叠消息
|
// 找到最新的折叠消息
|
||||||
const latestFolded = foldedGroups.reduce((latest, current) => {
|
const latestFolded = foldedGroups.reduce((latest, current) => {
|
||||||
const latestTime = latest.sortTimestamp || latest.lastTimestamp || latest.timestamp
|
const latestTime = latest.sortTimestamp || latest.lastTimestamp
|
||||||
const currentTime = current.sortTimestamp || current.lastTimestamp || current.timestamp
|
const currentTime = current.sortTimestamp || current.lastTimestamp
|
||||||
return currentTime > latestTime ? current : latest
|
return currentTime > latestTime ? current : latest
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2990,19 +2990,19 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
username: 'placeholder_foldgroup',
|
username: 'placeholder_foldgroup',
|
||||||
displayName: '折叠的聊天',
|
displayName: '折叠的聊天',
|
||||||
summary: `${latestFolded.displayName || latestFolded.username}: ${latestFolded.summary}`,
|
summary: `${latestFolded.displayName || latestFolded.username}: ${latestFolded.summary}`,
|
||||||
timestamp: latestFolded.timestamp,
|
type: 0,
|
||||||
sortTimestamp: latestFolded.sortTimestamp || latestFolded.lastTimestamp || latestFolded.timestamp,
|
sortTimestamp: latestFolded.sortTimestamp || latestFolded.lastTimestamp,
|
||||||
lastTimestamp: latestFolded.lastTimestamp || latestFolded.sortTimestamp || latestFolded.timestamp,
|
lastTimestamp: latestFolded.lastTimestamp || latestFolded.sortTimestamp,
|
||||||
|
lastMsgType: 0,
|
||||||
unreadCount: foldedGroups.reduce((sum, s) => sum + (s.unreadCount || 0), 0),
|
unreadCount: foldedGroups.reduce((sum, s) => sum + (s.unreadCount || 0), 0),
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
isFolded: false,
|
isFolded: false
|
||||||
isGroup: true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按时间戳插入到正确位置
|
// 按时间戳插入到正确位置
|
||||||
const foldTime = foldEntry.sortTimestamp || foldEntry.lastTimestamp || foldEntry.timestamp
|
const foldTime = foldEntry.sortTimestamp || foldEntry.lastTimestamp
|
||||||
const insertIndex = visible.findIndex(s => {
|
const insertIndex = visible.findIndex(s => {
|
||||||
const sTime = s.sortTimestamp || s.lastTimestamp || s.timestamp
|
const sTime = s.sortTimestamp || s.lastTimestamp
|
||||||
return sTime < foldTime
|
return sTime < foldTime
|
||||||
})
|
})
|
||||||
if (insertIndex === -1) {
|
if (insertIndex === -1) {
|
||||||
|
|||||||
@@ -7,76 +7,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
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 {
|
.image-viewport {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'
|
|||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { ZoomIn, ZoomOut, RotateCw, RotateCcw } from 'lucide-react'
|
import { ZoomIn, ZoomOut, RotateCw, RotateCcw } from 'lucide-react'
|
||||||
import { LivePhotoIcon } from '../components/LivePhotoIcon'
|
import { LivePhotoIcon } from '../components/LivePhotoIcon'
|
||||||
|
import TitleBar from '../components/TitleBar'
|
||||||
import './ImageWindow.scss'
|
import './ImageWindow.scss'
|
||||||
|
|
||||||
export default function ImageWindow() {
|
export default function ImageWindow() {
|
||||||
@@ -207,9 +208,12 @@ export default function ImageWindow() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="image-window-container">
|
<div className="image-window-container">
|
||||||
<div className="title-bar">
|
<TitleBar
|
||||||
<div className="window-drag-area"></div>
|
title="图片查看"
|
||||||
<div className="title-bar-controls">
|
showWindowControls={true}
|
||||||
|
showLogo={false}
|
||||||
|
customControls={
|
||||||
|
<div className="image-controls">
|
||||||
{hasLiveVideo && (
|
{hasLiveVideo && (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
@@ -231,7 +235,8 @@ export default function ImageWindow() {
|
|||||||
<button onClick={handleRotateCcw} title="逆时针旋转"><RotateCcw size={16} /></button>
|
<button onClick={handleRotateCcw} title="逆时针旋转"><RotateCcw size={16} /></button>
|
||||||
<button onClick={handleRotate} title="顺时针旋转 (R)"><RotateCw size={16} /></button>
|
<button onClick={handleRotate} title="顺时针旋转 (R)"><RotateCw size={16} /></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="image-viewport"
|
className="image-viewport"
|
||||||
|
|||||||
@@ -1379,15 +1379,12 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
scheduleConfigSave('keys', () => syncCurrentKeys({ imageAesKey: value, wxid }))
|
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' }}>
|
<div style={{ display: 'flex', gap: '8px', marginTop: '4px' }}>
|
||||||
<button className="btn btn-secondary btn-sm" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算(可能不准确)">
|
<button className="btn btn-primary btn-sm" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算">
|
||||||
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '快速获取(缓存计算)'}
|
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '缓存计算(推荐)'}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn btn-primary btn-sm" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存,准确率更高">
|
<button className="btn btn-secondary btn-sm" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存">
|
||||||
{isFetchingImageKey ? '扫描中...' : '内存扫描(推荐)'}
|
{isFetchingImageKey ? '扫描中...' : '内存扫描'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{isFetchingImageKey ? (
|
{isFetchingImageKey ? (
|
||||||
@@ -1399,7 +1396,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
) : (
|
) : (
|
||||||
imageKeyStatus && <div className="form-hint status-text" style={{ marginTop: '8px' }}>{imageKeyStatus}</div>
|
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>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
|
|||||||
@@ -780,9 +780,6 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
|
|||||||
|
|
||||||
{currentStep.id === 'image' && (
|
{currentStep.id === 'image' && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="field-hint" style={{ color: '#f59e0b', marginBottom: '12px' }}>
|
|
||||||
⚠️ 快速获取方案基于本地缓存计算,可能因账号信息不匹配而不准确。若图片无法解密,请使用下方「内存扫描」方案。
|
|
||||||
</div>
|
|
||||||
<div className="grid-2">
|
<div className="grid-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="field-label">图片 XOR 密钥</label>
|
<label className="field-label">图片 XOR 密钥</label>
|
||||||
@@ -795,11 +792,11 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
|
<div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
|
||||||
<button className="btn btn-secondary btn-block" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算(可能不准确)">
|
<button className="btn btn-primary btn-block" onClick={handleAutoGetImageKey} disabled={isFetchingImageKey} title="从本地缓存快速计算">
|
||||||
{isFetchingImageKey ? '获取中...' : '快速获取(缓存计算)'}
|
{isFetchingImageKey ? '获取中...' : '缓存计算(推荐)'}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn btn-primary btn-block" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存,准确率更高,需要微信正在运行">
|
<button className="btn btn-secondary btn-block" onClick={handleScanImageKeyFromMemory} disabled={isFetchingImageKey} title="扫描微信进程内存">
|
||||||
{isFetchingImageKey ? '扫描中...' : '内存扫描(推荐)'}
|
{isFetchingImageKey ? '扫描中...' : '内存扫描'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -813,7 +810,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) {
|
|||||||
imageKeyStatus && <div className="status-message" style={{ marginTop: '12px' }}>{imageKeyStatus}</div>
|
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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user