Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

157 linhas
8.3 KiB

  1. <template>
  2. <div class="relative overflow-hidden bg-gradient-to-b from-[var(--c-eaf5ff)] via-[var(--c-f5f9fd)] to-[var(--c-f3f7fb)]">
  3. <div class="pointer-events-none absolute -right-24 top-10 size-72 rounded-full bg-[var(--c-36b7ff)]/15 blur-3xl" />
  4. <div class="pointer-events-none absolute -left-28 top-72 size-72 rounded-full bg-[var(--c-0b8cff)]/10 blur-3xl" />
  5. <main class="relative mx-auto grid min-h-[calc(100vh-64px)] max-w-7xl items-center gap-8 px-4 py-10 sm:px-6 lg:grid-cols-12 lg:px-8 lg:py-16">
  6. <section class="lg:col-span-7 lg:pr-8" aria-labelledby="landing-title">
  7. <div class="inline-flex items-center gap-2 rounded-full border border-[var(--c-e8eef7)] bg-[var(--c-ffffff)]/80 px-3.5 py-2 text-xs font-extrabold text-[var(--c-0b8cff)] shadow-[0_6px_18px_var(--sh-11-140-255-100)] backdrop-blur">
  8. <UIcon name="i-lucide-gift" class="size-3.5" />
  9. 新用户邀请礼
  10. </div>
  11. <div class="mt-7 flex items-center gap-3">
  12. <div class="flex size-12 items-center justify-center overflow-hidden rounded-2xl bg-[var(--c-ffffff)] text-sm font-extrabold text-[var(--c-0b8cff)] shadow-[0_10px_24px_var(--sh-23-58-110-100)] ring-1 ring-[var(--c-e4edf7)]">
  13. <img v-if="inviter.avatar" :src="inviter.avatar" :alt="inviter.user_name || '邀请人'" class="size-full object-cover">
  14. <span v-else>AI</span>
  15. </div>
  16. <div>
  17. <div class="text-xs text-[var(--c-8a97a8)]">来自好友的邀请</div>
  18. <div class="mt-1 text-sm font-extrabold text-[var(--c-172033)]">{{ inviter.user_name || 'AI 在线好友' }}</div>
  19. </div>
  20. </div>
  21. <h1 id="landing-title" class="mt-7 max-w-2xl text-4xl font-extrabold leading-[1.12] tracking-tight text-[var(--c-172033)] sm:text-5xl lg:text-[54px]">
  22. {{ landingTitle }}
  23. </h1>
  24. <p class="mt-5 max-w-xl text-sm leading-7 text-[var(--c-5f6f84)] sm:text-base">{{ landingDesc }}</p>
  25. <div class="mt-7 flex flex-wrap gap-3 text-xs font-bold text-[var(--c-66758a)]">
  26. <span v-for="item in benefits" :key="item" class="inline-flex items-center gap-1.5 rounded-full bg-[var(--c-ffffff)]/80 px-3 py-2 shadow-sm">
  27. <UIcon name="i-lucide-check" class="size-3.5 text-[var(--c-17a65a)]" />
  28. {{ item }}
  29. </span>
  30. </div>
  31. </section>
  32. <section class="lg:col-span-5" aria-label="邀请奖励">
  33. <div class="rounded-[30px] border border-[var(--c-e8eef7)] bg-[var(--c-ffffff)]/90 p-5 shadow-[0_26px_70px_var(--sh-28-69-120-130)] backdrop-blur sm:p-7">
  34. <div v-if="pending" class="space-y-4" role="status" aria-label="正在加载邀请信息">
  35. <div class="mx-auto size-16 animate-pulse rounded-2xl bg-[var(--c-edf2f7)] motion-reduce:animate-none" />
  36. <div class="mx-auto h-5 w-40 animate-pulse rounded-full bg-[var(--c-edf2f7)] motion-reduce:animate-none" />
  37. <div class="grid grid-cols-2 gap-3">
  38. <div class="h-32 animate-pulse rounded-2xl bg-[var(--c-f1f5fa)] motion-reduce:animate-none" />
  39. <div class="h-32 animate-pulse rounded-2xl bg-[var(--c-f1f5fa)] motion-reduce:animate-none" />
  40. </div>
  41. </div>
  42. <template v-else>
  43. <div class="text-center">
  44. <div
  45. class="mx-auto flex size-16 items-center justify-center rounded-2xl"
  46. :class="validInvite && activityEnabled ? 'bg-[var(--c-eef7ff)] text-[var(--c-0b8cff)] ring-8 ring-[var(--c-f7fbff)]' : 'bg-[var(--c-f1f5fa)] text-[var(--c-8a97a8)] ring-8 ring-[var(--c-fafbfd)]'"
  47. >
  48. <UIcon :name="validInvite && activityEnabled ? 'i-lucide-party-popper' : 'i-lucide-link-2-off'" class="size-7" />
  49. </div>
  50. <h2 class="mt-6 text-xl font-extrabold text-[var(--c-172033)]">
  51. {{ validInvite && activityEnabled ? '登录即可领取邀请礼' : invalidTitle }}
  52. </h2>
  53. <p class="mt-2 text-sm leading-6 text-[var(--c-7b8797)]">
  54. {{ validInvite && activityEnabled ? '奖励将自动发放到双方的点数账户。' : invalidDesc }}
  55. </p>
  56. </div>
  57. <div v-if="validInvite && activityEnabled" class="mt-6 grid grid-cols-2 gap-3">
  58. <div class="rounded-2xl bg-[var(--c-f0f8ff)] px-3 py-5 text-center ring-1 ring-inset ring-[var(--c-dceefe)]">
  59. <div class="text-3xl font-extrabold text-[var(--c-0b8cff)] tabular-nums">{{ config.invitee_points || 0 }}</div>
  60. <div class="mt-2 text-xs font-bold text-[var(--c-66758a)]">你可领取点数</div>
  61. </div>
  62. <div class="rounded-2xl bg-[var(--c-edfdf4)] px-3 py-5 text-center ring-1 ring-inset ring-[var(--c-d8f4e5)]">
  63. <div class="text-3xl font-extrabold text-[var(--c-159a54)] tabular-nums">{{ config.inviter_points || 0 }}</div>
  64. <div class="mt-2 text-xs font-bold text-[var(--c-66758a)]">好友获得点数</div>
  65. </div>
  66. </div>
  67. <button
  68. type="button"
  69. class="mt-6 flex h-13 w-full cursor-pointer items-center justify-center gap-2 rounded-full bg-gradient-to-r from-[var(--c-0b8cff)] to-[var(--c-3aa9ff)] text-base font-extrabold text-white shadow-[0_12px_26px_var(--sh-11-140-255-240)] transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--c-0b8cff)] focus-visible:ring-offset-2"
  70. @click="handleAction"
  71. >
  72. <UIcon :name="isLogin ? 'i-lucide-arrow-right' : validInvite && activityEnabled ? 'i-lucide-log-in' : 'i-lucide-house'" class="size-4.5" />
  73. {{ actionText }}
  74. </button>
  75. <p v-if="validInvite && activityEnabled" class="mt-5 text-center text-[11px] leading-5 text-[var(--c-9aa5b5)]">
  76. {{ config.rule_text || '好友通过你的邀请完成新用户注册后,你和好友都可获得点数奖励。' }}
  77. </p>
  78. </template>
  79. </div>
  80. </section>
  81. </main>
  82. </div>
  83. </template>
  84. <script setup>
  85. const route = useRoute()
  86. const { post } = useApi()
  87. const { isLogin, showLogin } = useUser()
  88. const { clearInvite, normalizeInviteCode } = useInvite()
  89. const rawCode = route.query.invite_code || route.query.invite_open_id || ''
  90. const code = normalizeInviteCode(rawCode)
  91. const { data: inviteData, pending, error } = await useAsyncData(
  92. `invite-landing-${code || 'empty'}`,
  93. () => post('/invite/config', {
  94. invite_code: code,
  95. scene: String(route.query.scene || ''),
  96. }),
  97. )
  98. const config = computed(() => inviteData.value?.data?.config || {})
  99. const inviter = computed(() => inviteData.value?.data?.inviter || {})
  100. const validInvite = computed(() => Boolean(code && inviter.value.user_id))
  101. const activityEnabled = computed(() => Number(config.value.status ?? 1) === 1)
  102. const landingTitle = computed(() => {
  103. if (validInvite.value && activityEnabled.value) {
  104. return config.value.landing_title || '好友邀请你一起体验 AI 创作'
  105. }
  106. return '发现更多好用的 AI 创作工具'
  107. })
  108. const landingDesc = computed(() => {
  109. if (validInvite.value && activityEnabled.value) {
  110. return config.value.landing_desc || '登录后领取点数奖励,AI 取名、动漫头像、证件照等功能都能使用。'
  111. }
  112. return 'AI 在线提供取名、动漫头像与图像创作等能力,登录后即可开始体验。'
  113. })
  114. const invalidTitle = computed(() => {
  115. if (error.value) return '邀请信息加载失败'
  116. if (!activityEnabled.value) return '邀请活动暂未开放'
  117. return '邀请链接无效或已过期'
  118. })
  119. const invalidDesc = computed(() => error.value ? '网络开小差了,你仍然可以直接进入 AI 在线。' : '你仍然可以直接进入 AI 在线,体验全部创作工具。')
  120. const actionText = computed(() => {
  121. if (isLogin.value) return '进入 AI 在线'
  122. if (validInvite.value && activityEnabled.value) return '登录并领取'
  123. return '直接体验'
  124. })
  125. const benefits = ['点数多端通用', '无需订阅', '创作记录同步']
  126. useSeoMeta({
  127. title: () => `${landingTitle.value} - AI在线`,
  128. description: () => landingDesc.value,
  129. robots: 'noindex, nofollow',
  130. })
  131. if (code && !pending.value && !validInvite.value) clearInvite()
  132. function handleAction() {
  133. if (isLogin.value || !validInvite.value || !activityEnabled.value) {
  134. navigateTo('/')
  135. return
  136. }
  137. showLogin.value = true
  138. }
  139. </script>