diff --git a/front/src/App.vue b/front/src/App.vue
index 77a4eb4..ac11f6d 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -47,6 +47,7 @@ const activeTab = reactive({ shop: 'items', lottery: 'events' });
const toast = ref('');
const confirmState = reactive({ open: false, title: '', message: '', confirmText: '确认', danger: false, resolver: null as null | ((value: boolean) => void) });
const chatContextMenu = reactive({ open: false, x: 0, y: 0, item: null as ChatItem | null });
+const banContextMenu = reactive({ open: false, x: 0, y: 0, item: null as BanItem | null });
const banDialog = reactive({ open: false, mode: 'ban' as 'ban' | 'delete_ban', item: null as ChatItem | null, duration: 0, customMinutes: '' });
const banDurationOptions = [
{ label: '永久', value: 0 },
@@ -627,6 +628,18 @@ function closeChatContextMenu() {
chatContextMenu.open = false;
chatContextMenu.item = null;
}
+function openBanContextMenu(event: MouseEvent, item: BanItem) {
+ banContextMenu.item = item;
+ banContextMenu.open = true;
+ const menuWidth = 180;
+ const menuHeight = 92;
+ banContextMenu.x = Math.min(event.clientX, window.innerWidth - menuWidth - 8);
+ banContextMenu.y = Math.min(event.clientY, window.innerHeight - menuHeight - 8);
+}
+function closeBanContextMenu() {
+ banContextMenu.open = false;
+ banContextMenu.item = null;
+}
async function contextAddToRules() {
const item = chatContextMenu.item;
closeChatContextMenu();
@@ -778,12 +791,14 @@ async function deleteChatMessage(item: ChatItem, ban = false, durationMinutes =
item._deleting = false;
}
}
-async function unban(item: BanItem) {
- if (!await askConfirm('解除封禁', `确认解封 ${item.username || item.user_id}?`, { confirmText: '解封' })) return;
+async function unban(item: BanItem, confirmed = false) {
+ if (!confirmed && !await askConfirm('解除封禁', `确认解封 ${item.username || item.user_id}?`, { confirmText: '解封' })) return;
item._loading = true;
try {
- await fetchJson(`/api/unban/${item.user_id}`, { method: 'POST' });
+ const query = item.chat_id ? `?chat_id=${item.chat_id}` : '';
+ await fetchJson(`/api/unban/${item.user_id}${query}`, { method: 'POST' });
item.auto_unban = false;
+ await loadBans();
await loadStats();
} catch (e: any) {
showToast(`解封失败:${e.message}`);
@@ -791,6 +806,32 @@ async function unban(item: BanItem) {
item._loading = false;
}
}
+async function contextUnban() {
+ const item = banContextMenu.item;
+ closeBanContextMenu();
+ if (item) await unban(item);
+}
+async function contextWhitelistAndUnban() {
+ const item = banContextMenu.item;
+ closeBanContextMenu();
+ if (!item?.user_id || !item?.chat_id) return showToast('缺少群组或用户 ID,无法加入白名单');
+ if (!await askConfirm('加入白名单并解封', `确认将 @${item.username || item.user_id} 加入当前群组白名单并立即解封?`, { confirmText: '加入并解封', danger: true })) return;
+ item._loading = true;
+ try {
+ await fetchJson('/api/whitelist', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ chat_id: item.chat_id, user_id: item.user_id, username: item.username, first_name: item.first_name, reason: '封禁记录右键加入白名单' })
+ });
+ await unban(item, true);
+ await loadWhitelist();
+ showToast('已加入白名单并解封');
+ } catch (e: any) {
+ showToast(`加入白名单并解封失败:${e.message}`);
+ } finally {
+ item._loading = false;
+ }
+}
async function restartBot() {
if (!await askConfirm('重启 Bot', '确认只重启 Telegram Bot?Web 管理页不会重启。', { confirmText: '重启 Bot' })) return;
restartingBot.value = true;
@@ -839,13 +880,17 @@ async function bootstrapData() {
}
onMounted(async () => {
window.addEventListener('click', closeChatContextMenu);
+ window.addEventListener('click', closeBanContextMenu);
window.addEventListener('scroll', closeChatContextMenu, true);
+ window.addEventListener('scroll', closeBanContextMenu, true);
await checkAuth();
if (authenticated.value) await bootstrapData();
});
onBeforeUnmount(() => {
window.removeEventListener('click', closeChatContextMenu);
+ window.removeEventListener('click', closeBanContextMenu);
window.removeEventListener('scroll', closeChatContextMenu, true);
+ window.removeEventListener('scroll', closeBanContextMenu, true);
if (es) es.close();
if (statTimer) window.clearInterval(statTimer);
});
@@ -957,7 +1002,7 @@ onBeforeUnmount(() => {