mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-12 15:08:36 +00:00
实现了服务号的推送以及未读
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
}
|
||||
|
||||
.biz-account-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
@@ -46,6 +47,24 @@
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.biz-unread-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 52px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
border-radius: 9px;
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
border: 2px solid var(--bg-secondary);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.biz-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { useThemeStore } from '../stores/themeStore';
|
||||
import { Newspaper, MessageSquareOff } from 'lucide-react';
|
||||
import './BizPage.scss';
|
||||
@@ -10,6 +10,7 @@ export interface BizAccount {
|
||||
type: string;
|
||||
last_time: number;
|
||||
formatted_last_time: string;
|
||||
unread_count?: number;
|
||||
}
|
||||
|
||||
export const BizAccountList: React.FC<{
|
||||
@@ -36,25 +37,42 @@ export const BizAccountList: React.FC<{
|
||||
initWxid().then(_r => { });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
if (!myWxid) {
|
||||
return;
|
||||
}
|
||||
const fetchAccounts = useCallback(async () => {
|
||||
if (!myWxid) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await window.electronAPI.biz.listAccounts(myWxid)
|
||||
setAccounts(res || []);
|
||||
} catch (err) {
|
||||
console.error('获取服务号列表失败:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetch().then(_r => { } );
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await window.electronAPI.biz.listAccounts(myWxid)
|
||||
setAccounts(res || []);
|
||||
} catch (err) {
|
||||
console.error('获取服务号列表失败:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [myWxid]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccounts().then(_r => { });
|
||||
}, [fetchAccounts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!window.electronAPI.chat.onWcdbChange) return;
|
||||
const removeListener = window.electronAPI.chat.onWcdbChange((_event: any, data: { json?: string }) => {
|
||||
try {
|
||||
const payload = JSON.parse(data.json || '{}');
|
||||
const tableName = String(payload.table || '').toLowerCase();
|
||||
if (!tableName || tableName === 'session' || tableName.includes('message') || tableName.startsWith('msg_')) {
|
||||
fetchAccounts().then(_r => { });
|
||||
}
|
||||
} catch {
|
||||
fetchAccounts().then(_r => { });
|
||||
}
|
||||
});
|
||||
return () => removeListener();
|
||||
}, [fetchAccounts]);
|
||||
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
let result = accounts;
|
||||
@@ -80,7 +98,12 @@ export const BizAccountList: React.FC<{
|
||||
{filtered.map(item => (
|
||||
<div
|
||||
key={item.username}
|
||||
onClick={() => onSelect(item)}
|
||||
onClick={() => {
|
||||
setAccounts(prev => prev.map(account =>
|
||||
account.username === item.username ? { ...account, unread_count: 0 } : account
|
||||
));
|
||||
onSelect({ ...item, unread_count: 0 });
|
||||
}}
|
||||
className={`biz-account-item ${selectedUsername === item.username ? 'active' : ''} ${item.username === 'gh_3dfda90e39d6' ? 'pay-account' : ''}`}
|
||||
>
|
||||
<img
|
||||
@@ -88,6 +111,9 @@ export const BizAccountList: React.FC<{
|
||||
className="biz-avatar"
|
||||
alt=""
|
||||
/>
|
||||
{(item.unread_count || 0) > 0 && (
|
||||
<span className="biz-unread-badge">{(item.unread_count || 0) > 99 ? '99+' : item.unread_count}</span>
|
||||
)}
|
||||
<div className="biz-info">
|
||||
<div className="biz-info-top">
|
||||
<span className="biz-name">{item.name || item.username}</span>
|
||||
|
||||
@@ -1058,6 +1058,13 @@ const SessionItem = React.memo(function SessionItem({
|
||||
</div>
|
||||
<div className="session-bottom">
|
||||
<span className="session-summary">{session.summary || '查看公众号历史消息'}</span>
|
||||
<div className="session-badges">
|
||||
{session.unreadCount > 0 && (
|
||||
<span className="unread-badge">
|
||||
{session.unreadCount > 99 ? '99+' : session.unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5049,24 +5056,37 @@ function ChatPage(props: ChatPageProps) {
|
||||
return []
|
||||
}
|
||||
|
||||
const officialSessions = sessions.filter(s => s.username.startsWith('gh_'))
|
||||
|
||||
// 检查是否有折叠的群聊
|
||||
const foldedGroups = sessions.filter(s => s.isFolded && !s.username.toLowerCase().includes('placeholder_foldgroup'))
|
||||
const hasFoldedGroups = foldedGroups.length > 0
|
||||
|
||||
let visible = sessions.filter(s => {
|
||||
if (s.isFolded && !s.username.toLowerCase().includes('placeholder_foldgroup')) return false
|
||||
if (s.username.startsWith('gh_')) return false
|
||||
return true
|
||||
})
|
||||
|
||||
const latestOfficial = officialSessions.reduce<ChatSession | null>((latest, current) => {
|
||||
if (!latest) return current
|
||||
const latestTime = latest.sortTimestamp || latest.lastTimestamp
|
||||
const currentTime = current.sortTimestamp || current.lastTimestamp
|
||||
return currentTime > latestTime ? current : latest
|
||||
}, null)
|
||||
const officialUnreadCount = officialSessions.reduce((sum, s) => sum + (s.unreadCount || 0), 0)
|
||||
|
||||
const bizEntry: ChatSession = {
|
||||
username: OFFICIAL_ACCOUNTS_VIRTUAL_ID,
|
||||
displayName: '公众号',
|
||||
summary: '查看公众号历史消息',
|
||||
summary: latestOfficial
|
||||
? `${latestOfficial.displayName || latestOfficial.username}: ${latestOfficial.summary || '查看公众号历史消息'}`
|
||||
: '查看公众号历史消息',
|
||||
type: 0,
|
||||
sortTimestamp: 9999999999, // 放到最前面? 目前还没有严格的对时间进行排序, 后面可以改一下
|
||||
lastTimestamp: 0,
|
||||
lastMsgType: 0,
|
||||
unreadCount: 0,
|
||||
lastTimestamp: latestOfficial?.lastTimestamp || latestOfficial?.sortTimestamp || 0,
|
||||
lastMsgType: latestOfficial?.lastMsgType || 0,
|
||||
unreadCount: officialUnreadCount,
|
||||
isMuted: false,
|
||||
isFolded: false
|
||||
}
|
||||
|
||||
@@ -2349,6 +2349,24 @@
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.filter-panel-action {
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.16s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
border-color: color-mix(in srgb, var(--primary) 42%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 8%, var(--bg-tertiary));
|
||||
}
|
||||
}
|
||||
|
||||
.filter-panel-list {
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
@@ -2412,6 +2430,16 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-item-type {
|
||||
flex-shrink: 0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.filter-item-action {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
@@ -2421,6 +2449,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.push-filter-type-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.push-filter-type-tab {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-secondary);
|
||||
padding: 6px 10px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.16s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-primary);
|
||||
border-color: color-mix(in srgb, var(--primary) 38%, var(--border-color));
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--primary);
|
||||
border-color: color-mix(in srgb, var(--primary) 54%, var(--border-color));
|
||||
background: color-mix(in srgb, var(--primary) 8%, var(--bg-primary));
|
||||
}
|
||||
}
|
||||
|
||||
.filter-panel-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useThemeStore, themes } from '../stores/themeStore'
|
||||
import { useAnalyticsStore } from '../stores/analyticsStore'
|
||||
import { dialog } from '../services/ipc'
|
||||
import * as configService from '../services/config'
|
||||
import type { ContactInfo } from '../types/models'
|
||||
import {
|
||||
Eye, EyeOff, FolderSearch, FolderOpen, Search, Copy,
|
||||
RotateCcw, Trash2, Plug, Check, Sun, Moon, Monitor,
|
||||
@@ -225,6 +226,12 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [isTogglingApi, setIsTogglingApi] = useState(false)
|
||||
const [showApiWarning, setShowApiWarning] = useState(false)
|
||||
const [messagePushEnabled, setMessagePushEnabled] = useState(false)
|
||||
const [messagePushFilterMode, setMessagePushFilterMode] = useState<configService.MessagePushFilterMode>('all')
|
||||
const [messagePushFilterList, setMessagePushFilterList] = useState<string[]>([])
|
||||
const [messagePushFilterDropdownOpen, setMessagePushFilterDropdownOpen] = useState(false)
|
||||
const [messagePushFilterSearchKeyword, setMessagePushFilterSearchKeyword] = useState('')
|
||||
const [messagePushTypeFilter, setMessagePushTypeFilter] = useState<'all' | configService.MessagePushSessionType>('all')
|
||||
const [messagePushContactOptions, setMessagePushContactOptions] = useState<ContactInfo[]>([])
|
||||
const [antiRevokeSearchKeyword, setAntiRevokeSearchKeyword] = useState('')
|
||||
const [antiRevokeSelectedIds, setAntiRevokeSelectedIds] = useState<Set<string>>(new Set())
|
||||
const [antiRevokeStatusMap, setAntiRevokeStatusMap] = useState<Record<string, { installed?: boolean; loading?: boolean; error?: string }>>({})
|
||||
@@ -356,15 +363,16 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setFilterModeDropdownOpen(false)
|
||||
setPositionDropdownOpen(false)
|
||||
setCloseBehaviorDropdownOpen(false)
|
||||
setMessagePushFilterDropdownOpen(false)
|
||||
}
|
||||
}
|
||||
if (filterModeDropdownOpen || positionDropdownOpen || closeBehaviorDropdownOpen) {
|
||||
if (filterModeDropdownOpen || positionDropdownOpen || closeBehaviorDropdownOpen || messagePushFilterDropdownOpen) {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
}
|
||||
}, [closeBehaviorDropdownOpen, filterModeDropdownOpen, positionDropdownOpen])
|
||||
}, [closeBehaviorDropdownOpen, filterModeDropdownOpen, messagePushFilterDropdownOpen, positionDropdownOpen])
|
||||
|
||||
|
||||
const loadConfig = async () => {
|
||||
@@ -387,6 +395,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const savedNotificationFilterMode = await configService.getNotificationFilterMode()
|
||||
const savedNotificationFilterList = await configService.getNotificationFilterList()
|
||||
const savedMessagePushEnabled = await configService.getMessagePushEnabled()
|
||||
const savedMessagePushFilterMode = await configService.getMessagePushFilterMode()
|
||||
const savedMessagePushFilterList = await configService.getMessagePushFilterList()
|
||||
const contactsResult = await window.electronAPI.chat.getContacts({ lite: true })
|
||||
const savedLaunchAtStartupStatus = await window.electronAPI.app.getLaunchAtStartupStatus()
|
||||
const savedWindowCloseBehavior = await configService.getWindowCloseBehavior()
|
||||
const savedQuoteLayout = await configService.getQuoteLayout()
|
||||
@@ -437,6 +448,11 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setNotificationFilterMode(savedNotificationFilterMode)
|
||||
setNotificationFilterList(savedNotificationFilterList)
|
||||
setMessagePushEnabled(savedMessagePushEnabled)
|
||||
setMessagePushFilterMode(savedMessagePushFilterMode)
|
||||
setMessagePushFilterList(savedMessagePushFilterList)
|
||||
if (contactsResult.success && Array.isArray(contactsResult.contacts)) {
|
||||
setMessagePushContactOptions(contactsResult.contacts as ContactInfo[])
|
||||
}
|
||||
setLaunchAtStartup(savedLaunchAtStartupStatus.enabled)
|
||||
setLaunchAtStartupSupported(savedLaunchAtStartupStatus.supported)
|
||||
setLaunchAtStartupReason(savedLaunchAtStartupStatus.reason || '')
|
||||
@@ -2517,6 +2533,116 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
showMessage(enabled ? '已开启主动推送' : '已关闭主动推送', true)
|
||||
}
|
||||
|
||||
const getMessagePushSessionType = (session: { username: string; type?: ContactInfo['type'] | number }): configService.MessagePushSessionType => {
|
||||
const username = String(session.username || '').trim()
|
||||
if (username.endsWith('@chatroom')) return 'group'
|
||||
if (username.startsWith('gh_') || session.type === 'official') return 'official'
|
||||
if (username.toLowerCase().includes('placeholder_foldgroup')) return 'other'
|
||||
if (session.type === 'former_friend' || session.type === 'other') return 'other'
|
||||
return 'private'
|
||||
}
|
||||
|
||||
const getMessagePushTypeLabel = (type: configService.MessagePushSessionType) => {
|
||||
switch (type) {
|
||||
case 'private': return '私聊'
|
||||
case 'group': return '群聊'
|
||||
case 'official': return '订阅号/服务号'
|
||||
default: return '其他/非好友'
|
||||
}
|
||||
}
|
||||
|
||||
const handleSetMessagePushFilterMode = async (mode: configService.MessagePushFilterMode) => {
|
||||
setMessagePushFilterMode(mode)
|
||||
setMessagePushFilterDropdownOpen(false)
|
||||
await configService.setMessagePushFilterMode(mode)
|
||||
showMessage(
|
||||
mode === 'all' ? '主动推送已设为接收所有会话' :
|
||||
mode === 'whitelist' ? '主动推送已设为仅推送白名单' : '主动推送已设为屏蔽黑名单',
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
const handleAddMessagePushFilterSession = async (username: string) => {
|
||||
if (messagePushFilterList.includes(username)) return
|
||||
const next = [...messagePushFilterList, username]
|
||||
setMessagePushFilterList(next)
|
||||
await configService.setMessagePushFilterList(next)
|
||||
showMessage('已添加到主动推送过滤列表', true)
|
||||
}
|
||||
|
||||
const handleRemoveMessagePushFilterSession = async (username: string) => {
|
||||
const next = messagePushFilterList.filter(item => item !== username)
|
||||
setMessagePushFilterList(next)
|
||||
await configService.setMessagePushFilterList(next)
|
||||
showMessage('已从主动推送过滤列表移除', true)
|
||||
}
|
||||
|
||||
const handleAddAllMessagePushFilterSessions = async () => {
|
||||
const usernames = messagePushAvailableSessions.map(session => session.username)
|
||||
if (usernames.length === 0) return
|
||||
const next = Array.from(new Set([...messagePushFilterList, ...usernames]))
|
||||
setMessagePushFilterList(next)
|
||||
await configService.setMessagePushFilterList(next)
|
||||
showMessage(`已添加 ${usernames.length} 个会话`, true)
|
||||
}
|
||||
|
||||
const messagePushOptionMap = new Map<string, {
|
||||
username: string
|
||||
displayName: string
|
||||
avatarUrl?: string
|
||||
type: configService.MessagePushSessionType
|
||||
}>()
|
||||
|
||||
for (const session of chatSessions) {
|
||||
if (session.username.toLowerCase().includes('placeholder_foldgroup')) continue
|
||||
messagePushOptionMap.set(session.username, {
|
||||
username: session.username,
|
||||
displayName: session.displayName || session.username,
|
||||
avatarUrl: session.avatarUrl,
|
||||
type: getMessagePushSessionType(session)
|
||||
})
|
||||
}
|
||||
|
||||
for (const contact of messagePushContactOptions) {
|
||||
if (!contact.username) continue
|
||||
if (contact.type !== 'friend' && contact.type !== 'group' && contact.type !== 'official' && contact.type !== 'former_friend') continue
|
||||
const existing = messagePushOptionMap.get(contact.username)
|
||||
messagePushOptionMap.set(contact.username, {
|
||||
username: contact.username,
|
||||
displayName: existing?.displayName || contact.displayName || contact.remark || contact.nickname || contact.username,
|
||||
avatarUrl: existing?.avatarUrl || contact.avatarUrl,
|
||||
type: getMessagePushSessionType(contact)
|
||||
})
|
||||
}
|
||||
|
||||
const messagePushOptions = Array.from(messagePushOptionMap.values())
|
||||
.sort((a, b) => {
|
||||
const aSession = chatSessions.find(session => session.username === a.username)
|
||||
const bSession = chatSessions.find(session => session.username === b.username)
|
||||
return Number(bSession?.sortTimestamp || bSession?.lastTimestamp || 0) -
|
||||
Number(aSession?.sortTimestamp || aSession?.lastTimestamp || 0)
|
||||
})
|
||||
|
||||
const messagePushAvailableSessions = messagePushOptions.filter(session => {
|
||||
if (messagePushFilterList.includes(session.username)) return false
|
||||
if (messagePushTypeFilter !== 'all' && session.type !== messagePushTypeFilter) return false
|
||||
if (messagePushFilterSearchKeyword.trim()) {
|
||||
const keyword = messagePushFilterSearchKeyword.trim().toLowerCase()
|
||||
return String(session.displayName || '').toLowerCase().includes(keyword) ||
|
||||
session.username.toLowerCase().includes(keyword)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
const getMessagePushOptionInfo = (username: string) => {
|
||||
return messagePushOptionMap.get(username) || {
|
||||
username,
|
||||
displayName: username,
|
||||
avatarUrl: undefined,
|
||||
type: 'other' as configService.MessagePushSessionType
|
||||
}
|
||||
}
|
||||
|
||||
const handleTestInsightConnection = async () => {
|
||||
setIsTestingInsight(true)
|
||||
setInsightTestResult(null)
|
||||
@@ -3350,6 +3476,151 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>推送会话过滤</label>
|
||||
<span className="form-hint">选择只推送特定会话,或屏蔽特定会话</span>
|
||||
<div className="custom-select">
|
||||
<div
|
||||
className={`custom-select-trigger ${messagePushFilterDropdownOpen ? 'open' : ''}`}
|
||||
onClick={() => setMessagePushFilterDropdownOpen(!messagePushFilterDropdownOpen)}
|
||||
>
|
||||
<span className="custom-select-value">
|
||||
{messagePushFilterMode === 'all' ? '推送所有会话' :
|
||||
messagePushFilterMode === 'whitelist' ? '仅推送白名单' : '屏蔽黑名单'}
|
||||
</span>
|
||||
<ChevronDown size={14} className={`custom-select-arrow ${messagePushFilterDropdownOpen ? 'rotate' : ''}`} />
|
||||
</div>
|
||||
<div className={`custom-select-dropdown ${messagePushFilterDropdownOpen ? 'open' : ''}`}>
|
||||
{[
|
||||
{ value: 'all', label: '推送所有会话' },
|
||||
{ value: 'whitelist', label: '仅推送白名单' },
|
||||
{ value: 'blacklist', label: '屏蔽黑名单' }
|
||||
].map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={`custom-select-option ${messagePushFilterMode === option.value ? 'selected' : ''}`}
|
||||
onClick={() => { void handleSetMessagePushFilterMode(option.value as configService.MessagePushFilterMode) }}
|
||||
>
|
||||
{option.label}
|
||||
{messagePushFilterMode === option.value && <Check size={14} />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{messagePushFilterMode !== 'all' && (
|
||||
<div className="form-group">
|
||||
<label>{messagePushFilterMode === 'whitelist' ? '主动推送白名单' : '主动推送黑名单'}</label>
|
||||
<span className="form-hint">
|
||||
{messagePushFilterMode === 'whitelist'
|
||||
? '点击左侧会话添加到白名单,只有白名单会话会推送'
|
||||
: '点击左侧会话添加到黑名单,黑名单会话不会推送'}
|
||||
</span>
|
||||
<div className="push-filter-type-tabs">
|
||||
{[
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'private', label: '私聊' },
|
||||
{ value: 'group', label: '群聊' },
|
||||
{ value: 'official', label: '订阅号/服务号' },
|
||||
{ value: 'other', label: '其他/非好友' }
|
||||
].map(option => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
className={`push-filter-type-tab ${messagePushTypeFilter === option.value ? 'active' : ''}`}
|
||||
onClick={() => setMessagePushTypeFilter(option.value as 'all' | configService.MessagePushSessionType)}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="notification-filter-container">
|
||||
<div className="filter-panel">
|
||||
<div className="filter-panel-header">
|
||||
<span>可选会话</span>
|
||||
{messagePushAvailableSessions.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="filter-panel-action"
|
||||
onClick={() => { void handleAddAllMessagePushFilterSessions() }}
|
||||
>
|
||||
全选当前
|
||||
</button>
|
||||
)}
|
||||
<div className="filter-search-box">
|
||||
<Search size={14} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索会话..."
|
||||
value={messagePushFilterSearchKeyword}
|
||||
onChange={(e) => setMessagePushFilterSearchKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="filter-panel-list">
|
||||
{messagePushAvailableSessions.length > 0 ? (
|
||||
messagePushAvailableSessions.map(session => (
|
||||
<div
|
||||
key={session.username}
|
||||
className="filter-panel-item"
|
||||
onClick={() => { void handleAddMessagePushFilterSession(session.username) }}
|
||||
>
|
||||
<Avatar
|
||||
src={session.avatarUrl}
|
||||
name={session.displayName || session.username}
|
||||
size={28}
|
||||
/>
|
||||
<span className="filter-item-name">{session.displayName || session.username}</span>
|
||||
<span className="filter-item-type">{getMessagePushTypeLabel(session.type)}</span>
|
||||
<span className="filter-item-action">+</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="filter-panel-empty">
|
||||
{messagePushFilterSearchKeyword ? '没有匹配的会话' : '暂无可添加的会话'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="filter-panel">
|
||||
<div className="filter-panel-header">
|
||||
<span>{messagePushFilterMode === 'whitelist' ? '白名单' : '黑名单'}</span>
|
||||
{messagePushFilterList.length > 0 && (
|
||||
<span className="filter-panel-count">{messagePushFilterList.length}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="filter-panel-list">
|
||||
{messagePushFilterList.length > 0 ? (
|
||||
messagePushFilterList.map(username => {
|
||||
const session = getMessagePushOptionInfo(username)
|
||||
return (
|
||||
<div
|
||||
key={username}
|
||||
className="filter-panel-item selected"
|
||||
onClick={() => { void handleRemoveMessagePushFilterSession(username) }}
|
||||
>
|
||||
<Avatar
|
||||
src={session.avatarUrl}
|
||||
name={session.displayName || username}
|
||||
size={28}
|
||||
/>
|
||||
<span className="filter-item-name">{session.displayName || username}</span>
|
||||
<span className="filter-item-type">{getMessagePushTypeLabel(session.type)}</span>
|
||||
<span className="filter-item-action">×</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<div className="filter-panel-empty">尚未添加任何会话</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<label>推送地址</label>
|
||||
<span className="form-hint">外部软件连接这个 SSE 地址即可接收新消息推送;需要先开启上方 `HTTP API 服务`</span>
|
||||
@@ -3384,7 +3655,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
</div>
|
||||
<p className="api-desc">通过 SSE 长连接接收消息事件,建议接收端按 `messageKey` 去重。</p>
|
||||
<div className="api-params">
|
||||
{['event', 'sessionId', 'messageKey', 'avatarUrl', 'sourceName', 'groupName?', 'content'].map((param) => (
|
||||
{['event', 'sessionId', 'sessionType', 'messageKey', 'avatarUrl', 'sourceName', 'groupName?', 'content'].map((param) => (
|
||||
<span key={param} className="param">
|
||||
<code>{param}</code>
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user