+
+
🔑 订阅授权
+
+ {license?.state === 'active' ? (
+
+
+
✅ 授权有效
+
计划: {license.plan}
+
有效期至: {license.expires_at ? new Date(license.expires_at).toLocaleDateString() : '永久'}
+
最大房间数: {license.max_rooms}
+
+
+ ) : license?.state === 'expired' ? (
+
+ ) : (
+
+
🔒 尚未配置授权,请选择以下方式激活:
+
+
+
步骤 1: 登录/注册
+
访问客户服务中心获取授权码
+
+
+
+
+
+ )}
+
+ {license?.qrcode && (
+
+
扫码激活
+
+
+ )}
+
+ )
+}
+
+function ActivateForm({ token, setToken, status, onActivate }: {
+ token: string
+ setToken: (v: string) => void
+ status: string
+ onActivate: () => void
+}) {
+ return (
+
+
+
+
+ {room.title || room.anchor_id}
+
+
+
+
+
+
+
+
+ 👁 {room.viewers}
+ 💬 {room.total_msg}
+ 🎯 {room.lead_count}
+ {room.status === 'running' ? '● 直播中' : room.status === 'starting' ? '◌ 启动中' : room.status === 'error' ? '✕ 错误' : '○ 已停止'}
+
+ {lastMsgs.length > 0 && (
+
+ {lastMsgs.map(msg => (
+
+ {ICONS[msg.type as keyof typeof ICONS] || ICONS.chat}
+ {truncate(msg.content, 40)}
+ {formatTime(msg.timestamp)}
+
+ ))}
+
+ )}
+
+ )
+}
+
+function truncate(s: string, n: number) {
+ return s.length > n ? s.slice(0, n) + '...' : s
+}
+
+function formatTime(ts: number) {
+ if (!ts) return ''
+ const d = new Date(ts)
+ const pad = (n: number) => String(n).padStart(2, '0')
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}`
+}
diff --git a/frontend/src/components/RoomTools.tsx b/frontend/src/components/RoomTools.tsx
new file mode 100644
index 0000000..efc8f41
--- /dev/null
+++ b/frontend/src/components/RoomTools.tsx
@@ -0,0 +1,35 @@
+import React from 'react'
+
+interface RoomToolsProps {
+ viewMode?: 'card' | 'list'
+ cardCols?: number
+ listPerPage?: number
+ onToggleView?: (v: 'card' | 'list') => void
+ onSetCols?: (n: number) => void
+ onSetPerPage?: (n: number) => void
+}
+
+export default function RoomTools({ viewMode, cardCols, listPerPage, onToggleView, onSetCols, onSetPerPage }: RoomToolsProps) {
+ return (
+
+
+
+ {viewMode === 'card' && (
+
+ 列:
+ {[2, 3, 4, 5].map(n => (
+
+ ))}
+
+ )}
+ {viewMode === 'list' && (
+
+ 页:
+ {[10, 20, 50, 100].map(n => (
+
+ ))}
+
+ )}
+
+ )
+}
diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts
new file mode 100644
index 0000000..cd64217
--- /dev/null
+++ b/frontend/src/constants.ts
@@ -0,0 +1,39 @@
+export const ICONS = {
+ chat: '💬',
+ gift: '🎁',
+ enter: '👋',
+ like: '❤️',
+ follow: '➕',
+ stats: '📊',
+ fansclub: '⭐',
+ status: 'ℹ️',
+ rank: '🏆',
+ room: '🏠',
+ room_stats: '📊',
+ emoji_chat: '😀',
+ stream_adaptation: '⚙️',
+ lead: '🎯'
+} as const
+
+export const MARKS = [
+ { key: 'blue', label: '蓝', color: '#4a9eff', tint: 'rgba(74,158,255,0.10)' },
+ { key: 'red', label: '红', color: '#ff4d4f', tint: 'rgba(255,77,79,0.10)' },
+ { key: 'yellow', label: '黄', color: '#ffc53d', tint: 'rgba(255,197,61,0.10)' }
+] as const
+
+export const MARK_MAP = Object.fromEntries(MARKS.map(m => [m.key, m])) as Record