fix: scope whitelist to selected group

This commit is contained in:
ngfchl
2026-07-07 23:20:33 +08:00
parent a2679fef9d
commit 5eb083bc47
7 changed files with 348 additions and 343 deletions
+7 -5
View File
@@ -37,7 +37,7 @@ const rulesLoading = ref(false);
const ruleKeyword = ref('');
const whitelistLoading = ref(false);
const whitelistSaving = ref(false);
const whitelistForm = reactive({ chat_id: '', user_id: '', username: '', first_name: '', reason: '' });
const whitelistForm = reactive({ user_id: '', username: '', first_name: '', reason: '' });
const shopForm = reactive({ item_key: '', name: '', cost: 0, stock: -1, desc: '', enabled: true });
const shopDialogOpen = ref(false);
const lotteryForm = reactive({ id: 0, title: '', prize: '', description: '', condition_type: 'all', condition_value: 0, draw_type: 'people', end_type: 'people', end_value: 3, max_participants: 3, prize_count: 1, remaining_count: 1, draw_at: '', status: 'active' });
@@ -256,12 +256,14 @@ async function loadWhitelist() {
if (whitelistLoading.value) return;
whitelistLoading.value = true;
try {
if (!selectedChatId.value) { whitelistItems.value = []; return; }
const data = await fetchJson(`/api/whitelist${selectedQuery.value}`);
whitelistItems.value = data.map((w: WhitelistItem) => ({ ...w, _loading: false }));
} catch (e: any) { showToast(`加载白名单失败:${e.message}`); }
finally { whitelistLoading.value = false; }
}
async function saveWhitelist() {
if (!selectedChatId.value) return showToast('请先选择群组');
const userId = Number(whitelistForm.user_id || 0);
if (!userId) return showToast('请填写用户 ID');
whitelistSaving.value = true;
@@ -270,7 +272,7 @@ async function saveWhitelist() {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: whitelistForm.chat_id === '' ? 0 : Number(whitelistForm.chat_id),
chat_id: selectedChatId.value,
user_id: userId,
username: whitelistForm.username,
first_name: whitelistForm.first_name,
@@ -964,9 +966,9 @@ onBeforeUnmount(() => {
<section v-show="activePage === 'whitelist'" class="tab-page whitelist-page">
<SCard class="panel page-panel whitelist-panel" title="🛡️ 白名单管理" :description="`${whitelistItems.length} 个用户 · 命中垃圾时只删消息不封禁账号`">
<SCard class="panel page-panel whitelist-panel" title="🛡️ 白名单管理" :description="`${whitelistItems.length} 个用户 · 仅当前群组生效,命中垃圾时只删消息不封禁`">
<div class="whitelist-toolbar">
<label><span>群组 ID</span><input v-model="whitelistForm.chat_id" class="form-input" placeholder="0=全局 / 留空=全局" /></label>
<div class="scope-chip"><span>当前群组</span><strong>{{ selectedChatId || '-' }}</strong></div>
<label><span>用户 ID</span><input v-model="whitelistForm.user_id" class="form-input" placeholder="必填,如 5418812283" /></label>
<label><span>用户名</span><input v-model="whitelistForm.username" class="form-input" placeholder="可选,不带 @" /></label>
<label><span>昵称</span><input v-model="whitelistForm.first_name" class="form-input" placeholder="可选" /></label>
@@ -979,7 +981,7 @@ onBeforeUnmount(() => {
<div v-if="!whitelistItems.length" class="empty whitelist-empty">暂无白名单用户</div>
<div v-for="item in whitelistItems" :key="item.id" class="table-row">
<div><b>@{{ item.username || item.first_name || item.user_id }}</b><small>{{ item.user_id }}</small></div>
<span>{{ Number(item.chat_id || 0) === 0 ? '全局' : item.chat_id }}</span>
<span>{{ item.chat_id }}</span>
<em :title="item.reason">{{ item.reason || '-' }}</em>
<small>{{ formatTime(item.updated_at || item.created_at) }}</small>
<SButton size="xs" variant="outline" color="destructive" :loading="item._loading" :disabled="item._loading" @click="deleteWhitelist(item)">移出</SButton>
+3
View File
@@ -640,3 +640,6 @@ body { background: #f3f6fb; }
.whitelist-toolbar { grid-template-columns: 1fr; }
.whitelist-table { max-height: none; }
}
.scope-chip { height: 36px; display: grid; align-content: center; gap: 1px; padding: 4px 10px; border: 1px solid #e2e8f0; border-radius: 10px; background: #f8fafc; color: #64748b; font-size: 11px; }
.scope-chip strong { color: #0f172a; font-size: 12px; line-height: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+6 -6
View File
@@ -988,23 +988,23 @@ async def api_add_message_user_to_whitelist(message_id: int, request: Request):
@app.get("/api/whitelist")
async def api_list_whitelist(chat_id: int | None = None, limit: int = Query(default=200, ge=1, le=1000)):
qs = Whitelist.filter(enabled=True)
if chat_id is not None:
qs = qs.filter(Q(chat_id=chat_id) | Q(chat_id=0))
async def api_list_whitelist(chat_id: int = Query(...), limit: int = Query(default=200, ge=1, le=1000)):
qs = Whitelist.filter(enabled=True, chat_id=chat_id)
rows = await qs.order_by("-updated_at").limit(limit).values()
return JSONResponse(json_safe(list(rows)))
@app.post("/api/whitelist")
async def api_create_whitelist(request: Request):
"""手动添加白名单用户。chat_id=0 表示全局白名单。"""
"""手动添加当前群组白名单用户。白名单严格按群组隔离"""
data = await safe_json(request)
try:
user_id = int(data.get("user_id") or 0)
chat_id = int(data.get("chat_id") if data.get("chat_id") not in (None, "") else 0)
chat_id = int(data.get("chat_id") or 0)
except (TypeError, ValueError):
return JSONResponse({"ok": False, "error": "chat_id/user_id 必须是数字"}, status_code=400)
if not chat_id:
return JSONResponse({"ok": False, "error": "chat_id 不能为空,请先选择群组"}, status_code=400)
if not user_id:
return JSONResponse({"ok": False, "error": "user_id 不能为空"}, status_code=400)
username = str(data.get("username") or "").strip().lstrip("@")[:255] or None
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/static/icon.svg" />
<title>TG Spam Guard</title>
<script type="module" crossorigin src="/static/assets/index-CHB2F8f5.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-dR9FDmUV.css">
<script type="module" crossorigin src="/static/assets/index-BElvzQ0F.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-DK_IvB6m.css">
</head>
<body>
<div id="app"></div>