video_blacklist/index/smart-bodyguard.html

2039 lines
108 KiB
HTML
Raw Permalink Normal View History

2025-06-03 18:23:22 +08:00
<!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',
},
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;
}
.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;
/* 粘附在 aside 的顶部 */
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;
/* Tailwind 的 space-y-3 */
}
/* 侧边栏记录项中较小的时间文本样式 */
/* 如果 text-xs 仍然太大,使用 xxs并结合较低的透明度 */
.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和label的容器 */
.checkbox-item {
@apply flex items-center;
/* 保持这个样式,用于对齐 checkbox 和 label */
}
/* 移除 .checkbox-group 的定义 */
/* .checkbox-group {
@apply flex flex-wrap gap-x-6 gap-y-2 items-center;
} */
}
</style>
</head>
<body class="font-inter bg-neutral min-h-screen flex flex-col">
<!-- 顶部导航栏 (移动设备) -->
<header class="lg:hidden bg-white shadow-sm px-4 py-3 flex items-center justify-between">
<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>
<button id="mobile-menu-btn" class="p-2 rounded-lg hover:bg-gray-100">
<i class="fa-solid fa-bars text-neutral-dark"></i>
</button>
</header>
<div class="flex flex-1 overflow-hidden">
<!-- 侧边栏导航 -->
<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"><i class="fa-solid fa-video"></i> 直播管理</a>
</li>
<li><a href="smart-bodyguard.html" class="sidebar-item active"><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="Logging.html" class="sidebar-item"><i class="fa-solid fa-file-lines"></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>
<!-- 主内容区 (Rules and Settings) -->
<main class="flex-1 overflow-y-auto bg-neutral p-4 lg:p-6">
<!-- 主播信息 -->
<div class="bg-primary rounded-lg p-4 text-white mb-6 fade-in flex items-center">
<div class="flex items-center gap-3 flex-1">
<img src="https://picsum.photos/id/64/40/40" alt="用户头像"
class="w-10 h-10 rounded-full object-cover border-2 border-white">
<div>
<p id="anchor-name" class="text-base font-medium">主播名称</p>
<button id="change-account"
class="text-xs bg-white/20 hover:bg-white/30 px-2 py-1 rounded transition-all mt-1">
点击切换账号
</button>
</div>
</div>
<!-- 新增清除缓存按钮(移至最右边) -->
<button id="clear-cache-btn"
class="flex items-center justify-center w-10 h-10 rounded-full bg-white/10 hover:bg-white/20 transition-all text-white ml-2">
<i class="fa-solid fa-trash"></i>
</button>
</div>
<!-- 欢迎信息 -->
<div class="mb-6 fade-in">
<h2 class="text-[clamp(1.5rem,3vw,2rem)] font-bold text-gray-800">欢迎使用智能保镖</h2>
<p class="text-gray-500 mt-1">为您的直播间提供全方位保护,有效过滤不良用户</p>
</div>
<!-- 拉黑规则区域 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
<!-- id黑名单 -->
<!-- <div class="rule-card p-5 lg:col-span-2" data-rule-id="id-blacklist-rule">-->
<!-- <div class="flex items-center justify-between mb-4">-->
<!-- <div class="flex items-center gap-3">-->
<!-- <div class="w-8 h-8 rounded-lg bg-danger/10 flex items-center justify-center">-->
<!-- <i class="fas fa-ban text-danger"></i>-->
<!-- </div>-->
<!-- <h3 class="rule-title font-medium">id黑名单</h3>-->
<!-- </div>-->
<!-- <div class="flex items-center gap-2">-->
<!-- <label class="relative inline-flex items-center cursor-pointer">-->
<!-- <input type="checkbox" value="" class="sr-only peer rule-toggle"-->
<!-- data-rule-id="id-blacklist-rule">-->
<!-- <div-->
<!-- class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">-->
<!-- </div>-->
<!-- </label>-->
<!-- <button class="text-gray-500 hover:text-primary edit-rule-btn"-->
<!-- data-rule-id="id-blacklist-rule">-->
<!-- <i class="fa-solid fa-pencil"></i>-->
<!-- </button>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="space-y-3 rule-content ">-->
<!-- <textarea-->
<!-- class="w-full lg:w-[100%] px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"-->
<!-- data-rule-id="id-blacklist-rule" placeholder="一行一个关键词" rows="8"-->
<!-- style="min-height: 150px; resize: none;"></textarea>-->
<!-- <div class="text-xs text-gray-400 mt-2">-->
<!-- <i class="fa-solid fa-info-circle"></i> 设置的id将被拉黑-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- 弹幕关键词拉黑 -->
<div class="rule-card p-5 lg:col-span-2" data-rule-id="danmaku">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-warning/10 flex items-center justify-center">
<i class="fa-solid fa-comment text-warning"></i>
</div>
<h3 class="rule-title font-medium">弹幕关键词拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="danmaku">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="danmaku">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<textarea
class="w-full lg:w-[100%] px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="danmaku" placeholder="一行一个关键词" rows="8"
style="min-height: 150px; resize: none;"></textarea>
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 包含设置关键词的弹幕用户将被拉黑
</div>
</div>
</div>
<!-- 本地账号数据拉黑 -->
<div class="rule-card p-5" data-rule-id="local-data">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-success/10 flex items-center justify-center">
<i class="fa-solid fa-database text-success"></i>
</div>
<h3 class="rule-title font-medium">数据互通</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="local-data">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="local-data">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 数据互通符合特定条件的用户将被拉黑
</div>
</div>
</div>
<!-- 疑似账号 -->
<div class="rule-card p-5" data-rule-id="suspected-account">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-success/10 flex items-center justify-center">
<i class="fa-solid fa-user-secret text-success"></i>
</div>
<h3 class="rule-title font-medium">疑似账号</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="suspected-account">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn"
data-rule-id="suspected-account">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 疑似账号数据符合特定条件的用户将被拉黑
</div>
</div>
</div>
<!-- 关注拉黑 -->
<div class="rule-card p-5" data-rule-id="follow">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-secondary/10 flex items-center justify-center">
<i class="fa-solid fa-user-plus text-secondary"></i>
</div>
<h3 class="rule-title font-medium">关注拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="follow">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="follow">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 对关注行为进行拉黑
</div>
</div>
</div>
<!-- 通过分享进入直播间 -->
<div class="rule-card p-5" data-rule-id="share-entry">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<i class="fa-solid fa-link text-primary"></i>
</div>
<h3 class="rule-title font-medium">通过分享进入直播间</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="share-entry">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="share-entry">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 对通过分享进入直播间进行拉黑
</div>
</div>
</div>
<!-- 通过关注进入直播间 -->
<div class="rule-card p-5" data-rule-id="follow-entry">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-secondary/10 flex items-center justify-center">
<i class="fa-solid fa-user-plus text-secondary"></i>
</div>
<h3 class="rule-title font-medium">通过关注进入直播间</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="follow-entry">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="follow-entry">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 对通过关注进入直播间进行拉黑
</div>
</div>
</div>
<!-- 私密账号 -->
<div class="rule-card p-5" data-rule-id="private-account">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<i class="fa-solid fa-lock text-primary"></i>
</div>
<h3 class="rule-title font-medium">私密账号</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="private-account">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn"
data-rule-id="private-account">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 对私密账号进行拉黑
</div>
</div>
</div>
<!-- 开通蓝v -->
<div class="rule-card p-5" data-rule-id="blue-v">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-warning/10 flex items-center justify-center">
<i class="fa-solid fa-certificate text-warning"></i>
</div>
<h3 class="rule-title font-medium">开通蓝v</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="blue-v">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="blue-v">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 对开通蓝v的账号进行拉黑
</div>
</div>
</div>
<!-- 分享拉黑 -->
<div class="rule-card p-5" data-rule-id="share">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-danger/10 flex items-center justify-center">
<i class="fa-solid fa-share-alt text-danger"></i>
</div>
<h3 class="rule-title font-medium">分享拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="share">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="share">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<!-- 分享方式多选框 - 使用更小的水平间距 -->
<div class="flex flex-wrap gap-x-4 gap-y-2 items-center">
<label class="checkbox-item">
<input type="checkbox" value="all" class="mr-2 rule-checkbox-option"
data-rule-id="share" data-option-value="all">
<span>全部</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="douyin_dm" class="mr-2 rule-checkbox-option"
data-rule-id="share" data-option-value="douyin_dm">
<span>抖音私信</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="wechat_moments" class="mr-2 rule-checkbox-option"
data-rule-id="share" data-option-value="wechat_moments">
<span>微信朋友圈</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="wechat" class="mr-2 rule-checkbox-option" data-rule-id="share"
data-option-value="wechat">
<span>微信</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="qq" class="mr-2 rule-checkbox-option" data-rule-id="share"
data-option-value="qq">
<span>QQ</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="qq_zone" class="mr-2 rule-checkbox-option"
data-rule-id="share" data-option-value="qq_zone">
<span>QQ空间</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="weibo" class="mr-2 rule-checkbox-option" data-rule-id="share"
data-option-value="weibo">
<span>微博</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="link_image" class="mr-2 rule-checkbox-option"
data-rule-id="share" data-option-value="link_image">
<span>链接|图片</span>
</label>
</div>
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 选择的分享方式拉黑
</div>
</div>
</div>
<!-- 性别拉黑 -->
<div class="rule-card p-5" data-rule-id="gender">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-success/10 flex items-center justify-center">
<i class="fa-solid fa-venus-mars text-success"></i>
</div>
<h3 class="rule-title font-medium">性别拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="gender">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="gender">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<!-- 性别多选框 - 使用标准的水平间距 -->
<div class="flex flex-wrap gap-x-6 gap-y-2 items-center">
<label class="checkbox-item">
<input type="checkbox" value="all" class="mr-2 rule-checkbox-option" data-rule-id="gender"
data-option-value="all">
<span>全部</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="unknown" class="mr-2 rule-checkbox-option"
data-rule-id="gender" data-option-value="unknown">
<span>未知</span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="male" class="mr-2 rule-checkbox-option" data-rule-id="gender"
data-option-value="male">
<span></span>
</label>
<label class="checkbox-item">
<input type="checkbox" value="female" class="mr-2 rule-checkbox-option"
data-rule-id="gender" data-option-value="female">
<span></span>
</label>
</div>
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 选择的性别用户将被拉黑
</div>
</div>
</div>
<!-- 年龄区间拉黑 -->
<div class="rule-card p-5" data-rule-id="age-range">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-warning/10 flex items-center justify-center">
<i class="fa-solid fa-calendar-days text-warning"></i>
</div>
<h3 class="rule-title font-medium">年龄区间拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="age-range">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="age-range">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<input type="text"
class="flex-1 w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="age-range" placeholder="示例 18-31,46-60">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 设置的年龄区间用户将被拉黑
</div>
</div>
</div>
<!-- 粉丝数量大于拉黑 -->
<div class="rule-card p-5" data-rule-id="follower-count">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<i class="fa-solid fa-users text-primary"></i>
</div>
<h3 class="rule-title font-medium">粉丝数量大于拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="follower-count">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="follower-count">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<input type="number"
class="flex-1 w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="follower-count" placeholder="设置粉丝数量">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 粉丝数量大于设置值的用户将被拉黑
</div>
</div>
</div>
<!-- 关注数量大于拉黑 -->
<div class="rule-card p-5" data-rule-id="following-count">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-secondary/10 flex items-center justify-center">
<i class="fa-solid fa-user-plus text-secondary"></i>
</div>
<h3 class="rule-title font-medium">关注数量大于拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="following-count">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="following-count">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<input type="number"
class="flex-1 w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="following-count" placeholder="设置关注数量">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 关注数量大于设置值的用户将被拉黑
</div>
</div>
</div>
<!-- 作品数量大于拉黑 -->
<div class="rule-card p-5" data-rule-id="work-count">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-success/10 flex items-center justify-center">
<i class="fa-solid fa-file-alt text-success"></i>
</div>
<h3 class="rule-title font-medium">作品数量大于拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="work-count">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="work-count">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<input type="number"
class="flex-1 w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="work-count" placeholder="设置作品数量">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 作品数量大于设置值的用户将被拉黑
</div>
</div>
</div>
<!-- 用户进入次数拉黑 -->
<div class="rule-card p-5" data-rule-id="entry-count">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<i class="fa-solid fa-repeat text-primary"></i>
</div>
<h3 class="rule-title font-medium">用户进入次数拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle"
data-rule-id="entry-count">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="entry-count">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<input type="number"
class="flex-1 w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="entry-count" placeholder="设置进入次数">
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 进入次数大于设置值的用户将被拉黑
</div>
</div>
</div>
<!-- 昵称关键词拉黑 -->
<div class="rule-card p-5" data-rule-id="nickname">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-success/10 flex items-center justify-center">
<i class="fa-solid fa-user text-success"></i>
</div>
<h3 class="rule-title font-medium">昵称关键词</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="nickname">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="nickname">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<textarea
class="w-full lg:w-[100%] px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="nickname" placeholder="一行一个关键词" rows="4"
style="min-height: 150px; resize: none;"></textarea>
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 昵称包含设置关键词的用户将被拉黑
</div>
</div>
</div>
<!-- 个性签名关键词拉黑 -->
<div class="rule-card p-5" data-rule-id="signature">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<i class="fa-solid fa-file-signature text-primary"></i>
</div>
<h3 class="rule-title font-medium">个性签名关键词拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="signature">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="signature">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<textarea
class="w-full lg:w-[100%] px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="signature" placeholder="一行一个关键词" rows="4"
style="min-height: 150px; resize: none;"></textarea>
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 个性签名包含设置关键词的用户将被拉黑
</div>
</div>
</div>
<!-- 地区拉黑 -->
<div class="rule-card p-5" data-rule-id="region">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<i class="fa-solid fa-map-marker-alt text-warning"></i>
</div>
<h3 class="rule-title font-medium">地区拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="region">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="region">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<textarea
class="w-full lg:w-[100%] px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="region" placeholder="一行一个关键词" rows="4"
style="min-height: 150px; resize: none;"></textarea>
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 来自设置地区的用户将被拉黑
</div>
</div>
</div>
<!-- IP拉黑 -->
<div class="rule-card p-5" data-rule-id="ip">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
<i class="fa-solid fa-globe-americas text-secondary"></i>
</div>
<h3 class="rule-title font-medium">IP拉黑</h3>
</div>
<div class="flex items-center gap-2">
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" value="" class="sr-only peer rule-toggle" data-rule-id="ip">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary">
</div>
</label>
<button class="text-gray-500 hover:text-primary edit-rule-btn" data-rule-id="ip">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="space-y-3 rule-content ">
<textarea
class="w-full lg:w-[100%] px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all rule-input"
data-rule-id="ip" placeholder="一行一个关键词" rows="4"
style="min-height: 150px; resize: none;"></textarea>
<div class="text-xs text-gray-400 mt-2">
<i class="fa-solid fa-info-circle"></i> 来自设置IP的用户将被拉黑
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="mt-8 mb-8 flex flex-col sm:flex-row gap-4 justify-center">
<button id="save-settings" class="btn-primary min-w-[120px]">
<i class="fa-solid fa-save"></i> 保存设置
</button>
<button id="reset-settings" class="btn-outline min-w-[120px]">
<i class="fa-solid fa-refresh"></i> 重置设置
</button>
</div>
</main>
<!-- 右侧拉黑记录区域 -->
<aside class="hidden lg:block w-80 bg-white shadow-sm border-l border-gray-100 overflow-y-auto flex-shrink-0">
<div class="p-5 border-b border-gray-100 sticky-top-sidebar bg-white z-10 flex justify-between items-center">
<div>
<h3 class="font-medium flex items-center gap-2">
<i class="fa-solid fa-user-ban text-primary"></i> 拉黑记录
</h3>
<p class="text-xs text-gray-400 mt-1">最近7天共拉黑 <span id="total-blacklist-count">--</span></p>
</div>
<button id="refresh-blacklist" class="text-gray-400 hover:text-primary transition-colors" title="刷新记录">
<i class="fa-solid fa-refresh"></i>
</button>
</div>
<div class="p-4">
<div class="relative mb-4">
<input type="text" id="search-blacklist-sidebar" placeholder="搜索昵称、抖音号"
class="w-full pl-10 pr-8 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none text-sm">
<i class="fa-solid fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
<button id="clear-search-sidebar"
class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-primary hidden"
title="清除搜索">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<div id="blacklist-container-sidebar"
class="space-y-3 max-h-[calc(100vh-200px)] overflow-y-auto scrollbar-thin">
<!-- 拉黑记录项将在这里动态生成 -->
</div>
<div class="mt-4 text-center">
<button id="load-more-sidebar" class="text-primary text-sm hover:underline hidden">
查看更多 <i class="fa-solid fa-chevron-right text-xs ml-1"></i>
</button>
</div>
</div>
</aside>
</div>
<!-- 规则名称编辑弹窗 -->
<div id="edit-modal"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 pointer-events-none transition-opacity duration-300">
<div
class="bg-white rounded-xl shadow-lg p-6 w-full max-w-md transform scale-95 transition-transform duration-300">
<div class="flex justify-between items-center mb-4">
<h3 class="font-medium text-lg">编辑规则名称</h3>
<button id="close-edit-modal" class="text-gray-400 hover:text-gray-600">
<i class="fa-solid fa-times"></i>
</button>
</div>
<div class="mb-4">
<input type="text" id="rule-name-input"
class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none">
</div>
<div class="flex justify-end gap-3">
<button id="cancel-edit" class="btn-outline">取消</button>
<button id="save-edit" class="btn-primary">保存</button>
</div>
</div>
</div>
<!-- 操作成功弹窗 -->
<div id="success-modal"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 pointer-events-none transition-opacity duration-300">
<div
class="bg-white rounded-xl shadow-lg p-6 w-full max-w-sm transform scale-95 transition-transform duration-300 text-center">
<div class="w-16 h-16 bg-success/10 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fa-solid fa-check text-success text-xl"></i>
</div>
<h3 class="font-medium text-lg mb-2" id="success-message">操作成功</h3>
<p class="text-gray-500 text-sm mb-4">该弹窗将在5秒后自动关闭</p>
<button id="close-success-modal" class="btn-primary w-full">
关闭
</button>
</div>
</div>
<!-- 切换账号弹窗 -->
<div id="change-account-modal"
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 opacity-0 pointer-events-none transition-opacity duration-300">
<div
class="bg-white rounded-xl shadow-lg p-6 w-full max-w-md transform scale-95 transition-transform duration-300">
<div class="flex justify-between items-center mb-4">
<h3 class="font-medium text-lg">切换账号</h3>
<button id="close-change-account-modal" class="text-gray-400 hover:text-gray-600">
<i class="fa-solid fa-times"></i>
</button>
</div>
<div class="mb-4">
<select id="account-select"
class="w-full px-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none">
<option value="anchor1">主播名称1</option>
<option value="anchor2">主播名称2</option>
<option value="anchor3">主播名称3</option>
</select>
</div>
<div class="flex justify-end gap-3">
<button id="cancel-change-account" class="btn-outline">取消</button>
<button id="confirm-change-account" class="btn-primary">确认切换</button>
</div>
</div>
</div>
<!-- 用户操作选项菜单 (定位在侧边栏记录项旁) -->
<div id="user-options-menu"
class="fixed bg-white rounded-lg shadow-lg z-50 opacity-0 pointer-events-none transition-all duration-200 transform scale-95">
<ul class="py-2 min-w-[120px]">
<li class="px-4 py-2 hover:bg-gray-100 cursor-pointer text-sm" id="unblock-user">
<i class="fa-solid fa-user-plus mr-2"></i> 解除拉黑
</li>
<li class="px-4 py-2 hover:bg-gray-100 cursor-pointer text-sm" id="view-profile">
<i class="fa-solid fa-user mr-2"></i> 查看用户主页
</li>
</ul>
</div>
<script>
// 全局/共享变量(如果需要被两部分使用,现在保留在 DOMContentLoaded 或相关函数中)
const editModal = document.getElementById('edit-modal');
const ruleNameInput = document.getElementById('rule-name-input');
const successModalElement = document.getElementById('success-modal');
const successMessageElement = document.getElementById('success-message');
const changeAccountModal = document.getElementById('change-account-modal');
const userOptionsMenu = document.getElementById('user-options-menu');
let currentRuleId = null; // 需要保留这个变量
// --- 通用工具函数 ---
// 显示编辑弹窗
function showEditModal() {
if (editModal && ruleNameInput) {
editModal.classList.remove('opacity-0', 'pointer-events-none');
editModal.querySelector('div').classList.remove('scale-95');
editModal.querySelector('div').classList.add('scale-100');
ruleNameInput.focus();
}
}
// 隐藏编辑弹窗
function hideEditModal() {
if (editModal) {
editModal.classList.add('opacity-0', 'pointer-events-none');
editModal.querySelector('div').classList.remove('scale-100');
editModal.querySelector('div').classList.add('scale-95');
}
}
// 显示成功弹窗
function showSuccessModal(message) {
if (successMessageElement && successModalElement) {
successMessageElement.textContent = message || '操作成功';
successModalElement.classList.remove('opacity-0', 'pointer-events-none');
successModalElement.querySelector('div').classList.remove('scale-95');
successModalElement.querySelector('div').classList.add('scale-100');
// 5秒后自动关闭
setTimeout(hideSuccessModal, 5000);
}
}
// 隐藏成功弹窗
function hideSuccessModal() {
if (successModalElement) {
successModalElement.classList.add('opacity-0', 'pointer-events-none');
successModalElement.querySelector('div').classList.remove('scale-100');
successModalElement.querySelector('div').classList.add('scale-95');
}
}
// 显示切换账号弹窗
function showChangeAccountModal() {
if (changeAccountModal) {
changeAccountModal.classList.remove('opacity-0', 'pointer-events-none');
changeAccountModal.querySelector('div').classList.remove('scale-95');
changeAccountModal.querySelector('div').classList.add('scale-100');
}
}
// 隐藏切换账号弹窗
function hideChangeAccountModal() {
if (changeAccountModal) {
changeAccountModal.classList.add('opacity-0', 'pointer-events-none');
changeAccountModal.querySelector('div').classList.remove('scale-100');
changeAccountModal.querySelector('div').classList.add('scale-95');
}
}
// 格式化日期工具函数
function formatDate(date) {
const year = date.getFullYear();
const month = ('0' + (date.getMonth() + 1)).slice(-2);
const day = ('0' + date.getDate()).slice(-2);
const hours = ('0' + date.getHours()).slice(-2);
const minutes = ('0' + date.getMinutes()).slice(-2);
const seconds = ('0' + date.getSeconds()).slice(-2);
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// --- 规则设置和主播信息逻辑 ---
// 保存所有规则设置到本地存储
function saveAllSettings() {
let existingSettings = JSON.parse(localStorage.getItem('smartBodyguardSettings')) || {};
let currentSettings = {};
document.querySelectorAll('.rule-card').forEach(card => {
const ruleId = card.dataset.ruleId;
const ruleTitle = card.querySelector('.rule-title');
const ruleToggle = card.querySelector('.rule-toggle');
const ruleSelect = card.querySelector('.rule-select'); // 可能不存在
const ruleInput = card.querySelector('.rule-input'); // 可能不存在
const ruleCheckboxes = card.querySelectorAll('.rule-checkbox-option[data-rule-id="' + ruleId + '"]'); // 确保只选择当前规则的多选框
currentSettings[ruleId] = {
name: ruleTitle ? ruleTitle.textContent : '',
enabled: ruleToggle ? ruleToggle.checked : false,
// 根据输入类型保存不同的值
// 检查是否存在 input.rule-input
inputValue: ruleInput ? ruleInput.value : '',
// 检查是否存在 select.rule-select
option: ruleSelect ? ruleSelect.value : '',
// 检查是否存在 checkbox.rule-checkbox-option
// Save all checkbox values, including 'all' if it exists and is checked
options: ruleCheckboxes.length > 0 ? Array.from(ruleCheckboxes).filter(cb => cb.checked).map(cb => cb.value) : undefined
};
// 移除空的 option 或 inputValue 如果没有对应的元素类型
if (!ruleSelect) delete currentSettings[ruleId].option;
if (!ruleInput) delete currentSettings[ruleId].inputValue;
// If no checkboxes, remove the options property
if (ruleCheckboxes.length === 0) delete currentSettings[ruleId].options;
});
let mergedSettings = {
...existingSettings, ...currentSettings
};
localStorage.setItem('smartBodyguardSettings', JSON.stringify(mergedSettings));
console.log("设置已保存:", mergedSettings); // 打印保存信息
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 {
console.log('pywebview 未就绪,跳过 API 调用。');
}
}
// 从本地存储加载设置 (加载规则和主播信息)
function loadSettings() {
let settings = JSON.parse(localStorage.getItem('smartBodyguardSettings')) || {};
console.log("加载设置:", settings); // 打印加载信息
// 加载规则设置
document.querySelectorAll('.rule-card').forEach(card => {
const ruleId = card.dataset.ruleId;
const ruleTitle = card.querySelector('.rule-title');
const ruleToggle = card.querySelector('.rule-toggle');
const ruleSelect = card.querySelector('.rule-select'); // 可能不存在
const ruleInput = card.querySelector('.rule-input'); // 可能不存在
const ruleCheckboxes = card.querySelectorAll('.rule-checkbox-option[data-rule-id="' + ruleId + '"]'); // 确保只选择当前规则的多选框
if (settings[ruleId]) {
if (ruleTitle && settings[ruleId].name !== undefined) {
ruleTitle.textContent = settings[ruleId].name;
}
if (ruleToggle && settings[ruleId].enabled !== undefined) {
ruleToggle.checked = settings[ruleId].enabled;
}
// 根据输入类型加载不同的值
if (ruleSelect && settings[ruleId].option !== undefined) {
ruleSelect.value = settings[ruleId].option;
}
if (ruleInput && settings[ruleId].inputValue !== undefined) {
ruleInput.value = settings[ruleId].inputValue;
}
if (ruleCheckboxes.length > 0 && settings[ruleId].options !== undefined && Array.isArray(settings[ruleId].options)) {
// 如果是多选框加载选中的值settings[ruleId].options 应该是一个数组)
const savedOptions = settings[ruleId].options;
ruleCheckboxes.forEach(cb => {
// Use cb.value to check if it's in the savedOptions array
cb.checked = savedOptions.includes(cb.value);
});
}
}
});
// 加载当前主播账号
const currentAnchor = localStorage.getItem('currentAnchor');
const accountSelect = document.getElementById('account-select');
const anchorNameElement = document.getElementById('anchor-name');
if (accountSelect && anchorNameElement) {
if (currentAnchor) {
for (let i = 0; i < accountSelect.options.length; i++) {
if (accountSelect.options[i].value === currentAnchor) {
accountSelect.selectedIndex = i;
anchorNameElement.textContent = accountSelect.options[i].text;
break;
}
}
} else if (accountSelect.options.length > 0) {
// 如果没有保存的账号,默认为第一个选项
accountSelect.selectedIndex = 0; // Make sure the select element reflects the default
anchorNameElement.textContent = accountSelect.options[0].text;
}
}
// 加载后立即同步 '全部' 多选框的状态
synchronizeAllCheckboxesForRule('share');
synchronizeAllCheckboxesForRule('gender');
// Save immediately after loading to ensure consistency (includes default values if none were saved)
// No, let's not save immediately. The save happens on change or explicit save button.
// We just load and set the UI state.
saveAllSettings()
}
// 同步特定规则的 '全部' 多选框和单个多选框
function synchronizeAllCheckboxesForRule(ruleId) {
const ruleCard = document.querySelector(`.rule-card[data-rule-id="${ruleId}"]`);
if (!ruleCard) return;
const allCheckbox = ruleCard.querySelector('.rule-checkbox-option[data-option-value="all"]');
// 获取除 '全部' 多选框之外的所有单个多选框
const individualCheckboxes = ruleCard.querySelectorAll('.rule-checkbox-option:not([data-option-value="all"])');
if (allCheckbox && individualCheckboxes.length > 0) {
// 检查是否所有单个多选框当前都被选中
const allIndividualsAreChecked = Array.from(individualCheckboxes).every(cb => cb.checked);
// 设置 '全部' 多选框的状态
allCheckbox.checked = allIndividualsAreChecked;
}
}
// 设置主内容区的事件监听器
function setupMainContentListeners() {
// 移动菜单切换
// document.getElementById('mobile-menu-btn').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');
// }
// });
// 移动端菜单切换
// const mobileMenuButton = document.getElementById('mobile-menu-btn');
// const sidebar = document.getElementById('sidebar');
//
// if (mobileMenuButton && sidebar) {
// mobileMenuButton.addEventListener('click', () => {
// sidebar.classList.toggle('-translate-x-full');
// });
// }
// 规则开关事件监听器
document.querySelectorAll('.rule-toggle').forEach(toggle => {
toggle.addEventListener('change', saveAllSettings);
});
// 编辑规则名称按钮监听器
document.querySelectorAll('.edit-rule-btn').forEach(btn => {
btn.addEventListener('click', function (e) {
e.stopPropagation();
currentRuleId = this.dataset.ruleId;
const ruleCard = this.closest('.rule-card');
const ruleTitle = ruleCard.querySelector('.rule-title');
ruleNameInput.value = ruleTitle.textContent;
showEditModal();
});
});
// 保存编辑的规则名称
if (document.getElementById('save-edit')) document.getElementById('save-edit').addEventListener('click', function () {
if (currentRuleId && ruleNameInput.value.trim() !== '') {
const newName = ruleNameInput.value.trim();
const ruleCard = document.querySelector(`.rule-card[data-rule-id="${currentRuleId}"]`);
if (ruleCard) {
const ruleTitle = ruleCard.querySelector('.rule-title');
ruleTitle.textContent = newName;
saveAllSettings();
hideEditModal();
showSuccessModal('规则名称已更新');
} else {
hideEditModal();
}
} else if (currentRuleId && ruleNameInput.value.trim() === '') {
showSuccessModal('规则名称不能为空');
} else {
hideEditModal();
}
currentRuleId = null; // Reset
});
// 取消编辑
if (document.getElementById('cancel-edit')) document.getElementById('cancel-edit').addEventListener('click', hideEditModal);
// 关闭编辑弹窗按钮
if (document.getElementById('close-edit-modal')) document.getElementById('close-edit-modal').addEventListener('click', hideEditModal);
// 规则选择框变化监听器 (可能不存在于所有规则)
document.querySelectorAll('.rule-select').forEach(select => {
select.addEventListener('change', saveAllSettings);
});
// 规则输入框变化监听器 (可能不存在于所有规则)
document.querySelectorAll('.rule-input').forEach(input => {
input.addEventListener('input', saveAllSettings);
});
// 修改后:规则多选框组变化监听器
// 使用事件委托,在每个规则卡片上添加一个监听器来处理多选框变化
document.querySelectorAll('.rule-card').forEach(card => {
card.addEventListener('change', function (e) {
const target = e.target;
if (target.classList.contains('rule-checkbox-option')) {
const ruleId = target.dataset.ruleId;
const isAllCheckbox = target.dataset.optionValue === 'all';
const allCheckbox = card.querySelector('.rule-checkbox-option[data-option-value="all"]');
const individualCheckboxes = card.querySelectorAll('.rule-checkbox-option:not([data-option-value="all"])');
// 针对包含 '全部' 多选框和单个多选框的规则进行同步逻辑
if (allCheckbox && individualCheckboxes.length > 0) {
if (isAllCheckbox) {
// 如果改变的是 '全部' 多选框,更新所有单个多选框的状态
const isChecked = allCheckbox.checked;
individualCheckboxes.forEach(cb => {
cb.checked = isChecked;
});
} else {
// 如果改变的是单个多选框,检查是否所有单个多选框都被选中了
const allIndividualsAreChecked = Array.from(individualCheckboxes).every(cb => cb.checked);
// 更新 '全部' 多选框的状态
allCheckbox.checked = allIndividualsAreChecked;
}
}
// 在任何规则多选框变化后始终保存设置
saveAllSettings();
}
});
});
// 保存设置按钮
const saveSettingsButton = document.getElementById('save-settings');
if (saveSettingsButton) saveSettingsButton.addEventListener('click', function () {
saveAllSettings();
showSuccessModal('设置保存成功');
});
// 重置设置按钮
const resetSettingsButton = document.getElementById('reset-settings');
if (resetSettingsButton) resetSettingsButton.addEventListener('click', function () {
document.querySelectorAll('.rule-card').forEach(card => {
const ruleId = card.dataset.ruleId;
const ruleTitle = card.querySelector('.rule-title');
const ruleToggle = card.querySelector('.rule-toggle');
const ruleSelect = card.querySelector('.rule-select');
const ruleInput = card.querySelector('.rule-input');
const ruleCheckboxes = card.querySelectorAll('.rule-checkbox-option[data-rule-id="' + ruleId + '"]');
const defaultNames = {
'id-blacklist-rule': 'id黑名单',
'danmaku': '弹幕关键词拉黑',
'local-data': '数据互通',
'suspected-account': '疑似账号',
'share': '分享拉黑',
'follow': '关注拉黑',
'share-entry': '通过分享进入直播间',
'follow-entry': '通过关注进入直播间',
'blue-v': '开通蓝v',
'private-account': '私密账号',
'gender': '性别拉黑',
'age-range': '年龄区间拉黑',
'follower-count': '粉丝数量大于拉黑',
'following-count': '关注数量大于拉黑',
'work-count': '作品数量大于拉黑',
'entry-count': '用户进入次数拉黑',
'nickname': '昵称关键词',
'signature': '个性签名关键词拉黑',
'region': '地区拉黑',
'ip': 'IP拉黑',
};
if (ruleTitle && defaultNames[ruleId]) ruleTitle.textContent = defaultNames[ruleId];
if (ruleToggle) ruleToggle.checked = false;
// 根据输入类型重置
if (ruleSelect) ruleSelect.selectedIndex = 0;
if (ruleInput) ruleInput.value = '';
if (ruleCheckboxes.length > 0) {
ruleCheckboxes.forEach(cb => {
cb.checked = false; // 所有多选框取消选中
});
// 在取消选中单个多选框后,确保 '全部' 多选框也未选中
synchronizeAllCheckboxesForRule(ruleId); // 重新同步 '全部' 多选框状态
}
});
// 初始化
data = {
"id-whitelist": {"name": "id白名单", "enabled": false, "option": "", "inputValue": ""},
"id-blacklist-rule": {"name": "id黑名单", "enabled": false, "inputValue": ""},
"danmaku": {"name": "弹幕关键词拉黑", "enabled": false, "inputValue": ""},
"local-data": {"name": "数据互通", "enabled": false},
"suspected-account": {"name": "疑似账号", "enabled": false},
"follow": {"name": "关注拉黑", "enabled": false},
"share-entry": {"name": "通过分享进入直播间", "enabled": false},
"follow-entry": {"name": "通过关注进入直播间", "enabled": false},
"private-account": {"name": "私密账号", "enabled": false},
"blue-v": {"name": "开通蓝v", "enabled": false},
"share": {"name": "分享拉黑", "enabled": false, "options": []},
"gender": {"name": "性别拉黑", "enabled": false, "options": []},
"age-range": {"name": "年龄区间拉黑", "enabled": false, "inputValue": ""},
"follower-count": {"name": "粉丝数量大于拉黑", "enabled": false, "inputValue": ""},
"following-count": {"name": "关注数量大于拉黑", "enabled": false, "inputValue": ""},
"work-count": {"name": "作品数量大于拉黑", "enabled": false, "inputValue": ""},
"entry-count": {"name": "用户进入次数拉黑", "enabled": false, "inputValue": ""},
"nickname": {"name": "昵称关键词", "enabled": false, "inputValue": ""},
"signature": {"name": "个性签名关键词拉黑", "enabled": false, "inputValue": ""},
"region": {"name": "地区拉黑", "enabled": false, "inputValue": ""},
"ip": {"name": "IP拉黑", "enabled": false, "inputValue": ""}
}
pywebview.api.get_localStorage(JSON.stringify(data))
localStorage.removeItem('smartBodyguardSettings'); // 移除所有设置
// localStorage.removeItem('currentAnchor'); // 重置时保留主播账号?或者添加选项?
// 清除客户端存储的拉黑数据(示例 key
localStorage.removeItem('blacklistData'); // 假设拉黑数据是单独存储的,或者需要清除
// 不在此处调用 saveAllSettingsloadSettings 会处理初始保存
// location.reload(); // 重新加载页面以确保状态干净(可选但对于完全重置最安全)
// 或者手动重置 UI 和数据
updateBlacklistCount(); // 重置拉黑计数显示
testData = []; // 清空内存中的数据
applyFilterAndRenderBlacklist(); // 重新渲染空列表
showSuccessModal('设置已重置');
});
// 账号切换弹窗
const changeAccountButton = document.getElementById('change-account');
const closeChangeAccountModalButton = document.getElementById('close-change-account-modal');
const cancelChangeAccountButton = document.getElementById('cancel-change-account');
const confirmChangeAccountButton = document.getElementById('confirm-change-account');
const accountSelect = document.getElementById('account-select');
const anchorNameElement = document.getElementById('anchor-name');
if (changeAccountButton) changeAccountButton.addEventListener('click', showChangeAccountModal);
if (closeChangeAccountModalButton) closeChangeAccountModalButton.addEventListener('click', hideChangeAccountModal);
if (cancelChangeAccountButton) cancelChangeAccountButton.addEventListener('click', hideChangeAccountModal);
if (confirmChangeAccountButton && accountSelect && anchorNameElement) {
confirmChangeAccountButton.addEventListener('click', function () {
const selectedAccount = accountSelect.value;
const accountName = accountSelect.options[accountSelect.selectedIndex].text;
anchorNameElement.textContent = accountName;
localStorage.setItem('currentAnchor', selectedAccount);
hideChangeAccountModal();
showSuccessModal('账号已切换为 ' + accountName);
// 如果拉黑记录是账号特定的,可能需要在此处重新加载数据
// applyFilterAndRenderBlacklist(); // 假设 applyFilterAndRenderBlacklist 使用当前账号(此示例中未实现)
});
}
// 清除缓存按钮
const clearCacheButton = document.getElementById('clear-cache-btn');
if (clearCacheButton) clearCacheButton.addEventListener('click', function () {
// 询问用户确认?
if (confirm('确定要清除所有本地数据(设置、拉黑记录、当前账号等)吗?此操作不可撤销。')) {
localStorage.removeItem('smartBodyguardSettings');
localStorage.removeItem('currentAnchor');
// 清除客户端存储的拉黑数据(示例 key
localStorage.removeItem('blacklistData'); // 假设拉黑数据是单独存储的,或者需要清除
location.reload(); // 重新加载页面以获得一个全新的开始
data = {
"id-whitelist": {"name": "id白名单", "enabled": false, "option": "", "inputValue": ""},
"id-blacklist-rule": {"name": "id黑名单", "enabled": false, "inputValue": ""},
"danmaku": {"name": "弹幕关键词拉黑", "enabled": false, "inputValue": ""},
"local-data": {"name": "数据互通", "enabled": false},
"suspected-account": {"name": "疑似账号", "enabled": false},
"follow": {"name": "关注拉黑", "enabled": false},
"share-entry": {"name": "通过分享进入直播间", "enabled": false},
"follow-entry": {"name": "通过关注进入直播间", "enabled": false},
"private-account": {"name": "私密账号", "enabled": false},
"blue-v": {"name": "开通蓝v", "enabled": false},
"share": {"name": "分享拉黑", "enabled": false, "options": []},
"gender": {"name": "性别拉黑", "enabled": false, "options": []},
"age-range": {"name": "年龄区间拉黑", "enabled": false, "inputValue": ""},
"follower-count": {"name": "粉丝数量大于拉黑", "enabled": false, "inputValue": ""},
"following-count": {"name": "关注数量大于拉黑", "enabled": false, "inputValue": ""},
"work-count": {"name": "作品数量大于拉黑", "enabled": false, "inputValue": ""},
"entry-count": {"name": "用户进入次数拉黑", "enabled": false, "inputValue": ""},
"nickname": {"name": "昵称关键词", "enabled": false, "inputValue": ""},
"signature": {"name": "个性签名关键词拉黑", "enabled": false, "inputValue": ""},
"region": {"name": "地区拉黑", "enabled": false, "inputValue": ""},
"ip": {"name": "IP拉黑", "enabled": false, "inputValue": ""}
}
pywebview.api.get_localStorage(JSON.stringify(data))
}
});
// 成功弹窗关闭按钮
const closeSuccessModalButton = document.getElementById('close-success-modal');
if (closeSuccessModalButton) closeSuccessModalButton.addEventListener('click', hideSuccessModal);
// 用户操作菜单初始隐藏
if (userOptionsMenu) userOptionsMenu.style.display = 'none';
// 点击外部关闭菜单
document.addEventListener('click', function (event) {
// 只有在点击菜单外部且不是点击操作按钮时才隐藏
if (userOptionsMenu && !userOptionsMenu.contains(event.target) && !event.target.closest('.options-btn')) {
userOptionsMenu.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
userOptionsMenu.classList.remove('opacity-100', 'scale-100');
userOptionsMenu.style.display = 'none'; // 完全隐藏
// 此处无需重置 currentUserItemBlacklist它在具体操作中处理
}
});
}
// --- 侧边栏拉黑记录逻辑 ---
// 拉黑数据状态变量
let testData = []; // 所有原始拉黑数据
let currentSearchTermBlacklist = ''; // 侧边栏拉黑记录的当前搜索词
let filteredDataBlacklist = []; // 按搜索词过滤后的数据
let currentFilteredIndexBlacklist = 0; // 已加载的最后一个记录的索引
// 每次加载的记录数量
const itemsPerLoadBlacklist = 13;
// 保存当前点击操作按钮的记录项元素
let currentUserItemBlacklist = null;
// 生成初始测试拉黑数据
function generateInitialTestData() {
const data = [];
const initialCount = 100; // 生成足够滚动的数据
const now = new Date();
const baseTime = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 最近30天
for (let i = 1; i <= initialCount; i++) {
const randomUserId = `user_${100000 + i + Math.floor(Math.random() * 1000)}`;
const randomDouyinId = `dy${10000 + i + Math.floor(Math.random() * 90000)}`;
const itemTime = new Date(baseTime.getTime() + Math.random() * (now.getTime() - baseTime.getTime()));
data.push({
id: `blacklist_${Date.now()}_${i}_${Math.random().toString(36).substring(2, 15)}`, // 更独特的 ID
userId: randomUserId,
avatar: `https://picsum.photos/id/${60 + i + Math.floor(Math.random() * 100)}/40/40`,
// 使用更简单的随机昵称
nickname: Math.random() > 0.3 ? `用户_${i}` : `抖音朋友_${Math.floor(Math.random() * 1000)}`,
douyinId: randomDouyinId,
time: formatDate(itemTime),
reason: getRandomReasonBlacklist() // 使用原因生成器
});
}
// 按时间降序排序
data.sort((a, b) => new Date(b.time) - new Date(a.time));
return data;
}
// 随机原因生成器
function getRandomReasonBlacklist() {
const reasons = [
'弹幕包含敏感词',
'多次分享直播间',
'举报次数过多',
'有开播记录',
'短时间多次进出',
'用户行为异常',
'触发关键词拉黑'
];
return `因"${reasons[Math.floor(Math.random() * reasons.length)]}"被拉黑`;
}
// 生成新的拉黑数据 (用于刷新)
function generateNewBlacklistData() {
const timestamp = Date.now();
const newData = [];
const count = Math.floor(Math.random() * 3) + 2; // 生成2到4条新数据
for (let i = 0; i < count; i++) {
const randomUserId = `user_${Math.floor(Math.random() * 1000000)}`;
const randomDouyinId = `dy_${Math.floor(Math.random() * 90000) + 10000}`;
// 使用更简单的随机昵称
const randomNickname = Math.random() > 0.5 ? `新用户_${Math.floor(Math.random() * 1000)}` : `活跃用户_${Math.floor(Math.random() * 100)}`;
newData.push({
id: `blacklist_${timestamp}_${Math.floor(Math.random() * 1000) + i}_${Math.random().toString(36).substring(2, 15)}`, // 更独特的 ID
userId: randomUserId,
avatar: `https://picsum.photos/id/${Math.floor(Math.random() * 100)}/40/40`,
nickname: randomNickname,
douyinId: randomDouyinId,
time: formatDate(new Date()),
reason: getRandomReasonBlacklist() // 使用原因生成器
});
}
return newData;
}
// 更新拉黑总数显示
function updateBlacklistCount() {
const totalBlacklistCountElementSidebar = document.getElementById('total-blacklist-count');
if (totalBlacklistCountElementSidebar) {
totalBlacklistCountElementSidebar.textContent = testData.length;
}
}
// 为侧边栏创建单个简化版拉黑记录项的 HTML
function createBlacklistItemHTMLSidebar(item) {
// 构建用于主 div title 的 tooltip 文本: "昵称 (抖音号)" 如果抖音号存在
const tooltipText = `${item.nickname || '未知昵称'}${item.douyinId ? ' (' + item.douyinId + ')' : ''}`;
return `
<div class="blacklist-item-sidebar"
data-id="${item.id}"
data-user-id="${item.userId}"
title="${tooltipText}"> <!-- 在此处添加了整个记录项的 title -->
<img src="${item.avatar}" alt="用户头像" class="w-10 h-10 rounded-full object-cover flex-shrink-0">
<div class="flex-1 min-w-0 text-sm">
<div class="flex items-baseline justify-between gap-2"> <!-- 使用 items-baseline 以便更好的文本尺寸对齐 -->
<!-- 昵称带截断,内容即昵称本身 -->
<h4 class="nickname-text">${item.nickname || '未知昵称'}</h4> <!-- 昵称内容正确放在此处 -->
<!-- 带有特定样式的时间文本 -->
<span class="time-text">${item.time}</span>
</div>
<!-- 昵称/时间块下方的原因 -->
<p class="reason-text">${item.reason || '未知原因'}</p>
</div>
<button class="text-gray-400 hover:text-primary options-btn" title="操作"> <!-- 在此处为按钮添加了 title="操作" -->
<i class="fa-solid fa-ellipsis-v"></i>
</button>
</div>
`;
}
// 过滤拉黑数据
function filterDataBlacklist(data, searchTerm) {
if (!searchTerm) {
return [...data];
}
const lowerTerm = searchTerm.toLowerCase().trim();
return data.filter(item => {
const nickname = (item.nickname || '').toString().toLowerCase();
const douyinId = (item.douyinId || '').toString().toLowerCase();
// 搜索包含昵称和抖音号
return nickname.includes(lowerTerm) || douyinId.includes(lowerTerm);
});
}
// 在侧边栏显示一批过滤后的数据
function displayFilteredDataBlacklist(count) {
const blacklistContainerSidebar = document.getElementById('blacklist-container-sidebar');
if (!blacklistContainerSidebar) return;
const startIndex = currentFilteredIndexBlacklist;
const endIndex = Math.min(currentFilteredIndexBlacklist + count, filteredDataBlacklist.length);
const fragment = document.createDocumentFragment();
for (let i = startIndex; i < endIndex; i++) {
const item = filteredDataBlacklist[i];
const itemHTML = createBlacklistItemHTMLSidebar(item); // 使用更新后的 HTML 生成器
const tempDiv = document.createElement('div');
tempDiv.innerHTML = itemHTML.trim();
if (tempDiv.firstElementChild) {
fragment.appendChild(tempDiv.firstElementChild);
}
}
blacklistContainerSidebar.appendChild(fragment);
currentFilteredIndexBlacklist = endIndex;
updateLoadMoreButtonBlacklist();
}
// 更新侧边栏的“查看更多”按钮状态
function updateLoadMoreButtonBlacklist() {
const loadMoreButtonSidebar = document.getElementById('load-more-sidebar');
if (loadMoreButtonSidebar) {
if (currentFilteredIndexBlacklist < filteredDataBlacklist.length) {
loadMoreButtonSidebar.classList.remove('hidden');
} else {
loadMoreButtonSidebar.classList.add('hidden');
}
}
}
// 应用当前过滤并重新渲染整个侧边栏拉黑记录
function applyFilterAndRenderBlacklist() {
const blacklistContainerSidebar = document.getElementById('blacklist-container-sidebar');
filteredDataBlacklist = filterDataBlacklist(testData, currentSearchTermBlacklist);
if (blacklistContainerSidebar) {
blacklistContainerSidebar.innerHTML = ''; // 清空现有记录
}
currentFilteredIndexBlacklist = 0; // 重置索引
// 初始加载 exactly 13 条记录
displayFilteredDataBlacklist(13);
}
// 设置侧边栏拉黑记录的特定事件监听器
function setupSidebarBlacklistListeners() {
const searchInputSidebar = document.getElementById('search-blacklist-sidebar');
const clearSearchBtnSidebar = document.getElementById('clear-search-sidebar');
const refreshBlacklistButtonSidebar = document.getElementById('refresh-blacklist');
const blacklistContainerSidebar = document.getElementById('blacklist-container-sidebar');
const loadMoreButtonSidebar = document.getElementById('load-more-sidebar');
const unblockUserButton = document.getElementById('unblock-user');
const viewProfileButton = document.getElementById('view-profile');
// 搜索输入框清除按钮可见性
if (searchInputSidebar && clearSearchBtnSidebar) {
searchInputSidebar.addEventListener('input', function () {
clearSearchBtnSidebar.classList.toggle('hidden', this.value.trim() === '');
});
// 初始检查
searchInputSidebar.dispatchEvent(new Event('input'));
}
// 搜索输入监听器 (侧边栏)
if (searchInputSidebar) {
searchInputSidebar.addEventListener('input', function () {
currentSearchTermBlacklist = this.value;
applyFilterAndRenderBlacklist();
});
}
// 清除搜索按钮监听器 (侧边栏)
if (clearSearchBtnSidebar) {
clearSearchBtnSidebar.addEventListener('click', function () {
searchInputSidebar.value = '';
this.classList.add('hidden');
currentSearchTermBlacklist = '';
applyFilterAndRenderBlacklist();
});
}
// 刷新拉黑记录按钮监听器 (侧边栏)
if (refreshBlacklistButtonSidebar) {
refreshBlacklistButtonSidebar.addEventListener('click', function () {
const refreshIcon = this.querySelector('i');
refreshIcon.classList.add('fa-spin');
this.disabled = true;
// 模拟数据获取
setTimeout(() => {
const newData = generateNewBlacklistData();
testData.unshift(...newData); // 将新数据添加到开头
updateBlacklistCount();
applyFilterAndRenderBlacklist(); // 使用新数据重新渲染
refreshIcon.classList.remove('fa-spin');
this.disabled = false;
showSuccessModal(`拉黑记录已刷新,新增 ${newData.length} 条`);
}, 800);
});
}
// 用户操作菜单显示逻辑 (委托给侧边栏容器)
if (blacklistContainerSidebar && userOptionsMenu) {
blacklistContainerSidebar.addEventListener('click', function (e) {
const optionsBtn = e.target.closest('.options-btn');
if (optionsBtn) {
e.stopPropagation(); // 阻止事件冒泡
// 确保任何当前打开的菜单都被关闭
userOptionsMenu.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
userOptionsMenu.classList.remove('opacity-100', 'scale-100');
userOptionsMenu.style.display = 'none'; // 完全隐藏
currentUserItemBlacklist = optionsBtn.closest('.blacklist-item-sidebar');
// --- 开始修改后的定位逻辑 ---
// 获取按钮、记录项和侧边栏容器的位置信息
const rect = optionsBtn.getBoundingClientRect(); // 按钮的矩形
const itemRect = currentUserItemBlacklist.getBoundingClientRect(); // 记录项的矩形
const sidebarRect = optionsBtn.closest('aside').getBoundingClientRect(); // 侧边栏的矩形
// 临时显示菜单以测量其尺寸
userOptionsMenu.style.display = 'block';
const menuWidth = userOptionsMenu.offsetWidth;
const menuHeight = userOptionsMenu.offsetHeight;
// 此时不隐藏,直接设置位置
let menuTop = rect.top; // 初始垂直位置与按钮顶部对齐
// 水平定位: 将菜单右边缘与记录项右边缘对齐,然后确保不超出侧边栏左边缘
// 考虑菜单宽度和按钮宽度,将菜单左边缘放在按钮左边缘或稍微偏左的位置
// 假设菜单宽度约等于 itemRect.width / 3 或固定值 120px
// 更灵活的方式是:让菜单右侧对齐按钮右侧,再往左偏移菜单宽度-按钮宽度-间隙
let menuLeft = rect.right - menuWidth; // 菜单右侧与按钮右侧对齐
// 确保菜单不出现在侧边栏内容区域的左边之外
// 可以留出一点内边距 (例如 10px)
const minMenuLeft = sidebarRect.left + 10; // 菜单距离侧边栏左边缘至少10px
menuLeft = Math.max(menuLeft, minMenuLeft); // 确保不超出左边界
// 检查是否超出侧边栏右边界(虽然右对齐通常不会)
const maxMenuLeft = sidebarRect.right - menuWidth - 10; // 菜单距离侧边栏右边缘至少10px
menuLeft = Math.min(menuLeft, maxMenuLeft); // 确保不超出右边界
// 垂直定位: 检查是否超出屏幕底部
const windowHeight = window.innerHeight;
const bottomThreshold = 10; // 菜单距离窗口底部至少10px
if (menuTop + menuHeight > windowHeight - bottomThreshold) {
// 如果超出底部,将菜单底部与按钮底部对齐
menuTop = rect.bottom - menuHeight;
}
// 同时确保调整后的菜单不超出屏幕顶部
const topThreshold = 5; // 菜单距离窗口顶部至少5px
menuTop = Math.max(menuTop, topThreshold);
// 设置菜单位置
userOptionsMenu.style.top = `${menuTop}px`;
userOptionsMenu.style.left = `${menuLeft}px`;
// 现在带过渡效果显示菜单
userOptionsMenu.classList.remove('opacity-0', 'pointer-events-none', 'scale-95');
userOptionsMenu.classList.add('opacity-100', 'scale-100');
// --- 结束修改后的定位逻辑 ---
}
});
}
// 点击外部关闭菜单 (监听器已在 setupMainContentListeners 中全局设置)
// 解除拉黑操作 (针对侧边栏)
if (unblockUserButton && blacklistContainerSidebar) { // 确保两个元素都存在
unblockUserButton.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡
if (currentUserItemBlacklist) {
const blacklistRecordIdToRemove = currentUserItemBlacklist.dataset.id;
const initialLength = testData.length;
testData = testData.filter(item => item.id !== blacklistRecordIdToRemove);
const removedCount = initialLength - testData.length;
if (removedCount > 0) {
// 重新渲染列表以在视觉上移除记录项
applyFilterAndRenderBlacklist();
updateBlacklistCount();
showSuccessModal('已解除拉黑');
} else {
console.warn(`尝试移除 ID 为 ${blacklistRecordIdToRemove} 的记录,但在 testData 中未找到。`); // 警告信息
showSuccessModal('未能找到该用户进行解除拉黑操作'); // UI 同步时应不会发生
}
// 隐藏菜单
if (userOptionsMenu) {
userOptionsMenu.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
userOptionsMenu.classList.remove('opacity-100', 'scale-100');
userOptionsMenu.style.display = 'none';
}
currentUserItemBlacklist = null; // 重置
}
});
}
// 查看主页操作 (针对侧边栏)
if (viewProfileButton && blacklistContainerSidebar) { // 确保两个元素都存在
viewProfileButton.addEventListener('click', function (e) {
e.stopPropagation(); // 阻止事件冒泡
if (currentUserItemBlacklist) {
const userId = currentUserItemBlacklist.dataset.userId;
if (userId) {
const douyinUrl = `https://www.douyin.com/user/${userId}`; // 假设这是正确的抖音用户主页基础 URL
console.log(`正在打开用户主页 URL: ${douyinUrl}`); // 打印打开的主页 URL
window.open(douyinUrl, '_blank'); // 在新标签页打开
} else {
console.warn("在选定的记录项中未找到用户 ID无法查看主页。"); // 警告信息
showSuccessModal('未能获取用户主页信息');
}
}
// 隐藏菜单
if (userOptionsMenu) {
userOptionsMenu.classList.add('opacity-0', 'pointer-events-none', 'scale-95');
userOptionsMenu.classList.remove('opacity-100', 'scale-100');
userOptionsMenu.style.display = 'none';
}
});
}
// “查看更多”按钮监听器 (针对侧边栏拉黑记录)
if (loadMoreButtonSidebar) {
loadMoreButtonSidebar.addEventListener('click', () => {
// 这里仍然使用 itemsPerLoadBlacklist现在是 13
displayFilteredDataBlacklist(itemsPerLoadBlacklist);
});
}
}
// --- DOMContentLoaded 初始化 ---
document.addEventListener('DOMContentLoaded', function () {
console.log("DOMContentLoaded 事件触发。开始初始化主内容..."); // 打印初始化开始信息
// 创建同步的 XMLHttpRequest
const xhr = new XMLHttpRequest();
xhr.open('GET', 'user_Settings_Options.json', false); // 第三个参数设为 false 表示同步请求
xhr.send();
if (xhr.status === 200) {
// 解析 JSON 并写入 localStorage
const data = JSON.parse(xhr.responseText);
localStorage.setItem('smartBodyguardSettings', JSON.stringify(data));
console.log('配置已同步保存到 localStorage');
// showSuccessModal('配置已同步');
} else {
throw new Error(`请求失败,状态码: ${xhr.status}`);
}
// 1. 初始化主内容 (规则、主播信息、弹窗)
loadSettings(); // 加载规则和主播名称,并在加载后同步 '全部' 多选框
setupMainContentListeners(); // 添加规则、保存/重置、账号切换、清除缓存以及全局菜单关闭的监听器
console.log("主内容初始化完成。安排侧边栏拉黑记录初始化..."); // 打印主内容完成信息,安排侧边栏初始化
// 2. 初始化侧边栏拉黑记录 (延迟执行)
// 使用 requestAnimationFrame 进行轻微延迟,以便主内容先渲染
requestAnimationFrame(function () {
console.log("开始初始化侧边栏拉黑记录..."); // 打印侧边栏初始化开始信息
// 生成数据
testData = generateInitialTestData(); // 获取初始数据
// 更新计数显示
updateBlacklistCount();
// 设置侧边栏特有的事件监听器
setupSidebarBlacklistListeners();
// 初始渲染拉黑数据
applyFilterAndRenderBlacklist(); // 这个调用已修改为加载 13 条记录
console.log("侧边栏拉黑记录初始化完成。"); // 打印侧边栏初始化完成信息
});
// pywebviewready 监听器如果 pywebview 异步加载,应该放在 DOMContentLoaded 外部
// 但基于原始代码结构,暂时保留在此
}); // 结束 DOMContentLoaded
</script>
</body>
</html>