mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
Merge upstream main into feat/500-api-key-ip-whitelist
This commit is contained in:
@@ -132,6 +132,13 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-sm font-medium text-foreground">{{ announcement.title }}</span>
|
||||
<Badge
|
||||
v-if="announcement.requires_ack"
|
||||
variant="outline"
|
||||
class="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
必读
|
||||
</Badge>
|
||||
<Pin
|
||||
v-if="announcement.is_pinned"
|
||||
class="w-3.5 h-3.5 text-muted-foreground flex-shrink-0"
|
||||
@@ -240,6 +247,13 @@
|
||||
:class="getIconColor(announcement.type)"
|
||||
/>
|
||||
<span class="font-medium text-sm">{{ announcement.title }}</span>
|
||||
<Badge
|
||||
v-if="announcement.requires_ack"
|
||||
variant="outline"
|
||||
class="text-[10px] shrink-0"
|
||||
>
|
||||
必读
|
||||
</Badge>
|
||||
<Pin
|
||||
v-if="announcement.is_pinned"
|
||||
class="w-3.5 h-3.5 text-muted-foreground shrink-0"
|
||||
@@ -433,6 +447,18 @@
|
||||
class="cursor-pointer text-sm"
|
||||
>置顶公告</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
id="requires-ack"
|
||||
v-model="formData.requires_ack"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-gray-300 cursor-pointer"
|
||||
>
|
||||
<Label
|
||||
for="requires-ack"
|
||||
class="cursor-pointer text-sm"
|
||||
>必读确认</Label>
|
||||
</div>
|
||||
<div
|
||||
v-if="editingAnnouncement"
|
||||
class="flex items-center gap-2"
|
||||
@@ -611,7 +637,8 @@ const formData = ref({
|
||||
type: 'info' as 'info' | 'warning' | 'maintenance' | 'important',
|
||||
priority: 0,
|
||||
is_pinned: false,
|
||||
is_active: true
|
||||
is_active: true,
|
||||
requires_ack: false
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@@ -663,7 +690,8 @@ function openCreateDialog() {
|
||||
type: 'info',
|
||||
priority: 0,
|
||||
is_pinned: false,
|
||||
is_active: true
|
||||
is_active: true,
|
||||
requires_ack: false
|
||||
}
|
||||
dialogOpen.value = true
|
||||
}
|
||||
@@ -676,7 +704,8 @@ function openEditDialog(announcement: Announcement) {
|
||||
type: announcement.type,
|
||||
priority: announcement.priority,
|
||||
is_pinned: announcement.is_pinned,
|
||||
is_active: announcement.is_active
|
||||
is_active: announcement.is_active,
|
||||
requires_ack: !!announcement.requires_ack
|
||||
}
|
||||
dialogOpen.value = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="space-y-6 pb-8">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-foreground">
|
||||
我的邀请
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
分享邀请码后,符合规则的返利会进入赠款余额
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="loading"
|
||||
class="rounded-lg border border-border bg-card p-6 text-sm text-muted-foreground"
|
||||
>
|
||||
正在加载...
|
||||
</div>
|
||||
|
||||
<template v-else-if="dashboard">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<Card class="p-5">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
总邀请
|
||||
</p>
|
||||
<p class="mt-2 text-2xl font-semibold">
|
||||
{{ dashboard.summary.total_invites }}
|
||||
</p>
|
||||
</Card>
|
||||
<Card class="p-5">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
有效邀请
|
||||
</p>
|
||||
<p class="mt-2 text-2xl font-semibold">
|
||||
{{ dashboard.summary.effective_invites }}
|
||||
</p>
|
||||
</Card>
|
||||
<Card class="p-5">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
已发返利
|
||||
</p>
|
||||
<p class="mt-2 text-2xl font-semibold">
|
||||
{{ formatUsd(dashboard.summary.paid_reward_usd) }}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="p-5">
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-[240px_1fr]">
|
||||
<div>
|
||||
<Label class="text-xs text-muted-foreground">
|
||||
邀请码
|
||||
</Label>
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<code class="rounded-lg border border-border bg-muted px-3 py-2 font-mono text-sm">
|
||||
{{ dashboard.invite_code }}
|
||||
</code>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="copyToClipboard(dashboard.invite_code)"
|
||||
>
|
||||
<Copy class="mr-2 h-4 w-4" />
|
||||
复制
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-xs text-muted-foreground">
|
||||
邀请链接
|
||||
</Label>
|
||||
<div class="mt-2 flex min-w-0 items-center gap-2">
|
||||
<Input
|
||||
:model-value="dashboard.invitation_link"
|
||||
readonly
|
||||
class="min-w-0"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="shrink-0"
|
||||
@click="copyToClipboard(dashboard.invitation_link)"
|
||||
>
|
||||
<Copy class="mr-2 h-4 w-4" />
|
||||
复制
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<Card class="p-5">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
待发返利
|
||||
</p>
|
||||
<p class="mt-2 text-xl font-semibold">
|
||||
{{ formatUsd(dashboard.summary.pending_reward_usd) }}
|
||||
</p>
|
||||
</Card>
|
||||
<Card class="p-5">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
已冲回返利
|
||||
</p>
|
||||
<p class="mt-2 text-xl font-semibold">
|
||||
{{ formatUsd(dashboard.summary.reversed_reward_usd) }}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="rounded-lg border border-border bg-card p-6 text-sm text-muted-foreground"
|
||||
>
|
||||
邀请数据暂不可用
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { Copy } from 'lucide-vue-next'
|
||||
import { referralApi, type ReferralDashboardResponse } from '@/api/referrals'
|
||||
import { Button, Card, Input, Label } from '@/components/ui'
|
||||
import { useClipboard } from '@/composables/useClipboard'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const dashboard = ref<ReferralDashboardResponse | null>(null)
|
||||
const loading = ref(false)
|
||||
const { copyToClipboard } = useClipboard()
|
||||
const { error: showError } = useToast()
|
||||
|
||||
function formatUsd(value: number): string {
|
||||
return `$${Number(value || 0).toFixed(2)}`
|
||||
}
|
||||
|
||||
async function loadReferralDashboard() {
|
||||
loading.value = true
|
||||
try {
|
||||
dashboard.value = await referralApi.getMyReferral()
|
||||
} catch {
|
||||
dashboard.value = null
|
||||
showError('加载邀请数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadReferralDashboard()
|
||||
})
|
||||
</script>
|
||||
@@ -387,7 +387,7 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
class="oauth-icon shrink-0"
|
||||
v-html="getOAuthIcon(p.provider_type)"
|
||||
v-html="getOAuthIcon(p.provider_type, p.icon_url)"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<div class="min-w-0">
|
||||
|
||||
Reference in New Issue
Block a user