- Logo 渐变从粉紫改为橙/粉/蓝蜡笔色,快门圆点改橙色 - 批量替换 accent:violet→orange、fuchsia→rose、indigo→sky、purple→orange, 十六进制与 rgb 字面量同步换算为 Tailwind 对应色 - 修复类名合并冲突:管理员筛选 pill 改 fp-orange、会员号改 fp-pink(新增), style.css 中去重 fp-rose/fp-orange 并补 fp-pink 的浅色模式变体 - 选区色、标签输入框、筛选 pill、图表、滑块等所有紫色残留全部归位 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
103 lines
4.9 KiB
Vue
103 lines
4.9 KiB
Vue
<script setup>
|
|
// Admin invite log — global view of every invite relationship across accounts.
|
|
import { ref, onMounted } from 'vue'
|
|
import { api } from '../api'
|
|
import { fmtIso } from '../utils/format'
|
|
import Icon from '../components/Icon.vue'
|
|
|
|
const items = ref([])
|
|
const stats = ref({ total: 0, completed: 0, pending: 0, reward_paid: 0 })
|
|
const loading = ref(false)
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
const r = await api('/invites')
|
|
loading.value = false
|
|
if (r.ok) { items.value = r.data?.data || []; stats.value = r.data?.stats || stats.value }
|
|
}
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<template>
|
|
<section class="space-y-4">
|
|
<!-- KPI strip — same shape as the LogsView so the admin shell stays
|
|
consistent. /invites already returns the stats payload. -->
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
<div class="card p-4">
|
|
<div class="text-[11px] uppercase tracking-wider text-white/45">总邀请</div>
|
|
<div class="text-2xl font-semibold mt-1 tabular-nums">{{ stats.total }}</div>
|
|
</div>
|
|
<div class="card p-4">
|
|
<div class="text-[11px] uppercase tracking-wider text-emerald-300/80">已完成</div>
|
|
<div class="text-2xl font-semibold mt-1 tabular-nums text-emerald-300">{{ stats.completed }}</div>
|
|
</div>
|
|
<div class="card p-4">
|
|
<div class="text-[11px] uppercase tracking-wider text-amber-300/80">待生图</div>
|
|
<div class="text-2xl font-semibold mt-1 tabular-nums text-amber-300">{{ stats.pending }}</div>
|
|
</div>
|
|
<div class="card p-4">
|
|
<div class="text-[11px] uppercase tracking-wider text-rose-300/80">已发奖励</div>
|
|
<div class="text-2xl font-semibold mt-1 tabular-nums text-rose-300">{{ Number(stats.reward_paid || 0).toLocaleString('en-US') }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card overflow-hidden">
|
|
<div class="px-5 py-3 border-b border-white/[0.06] flex items-center justify-between">
|
|
<div class="text-xs text-white/55">全站邀请记录:谁邀请了谁、注册与完成时间、奖励状态。</div>
|
|
<button @click="load" class="btn-soft"><Icon name="refresh" class="w-3.5 h-3.5" /> 刷新</button>
|
|
</div>
|
|
|
|
<div v-if="loading && !items.length" class="text-center text-sm text-white/40 py-16">加载中…</div>
|
|
<div v-else-if="!items.length" class="text-center text-sm text-white/40 py-16">还没有邀请记录</div>
|
|
|
|
<table v-else class="w-full text-sm">
|
|
<colgroup>
|
|
<col />
|
|
<col />
|
|
<col class="w-24" />
|
|
<col class="w-44" />
|
|
<col class="w-44" />
|
|
<col class="w-28" />
|
|
</colgroup>
|
|
<thead>
|
|
<tr class="text-[10px] uppercase tracking-[0.2em] text-white/40 border-b border-white/[0.06]">
|
|
<th class="text-left px-5 py-3 font-medium">邀请人</th>
|
|
<th class="text-left px-3 py-3 font-medium">被邀请人</th>
|
|
<th class="text-right px-3 py-3 font-medium">奖励</th>
|
|
<th class="text-left px-3 py-3 font-medium">注册时间</th>
|
|
<th class="text-left px-3 py-3 font-medium">完成时间</th>
|
|
<th class="text-right px-5 py-3 font-medium">状态</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(r, i) in items" :key="i"
|
|
class="border-b border-white/[0.04] hover:bg-white/[0.03] transition-colors">
|
|
<td class="px-5 py-3.5 align-middle font-medium text-white/90 truncate">{{ r.inviter }}</td>
|
|
<td class="px-3 py-3.5 align-middle text-white/75 truncate">{{ r.invitee }}</td>
|
|
<td class="px-3 py-3.5 align-middle text-right tabular-nums whitespace-nowrap"
|
|
:class="r.reward ? 'text-emerald-300' : 'text-white/25'">
|
|
{{ r.reward ? '+' + r.reward : '—' }}
|
|
</td>
|
|
<td class="px-3 py-3.5 align-middle text-xs text-white/55 tabular-nums whitespace-nowrap">
|
|
{{ r.registered_at ? fmtIso(r.registered_at) : '—' }}
|
|
</td>
|
|
<td class="px-3 py-3.5 align-middle text-xs text-white/55 tabular-nums whitespace-nowrap">
|
|
{{ r.completed_at ? fmtIso(r.completed_at) : '—' }}
|
|
</td>
|
|
<td class="px-5 py-3.5 align-middle text-right">
|
|
<span class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium ring-1"
|
|
:class="r.status === 'completed'
|
|
? 'bg-emerald-500/10 text-emerald-300 ring-emerald-400/30'
|
|
: 'bg-amber-500/10 text-amber-300 ring-amber-400/30'">
|
|
<span class="w-1.5 h-1.5 rounded-full"
|
|
:class="r.status === 'completed' ? 'bg-emerald-400' : 'bg-amber-400'"></span>
|
|
{{ r.status === 'completed' ? '已完成' : '待生图' }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</template>
|