feat: 添加应用详情页面和首页功能,支持应用搜索和过滤
This commit is contained in:
217
packages/distribute/pages/apps/[id].vue
Normal file
217
packages/distribute/pages/apps/[id].vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<script setup lang="ts">
|
||||
import type { AppInfo } from '~/types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
// 获取应用详情
|
||||
const { data: app, error } = await useFetch<AppInfo>(`/api/apps/${route.params.id}`)
|
||||
|
||||
// 如果应用不存在,跳转回首页
|
||||
if (error.value) {
|
||||
navigateTo('/')
|
||||
}
|
||||
|
||||
// 下载处理
|
||||
async function handleDownload(type: 'ios' | 'android' | 'h5') {
|
||||
if (!app.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = app.value.downloads[type]
|
||||
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
|
||||
if (type === 'h5') {
|
||||
navigateTo(url, { external: true, open: { target: '_blank' } })
|
||||
}
|
||||
else {
|
||||
navigateTo(url, { external: true })
|
||||
}
|
||||
|
||||
await $fetch(`/api/track/${type}`, {
|
||||
method: 'POST',
|
||||
body: { appId: app.value.id },
|
||||
})
|
||||
}
|
||||
|
||||
// 返回首页
|
||||
function goBack() {
|
||||
router.back()
|
||||
}
|
||||
|
||||
// SEO
|
||||
useHead({
|
||||
title: app.value ? `${app.value.name} - Riwa App Store` : 'Riwa App Store',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: app.value?.shortDescription[locale.value as 'zh-CN' | 'en-US'] || '',
|
||||
},
|
||||
],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="app" class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<UContainer>
|
||||
<!-- Header -->
|
||||
<header class="sticky top-0 z-50">
|
||||
<div class="flex items-center gap-4 py-4">
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-left"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
@click="goBack"
|
||||
/>
|
||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
{{ app.name }}
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="py-8">
|
||||
<!-- App Header -->
|
||||
<div class="flex items-start gap-6 mb-8">
|
||||
<div class="size-24 rounded-3xl bg-linear-to-br from-primary-500 to-primary-600 flex items-center justify-center text-white font-bold text-4xl flex-shrink-0 shadow-xl">
|
||||
{{ app.name.charAt(0) }}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||
{{ app.name }}
|
||||
</h2>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-400 mb-4">
|
||||
{{ app.shortDescription[locale as 'zh-CN' | 'en-US'] }}
|
||||
</p>
|
||||
<div class="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-500">
|
||||
<span>{{ locale === 'zh-CN' ? '版本' : 'Version' }} {{ app.version }}</span>
|
||||
<span>•</span>
|
||||
<span>{{ app.releaseDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download Buttons -->
|
||||
<div class="grid md:grid-cols-3 gap-4 mb-8">
|
||||
<UButton
|
||||
v-if="app.downloads.ios"
|
||||
icon="i-heroicons-device-phone-mobile"
|
||||
size="xl"
|
||||
block
|
||||
@click="handleDownload('ios')"
|
||||
>
|
||||
<div class="text-left w-full">
|
||||
<div class="font-semibold text-base">iOS</div>
|
||||
<div v-if="app.size?.ios" class="text-xs opacity-80">
|
||||
{{ app.size.ios }}
|
||||
</div>
|
||||
</div>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="app.downloads.android"
|
||||
icon="i-heroicons-device-tablet"
|
||||
size="xl"
|
||||
block
|
||||
@click="handleDownload('android')"
|
||||
>
|
||||
<div class="text-left w-full">
|
||||
<div class="font-semibold text-base">Android</div>
|
||||
<div v-if="app.size?.android" class="text-xs opacity-80">
|
||||
{{ app.size.android }}
|
||||
</div>
|
||||
</div>
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="app.downloads.h5"
|
||||
icon="i-heroicons-globe-alt"
|
||||
size="xl"
|
||||
block
|
||||
@click="handleDownload('h5')"
|
||||
>
|
||||
<div class="text-left w-full">
|
||||
<div class="font-semibold text-base">Web</div>
|
||||
<div class="text-xs opacity-80">
|
||||
PWA
|
||||
</div>
|
||||
</div>
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||
<UCard>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-primary-500">
|
||||
{{ app.stats.total.toLocaleString() }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
{{ locale === 'zh-CN' ? '总下载' : 'Total Downloads' }}
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
<UCard>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-primary-500">
|
||||
{{ app.stats.today.toLocaleString() }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
{{ locale === 'zh-CN' ? '今日下载' : 'Today' }}
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
<UCard>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-primary-500">
|
||||
{{ app.stats.ios.toLocaleString() }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
iOS
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
<UCard>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-primary-500">
|
||||
{{ app.stats.android.toLocaleString() }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
Android
|
||||
</div>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<UCard class="mb-8">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{{ locale === 'zh-CN' ? '应用介绍' : 'Description' }}
|
||||
</h3>
|
||||
<p class="text-gray-700 dark:text-gray-300 leading-relaxed">
|
||||
{{ app.description[locale as 'zh-CN' | 'en-US'] }}
|
||||
</p>
|
||||
</UCard>
|
||||
|
||||
<!-- What's New -->
|
||||
<UCard>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{{ locale === 'zh-CN' ? '更新内容' : "What's New" }}
|
||||
</h3>
|
||||
<ul class="space-y-3">
|
||||
<li
|
||||
v-for="(note, index) in app.releaseNotes[locale as 'zh-CN' | 'en-US']"
|
||||
:key="index"
|
||||
class="flex items-start gap-3 text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<span class="text-primary-500 mt-1 text-lg">•</span>
|
||||
<span>{{ note }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</UCard>
|
||||
</div>
|
||||
</UContainer>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user