feat: 抖音直播间弹幕监控 - Go+Wails 实现

后端:
- SQLite 数据层 (rooms/users/chat_logs/settings/error_logs)
- 抖音 WebSocket 抓取器 (proto 解析, 心跳, watchdog)
- __ac_signature 算法 (纯 Go 移植)
- a_bogus/sign.js 签名 (goja JS 引擎)
- Echo HTTP API 服务器 (端口 5000)
- SSE 事件广播
- 自动保活 + 授权守护
- PAYS 云授权对接

前端:
- 顶栏: 品牌 + 日志/调试/客户名单/授权/文档/房间控制/主题切换
- 添加直播间 (链接解析 + 关键词)
-  Dashboard (卡片/列表视图, 公开/匿名房筛选, 分页)
- 详情页 (多 tab 消息流, 右侧用户面板, 颜色标记, 精准客户, 已看标记)
- 精准客户名单 (跨房间汇总)
- 订阅授权页面 (登录/注册/购买)
- 使用帮助 + 系统调试
- 白昼/黑夜主题切换 (CSS 变量 + iconfont)
- 底部版权栏
This commit is contained in:
2026-09-18 04:25:57 +08:00
commit e266d3098a
56 changed files with 29905 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
import React, { useState, useMemo } from 'react'
import RoomCard from './RoomCard'
import AddRoomBar from './AddRoomBar'
import type { RoomData } from '../types'
interface DashboardProps {
rooms: RoomData[]
viewMode: 'card' | 'list'
cardCols: number
listPerPage: number
onAddRoom: (url: string) => void
onToggleRoom: (id: string, action?: string) => void
onRemoveRoom: (id: string) => void
onSetActive: (id: string) => void
}
export default function Dashboard({ rooms, viewMode, cardCols, listPerPage, onAddRoom, onToggleRoom, onRemoveRoom, onSetActive }: DashboardProps) {
const [page, setPage] = useState(0)
const paginatedRooms = useMemo(() => {
if (viewMode === 'list') {
const start = page * listPerPage
return rooms.slice(start, start + listPerPage)
}
return rooms
}, [rooms, page, listPerPage, viewMode])
const totalPages = viewMode === 'list' ? Math.ceil(rooms.length / listPerPage) : 0
return (
<div className="dash-zone">
<AddRoomBar onAdd={onAddRoom} />
{viewMode === 'card' ? (
<div className="room-grid" style={{ gridTemplateColumns: `repeat(${cardCols}, 1fr)` }}>
{paginatedRooms.map(room => (
<RoomCard
key={room.id}
room={room}
onToggle={(a) => onToggleRoom(room.id, a)}
onRemove={() => onRemoveRoom(room.id)}
onDetail={() => onSetActive(room.id)}
/>
))}
{paginatedRooms.length === 0 && (
<div className="empty-state">
<p>暂无房间</p>
<p>在上方输入框添加直播间URL开始监控</p>
</div>
)}
</div>
) : (
<div className="room-list">
<table className="list-table">
<thead>
<tr>
<th>状态</th>
<th>房间</th>
<th>主播</th>
<th>类型</th>
<th>观众</th>
<th>消息</th>
<th>精准</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{paginatedRooms.map(room => (
<tr key={room.id} className={`list-row ${room.status}`} onClick={() => onSetActive(room.id)}>
<td><span className={`status-dot ${room.status}`} /></td>
<td className="list-title">{room.title}</td>
<td>{room.anchor_id}</td>
<td><span className={`kind-badge ${room.kind}`}>{room.kind === 'public' ? '公开' : '匿名'}</span></td>
<td>{room.viewers}</td>
<td>{room.total_msg}</td>
<td>{room.lead_count}</td>
<td className="list-actions">
<button className="list-btn start" onClick={e => { e.stopPropagation(); onToggleRoom(room.id, 'start') }}>▶</button>
<button className="list-btn stop" onClick={e => { e.stopPropagation(); onToggleRoom(room.id, 'stop') }}>⏹</button>
<button className="list-btn del" onClick={e => { e.stopPropagation(); onRemoveRoom(room.id) }}>✕</button>
</td>
</tr>
))}
</tbody>
</table>
{paginatedRooms.length === 0 && (
<div className="empty-state">
<p>暂无房间</p>
<p>在上方输入框添加直播间URL开始监控</p>
</div>
)}
</div>
)}
{totalPages > 1 && (
<div className="pagination">
<button className="page-btn" disabled={page === 0} onClick={() => setPage(p => p - 1)}>‹</button>
<span className="page-info">{page + 1} / {totalPages}</span>
<button className="page-btn" disabled={page >= totalPages - 1} onClick={() => setPage(p => p + 1)}>›</button>
</div>
)}
</div>
)
}