mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-02 23:15:59 +00:00
refactor: 样式对齐
This commit is contained in:
@@ -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 })
|
||||||
@@ -179,7 +133,7 @@ export class BizService {
|
|||||||
username: uname,
|
username: uname,
|
||||||
name: info?.displayName || contact.displayName || uname,
|
name: info?.displayName || contact.displayName || uname,
|
||||||
avatar: info?.avatarUrl || '',
|
avatar: info?.avatarUrl || '',
|
||||||
type: 0,
|
type: 0,
|
||||||
last_time: lastTime,
|
last_time: lastTime,
|
||||||
formatted_last_time: lastTime ? new Date(lastTime * 1000).toISOString().split('T')[0] : ''
|
formatted_last_time: lastTime ? new Date(lastTime * 1000).toISOString().split('T')[0] : ''
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
const bizMessages: BizMessage[] = rawMessages.map(msg => {
|
if (!res.success || !res.messages) return []
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
@@ -257,4 +252,4 @@ const BizPage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BizPage;
|
export default BizPage;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,29 +4989,25 @@ 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,
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
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}
|
||||||
|
|||||||
Reference in New Issue
Block a user