Initial open-source release (MIT): image2api AI gateway

Full Go backend + Vue 3 frontend, OpenAI-compatible API, multi-provider
account pools, billing/admin, Docker one-command deploy with auto HTTPS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 22:59:04 +08:00
co-authored by Claude Opus 4.8
commit 606caaf047
142 changed files with 33648 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
<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-fuchsia-300/80">已发奖励</div>
<div class="text-2xl font-semibold mt-1 tabular-nums text-fuchsia-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>