feat: add loading states to manager actions

This commit is contained in:
ngfchl
2026-07-07 14:30:13 +08:00
parent 09b4024a70
commit 813ea433bc
3 changed files with 392 additions and 36 deletions
+63 -35
View File
@@ -6,8 +6,8 @@ type ChatItem = Record<string, any> & { _deleting?: boolean };
type BanItem = Record<string, any> & { _loading?: boolean };
type LogItem = { time?: string; level?: string; message?: string };
type GroupItem = { chat_id: number; name: string };
type ShopItem = Record<string, any>;
type LotteryItem = Record<string, any>;
type ShopItem = Record<string, any> & { _loading?: boolean };
type LotteryItem = Record<string, any> & { _loading?: boolean };
const sseConnected = ref(false);
const chatLogs = ref<ChatItem[]>([]);
@@ -29,6 +29,10 @@ const authReady = ref(false);
const authenticated = ref(false);
const loginForm = reactive({ username: '', password: '' });
const loggingIn = ref(false);
const shopSaving = ref(false);
const lotterySaving = ref(false);
const logsRefreshing = ref(false);
const copyingLogs = ref(false);
const newMessage = ref('');
const sending = ref(false);
const restarting = ref(false);
@@ -174,57 +178,81 @@ async function loadGroups() {
} catch {}
}
async function loadShopItems() {
try { shopItems.value = await fetchJson(`/api/shop-items${selectedQuery.value}`); } catch {}
try { shopItems.value = (await fetchJson(`/api/shop-items${selectedQuery.value}`)).map((x: ShopItem) => ({ ...x, _loading: false })); } catch {}
}
async function loadShopTransactions() {
try { shopTransactions.value = await fetchJson(`/api/shop-transactions${selectedQuery.value}`); } catch {}
}
async function saveShopItem() {
if (!shopForm.item_key || !shopForm.name) return showToast('请填写商品编号和名称');
await fetchJson('/api/shop-items', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...shopForm, chat_id: selectedChatId.value || 0 })
});
Object.assign(shopForm, { item_key: '', name: '', cost: 0, desc: '', enabled: true });
await loadShopItems();
if (shopSaving.value) return;
shopSaving.value = true;
try {
await fetchJson('/api/shop-items', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...shopForm, chat_id: selectedChatId.value || 0 })
});
Object.assign(shopForm, { item_key: '', name: '', cost: 0, desc: '', enabled: true });
await loadShopItems();
} catch (e: any) { showToast(`保存商品失败:${e.message}`); }
finally { shopSaving.value = false; }
}
async function editShopItem(item: ShopItem) { Object.assign(shopForm, item); }
async function deleteShopItem(item: ShopItem) {
if (item._loading) return;
if (!await askConfirm('删除商品', `确认删除商品「${item.name}」?交易记录会保留。`, { confirmText: '删除', danger: true })) return;
await fetchJson(`/api/shop-items/${item.item_key}${selectedQuery.value}`, { method: 'DELETE' });
await loadShopItems();
item._loading = true;
try {
await fetchJson(`/api/shop-items/${item.item_key}${selectedQuery.value}`, { method: 'DELETE' });
await loadShopItems();
} catch (e: any) { showToast(`删除商品失败:${e.message}`); }
finally { item._loading = false; }
}
async function loadLotteries() {
try { lotteries.value = await fetchJson(`/api/lotteries${selectedQuery.value}`); } catch {}
try { lotteries.value = (await fetchJson(`/api/lotteries${selectedQuery.value}`)).map((x: LotteryItem) => ({ ...x, _loading: false })); } catch {}
}
function resetLotteryForm() { Object.assign(lotteryForm, { id: 0, title: '', prize: '', description: '', condition_type: 'all', condition_value: 0, draw_type: 'people', end_type: 'people', end_value: 3, max_participants: 3, draw_at: '', status: 'draft' }); }
function editLottery(item: LotteryItem) { Object.assign(lotteryForm, { ...item, id: item.id || 0 }); }
async function saveLottery() {
if (!lotteryForm.title || !lotteryForm.prize) return showToast('请填写抽奖标题和奖品');
lotteryForm.end_type = lotteryForm.draw_type;
if (lotteryForm.draw_type === 'people') lotteryForm.max_participants = Number(lotteryForm.end_value || lotteryForm.max_participants || 1);
const url = lotteryForm.id ? `/api/lotteries/${lotteryForm.id}` : '/api/lotteries';
await fetchJson(url, { method: lotteryForm.id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...lotteryForm, chat_id: selectedChatId.value || 0 }) });
resetLotteryForm();
await loadLotteries();
if (lotterySaving.value) return;
lotterySaving.value = true;
try {
lotteryForm.end_type = lotteryForm.draw_type;
if (lotteryForm.draw_type === 'people') lotteryForm.max_participants = Number(lotteryForm.end_value || lotteryForm.max_participants || 1);
const url = lotteryForm.id ? `/api/lotteries/${lotteryForm.id}` : '/api/lotteries';
await fetchJson(url, { method: lotteryForm.id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...lotteryForm, chat_id: selectedChatId.value || 0 }) });
resetLotteryForm();
await loadLotteries();
} catch (e: any) { showToast(`保存抽奖失败:${e.message}`); }
finally { lotterySaving.value = false; }
}
async function deleteLottery(item: LotteryItem) {
if (item._loading) return;
if (!await askConfirm('删除抽奖', `确认删除抽奖「${item.title || item.prize}」?参与记录会一并移除。`, { confirmText: '删除', danger: true })) return;
await fetchJson(`/api/lotteries/${item.id}`, { method: 'DELETE' });
await loadLotteries();
item._loading = true;
try { await fetchJson(`/api/lotteries/${item.id}`, { method: 'DELETE' }); await loadLotteries(); }
catch (e: any) { showToast(`删除抽奖失败:${e.message}`); }
finally { item._loading = false; }
}
async function loadLotteryParticipants(item: LotteryItem) {
lotteryParticipants.value = await fetchJson(`/api/lotteries/${item.id}/participants`);
}
async function cancelLottery(item: LotteryItem) {
if (item._loading) return;
if (!await askConfirm('取消抽奖', '确认取消这个抽奖?已参与用户不会自动补偿。', { confirmText: '取消抽奖', danger: true })) return;
await fetchJson(`/api/lotteries/${item.id}/cancel`, { method: 'POST' });
await loadLotteries();
item._loading = true;
try { await fetchJson(`/api/lotteries/${item.id}/cancel`, { method: 'POST' }); await loadLotteries(); }
catch (e: any) { showToast(`取消抽奖失败:${e.message}`); }
finally { item._loading = false; }
}
async function drawLottery(item: LotteryItem) {
if (item._loading) return;
if (!await askConfirm('立即开奖', '确认现在开奖?开奖后状态会变更。', { confirmText: '立即开奖' })) return;
await fetchJson(`/api/lotteries/${item.id}/draw`, { method: 'POST' });
await loadLotteries();
item._loading = true;
try { await fetchJson(`/api/lotteries/${item.id}/draw`, { method: 'POST' }); await loadLotteries(); }
catch (e: any) { showToast(`开奖失败:${e.message}`); }
finally { item._loading = false; }
}
function reloadByGroup() { loadChatHistory(); loadBans(); loadStats(); loadShopItems(); loadShopTransactions(); loadLotteries(); }
@@ -367,7 +395,7 @@ onBeforeUnmount(() => {
<template>
<SConfigProvider :theme="{ theme: { primary: 'indigo' }, size: 'sm' }">
<div v-if="!authReady" class="login-page"><div class="login-card">正在检查登录状态...</div></div>
<div v-else-if="!authenticated" class="login-page"><form class="login-card" @submit.prevent="login"><img class="brand-icon" src="/icon.svg" alt="TG Spam Guard" /><h1>TG Spam Guard</h1><p>请输入管理账号密码</p><SInput v-model="loginForm.username" placeholder="账号" /><SInput v-model="loginForm.password" type="password" placeholder="密码" /><SButton type="submit" color="primary" :disabled="loggingIn">登录</SButton></form></div>
<div v-else-if="!authenticated" class="login-page"><form class="login-card" @submit.prevent="login"><img class="brand-icon" src="/icon.svg" alt="TG Spam Guard" /><h1>TG Spam Guard</h1><p>请输入管理账号密码</p><SInput v-model="loginForm.username" placeholder="账号" /><SInput v-model="loginForm.password" type="password" placeholder="密码" /><SButton type="submit" color="primary" :loading="loggingIn" :disabled="loggingIn">登录</SButton></form></div>
<main v-else class="admin-shell">
<aside class="sidebar">
<div class="side-brand">
@@ -405,8 +433,8 @@ onBeforeUnmount(() => {
<SButton size="xs" variant="outline" @click="goPage('lottery')">抽奖管理</SButton>
<SButton size="xs" variant="outline" @click="goPage('bans')">封禁记录</SButton>
<SButton size="xs" variant="outline" @click="logout">退出</SButton>
<SButton size="xs" variant="outline" color="primary" :disabled="restartingBot" @click="restartBot">重启 Bot</SButton>
<SButton size="xs" variant="soft" color="warning" :disabled="restarting" @click="restartService">重启服务</SButton>
<SButton size="xs" variant="outline" color="primary" :loading="restartingBot" :disabled="restartingBot" @click="restartBot">重启 Bot</SButton>
<SButton size="xs" variant="soft" color="warning" :loading="restarting" :disabled="restarting" @click="restartService">重启服务</SButton>
</div>
</header>
@@ -440,8 +468,8 @@ onBeforeUnmount(() => {
<div class="chat-actions">
<STag v-if="item.deleted" size="xs" color="secondary" variant="soft">已删 · {{ formatTime(item.deleted_at) }}</STag>
<template v-else>
<SButton size="xs" variant="link" color="destructive" :disabled="item._deleting" @click="deleteChatMessage(item, false)">删除</SButton>
<SButton v-if="item.user_id" size="xs" variant="link" color="warning" :disabled="item._deleting" @click="deleteChatMessage(item, true)">删封</SButton>
<SButton size="xs" variant="link" color="destructive" :loading="item._deleting" :disabled="item._deleting" @click="deleteChatMessage(item, false)">删除</SButton>
<SButton v-if="item.user_id" size="xs" variant="link" color="warning" :loading="item._deleting" :disabled="item._deleting" @click="deleteChatMessage(item, true)">删封</SButton>
</template>
</div>
</div>
@@ -468,22 +496,22 @@ onBeforeUnmount(() => {
</section>
<section v-show="activePage === 'shop'" class="tab-page manager-layout">
<aside class="editor-panel"><div class="section-title"><span>商品编辑</span><small>{{ shopForm.item_key ? '编辑中' : '新建' }}</small></div><label>商品编号<SInput v-model="shopForm.item_key" placeholder="如 1 / vip_card" /></label><label>商品名称<SInput v-model="shopForm.name" placeholder="补签卡" /></label><label>积分价格<SInput v-model="shopForm.cost" placeholder="50" /></label><label>说明<SInput v-model="shopForm.desc" placeholder="展示在 /shop 内" /></label><label class="switch-line"><input v-model="shopForm.enabled" type="checkbox" /> 上架销售</label><div class="editor-actions"><SButton size="xs" color="primary" @click="saveShopItem">保存商品</SButton><SButton size="xs" variant="outline" @click="Object.assign(shopForm, { item_key: '', name: '', cost: 0, desc: '', enabled: true })">新建</SButton></div></aside>
<section class="data-panel"><div class="tabbar"><button :class="{active: activeTab.shop === 'items'}" @click="activeTab.shop='items'">商品列表</button><button :class="{active: activeTab.shop === 'tx'}" @click="activeTab.shop='tx'; loadShopTransactions()">交易记录</button></div><div v-if="activeTab.shop === 'items'" class="data-table"><div class="table-head"><span>编号/商品</span><span>价格</span><span>状态</span><span>操作</span></div><div v-for="item in shopItems" :key="item.item_key" class="table-row"><span><b>{{ item.item_key }}</b><em>{{ item.name }}</em><small>{{ item.desc || '无说明' }}</small></span><span>{{ item.cost }} 分</span><span><STag size="xs" :color="item.enabled ? 'success' : 'secondary'" variant="soft">{{ item.enabled ? '上架' : '下架' }}</STag></span><span class="row-actions"><SButton size="xs" variant="outline" @click="editShopItem(item)">编辑</SButton><SButton size="xs" variant="soft" color="destructive" @click="deleteShopItem(item)">删除</SButton></span></div></div><div v-else class="data-table tx-table"><div class="table-head"><span>用户</span><span>商品</span><span>积分</span><span>时间</span></div><div v-for="tx in shopTransactions" :key="tx.id" class="table-row"><span>@{{ tx.username || tx.user_id }}</span><span>{{ tx.item_name }}</span><span>{{ tx.cost }} </span><span>{{ formatTime(tx.created_at) }}</span></div></div></section>
<aside class="editor-panel"><div class="section-title"><span>商品编辑</span><small>{{ shopForm.item_key ? '编辑中' : '新建' }}</small></div><label>商品编号<SInput v-model="shopForm.item_key" placeholder="如 1 / vip_card" /></label><label>商品名称<SInput v-model="shopForm.name" placeholder="补签卡" /></label><label>积分价格<SInput v-model="shopForm.cost" placeholder="50" /></label><label>说明<SInput v-model="shopForm.desc" placeholder="展示在 /shop 内" /></label><label class="switch-line"><input v-model="shopForm.enabled" type="checkbox" /> 上架销售</label><div class="editor-actions"><SButton size="xs" color="primary" :loading="shopSaving" :disabled="shopSaving" @click="saveShopItem">保存商品</SButton><SButton size="xs" variant="outline" @click="Object.assign(shopForm, { item_key: '', name: '', cost: 0, desc: '', enabled: true })">新建</SButton></div></aside>
<section class="data-panel"><div class="tabbar"><button :class="{active: activeTab.shop === 'items'}" @click="activeTab.shop='items'">商品列表</button><button :class="{active: activeTab.shop === 'tx'}" @click="activeTab.shop='tx'; loadShopTransactions()">交易记录</button></div><div v-if="activeTab.shop === 'items'" class="data-table"><div class="table-head"><span>编号/商品</span><span>价格</span><span>状态</span><span>操作</span></div><div v-for="item in shopItems" :key="item.item_key" class="table-row"><span><b>{{ item.item_key }}</b><em>{{ item.name }}</em><small>{{ item.desc || '无说明' }}</small></span><span>{{ item.cost }} 分</span><span><STag size="xs" :color="item.enabled ? 'success' : 'secondary'" variant="soft">{{ item.enabled ? '上架' : '下架' }}</STag></span><span class="row-actions"><SButton size="xs" variant="outline" @click="editShopItem(item)">编辑</SButton><SButton size="xs" variant="soft" color="destructive" :loading="item._loading" :disabled="item._loading" @click="deleteShopItem(item)">删除</SButton></span></div></div><div v-else class="data-table tx-table"><div class="table-head"><span>用户</span><span>商品</span><span>积分</span><span>时间</span></div><div v-for="tx in shopTransactions" :key="tx.id" class="table-row"><span>@{{ tx.username || tx.user_id }}</span><span>{{ tx.item_name }}</span><span>{{ tx.cost }} </span><span>{{ formatTime(tx.created_at) }}</span></div></div></section>
</section>
<section v-show="activePage === 'lottery'" class="tab-page manager-layout">
<aside class="editor-panel wide-editor"><div class="section-title"><span>抽奖项目</span><small>{{ lotteryForm.id ? `#${lotteryForm.id}` : '新建' }}</small></div><label>标题<SInput v-model="lotteryForm.title" placeholder="七月群友福利" /></label><label>奖品<SInput v-model="lotteryForm.prize" placeholder="月卡 / 邀请码 / 积分" /></label><label>描述<SInput v-model="lotteryForm.description" placeholder="规则说明,会展示给群友" /></label><div class="form-two"><label>参与条件<select v-model="lotteryForm.condition_type" class="form-select"><option value="all">所有人</option><option value="checkin">今日签到</option><option value="min_points">积分门槛</option></select></label><label>条件值<SInput v-model="lotteryForm.condition_value" placeholder="积分门槛" /></label></div><div class="form-two"><label>开奖方式<select v-model="lotteryForm.draw_type" class="form-select"><option value="instant">即开即中</option><option value="people">人满开奖</option><option value="time">指定时间</option><option value="manual">手动开奖</option></select></label><label>人数/分钟<SInput v-model="lotteryForm.end_value" placeholder="3" /></label></div><label>指定时间<SInput v-model="lotteryForm.draw_at" placeholder="2026-07-07 20:00" /></label><label>状态<select v-model="lotteryForm.status" class="form-select"><option value="draft">草稿</option><option value="active">进行中</option><option value="cancelled">取消</option></select></label><div class="editor-actions"><SButton size="xs" color="primary" @click="saveLottery">保存抽奖</SButton><SButton size="xs" variant="outline" @click="resetLotteryForm">新建</SButton></div></aside>
<section class="data-panel"><div class="tabbar"><button :class="{active: activeTab.lottery === 'events'}" @click="activeTab.lottery='events'">抽奖项目</button><button :class="{active: activeTab.lottery === 'participants'}" @click="activeTab.lottery='participants'">参与记录</button></div><div v-if="activeTab.lottery === 'events'" class="data-table lottery-table"><div class="table-head"><span>项目</span><span>方式</span><span>状态</span><span>操作</span></div><div v-for="item in lotteries" :key="item.id" class="table-row"><span><b>#{{ item.id }} {{ item.title || item.prize }}</b><em>{{ item.prize }}</em><small>{{ item.description || '无描述' }}</small></span><span>{{ item.draw_type || item.end_type }}</span><span><STag size="xs" variant="soft">{{ item.status }}</STag></span><span class="row-actions"><SButton size="xs" variant="outline" @click="editLottery(item); loadLotteryParticipants(item); activeTab.lottery='participants'">编辑/参与</SButton><SButton size="xs" variant="outline" :disabled="item.status !== 'active'" @click="drawLottery(item)">开奖</SButton><SButton size="xs" variant="soft" color="warning" :disabled="item.status !== 'active'" @click="cancelLottery(item)">取消</SButton><SButton size="xs" variant="soft" color="destructive" @click="deleteLottery(item)">删除</SButton></span></div></div><div v-else class="data-table"><div class="table-head"><span>用户</span><span>用户ID</span><span>参与时间</span><span>抽奖</span></div><div v-for="p in lotteryParticipants" :key="p.id" class="table-row"><span>@{{ p.username || p.first_name || 'unknown' }}</span><span>{{ p.user_id }}</span><span>{{ formatTime(p.created_at) }}</span><span>#{{ p.event_id }}</span></div><div v-if="!lotteryParticipants.length" class="empty">选择某个抽奖后查看参与记录</div></div></section>
<aside class="editor-panel wide-editor"><div class="section-title"><span>抽奖项目</span><small>{{ lotteryForm.id ? `#${lotteryForm.id}` : '新建' }}</small></div><label>标题<SInput v-model="lotteryForm.title" placeholder="七月群友福利" /></label><label>奖品<SInput v-model="lotteryForm.prize" placeholder="月卡 / 邀请码 / 积分" /></label><label>描述<SInput v-model="lotteryForm.description" placeholder="规则说明,会展示给群友" /></label><div class="form-two"><label>参与条件<select v-model="lotteryForm.condition_type" class="form-select"><option value="all">所有人</option><option value="checkin">今日签到</option><option value="min_points">积分门槛</option></select></label><label>条件值<SInput v-model="lotteryForm.condition_value" placeholder="积分门槛" /></label></div><div class="form-two"><label>开奖方式<select v-model="lotteryForm.draw_type" class="form-select"><option value="instant">即开即中</option><option value="people">人满开奖</option><option value="time">指定时间</option><option value="manual">手动开奖</option></select></label><label>人数/分钟<SInput v-model="lotteryForm.end_value" placeholder="3" /></label></div><label>指定时间<SInput v-model="lotteryForm.draw_at" placeholder="2026-07-07 20:00" /></label><label>状态<select v-model="lotteryForm.status" class="form-select"><option value="draft">草稿</option><option value="active">进行中</option><option value="cancelled">取消</option></select></label><div class="editor-actions"><SButton size="xs" color="primary" :loading="lotterySaving" :disabled="lotterySaving" @click="saveLottery">保存抽奖</SButton><SButton size="xs" variant="outline" @click="resetLotteryForm">新建</SButton></div></aside>
<section class="data-panel"><div class="tabbar"><button :class="{active: activeTab.lottery === 'events'}" @click="activeTab.lottery='events'">抽奖项目</button><button :class="{active: activeTab.lottery === 'participants'}" @click="activeTab.lottery='participants'">参与记录</button></div><div v-if="activeTab.lottery === 'events'" class="data-table lottery-table"><div class="table-head"><span>项目</span><span>方式</span><span>状态</span><span>操作</span></div><div v-for="item in lotteries" :key="item.id" class="table-row"><span><b>#{{ item.id }} {{ item.title || item.prize }}</b><em>{{ item.prize }}</em><small>{{ item.description || '无描述' }}</small></span><span>{{ item.draw_type || item.end_type }}</span><span><STag size="xs" variant="soft">{{ item.status }}</STag></span><span class="row-actions"><SButton size="xs" variant="outline" @click="editLottery(item); loadLotteryParticipants(item); activeTab.lottery='participants'">编辑/参与</SButton><SButton size="xs" variant="outline" :loading="item._loading" :disabled="item._loading || item.status !== 'active'" @click="drawLottery(item)">开奖</SButton><SButton size="xs" variant="soft" color="warning" :loading="item._loading" :disabled="item._loading || item.status !== 'active'" @click="cancelLottery(item)">取消</SButton><SButton size="xs" variant="soft" color="destructive" :loading="item._loading" :disabled="item._loading" @click="deleteLottery(item)">删除</SButton></span></div></div><div v-else class="data-table"><div class="table-head"><span>用户</span><span>用户ID</span><span>参与时间</span><span>抽奖</span></div><div v-for="p in lotteryParticipants" :key="p.id" class="table-row"><span>@{{ p.username || p.first_name || 'unknown' }}</span><span>{{ p.user_id }}</span><span>{{ formatTime(p.created_at) }}</span><span>#{{ p.event_id }}</span></div><div v-if="!lotteryParticipants.length" class="empty">选择某个抽奖后查看参与记录</div></div></section>
</section>
<section v-show="activePage === 'bans'" class="tab-page">
<SCard class="ban-panel page-panel" title="封禁记录" :description="`${bans.length} 条`"><template #extra><SButton size="xs" variant="outline" @click="loadBans">刷新</SButton></template><div class="ban-list"><div v-if="!bans.length" class="empty">暂无封禁记录</div><div v-for="(item, i) in bans" :key="item.id || i" class="ban-item"><div class="ban-main"><b>@{{ item.username || item.first_name || item.user_id }}</b><span>{{ item.reason || '-' }}</span></div><div class="ban-meta"><span>{{ formatTime(item.time) }}</span><SButton v-if="item.auto_unban" size="xs" variant="outline" color="success" :disabled="item._loading" @click="unban(item)">解封</SButton><STag v-else size="xs" color="secondary" variant="soft">已处理</STag></div></div></div></SCard>
<SCard class="ban-panel page-panel" title="封禁记录" :description="`${bans.length} 条`"><template #extra><SButton size="xs" variant="outline" @click="loadBans">刷新</SButton></template><div class="ban-list"><div v-if="!bans.length" class="empty">暂无封禁记录</div><div v-for="(item, i) in bans" :key="item.id || i" class="ban-item"><div class="ban-main"><b>@{{ item.username || item.first_name || item.user_id }}</b><span>{{ item.reason || '-' }}</span></div><div class="ban-meta"><span>{{ formatTime(item.time) }}</span><SButton v-if="item.auto_unban" size="xs" variant="outline" color="success" :loading="item._loading" :disabled="item._loading" @click="unban(item)">解封</SButton><STag v-else size="xs" color="secondary" variant="soft">已处理</STag></div></div></div></SCard>
</section>
<form v-if="activePage === 'dashboard'" class="send-box floating-chat-send" @submit.prevent="sendMessage">
<SInput v-model="newMessage" placeholder="发送消息到 TG 群" />
<SButton type="submit" color="primary" :disabled="sending || !newMessage.trim()">发送</SButton>
<SButton type="submit" color="primary" :loading="sending" :disabled="sending || !newMessage.trim()">发送</SButton>
</form>
<div v-if="toast" class="toast">{{ toast }}</div>
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<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-D_B5Zn2J.js"></script>
<script type="module" crossorigin src="/static/assets/index-DmSoVPta.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-Djy9tMxw.css">
</head>
<body>