video_blacklist/index/smart-bodyguard.html

2039 lines
108 KiB
HTML
Raw 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',
},
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>