refactor: 样式对齐

This commit is contained in:
H3CoF6
2026-04-03 05:20:58 +08:00
parent 17b8af4bc4
commit 510b956649
5 changed files with 193 additions and 147 deletions

View File

@@ -23,7 +23,6 @@ export interface BizMessage {
url: string url: string
cover: string cover: string
content_list: any[] content_list: any[]
raw?: any // 调试用
} }
export interface BizPayRecord { export interface BizPayRecord {
@@ -69,7 +68,7 @@ export class BizService {
} }
if (itemStruct.title) contentList.push(itemStruct) if (itemStruct.title) contentList.push(itemStruct)
} }
} catch (e) {} } catch (e) { }
return contentList return contentList
} }
@@ -86,51 +85,6 @@ export class BizService {
} catch (e) { return null } } catch (e) { return null }
} }
/**
* 核心:获取公众号消息,支持从 biz_message*.db 自动定位
*/
private async getBizRawMessages(username: string, account: string, limit: number, offset: number): Promise<Message[]> {
console.log(`[BizService] getBizRawMessages: ${username}, offset=${offset}, limit=${limit}`)
// 1. 首先尝试直接用 chatService.getMessages (如果 Native 层支持路由)
const chatRes = await chatService.getMessages(username, offset, limit)
if (chatRes.success && chatRes.messages && chatRes.messages.length > 0) {
console.log(`[BizService] chatService found ${chatRes.messages.length} messages for ${username}`)
return chatRes.messages
}
// 2. 如果 chatService 没找到,手动扫描 biz_message*.db (类似 Python 逻辑)
console.log(`[BizService] chatService empty, manual scanning biz_message*.db...`)
const root = this.configService.get('dbPath')
const accountWxid = account || this.configService.get('myWxid')
if (!root || !accountWxid) return []
const dbDir = join(root, accountWxid, 'db_storage', 'message')
if (!existsSync(dbDir)) return []
const md5Id = createHash('md5').update(username).digest('hex').toLowerCase()
const tableName = `Msg_${md5Id}`
const bizDbFiles = readdirSync(dbDir).filter(f => f.startsWith('biz_message') && f.endsWith('.db'))
for (const file of bizDbFiles) {
const dbPath = join(dbDir, file)
// 检查表是否存在
const checkRes = await wcdbService.execQuery('message', dbPath, `SELECT name FROM sqlite_master WHERE type='table' AND lower(name)='${tableName}'`)
if (checkRes.success && checkRes.rows && checkRes.rows.length > 0) {
console.log(`[BizService] Found table ${tableName} in ${file}`)
// 分页查询原始行
const sql = `SELECT * FROM ${tableName} ORDER BY create_time DESC LIMIT ${limit} OFFSET ${offset}`
const queryRes = await wcdbService.execQuery('message', dbPath, sql)
if (queryRes.success && queryRes.rows) {
// *** 复用 chatService 的解析逻辑 ***
return chatService.mapRowsToMessagesForApi(queryRes.rows)
}
}
}
return []
}
async listAccounts(account?: string): Promise<BizAccount[]> { async listAccounts(account?: string): Promise<BizAccount[]> {
try { try {
const contactsResult = await chatService.getContacts({ lite: true }) const contactsResult = await chatService.getContacts({ lite: true })
@@ -194,23 +148,24 @@ export class BizService {
for (const acc of result) if (typeMap[acc.username] !== undefined) acc.type = typeMap[acc.username] for (const acc of result) if (typeMap[acc.username] !== undefined) acc.type = typeMap[acc.username]
} }
} }
// 6. 排序与过滤:微信支付置顶,过滤朋友圈广告,其余按时间降序
return result return result
.filter(acc => !acc.name.includes('朋友圈广告')) .filter(acc => !acc.name.includes('朋友圈广告'))
.sort((a, b) => { .sort((a, b) => {
if (a.username === 'gh_3dfda90e39d6') return -1 if (a.username === 'gh_3dfda90e39d6') return -1
if (b.username === 'gh_3dfda90e39d6') return 1 if (b.username === 'gh_3dfda90e39d6') return 1
return b.last_time - a.last_time return b.last_time - a.last_time
}) })
} catch (e) { return [] } } catch (e) { return [] }
} }
async listMessages(username: string, account?: string, limit: number = 20, offset: number = 0): Promise<BizMessage[]> { async listMessages(username: string, account?: string, limit: number = 20, offset: number = 0): Promise<BizMessage[]> {
console.log(`[BizService] listMessages: ${username}, limit=${limit}, offset=${offset}`)
try { try {
const rawMessages = await this.getBizRawMessages(username, account || '', limit, offset) // 仅保留核心路径:利用 chatService 的自动路由能力
const res = await chatService.getMessages(username, offset, limit)
if (!res.success || !res.messages) return []
const bizMessages: BizMessage[] = rawMessages.map(msg => { return res.messages.map(msg => {
const bizMsg: BizMessage = { const bizMsg: BizMessage = {
local_id: msg.localId, local_id: msg.localId,
create_time: msg.createTime, create_time: msg.createTime,
@@ -229,19 +184,17 @@ return result
} }
return bizMsg return bizMsg
}) })
return bizMessages } catch (e) { return [] }
} catch (e) {
console.error(`[BizService] listMessages error:`, e)
return []
}
} }
async listPayRecords(account?: string, limit: number = 20, offset: number = 0): Promise<BizPayRecord[]> { async listPayRecords(account?: string, limit: number = 20, offset: number = 0): Promise<BizPayRecord[]> {
const username = 'gh_3dfda90e39d6' const username = 'gh_3dfda90e39d6'
try { try {
const rawMessages = await this.getBizRawMessages(username, account || '', limit, offset) const res = await chatService.getMessages(username, offset, limit)
if (!res.success || !res.messages) return []
const records: BizPayRecord[] = [] const records: BizPayRecord[] = []
for (const msg of rawMessages) { for (const msg of res.messages) {
if (!msg.rawContent) continue if (!msg.rawContent) continue
const parsedData = this.parsePayXml(msg.rawContent) const parsedData = this.parsePayXml(msg.rawContent)
if (parsedData) { if (parsedData) {

View File

@@ -1,13 +1,13 @@
.biz-account-list { .biz-account-list {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
background-color: var(--bg-primary); background-color: var(--bg-secondary); // 对齐会话列表背景
.biz-loading { .biz-loading {
padding: 20px; padding: 20px;
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
color: var(--text-muted); color: var(--text-tertiary);
} }
.biz-account-item { .biz-account-item {
@@ -16,25 +16,31 @@
gap: 12px; gap: 12px;
padding: 12px 16px; padding: 12px 16px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: all 0.2s;
border-bottom: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-color);
&:hover { &:hover {
background-color: var(--bg-tertiary); background-color: var(--bg-hover);
} }
&.active { &.active {
background-color: var(--bg-active) !important; background-color: var(--primary-light) !important;
border-left: 3px solid var(--primary);
padding-left: 13px; // 补偿 border-left
} }
&.pay-account { &.pay-account {
background-color: var(--bg-soft); background-color: var(--bg-primary);
&.active {
background-color: var(--primary-light) !important;
border-left: 3px solid var(--primary);
}
} }
.biz-avatar { .biz-avatar {
width: 40px; width: 48px;
height: 40px; height: 48px;
border-radius: 4px; border-radius: 8px; // 对齐会话列表头像圆角
object-fit: cover; object-fit: cover;
flex-shrink: 0; flex-shrink: 0;
background-color: var(--bg-tertiary); background-color: var(--bg-tertiary);
@@ -55,7 +61,7 @@
.biz-name { .biz-name {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: var(--text-main); color: var(--text-primary);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -63,7 +69,7 @@
.biz-time { .biz-time {
font-size: 11px; font-size: 11px;
color: var(--text-muted); color: var(--text-tertiary);
flex-shrink: 0; flex-shrink: 0;
} }
} }
@@ -75,10 +81,10 @@
width: fit-content; width: fit-content;
margin-top: 2px; margin-top: 2px;
&.type-service { color: #03C160; background: rgba(3, 193, 96, 0.1); } &.type-service { color: #07c160; background: rgba(7, 193, 96, 0.1); }
&.type-sub { color: #108ee9; background: rgba(16, 142, 233, 0.1); } &.type-sub { color: var(--primary); background: var(--primary-light); }
&.type-enterprise { color: #f5222d; background: rgba(245, 34, 45, 0.1); } &.type-enterprise { color: #f5222d; background: rgba(245, 34, 45, 0.1); }
&.type-unknown { color: #8c8c8c; background: rgba(140, 140, 140, 0.1); } &.type-unknown { color: var(--text-tertiary); background: var(--bg-tertiary); }
} }
} }
} }
@@ -88,21 +94,21 @@
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--bg-tertiary); background-color: var(--bg-secondary); // 对齐聊天页背景
.main-header { .main-header {
height: 56px; height: 56px;
padding: 0 20px; padding: 0 20px;
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: 1px solid var(--border-dim); border-bottom: 1px solid var(--border-color);
background-color: var(--bg-primary); background-color: var(--card-bg);
flex-shrink: 0; flex-shrink: 0;
h2 { h2 {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: var(--text-main); color: var(--text-primary);
} }
} }
@@ -110,6 +116,8 @@
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
padding: 24px 16px; padding: 24px 16px;
background: var(--chat-pattern);
background-color: var(--bg-tertiary); // 对齐聊天背景色
.messages-wrapper { .messages-wrapper {
width: 100%; width: 100%;
@@ -121,16 +129,57 @@
} }
} }
// 占位状态:对齐 Chat 页面风格
.biz-no-record-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
background: var(--bg-tertiary);
.no-record-icon {
width: 64px;
height: 64px;
border-radius: 50%;
background-color: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
color: var(--text-tertiary);
opacity: 0.5;
svg { width: 32px; height: 32px; }
}
h3 {
font-size: 16px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 8px;
}
p {
font-size: 13px;
color: var(--text-secondary);
max-width: 280px;
line-height: 1.5;
}
}
.biz-loading-more { .biz-loading-more {
text-align: center; text-align: center;
padding: 20px; padding: 20px;
font-size: 12px; font-size: 12px;
color: var(--text-muted); color: var(--text-tertiary);
} }
.pay-card { .pay-card {
background-color: var(--bg-primary); background-color: var(--card-bg);
border: 1px solid var(--border-dim); border: 1px solid var(--border-color);
border-radius: 12px; border-radius: 12px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05); box-shadow: 0 2px 8px rgba(0,0,0,0.05);
@@ -140,7 +189,7 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
font-size: 13px; font-size: 13px;
color: var(--text-muted); color: var(--text-tertiary);
margin-bottom: 20px; margin-bottom: 20px;
.pay-icon { .pay-icon {
@@ -148,14 +197,17 @@
height: 24px; height: 24px;
border-radius: 50%; border-radius: 50%;
object-fit: cover; object-fit: cover;
}
&.placeholder { .pay-icon-placeholder {
background-color: #03C160; width: 24px;
color: white; height: 24px;
display: flex; border-radius: 50%;
align-items: center; background-color: #07c160;
justify-content: center; color: white;
} display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
} }
} }
@@ -163,30 +215,30 @@
text-align: center; text-align: center;
font-size: 22px; font-size: 22px;
font-weight: 500; font-weight: 500;
color: var(--text-main); color: var(--text-primary);
margin-bottom: 24px; margin-bottom: 24px;
} }
.pay-desc { .pay-desc {
font-size: 13px; font-size: 13px;
line-height: 1.6; line-height: 1.6;
color: var(--text-muted); color: var(--text-secondary);
white-space: pre-wrap; white-space: pre-wrap;
} }
.pay-footer { .pay-footer {
margin-top: 16px; margin-top: 16px;
padding-top: 12px; padding-top: 12px;
border-top: 1px solid var(--border-subtle); border-top: 1px solid var(--border-color);
font-size: 12px; font-size: 12px;
color: var(--text-muted); color: var(--text-tertiary);
text-align: right; text-align: right;
} }
} }
.article-card { .article-card {
background-color: var(--bg-primary); background-color: var(--card-bg);
border: 1px solid var(--border-dim); border: 1px solid var(--border-color);
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05); box-shadow: 0 2px 8px rgba(0,0,0,0.05);
@@ -226,8 +278,8 @@
.article-digest { .article-digest {
padding: 12px 16px; padding: 12px 16px;
font-size: 14px; font-size: 14px;
color: var(--text-muted); color: var(--text-secondary);
border-bottom: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-color);
} }
.sub-articles { .sub-articles {
@@ -236,15 +288,15 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 16px; padding: 16px;
border-top: 1px solid var(--border-subtle); border-top: 1px solid var(--border-color);
cursor: pointer; cursor: pointer;
&:hover { background-color: var(--bg-secondary); } &:hover { background-color: var(--bg-hover); }
.sub-title { .sub-title {
flex: 1; flex: 1;
font-size: 15px; font-size: 15px;
color: var(--text-main); color: var(--text-primary);
padding-right: 12px; padding-right: 12px;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
@@ -258,7 +310,7 @@
border-radius: 4px; border-radius: 4px;
object-fit: cover; object-fit: cover;
flex-shrink: 0; flex-shrink: 0;
border: 1px solid var(--border-subtle); border: 1px solid var(--border-color);
} }
} }
} }
@@ -273,6 +325,7 @@
justify-content: center; justify-content: center;
text-align: center; text-align: center;
height: 100%; height: 100%;
background: var(--bg-tertiary); // 对齐 Chat 页面空白背景
.empty-icon { .empty-icon {
width: 80px; width: 80px;
@@ -288,6 +341,5 @@
svg { width: 40px; height: 40px; } svg { width: 40px; height: 40px; }
} }
p { color: var(--text-muted); font-size: 14px; } p { color: var(--text-tertiary); font-size: 14px; }
} }

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useMemo, useRef } from 'react'; import React, { useState, useEffect, useMemo, useRef } from 'react';
import { useThemeStore } from '../stores/themeStore'; import { useThemeStore } from '../stores/themeStore';
import { Newspaper } from 'lucide-react'; import { Newspaper, MessageSquareOff } from 'lucide-react';
import './BizPage.scss'; import './BizPage.scss';
export interface BizAccount { export interface BizAccount {
@@ -33,7 +33,7 @@ export const BizAccountList: React.FC<{
console.error("获取 myWxid 失败:", e); console.error("获取 myWxid 失败:", e);
} }
}; };
initWxid(); initWxid().then(_r => { });
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -52,7 +52,7 @@ export const BizAccountList: React.FC<{
setLoading(false); setLoading(false);
} }
}; };
fetch(); fetch().then(_r => { } );
}, [myWxid]); }, [myWxid]);
const filtered = useMemo(() => { const filtered = useMemo(() => {
@@ -75,7 +75,7 @@ export const BizAccountList: React.FC<{
className={`biz-account-item ${selectedUsername === item.username ? 'active' : ''} ${item.username === 'gh_3dfda90e39d6' ? 'pay-account' : ''}`} className={`biz-account-item ${selectedUsername === item.username ? 'active' : ''} ${item.username === 'gh_3dfda90e39d6' ? 'pay-account' : ''}`}
> >
<img <img
src={item.avatar} src={item.avatar || 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.png'}
className="biz-avatar" className="biz-avatar"
alt="" alt=""
/> />
@@ -89,7 +89,7 @@ export const BizAccountList: React.FC<{
item.type === 0 ? 'type-sub' : item.type === 0 ? 'type-sub' :
item.type === 2 ? 'type-enterprise' : 'type-unknown' item.type === 2 ? 'type-enterprise' : 'type-unknown'
}`}> }`}>
{item.type === 0 ? '服务号' : item.type === 1 ? '订阅号' : item.type === 2 ? '企业号' : '未知'} {item.type === 1 ? '服务号' : item.type === 0 ? '订阅号' : item.type === 2 ? '企业号' : '未知'}
</div> </div>
</div> </div>
</div> </div>
@@ -98,7 +98,7 @@ export const BizAccountList: React.FC<{
); );
}; };
// 2. 公众号消息区域组件 (展示在右侧消息区) // 2. 公众号消息区域组件
export const BizMessageArea: React.FC<{ export const BizMessageArea: React.FC<{
account: BizAccount | null; account: BizAccount | null;
}> = ({ account }) => { }> = ({ account }) => {
@@ -110,7 +110,6 @@ export const BizMessageArea: React.FC<{
const limit = 20; const limit = 20;
const messageListRef = useRef<HTMLDivElement>(null); const messageListRef = useRef<HTMLDivElement>(null);
// ======== 修改开始:独立从底层获取 myWxid ========
const [myWxid, setMyWxid] = useState<string>(''); const [myWxid, setMyWxid] = useState<string>('');
useEffect(() => { useEffect(() => {
@@ -120,13 +119,10 @@ export const BizMessageArea: React.FC<{
if (wxid) { if (wxid) {
setMyWxid(wxid as string); setMyWxid(wxid as string);
} }
} catch (e) { } catch (e) { }
console.error("获取 myWxid 失败:", e);
}
}; };
initWxid(); initWxid();
}, []); }, []);
// ======== 修改结束 ========
const isDark = useMemo(() => { const isDark = useMemo(() => {
if (themeMode === 'dark') return true; if (themeMode === 'dark') return true;
@@ -136,8 +132,6 @@ export const BizMessageArea: React.FC<{
return false; return false;
}, [themeMode]); }, [themeMode]);
// ======== 补充修改:添加 myWxid 依赖 ========
// 必须加上 myWxid 作为依赖项,否则第一次点击左侧账号时,如果 wxid 还没异步拿回来,就不会触发加载
useEffect(() => { useEffect(() => {
if (account && myWxid) { if (account && myWxid) {
setMessages([]); setMessages([]);
@@ -146,19 +140,16 @@ export const BizMessageArea: React.FC<{
loadMessages(account.username, 0); loadMessages(account.username, 0);
} }
}, [account, myWxid]); }, [account, myWxid]);
// ======== 补充修改结束 ========
const loadMessages = async (username: string, currentOffset: number) => { const loadMessages = async (username: string, currentOffset: number) => {
if (loading || !myWxid) return; // 没账号直接 return if (loading || !myWxid) return;
setLoading(true); setLoading(true);
try { try {
let res; let res;
if (username === 'gh_3dfda90e39d6') { if (username === 'gh_3dfda90e39d6') {
// 传入 myWxid
res = await window.electronAPI.biz.listPayRecords(myWxid, limit, currentOffset); res = await window.electronAPI.biz.listPayRecords(myWxid, limit, currentOffset);
} else { } else {
// 传入 myWxid替换掉 undefined
res = await window.electronAPI.biz.listMessages(username, myWxid, limit, currentOffset); res = await window.electronAPI.biz.listMessages(username, myWxid, limit, currentOffset);
} }
if (res) { if (res) {
@@ -201,15 +192,20 @@ export const BizMessageArea: React.FC<{
<div className="message-container" onScroll={handleScroll} ref={messageListRef}> <div className="message-container" onScroll={handleScroll} ref={messageListRef}>
<div className="messages-wrapper"> <div className="messages-wrapper">
{!loading && messages.length === 0 && ( {!loading && messages.length === 0 && (
<div className="biz-no-record"> <div className="biz-no-record-container">
<p></p> <div className="no-record-icon">
<MessageSquareOff size={48} />
</div>
<h3></h3>
<p></p>
</div> </div>
)} )}
{messages.map((msg) => ( <div key={msg.local_id}> {messages.map((msg) => (
<div key={msg.local_id}>
{account.username === 'gh_3dfda90e39d6' ? ( {account.username === 'gh_3dfda90e39d6' ? (
<div className="pay-card"> <div className="pay-card">
<div className="pay-header"> <div className="pay-header">
{msg.merchant_icon ? <img src={msg.merchant_icon} className="pay-icon" alt=""/> : <div className="pay-icon placeholder">¥</div>} {msg.merchant_icon ? <img src={msg.merchant_icon} className="pay-icon" alt=""/> : <div className="pay-icon-placeholder">¥</div>}
<span>{msg.merchant_name || '微信支付'}</span> <span>{msg.merchant_name || '微信支付'}</span>
</div> </div>
<div className="pay-title">{msg.title}</div> <div className="pay-title">{msg.title}</div>
@@ -223,9 +219,9 @@ export const BizMessageArea: React.FC<{
<div className="article-overlay"><h3 className="article-title">{msg.title}</h3></div> <div className="article-overlay"><h3 className="article-title">{msg.title}</h3></div>
</div> </div>
{msg.des && <div className="article-digest">{msg.des}</div>} {msg.des && <div className="article-digest">{msg.des}</div>}
{msg.content_list && msg.content_list.length > 0 && ( {msg.content_list && msg.content_list.length > 1 && (
<div className="sub-articles"> <div className="sub-articles">
{msg.content_list.map((item: any, idx: number) => ( {msg.content_list.slice(1).map((item: any, idx: number) => (
<div key={idx} onClick={() => window.electronAPI.shell.openExternal(item.url)} className="sub-item"> <div key={idx} onClick={() => window.electronAPI.shell.openExternal(item.url)} className="sub-item">
<span className="sub-title">{item.title}</span> <span className="sub-title">{item.title}</span>
{item.cover && <img src={item.cover} className="sub-cover" alt=""/>} {item.cover && <img src={item.cover} className="sub-cover" alt=""/>}
@@ -244,7 +240,6 @@ export const BizMessageArea: React.FC<{
); );
}; };
// 保持 BizPage 作为入口 (如果需要独立页面)
const BizPage: React.FC = () => { const BizPage: React.FC = () => {
const [selectedAccount, setSelectedAccount] = useState<BizAccount | null>(null); const [selectedAccount, setSelectedAccount] = useState<BizAccount | null>(null);
return ( return (

View File

@@ -4487,6 +4487,32 @@
font-weight: 500; font-weight: 500;
} }
} }
// 公众号入口样式
.session-item.biz-entry {
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background: var(--hover-bg, rgba(0,0,0,0.05));
}
.biz-entry-avatar {
width: 48px;
height: 48px;
border-radius: 8px;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: #07c160;
}
.session-name {
font-weight: 500;
}
}
// 消息信息弹窗 // 消息信息弹窗
.message-info-overlay { .message-info-overlay {
position: fixed; position: fixed;

View File

@@ -986,6 +986,7 @@ const SessionItem = React.memo(function SessionItem({
) )
const isFoldEntry = session.username.toLowerCase().includes('placeholder_foldgroup') const isFoldEntry = session.username.toLowerCase().includes('placeholder_foldgroup')
const isBizEntry = session.username === OFFICIAL_ACCOUNTS_VIRTUAL_ID
// 折叠入口:专属名称和图标 // 折叠入口:专属名称和图标
if (isFoldEntry) { if (isFoldEntry) {
@@ -1010,6 +1011,29 @@ const SessionItem = React.memo(function SessionItem({
) )
} }
// 公众号入口:专属名称和图标
if (isBizEntry) {
return (
<div
className={`session-item biz-entry ${isActive ? 'active' : ''}`}
onClick={() => onSelect(session)}
>
<div className="biz-entry-avatar">
<Newspaper size={22} />
</div>
<div className="session-info">
<div className="session-top">
<span className="session-name">/</span>
<span className="session-time">{timeText}</span>
</div>
<div className="session-bottom">
<span className="session-summary">{session.summary || '查看公众号历史消息'}</span>
</div>
</div>
</div>
)
}
// 根据匹配字段显示不同的 summary // 根据匹配字段显示不同的 summary
const summaryContent = useMemo(() => { const summaryContent = useMemo(() => {
if (session.matchedField === 'wxid') { if (session.matchedField === 'wxid') {
@@ -4965,13 +4989,12 @@ function ChatPage(props: ChatPageProps) {
return true return true
}) })
// 注入“订阅号/服务号”虚拟项
const bizEntry: ChatSession = { const bizEntry: ChatSession = {
username: OFFICIAL_ACCOUNTS_VIRTUAL_ID, username: OFFICIAL_ACCOUNTS_VIRTUAL_ID,
displayName: '订阅号/服务号', displayName: '公众号',
summary: '查看公众号历史消息', summary: '查看公众号历史消息',
type: 0, type: 0,
sortTimestamp: 9999999999, // 确保在前面,或者您可以根据需要调整排序 sortTimestamp: 9999999999, // 放到最前面? 目前还没有严格的对时间进行排序, 后面可以改一下
lastTimestamp: 0, lastTimestamp: 0,
lastMsgType: 0, lastMsgType: 0,
unreadCount: 0, unreadCount: 0,
@@ -4979,15 +5002,12 @@ function ChatPage(props: ChatPageProps) {
isFolded: false isFolded: false
} }
// 检查是否已经存在(防止重复注入)
if (!visible.some(s => s.username === OFFICIAL_ACCOUNTS_VIRTUAL_ID)) { if (!visible.some(s => s.username === OFFICIAL_ACCOUNTS_VIRTUAL_ID)) {
// 插入到首位或者折叠项之后
visible.unshift(bizEntry) visible.unshift(bizEntry)
} }
// 如果有折叠的群聊,但列表中没有入口,则插入入口
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 const latestTime = latest.sortTimestamp || latest.lastTimestamp
const currentTime = current.sortTimestamp || current.lastTimestamp const currentTime = current.sortTimestamp || current.lastTimestamp
@@ -6239,7 +6259,7 @@ function ChatPage(props: ChatPageProps) {
<SessionItem <SessionItem
key={session.username} key={session.username}
session={session} session={session}
isActive={currentSessionId === session.username} isActive={currentSessionId === session.username || (bizView && session.username === OFFICIAL_ACCOUNTS_VIRTUAL_ID)}
onSelect={handleSelectSession} onSelect={handleSelectSession}
formatTime={formatSessionTime} formatTime={formatSessionTime}
searchKeyword={searchKeyword} searchKeyword={searchKeyword}
@@ -6265,7 +6285,7 @@ function ChatPage(props: ChatPageProps) {
<SessionItem <SessionItem
key={session.username} key={session.username}
session={session} session={session}
isActive={currentSessionId === session.username} isActive={currentSessionId === session.username || (bizView && session.username === OFFICIAL_ACCOUNTS_VIRTUAL_ID)}
onSelect={handleSelectSession} onSelect={handleSelectSession}
formatTime={formatSessionTime} formatTime={formatSessionTime}
searchKeyword={searchKeyword} searchKeyword={searchKeyword}