video_blacklist/new/live-management.html

1305 lines
31 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>直播管理系统</title>
<script src="./js/tailwindcss.js"></script>
<link href="./fontawesome-free-6.4.0-web/css/all.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#36CFC9',
neutral: '#F5F7FA',
'neutral-dark': '#4E5969',
success: '#00B42A',
warning: '#FF7D00',
danger: '#F53F3F',
info: '#1677FF', // 用于监听中状态 (此颜色定义保留但监听中UI元素将被移除)
},
fontFamily: {
inter: ['Inter', 'system-ui', 'sans-serif'],
},
spacing: { // 确保所需间距已定义
'4': '1rem', // 标准间距/内边距
'3': '0.75rem', // 较小间距/内边距
'8': '2rem', // 示例: 操作按钮宽度
'10': '2.5rem', // 示例: 头像尺寸
},
fontSize: { // 添加可能需要的小字体尺寸
'xxs': '0.6rem', // ~10px
'xs': '.75rem', // 12px (default text-xs)
'sm': '.875rem', // 14px (default text-sm)
'base': '1rem', // 16px
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.sidebar-item {
@apply flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 hover:bg-primary/10 hover:text-primary;
}
.sidebar-item.active {
@apply bg-primary/10 text-primary font-medium;
}
.rule-card {
@apply bg-white rounded-xl shadow-sm border border-gray-100 transition-all duration-300 hover:shadow-md;
}
.btn-primary {
@apply bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg transition-all duration-200 shadow-sm hover:shadow flex items-center justify-center gap-2;
}
.btn-outline {
@apply border border-gray-200 hover:border-primary/50 text-neutral-dark hover:text-primary px-4 py-2 rounded-lg transition-all duration-200 flex items-center justify-center gap-2;
}
.btn-danger { /* 新增删除按钮样式 */
@apply bg-danger hover:bg-danger/90 text-white px-4 py-2 rounded-lg transition-all duration-200 shadow-sm hover:shadow flex items-center justify-center gap-2;
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.scrollbar-thin {
scrollbar-width: thin;
}
.scrollbar-thin::-webkit-scrollbar {
width: 4px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: rgba(156, 163, 175, 0.5);
border-radius: 4px;
}
.sticky-top-sidebar {
position: sticky;
top: 0;
z-index: 10;
}
.blacklist-item-sidebar {
@apply bg-white rounded-lg p-3 flex items-center gap-3 hover:bg-primary/5 transition-all;
}
.blacklist-item-sidebar .options-btn {
@apply flex-shrink-0 w-8 flex items-center justify-center;
}
#blacklist-container-sidebar .blacklist-item-sidebar:not(:last-child) {
margin-bottom: 0.75rem;
}
.blacklist-item-sidebar .time-text {
@apply text-xxs text-gray-400 flex-shrink-0 opacity-75;
}
.blacklist-item-sidebar .nickname-text {
@apply text-sm font-medium truncate;
}
.blacklist-item-sidebar .reason-text {
@apply text-xs text-gray-500 mt-1 truncate;
}
.checkbox-item {
@apply flex items-center;
}
/* 监听中的样式 (已移除相关UI元素的使用此CSS类保留但可能不再作用于任何元素) */
.monitoring-status {
@apply text-info text-xs ml-2 animate-pulse;
}
}
</style>
</head>
<body class="bg-neutral font-inter text-gray-800">
<!-- 顶部导航栏 - 移动设备 -->
<header class="lg:hidden fixed top-0 left-0 right-0 bg-white shadow-sm z-10">
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center gap-2">
<i class="fa-solid fa-shield text-primary text-lg"></i>
<span class="text-lg font-bold text-primary">智能保镖</span>
</div>
<button id="mobile-menu-button" class="p-2 rounded-lg hover:bg-gray-100">
<i class="fa-solid fa-bars"></i>
</button>
</div>
</header>
<div class="flex min-h-screen pt-14 lg:pt-0">
<!-- 侧边栏导航 - 桌面端 -->
<aside id="sidebar"
class="lg:w-64 bg-white shadow-sm flex-shrink-0 hidden lg:block transition-all duration-300 z-20">
<div class="p-4 border-b border-gray-100">
<div class="flex items-center gap-2">
<i class="fa-solid fa-shield text-primary text-xl"></i>
<h1 class="text-lg font-bold text-primary">智能保镖</h1>
</div>
<p class="text-xs text-gray-400 mt-1">直播间智能管理助手</p>
</div>
<nav class="p-4">
<ul class="space-y-1">
<li><a href="live-management.html" class="sidebar-item active"><i class="fa-solid fa-video"></i> 直播管理</a>
</li>
<li><a href="smart-bodyguard.html" class="sidebar-item"><i class="fa-solid fa-cogs"></i>
保镖设置</a>
</li>
<li><a href="id-whitelist-blacklist.html" class="sidebar-item"><i class="fa-solid fa-user-check"></i> ID
黑白名单</a></li>
<li><a href="id-blacklist.html" class="sidebar-item"><i class="fas fa-ban"></i> 拉黑记录</a></li>
<li><a href="#" class="sidebar-item"><i class="fa-solid fa-book"></i> 使用教程</a></li>
<li><a href="#" class="sidebar-item"><i class="fa-solid fa-comment-dots"></i> 意见反馈</a></li>
</ul>
</nav>
</aside>
<!-- 主内容区 -->
<main class="flex-1 overflow-y-auto bg-neutral p-4 lg:p-6">
<!-- 直播管理表格 -->
<div class="bg-white rounded-xl shadow-sm overflow-hidden mb-6">
<div class="border-b p-4 flex justify-between items-center">
<h2 class="font-medium text-lg">直播账号管理</h2>
<div class="flex gap-3">
<!-- 新增按钮调用 login() -->
<button class="btn-primary flex items-center gap-2" onclick="login()">
<i class="fa-solid fa-plus"></i>
<span>新增</span>
</button>
<!-- 刷新按钮调用 refreshTable() -->
<button class="btn-outline flex items-center gap-2" onclick="refreshTable()">
<i class="fa-solid fa-sync"></i>
<span>刷新</span>
</button>
<!-- 全局停止监听按钮 (保留,但逻辑可能依赖后端判断是否有监听任务) -->
<button id="global-stop-monitor-btn" class="btn-danger flex items-center gap-2" onclick="stopMonitoring()">
<i class="fa-solid fa-stop"></i>
<span>停止监听</span>
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="bg-neutral">
<th class="py-3 px-4 text-left text-sm font-medium text-gray-600">序号</th>
<th class="py-3 px-4 text-left text-sm font-medium text-gray-600">抖音号</th>
<th class="py-3 px-4 text-left text-sm font-medium text-gray-600">到期时间</th>
<th class="py-3 px-4 text-left text-sm font-medium text-gray-600">主播昵称</th>
<th class="py-3 px-4 text-left text-sm font-medium text-gray-600">操作</th>
</tr>
</thead>
<tbody id="account-table-body">
<!-- 表格行将由 JavaScript 动态生成 -->
</tbody>
<tfoot class="bg-neutral">
<tr>
<td colspan="5" class="py-3 px-4 text-sm text-gray-600">
<span id="account-count-info">第 0-0 条/总共 0 条</span>
<!-- 简单的分页占位实际分页需复杂JS逻辑 -->
<!-- <a href="#" class="px-2 py-1 rounded hover:bg-primary/10 hover:text-primary transition-all">1</a> -->
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</main>
</div>
<script>
// 移动端菜单切换
document.getElementById('mobile-menu-button').addEventListener('click', function () {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('hidden');
if (!sidebar.classList.contains('hidden')) {
sidebar.classList.add('fixed', 'inset-0');
sidebar.classList.remove('lg:block', 'lg:w-64');
} else {
sidebar.classList.remove('fixed', 'inset-0');
sidebar.classList.add('lg:block', 'lg:w-64');
}
});
// --- 全局状态和 UI 更新函数 (UI 更新逻辑已移除) ---
// 跟踪当前正在监听的账号 url_part (此变量已不再用于UI状态切换)
// let currentlyMonitoringUrlPart = null; // 移除此行
// 更新单个账号行的操作按钮 UI (此函数已移除UI不再根据状态切换按钮)
// function updateAccountRowUI(urlPart, isMonitoring) { ... } // 移除此函数
// 启用或禁用除指定 urlPart 以外的所有"监听"按钮 (此函数已移除)
// function toggleOtherMonitorButtons(enable, currentUrlPart) { ... } // 移除此函数
// 由 Python 调用,通知前端开始监听某个账号 (仅保留作为Python的调用接口不再进行UI更新)
function startMonitoringUI(urlPart) {
console.log('前端接到通知: Python开始监听', urlPart);
// currentlyMonitoringUrlPart = urlPart; // 移除此行
// updateAccountRowUI(urlPart, true); // 移除此行
// UI不再根据监听状态变化
// 可以选择刷新表格来更新可能的"监听中"状态显示如果Python将状态包含在account对象中
// refreshTable(); // 根据需求决定是否需要刷新整个表格
}
// 由 Python 调用,通知前端停止监听某个账号 (仅保留作为Python的调用接口不再进行UI更新)
function stopMonitoringUI(urlPart, reason) {
console.log('前端接到通知: Python停止监听', urlPart, '原因:', reason);
// currentlyMonitoringUrlPart = null; // 移除此行
// updateAccountRowUI(urlPart, false); // 移除此行
// UI不再根据监听状态变化
// 可以选择刷新表格来更新可能的"监听中"状态显示如果Python将状态包含在account对象中
// refreshTable(); // 根据需求决定是否需要刷新整个表格
// 可选: 显示停止原因给用户 (alert 或其他方式)
// if (reason === 'stopped_manually') {
// alert(`账号 ${urlPart} 监听已停止。`);
// } else if (reason === 'live_detected') {
// alert(`账号 ${urlPart} 已开播,正在跳转...`);
// } else if (reason === 'error') {
// alert(`账号 ${urlPart} 监听过程中发生错误并停止。`);
// }
}
// --- pywebview API 调用函数 ---
// 调用Python的login_click方法
function login() {
console.log('调用Python登录...');
if (window.pywebview) {
// 禁用新增和刷新按钮,避免重复操作
const addButton = document.querySelector('.btn-primary');
const refreshButton = document.querySelector('.btn-outline');
if (addButton) {
addButton.disabled = true;
addButton.classList.add('opacity-50', 'cursor-not-allowed');
}
if (refreshButton) {
refreshButton.disabled = true;
refreshButton.classList.add('opacity-50', 'cursor-not-allowed');
}
window.pywebview.api.login_click()
.then(success => {
if (success) {
console.log('Python登录流程已启动');
// Note: The actual UI refresh happens when Python calls updateAccountTable
} else {
console.log('Python登录流程启动失败');
// 可以显示一个提示
alert('登录失败,请重试。');
}
})
.catch(error => {
console.error('调用Python login_click出错:', error);
// 可以显示一个错误提示
alert(`登录发生错误: ${error}`);
})
.finally(() => {
// 无论成功或失败,重新启用按钮
if (addButton) {
addButton.disabled = false;
addButton.classList.remove('opacity-50', 'cursor-not-allowed');
}
if (refreshButton) {
refreshButton.disabled = false;
refreshButton.classList.remove('opacity-50', 'cursor-not-allowed');
}
});
} else {
console.error('pywebview API 未就绪.');
alert('应用正在启动中,请稍后再试。');
}
}
// 调用Python的refresh_accounts方法
function refreshTable() {
console.log('调用Python刷新账号列表...');
if (window.pywebview) {
// Python 的 refresh_accounts 会读取文件并调用 updateAccountTable 来更新 UI
window.pywebview.api.refresh_accounts()
.catch(error => {
console.error('调用Python refresh_accounts出错:', error);
// 可以显示一个错误提示
alert(`刷新账号列表发生错误: ${error}`);
});
} else {
console.error('pywebview API 未就绪.');
alert('应用正在启动中,请稍后再试。');
}
}
// 调用Python的删除账号方法
// 接收 urlPart, nickname, douyinId 作为参数
function deleteAccount(urlPart, nickname, douyinId) {
console.log('前端请求删除账号:', urlPart, '昵称:', nickname, '抖音号:', douyinId);
// 移除前端检查是否正在监听的逻辑,由后端处理
// if (currentlyMonitoringUrlPart === urlPart) { ... return; } // 移除此段
if (window.pywebview) {
const displayNickname = nickname || '未知昵称';
const displayDouyinId = douyinId || '未知抖音号';
const confirmMessage = `确定要删除账号 "${displayNickname}" (抖音号: ${displayDouyinId}) 吗?`;
const confirmDelete = confirm(confirmMessage);
if (confirmDelete) {
window.pywebview.api.delete_account(urlPart)
.then(success => {
if (success) {
console.log(`Python 成功处理删除请求url_part: ${urlPart}`);
// 删除成功后刷新表格
refreshTable();
} else {
console.warn(`Python 处理删除请求失败或未找到账号url_part: ${urlPart}`);
// Python端在无法删除正在监听的账号时可能已经弹出了提示这里可以不重复弹
// alert(`删除账号 ${displayNickname} (抖音号: ${displayDouyinId}) 失败或未找到。`);
}
})
.catch(error => {
console.error(`调用Python delete_account 出错url_part: ${urlPart}:`, error);
alert(`删除账号发生错误: ${error}`);
});
} else {
console.log('用户取消删除。');
}
} else {
console.error('pywebview API 未就绪.');
alert('应用正在启动中,请稍后再试。');
}
}
// 调用Python的监听账号方法
function monitorAccount(urlPart, nickname) {
console.log('前端请求监听账号:', urlPart);
// 移除前端检查是否已有账号在监听的逻辑,由后端处理
// if (currentlyMonitoringUrlPart) { ... return; } // 移除此段
if (window.pywebview) {
window.pywebview.api.monitor_account(urlPart, nickname)
.then(success => {
if (success) {
console.log(`Python 成功启动账号 ${urlPart} 的监听流程。`);
// UI更新将通过 refreshTable 或其他方式由Python触发如果需要显示监听中状态
// refreshTable(); // 如果Python需要在状态列显示"监听中",则需要刷新表格
} else {
console.warn(`Python 启动账号 ${urlPart} 监听失败。`);
// Python 在失败时可能已经弹窗提示,这里可以不处理或加一个通用提示
// alert('启动监听失败,请检查日志或稍后再试。');
}
})
.catch(error => {
console.error(`调用Python monitor_account 出错url_part: ${urlPart}:`, error);
alert(`启动监听时发生错误: ${error}`);
});
} else {
console.error('pywebview API 未就绪.');
alert('应用正在启动中,请稍后再试。');
}
}
// 调用Python的停止监听方法 (现在是全局停止按钮触发不依赖特定urlPart)
function stopMonitoring() { // urlPart 参数不再需要,函数签名修改
console.log('前端请求停止当前所有监听任务');
// 移除前端检查是否正在监听的逻辑,由后端处理
// if (!currentlyMonitoringUrlPart) { ... return; } // 移除此段
if (window.pywebview) {
window.pywebview.api.stop_monitoring() // 调用全局停止方法
.then(success => {
if (success) {
console.log('Python 成功停止监听。');
// UI更新将通过 refreshTable 或其他方式由Python触发
// refreshTable(); // 如果需要移除"监听中"状态,则需要刷新表格
} else {
console.warn('Python 停止监听失败 (可能没有监听任务在运行)。');
// 后端会处理没有监听任务的情况,前端无需额外处理
}
})
.catch(error => {
console.error('调用Python stop_monitoring 出错:', error);
alert(`停止监听时发生错误: ${error}`);
});
} else {
console.error('pywebview API 未就绪.');
alert('应用正在启动中,请稍后再试。');
}
}
// --- JavaScript 动态渲染表格函数 ---
// 这个函数由 Python 调用,用于更新账号列表
// 现在它总是渲染"监听"和"删除"按钮不再根据监听状态切换UI
function updateAccountTable(accounts) {
console.log('接收到账号数据,正在更新表格:', accounts);
const tbody = document.getElementById('account-table-body');
const countInfo = document.getElementById('account-count-info');
tbody.innerHTML = ''; // 清空当前表格内容
if (!accounts || accounts.length === 0) {
// 如果没有账号,显示提示信息或空状态
const noDataRow = `<tr><td colspan="5" class="py-3 px-4 text-sm text-center text-gray-500">暂无账号,请点击"新增"按钮添加。</td></tr>`;
tbody.innerHTML = noDataRow;
countInfo.textContent = '第 0-0 条/总共 0 条';
// currentlyMonitoringUrlPart = null; // 移除此行
// toggleOtherMonitorButtons(true, null); // 移除此行
return;
}
// 遍历账号数据,创建并添加表格行
accounts.forEach((account, index) => {
const row = document.createElement('tr');
// 添加 data 属性以便通过 url_part 定位行
row.dataset.urlPart = account.url_part;
row.dataset.nickname = account.nickname || ''; // 存储昵称和抖音号,方便 deleteAccount 使用
row.dataset.douyinId = account.douyin_id || '';
row.classList.add('border-b', 'hover:bg-gray-50'); // 添加样式
// 序号
const indexCell = document.createElement('td');
indexCell.classList.add('py-3', 'px-4', 'text-sm');
indexCell.textContent = index + 1; // 从1开始计数
row.appendChild(indexCell);
// 抖音号
const douyinIdCell = document.createElement('td');
douyinIdCell.classList.add('py-3', 'px-4', 'text-sm');
douyinIdCell.textContent = account.douyin_id || '未知抖音号';
row.appendChild(douyinIdCell);
// 到期时间
const expiryCell = document.createElement('td');
expiryCell.classList.add('py-3', 'px-4', 'text-sm');
expiryCell.innerHTML = `${account.expiry_time || '未知到期时间'} <a href="#" class="text-primary hover:underline ml-2">充值</a>`;
row.appendChild(expiryCell);
// 主播昵称
const nicknameCell = document.createElement('td');
nicknameCell.classList.add('py-3', 'px-4', 'text-sm');
nicknameCell.textContent = account.nickname || '未知昵称';
row.appendChild(nicknameCell);
// 操作 (总是显示监听和删除按钮)
const actionsCell = document.createElement('td');
actionsCell.classList.add('py-3', 'px-4', 'text-sm', 'whitespace-nowrap'); // 防止按钮换行
// 总是显示监听和删除按钮
actionsCell.innerHTML = `
<button class="text-primary hover:underline mr-3" onclick="monitorAccount('${account.url_part}', '${account.nickname}')">
<i class="fa-solid fa-play mr-1"></i>监听
</button>
<button class="text-danger hover:underline mr-3" onclick="deleteAccount('${account.url_part}', '${account.nickname}', '${account.douyin_id}')">
<i class="fa-solid fa-trash-can mr-1"></i>删除
</button>
`;
// 移除根据状态切换按钮的逻辑
row.appendChild(actionsCell);
tbody.appendChild(row);
});
// 更新总数信息
countInfo.textContent = `第 1-${accounts.length} 条/总共 ${accounts.length}`;
// 移除根据是否有正在监听的账号,启用或禁用其他监听按钮的逻辑
// if (currentlyMonitoringUrlPart) { toggleOtherMonitorButtons(false, currentlyMonitoringUrlPart); } else { toggleOtherMonitorButtons(true, null); } // 移除此段
console.log('表格更新完成。');
}
// --- 页面加载完成后执行 ---
// 等待DOM完全加载
window.addEventListener('DOMContentLoaded', (event) => {
console.log('DOM完全加载完成');
// 创建同步的 XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', 'user_Settings_Options.json', false); // 第三个参数设为 false 表示同步请求
xhr.send();
if (xhr.status === 200) {
// console.log('配置文件已加载');
// 解析 JSON 并写入 localStorage
const data = JSON.parse(xhr.responseText);
// console.log('配置数据:', data);
localStorage.setItem('smartBodyguardSettings', JSON.stringify(data));
console.log('配置已同步保存到 localStorage');
// showSuccessModal('配置已同步');
window.addEventListener('pywebviewready', function () {
console.log('window.addEventListener.pywebview 已就绪,重新发送设置。');
// hideEditModal();
// var container = document.getElementById('pywebview-status')
// container.innerHTML = '<i>pywebview</i> is ready'
// 在 pywebview 就绪后发送当前设置
pywebview.api.get_localStorage(localStorage.getItem('smartBodyguardSettings'))
});
// Notify pywebview (if available) - assuming this is how you communicate
if (window.pywebview) {
console.log('window.pywebview.pywebview 已就绪,重新发送设置。');
// var container = document.getElementById('pywebview-status')
// container.innerHTML = '<i>pywebview</i> is ready'
pywebview.api.get_localStorage(localStorage.getItem('smartBodyguardSettings'));
} else {
// Fallback for pywebview not being ready immediately (already handled by event listener above)
console.log('pywebview 未就绪,跳过 API 调用。');
}
} else {
// This throws an error but doesn't stop script execution immediately
console.error(`请求配置文件失败,状态码: ${xhr.status}`);
// You might want to handle this failure more gracefully
}
// 等待pywebview API 就绪
if (window.pywebview) {
console.log('pywebview API 已就绪。');
// 在API就绪后立即调用Python方法获取初始账号列表并渲染
window.pywebview.api.get_initial_accounts()
.then(() => {
console.log('请求初始账号列表已发送到 Python。');
// updateAccountTable will be called by Python when data is ready
})
.catch(error => {
console.error('调用Python get_initial_accounts出错:', error);
// Optionally display an error in the UI
alert(`获取初始账号列表失败: ${error}`);
});
} else {
console.warn('pywebview API 尚未就绪,等待 pywebviewready 事件...');
// 如果API未就绪监听 'pywebviewready' 事件
window.addEventListener('pywebviewready', function () {
console.log('pywebviewready 事件触发API 已就绪。');
window.pywebview.api.get_initial_accounts()
.then(() => {
console.log('请求初始账号列表已发送到 Python (通过事件监听)。');
})
.catch(error => {
console.error('调用Python get_initial_accounts出错 (通过事件监听):', error);
alert(`获取初始账号列表失败 (通过事件监听): ${error}`);
});
});
}
// 添加一个事件监听,尝试在窗口关闭前停止监听(不保证总能触发)
// 这是一个尽力而为的尝试,更可靠的方式是在 Python 应用关闭时处理
window.addEventListener('beforeunload', (event) => {
console.log('窗口即将关闭,尝试停止监听...');
// 移除基于currentlyMonitoringUrlPart的判断
if (window.pywebview && window.pywebview.api && window.pywebview.api.stop_monitoring) {
// 使用 async/await 或 Promise 链,但 beforeunload 不支持异步
// 简单的同步调用可能会卡住关闭,或者不执行
// 更好的方法是在 Python 应用级别监听窗口关闭事件并停止线程
// 这里只是一个示例性的尝试,可能无效
// window.pywebview.api.stop_monitoring(); // 同步调用,不推荐在 beforeunload
}
// event.preventDefault(); // 取消关闭(用于调试)
// event.returnValue = ''; // 某些浏览器需要这个来显示提示
});
});
</script>
</body>
</html>