256 lines
10 KiB
Vue
256 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import type { AppInfo } from "~/types";
|
|
|
|
import { apps as appsData, categories as categoriesData } from "~/data/apps";
|
|
|
|
const { t, locale, setLocale } = useI18n();
|
|
const colorMode = useColorMode();
|
|
|
|
// 直接使用数据文件
|
|
const apps = computed(() => appsData);
|
|
const categories = computed(() => categoriesData);
|
|
|
|
// 当前选中的分类
|
|
const selectedCategory = ref("all");
|
|
|
|
// 搜索关键词
|
|
const searchKeyword = ref("");
|
|
|
|
// 过滤后的应用列表
|
|
const filteredApps = computed(() => {
|
|
let result = apps.value;
|
|
|
|
// 按分类过滤
|
|
if (selectedCategory.value !== "all") {
|
|
result = result.filter(app => app.category === selectedCategory.value);
|
|
}
|
|
|
|
// 按搜索关键词过滤
|
|
if (searchKeyword.value.trim()) {
|
|
const keyword = searchKeyword.value.toLowerCase();
|
|
result = result.filter(app =>
|
|
app.name.toLowerCase().includes(keyword)
|
|
|| app.shortDescription[locale.value as "zh-CN" | "en-US"].toLowerCase().includes(keyword),
|
|
);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
// 切换语言
|
|
function toggleLanguage() {
|
|
setLocale(locale.value === "zh-CN" ? "en-US" : "zh-CN");
|
|
}
|
|
|
|
// 打开应用详情
|
|
function openAppDetail(app: AppInfo) {
|
|
navigateTo(`/apps/${app.id}`);
|
|
}
|
|
|
|
// 下载处理
|
|
async function handleDownload(app: AppInfo, type: "ios" | "android" | "h5", event?: MouseEvent | TouchEvent) {
|
|
const url = app.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.id },
|
|
});
|
|
}
|
|
|
|
const isDark = computed(() => colorMode.value === "dark");
|
|
|
|
// SEO
|
|
useHead({
|
|
title: locale.value === "zh-CN" ? "Riwa 应用商店" : "Riwa App Store",
|
|
meta: [
|
|
{
|
|
name: "description",
|
|
content: locale.value === "zh-CN"
|
|
? "下载 Riwa 系列应用,包括 Riwa 主应用、Riwa 钱包和 Riwa 聊天等"
|
|
: "Download Riwa apps including Riwa main app, Riwa Wallet, and Riwa Chat",
|
|
},
|
|
],
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-screen relative overflow-hidden bg-linear-to-br from-gray-50 via-blue-50/30 to-purple-50/30 dark:from-gray-950 dark:via-blue-950/20 dark:to-purple-950/20">
|
|
<!-- 科技感网格背景 -->
|
|
<div class="fixed inset-0 opacity-30 dark:opacity-20 pointer-events-none">
|
|
<div class="absolute inset-0 animate-pulse-subtle" style="background-image: linear-gradient(rgba(99, 102, 241, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(99, 102, 241, 0.1) 1px, transparent 1px); background-size: 50px 50px;" />
|
|
</div>
|
|
|
|
<!-- 动态发光球体背景 -->
|
|
<div class="fixed inset-0 pointer-events-none overflow-hidden">
|
|
<div class="absolute top-1/4 -left-48 w-96 h-96 bg-primary-500/20 rounded-full blur-3xl animate-breathe" />
|
|
<div class="absolute bottom-1/4 -right-48 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-breathe" style="animation-delay: 1s;" />
|
|
<div class="absolute top-1/2 left-1/2 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl animate-breathe" style="animation-delay: 2s;" />
|
|
<!-- 额外的动态光球 -->
|
|
<div class="absolute top-1/3 right-1/4 w-64 h-64 bg-cyan-500/15 rounded-full blur-2xl animate-float" style="animation-delay: 0.5s;" />
|
|
<div class="absolute bottom-1/3 left-1/3 w-72 h-72 bg-indigo-500/15 rounded-full blur-2xl animate-float" style="animation-delay: 1.5s;" />
|
|
</div>
|
|
|
|
<!-- Header -->
|
|
<UContainer class="relative z-10">
|
|
<header class="sticky top-0 z-50">
|
|
<div class="flex items-center justify-between py-4">
|
|
<div class="flex items-center gap-3 group">
|
|
<img src="/riwsan.png" alt="Riwa" class="w-10 h-10">
|
|
<h1 class="text-xl font-bold bg-linear-to-r from-gray-900 via-primary-600 to-purple-600 dark:from-white dark:via-primary-400 dark:to-purple-400 bg-clip-text text-transparent animate-gradient">
|
|
{{ t('appName') }}
|
|
</h1>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<UButton
|
|
:label="locale === 'zh-CN' ? 'EN' : '中文'"
|
|
color="neutral"
|
|
variant="ghost"
|
|
class="transition-all duration-300 active:scale-90 touch-manipulation"
|
|
@click="toggleLanguage"
|
|
/>
|
|
<UButton
|
|
:icon="isDark ? 'i-heroicons-sun' : 'i-heroicons-moon'"
|
|
color="neutral"
|
|
variant="ghost"
|
|
class="transition-all duration-300 active:scale-90 active:rotate-180 touch-manipulation"
|
|
@click="colorMode.preference = isDark ? 'light' : 'dark'"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
</UContainer>
|
|
|
|
<!-- Main Content -->
|
|
<UContainer>
|
|
<!-- PWA Install Banner -->
|
|
<div class="mb-6">
|
|
<PWAInstallBanner />
|
|
</div>
|
|
|
|
<!-- Search and Filter -->
|
|
<div class="mb-8 space-y-4">
|
|
<!-- Search -->
|
|
<div class="max-w-2xl mx-auto">
|
|
<UInput
|
|
v-model="searchKeyword"
|
|
icon="i-heroicons-magnifying-glass"
|
|
size="xl"
|
|
:placeholder="locale === 'zh-CN' ? '搜索应用...' : 'Search apps...'"
|
|
class="w-full"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Categories -->
|
|
<div class="flex items-center gap-2 overflow-x-auto pb-2">
|
|
<UButton
|
|
v-for="category in categories"
|
|
:key="category.id"
|
|
:label="category.name[locale as 'zh-CN' | 'en-US']"
|
|
:color="selectedCategory === category.id ? 'primary' : 'neutral'"
|
|
:variant="selectedCategory === category.id ? 'solid' : 'ghost'"
|
|
@click="selectedCategory = category.id"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Apps Grid -->
|
|
<div v-if="filteredApps.length > 0" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<div
|
|
v-for="(app, index) in filteredApps"
|
|
:key="app.id"
|
|
class="group relative animate-fade-in-up"
|
|
>
|
|
<UCard
|
|
class="cursor-pointer backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 duration-500 hover:shadow-2xl hover:shadow-primary-500/20 hover:-translate-y-2 hover:rotate-y-2 active:scale-95 active:shadow-lg active:shadow-primary-500/40 relative overflow-hidden"
|
|
@click="openAppDetail(app)"
|
|
>
|
|
<!-- 内部发光效果 -->
|
|
<div class="absolute inset-0 bg-linear-to-br from-primary-500/5 to-purple-500/5" />
|
|
|
|
<div class="flex items-start gap-4 relative z-10">
|
|
<!-- App Icon -->
|
|
<!-- 动态发光效果 -->
|
|
<img :src="app.icon" :alt="app.name" class="size-16 object-contain relative z-10 rounded-lg transition-all duration-300 group-active:scale-95 group-hover:-rotate-3">
|
|
|
|
<!-- App Info -->
|
|
<div class="flex-1 min-w-0">
|
|
<h3 class="font-bold text-lg text-gray-900 dark:text-white truncate group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors duration-300">
|
|
{{ app.name }}
|
|
</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 mt-1 transition-colors duration-300 group-hover:text-gray-700 dark:group-hover:text-gray-300">
|
|
{{ app.shortDescription[locale as 'zh-CN' | 'en-US'] }}
|
|
</p>
|
|
<div class="flex items-center gap-3 mt-3 text-xs text-gray-500 dark:text-gray-500 group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors duration-300">
|
|
<span class="animate-pulse-subtle">v{{ app.version }}</span>
|
|
<span>•</span>
|
|
<span class="font-medium">{{ app.stats.total.toLocaleString() }} {{ locale === 'zh-CN' ? '次下载' : 'downloads' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Download Buttons -->
|
|
<div class="grid grid-cols-3 gap-2 mt-4 relative z-10">
|
|
<UButton
|
|
v-if="app.downloads.ios"
|
|
icon="i-heroicons-device-phone-mobile"
|
|
label="iOS"
|
|
size="lg"
|
|
block
|
|
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-xl active:shadow-blue-500/60 relative overflow-hidden group/btn"
|
|
@click.stop="(e) => handleDownload(app, 'ios', e)"
|
|
/>
|
|
<UButton
|
|
v-if="app.downloads.android"
|
|
icon="i-heroicons-device-tablet"
|
|
label="Android"
|
|
size="lg"
|
|
block
|
|
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-xl active:shadow-blue-500/60 relative overflow-hidden group/btn"
|
|
@click.stop="(e) => handleDownload(app, 'android', e)"
|
|
/>
|
|
<UButton
|
|
v-if="app.downloads.h5"
|
|
icon="i-heroicons-globe-alt"
|
|
label="Web"
|
|
size="lg"
|
|
block
|
|
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1 active:scale-95 active:shadow-xl active:shadow-blue-500/60 relative overflow-hidden group/btn"
|
|
@click.stop="(e) => handleDownload(app, 'h5', e)"
|
|
/>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div v-else class="text-center py-20">
|
|
<UIcon name="i-heroicons-inbox" class="size-20 mx-auto text-gray-400 dark:text-gray-600 mb-4" />
|
|
<p class="text-gray-600 dark:text-gray-400">
|
|
{{ locale === 'zh-CN' ? '没有找到应用' : 'No apps found' }}
|
|
</p>
|
|
</div>
|
|
</UContainer>
|
|
|
|
<!-- Footer -->
|
|
<footer class="mt-20 py-8 border-t border-gray-200 dark:border-gray-800">
|
|
<UContainer>
|
|
<div class="text-center text-sm text-gray-600 dark:text-gray-500">
|
|
<p>© 2025 Riwa. All rights reserved.</p>
|
|
</div>
|
|
</UContainer>
|
|
</footer>
|
|
</div>
|
|
</template>
|