2039 lines
108 KiB
HTML
2039 lines
108 KiB
HTML
|
<!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'); // 假设拉黑数据是单独存储的,或者需要清除
|
|||
|
// 不在此处调用 saveAllSettings,loadSettings 会处理初始保存
|
|||
|
// 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>
|