feat: optimize lottery manager form
This commit is contained in:
+164
-13
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import { SButton, SCard, SConfigProvider, SInput, STag } from '@soybeanjs/ui';
|
||||
import { SButton, SButtonLoading, SCard, SConfigProvider, SInput, STag } from '@soybeanjs/ui';
|
||||
|
||||
type ChatItem = Record<string, any> & { _deleting?: boolean };
|
||||
type BanItem = Record<string, any> & { _loading?: boolean };
|
||||
@@ -20,7 +20,7 @@ const shopTransactions = ref<Record<string, any>[]>([]);
|
||||
const lotteries = ref<LotteryItem[]>([]);
|
||||
const lotteryParticipants = ref<Record<string, any>[]>([]);
|
||||
const shopForm = reactive({ item_key: '', name: '', cost: 0, desc: '', enabled: true });
|
||||
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, draw_at: '', status: 'draft' });
|
||||
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, draw_at: '', status: 'active' });
|
||||
const activePage = ref<'dashboard' | 'shop' | 'lottery' | 'bans'>('dashboard');
|
||||
const activeTab = reactive({ shop: 'items', lottery: 'events' });
|
||||
const toast = ref('');
|
||||
@@ -45,6 +45,55 @@ let statTimer: number | undefined;
|
||||
const chatCount = computed(() => chatLogs.value.length);
|
||||
const selectedQuery = computed(() => selectedChatId.value ? `?chat_id=${selectedChatId.value}` : '');
|
||||
|
||||
const conditionOptions = [
|
||||
{ value: 'all', label: '所有人', desc: '无需门槛' },
|
||||
{ value: 'checkin', label: '已签到', desc: '今日签到后可参与' },
|
||||
{ value: 'points', label: '积分门槛', desc: '达到指定积分' }
|
||||
];
|
||||
const drawOptions = [
|
||||
{ value: 'people', label: '满人开奖', desc: '达到人数自动开奖' },
|
||||
{ value: 'time', label: '定时开奖', desc: '到指定时间自动开奖' }
|
||||
];
|
||||
const statusOptions = [
|
||||
{ value: 'draft', label: '草稿' },
|
||||
{ value: 'active', label: '启用' }
|
||||
];
|
||||
const lotteryEditorTitle = computed(() => lotteryForm.id ? '编辑抽奖' : '新建抽奖');
|
||||
const lotteryConditionSummary = computed(() => {
|
||||
if (lotteryForm.condition_type === 'checkin') return '今日已签到用户可参与';
|
||||
if (lotteryForm.condition_type === 'points') return `积分 ≥ ${Number(lotteryForm.condition_value || 0)} 可参与`;
|
||||
return '所有群成员均可参与';
|
||||
});
|
||||
const lotteryDrawSummary = computed(() => lotteryForm.draw_type === 'time'
|
||||
? `定时开奖:${lotteryForm.draw_at ? formatTime(lotteryForm.draw_at) : '未设置时间'}`
|
||||
: `满 ${Number(lotteryForm.end_value || lotteryForm.max_participants || 1)} 人自动开奖`);
|
||||
function selectLotteryCondition(type: string) {
|
||||
lotteryForm.condition_type = type;
|
||||
if (type !== 'points') lotteryForm.condition_value = 0;
|
||||
}
|
||||
function selectLotteryDraw(type: string) {
|
||||
lotteryForm.draw_type = type;
|
||||
lotteryForm.end_type = type;
|
||||
if (type === 'people') {
|
||||
lotteryForm.draw_at = '';
|
||||
lotteryForm.end_value = Number(lotteryForm.end_value || lotteryForm.max_participants || 3);
|
||||
lotteryForm.max_participants = Number(lotteryForm.end_value || 3);
|
||||
} else {
|
||||
lotteryForm.end_value = 0;
|
||||
lotteryForm.max_participants = 0;
|
||||
if (!lotteryForm.draw_at) setLotteryDrawAtMinutes(10);
|
||||
}
|
||||
}
|
||||
function setLotteryDrawAtMinutes(minutes: number) {
|
||||
const d = new Date(Date.now() + minutes * 60000);
|
||||
d.setSeconds(0, 0);
|
||||
lotteryForm.draw_at = toDatetimeLocal(d);
|
||||
}
|
||||
function toDatetimeLocal(date: Date) {
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
const pageTitle = computed(() => ({
|
||||
dashboard: '群组安全总览', shop: '商城管理', lottery: '抽奖管理', bans: '封禁记录'
|
||||
}[activePage.value]));
|
||||
@@ -211,17 +260,29 @@ async function deleteShopItem(item: ShopItem) {
|
||||
async function loadLotteries() {
|
||||
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 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: 'active' }); }
|
||||
function editLottery(item: LotteryItem) { Object.assign(lotteryForm, { ...item, id: item.id || 0 }); }
|
||||
async function saveLottery() {
|
||||
if (!lotteryForm.title || !lotteryForm.prize) return showToast('请填写抽奖标题和奖品');
|
||||
if (lotteryForm.condition_type === 'points' && Number(lotteryForm.condition_value || 0) <= 0) return showToast('请填写积分门槛');
|
||||
if (lotteryForm.draw_type === 'people' && Number(lotteryForm.end_value || 0) <= 0) return showToast('请填写开奖人数');
|
||||
if (lotteryForm.draw_type === 'time' && !lotteryForm.draw_at) return showToast('请选择开奖时间');
|
||||
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 payload = { ...lotteryForm, chat_id: selectedChatId.value || 0 };
|
||||
payload.end_type = payload.draw_type;
|
||||
if (payload.draw_type === 'people') {
|
||||
payload.end_value = Number(payload.end_value || payload.max_participants || 1);
|
||||
payload.max_participants = payload.end_value;
|
||||
payload.draw_at = '';
|
||||
} else {
|
||||
payload.end_value = 0;
|
||||
payload.max_participants = 0;
|
||||
}
|
||||
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 }) });
|
||||
await fetchJson(url, { method: lotteryForm.id ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||
showToast(lotteryForm.id ? '抽奖已更新' : '抽奖已创建');
|
||||
resetLotteryForm();
|
||||
await loadLotteries();
|
||||
} catch (e: any) { showToast(`保存抽奖失败:${e.message}`); }
|
||||
@@ -366,9 +427,19 @@ async function restartService() {
|
||||
} catch {}
|
||||
window.setTimeout(() => location.reload(), 8000);
|
||||
}
|
||||
function copyLogs() {
|
||||
async function refreshLogs() {
|
||||
if (logsRefreshing.value) return;
|
||||
logsRefreshing.value = true;
|
||||
try { await loadLogs(); } finally { logsRefreshing.value = false; }
|
||||
}
|
||||
async function copyLogs() {
|
||||
if (copyingLogs.value) return;
|
||||
copyingLogs.value = true;
|
||||
try {
|
||||
const text = logs.value.map(l => `${formatTime(l.time)} ${l.level || ''} ${compactLog(l.message)}`).join('\n');
|
||||
navigator.clipboard?.writeText(text);
|
||||
await navigator.clipboard?.writeText(text);
|
||||
showToast('日志已复制');
|
||||
} finally { copyingLogs.value = false; }
|
||||
}
|
||||
|
||||
async function bootstrapData() {
|
||||
@@ -480,8 +551,8 @@ onBeforeUnmount(() => {
|
||||
<SCard class="panel log-panel" title="📜 实时日志" :description="`${logs.length} 条`">
|
||||
<template #extra>
|
||||
<div class="panel-extra">
|
||||
<SButton size="xs" variant="outline" @click="copyLogs">复制</SButton>
|
||||
<SButton size="xs" variant="outline" @click="loadLogs">刷新</SButton>
|
||||
<SButtonLoading size="xs" variant="outline" :loading="copyingLogs" :disabled="copyingLogs" @click="copyLogs">复制</SButtonLoading>
|
||||
<SButtonLoading size="xs" variant="outline" :loading="logsRefreshing" :disabled="logsRefreshing" @click="refreshLogs">刷新</SButtonLoading>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="logScroll" class="log-window">
|
||||
@@ -500,9 +571,89 @@ onBeforeUnmount(() => {
|
||||
<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" :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 v-show="activePage === 'lottery'" class="tab-page manager-layout lottery-page">
|
||||
<aside class="editor-panel lottery-editor">
|
||||
<div class="section-title"><span>{{ lotteryEditorTitle }}</span><small>{{ lotteryConditionSummary }} · {{ lotteryDrawSummary }}</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-block">
|
||||
<span class="field-label">参与条件</span>
|
||||
<div class="seg-buttons">
|
||||
<SButton v-for="option in conditionOptions" :key="option.value" size="sm" :variant="lotteryForm.condition_type === option.value ? 'solid' : 'outline'" :color="lotteryForm.condition_type === option.value ? 'primary' : 'secondary'" @click="selectLotteryCondition(option.value)">
|
||||
{{ option.label }}
|
||||
</SButton>
|
||||
</div>
|
||||
<p class="field-help">{{ conditionOptions.find(o => o.value === lotteryForm.condition_type)?.desc }}</p>
|
||||
<label v-if="lotteryForm.condition_type === 'points'" class="inline-field">最低积分<SInput v-model="lotteryForm.condition_value" type="number" placeholder="100" /></label>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<span class="field-label">开奖方式</span>
|
||||
<div class="seg-buttons">
|
||||
<SButton v-for="option in drawOptions" :key="option.value" size="sm" :variant="lotteryForm.draw_type === option.value ? 'solid' : 'outline'" :color="lotteryForm.draw_type === option.value ? 'primary' : 'secondary'" @click="selectLotteryDraw(option.value)">
|
||||
{{ option.label }}
|
||||
</SButton>
|
||||
</div>
|
||||
<p class="field-help">{{ drawOptions.find(o => o.value === lotteryForm.draw_type)?.desc }}</p>
|
||||
<label v-if="lotteryForm.draw_type === 'people'" class="inline-field">开奖人数<SInput v-model="lotteryForm.end_value" type="number" placeholder="3" /></label>
|
||||
<div v-else class="time-picker-block">
|
||||
<label class="inline-field">开奖时间<input v-model="lotteryForm.draw_at" class="form-input native-time" type="datetime-local" /></label>
|
||||
<div class="quick-times">
|
||||
<SButton size="xs" variant="outline" @click="setLotteryDrawAtMinutes(10)">10 分钟后</SButton>
|
||||
<SButton size="xs" variant="outline" @click="setLotteryDrawAtMinutes(30)">30 分钟后</SButton>
|
||||
<SButton size="xs" variant="outline" @click="setLotteryDrawAtMinutes(60)">1 小时后</SButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<span class="field-label">发布状态</span>
|
||||
<div class="seg-buttons">
|
||||
<SButton v-for="option in statusOptions" :key="option.value" size="sm" :variant="lotteryForm.status === option.value ? 'solid' : 'outline'" :color="lotteryForm.status === option.value ? 'success' : 'secondary'" @click="lotteryForm.status = option.value">
|
||||
{{ option.label }}
|
||||
</SButton>
|
||||
</div>
|
||||
<p class="field-help">新建建议直接启用;草稿仅保存后台配置。</p>
|
||||
</div>
|
||||
|
||||
<div class="form-preview">
|
||||
<strong>{{ lotteryForm.title || '抽奖标题' }}</strong>
|
||||
<span>{{ lotteryForm.prize || '奖品内容' }}</span>
|
||||
<small>{{ lotteryConditionSummary }} / {{ lotteryDrawSummary }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<SButtonLoading color="primary" :loading="lotterySaving" :disabled="lotterySaving" @click="saveLottery">{{ lotteryForm.id ? '保存修改' : '创建抽奖' }}</SButtonLoading>
|
||||
<SButton variant="outline" :disabled="lotterySaving" @click="resetLotteryForm">清空</SButton>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<SCard class="panel data-panel" title="🎁 抽奖项目" :description="`${lotteries.length} 个活动`">
|
||||
<div class="table-list lottery-list">
|
||||
<div v-for="item in lotteries" :key="item.id" class="data-row lottery-row">
|
||||
<div class="data-main">
|
||||
<div class="row-title">
|
||||
<strong>{{ item.title || item.prize }}</strong>
|
||||
<STag size="xs" :color="item.status === 'active' ? 'success' : item.status === 'drawn' ? 'primary' : item.status === 'cancelled' ? 'destructive' : 'secondary'" variant="soft">{{ item.status }}</STag>
|
||||
</div>
|
||||
<div class="row-desc">奖品:{{ item.prize }} · 条件:{{ item.condition_type }}{{ item.condition_value ? ':' + item.condition_value : '' }}</div>
|
||||
<div class="row-desc">开奖:{{ item.draw_type === 'time' ? formatTime(item.draw_at) : `满 ${item.max_participants || item.end_value || '-'} 人` }}</div>
|
||||
</div>
|
||||
<div class="row-actions">
|
||||
<SButton size="xs" variant="outline" @click="editLottery(item)">编辑</SButton>
|
||||
<SButton size="xs" variant="outline" @click="showParticipants(item)">参与</SButton>
|
||||
<SButton size="xs" variant="outline" color="primary" :loading="item._loading" :disabled="item._loading || item.status !== 'active'" @click="drawLottery(item)">开奖</SButton>
|
||||
<SButton size="xs" variant="outline" color="warning" :loading="item._loading" :disabled="item._loading || item.status !== 'active'" @click="cancelLottery(item)">取消</SButton>
|
||||
<SButton size="xs" variant="link" color="destructive" :loading="item._loading" :disabled="item._loading" @click="deleteLottery(item)">删除</SButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!lotteries.length" class="empty">暂无抽奖项目</div>
|
||||
</div>
|
||||
<div v-if="lotteryParticipants.length" class="sub-list"><h4>参与记录</h4><div v-for="p in lotteryParticipants" :key="p.id" class="mini-row">用户 {{ p.user_id }} · {{ formatTime(p.created_at) }}</div></div>
|
||||
</SCard>
|
||||
</section>
|
||||
|
||||
<section v-show="activePage === 'bans'" class="tab-page">
|
||||
|
||||
@@ -219,3 +219,23 @@ body { background: #f3f6fb; }
|
||||
.admin-shell .floating-chat-send { left: 284px; right: 20px; bottom: 18px; }
|
||||
.main-grid { padding-bottom: 72px; }
|
||||
@media (max-width: 1100px) { .admin-shell .floating-chat-send { left: 16px; right: 16px; } }
|
||||
|
||||
|
||||
.lottery-editor { gap: 14px; }
|
||||
.form-block { display: flex; flex-direction: column; gap: 8px; padding: 12px; border: 1px solid #e2e8f0; border-radius: 14px; background: #f8fafc; }
|
||||
.field-label { font-size: 13px; font-weight: 800; color: #334155; }
|
||||
.field-help { margin: 0; font-size: 12px; color: #64748b; }
|
||||
.seg-buttons { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.inline-field { display: grid; gap: 6px; font-size: 12px; color: #475569; }
|
||||
.time-picker-block { display: grid; gap: 8px; }
|
||||
.native-time { width: 100%; height: 38px; color: #0f172a; }
|
||||
.quick-times { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.form-preview { display: grid; gap: 5px; padding: 12px; border: 1px dashed #c7d2fe; border-radius: 14px; background: linear-gradient(135deg, #eef2ff, #f8fafc); }
|
||||
.form-preview strong { font-size: 15px; color: #1e293b; }
|
||||
.form-preview span { color: #334155; }
|
||||
.form-preview small { color: #64748b; }
|
||||
.lottery-row { align-items: flex-start; }
|
||||
.lottery-list .row-desc { line-height: 1.6; }
|
||||
@media (max-width: 860px) {
|
||||
.seg-buttons, .quick-times { display: grid; grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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-DmSoVPta.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/index-Djy9tMxw.css">
|
||||
<script type="module" crossorigin src="/static/assets/index-DPwRdHjl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/index-CGm8xHHJ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Reference in New Issue
Block a user