feat: 添加应用配置和动画效果,优化首页和应用详情页面样式

This commit is contained in:
2026-01-02 23:40:52 +07:00
parent cedb6cc1a5
commit 6d61715407
7 changed files with 350 additions and 396 deletions

View File

@@ -1,216 +0,0 @@
# Docker 部署指南
Riwa App 分发页使用预构建模式部署,无需在服务器上安装依赖,快速启动。
## 📋 前置要求
- 本地环境: Node.js 20+, pnpm
- 服务器环境: Docker, Docker Compose
## 🚀 快速部署
### 1. 本地构建
```bash
# 安装依赖
pnpm install
# 构建生产版本
pnpm run build
# 或使用部署脚本
./deploy.sh
```
构建完成后会生成 `.output/` 目录。
### 2. 上传到服务器
#### 方式一:上传整个项目(推荐)
```bash
# 使用 rsync 上传(排除 node_modules
rsync -avz --exclude 'node_modules' --exclude '.nuxt' --exclude '.git' \
./ user@your-server:/path/to/riwa-distribute/
# 或使用 scp
scp -r .output Dockerfile docker-compose.yml .dockerignore \
user@your-server:/path/to/riwa-distribute/
```
#### 方式二:使用 Git推荐用于生产环境
```bash
# 服务器上
git clone your-repo
cd riwa-distribute
# 本地构建后,将 .output 目录上传
rsync -avz .output/ user@your-server:/path/to/riwa-distribute/.output/
```
### 3. 服务器上启动
```bash
# SSH 登录到服务器
ssh user@your-server
# 进入项目目录
cd /path/to/riwa-distribute/
# 启动容器
docker-compose up -d
# 查看日志
docker-compose logs -f
# 查看运行状态
docker-compose ps
```
## 📊 访问应用
- 应用地址: http://your-server:3000
- API 健康检查: http://your-server:3000/api/version
## 🔧 常用命令
```bash
# 查看日志
docker-compose logs -f app
# 重启服务
docker-compose restart app
# 停止服务
docker-compose down
# 更新部署(本地构建后)
./deploy.sh
rsync -avz .output/ user@your-server:/path/to/riwa-distribute/.output/
docker-compose restart app
```
## 🎯 生产环境建议
### 使用 Nginx 反向代理
```nginx
server {
listen 80;
server_name distribute.riwa.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
### 配置 SSL
```bash
# 使用 Let's Encrypt
sudo certbot --nginx -d distribute.riwa.com
```
### 环境变量配置
在服务器上创建 `.env` 文件:
```env
NODE_ENV=production
HOST=0.0.0.0
PORT=3000
```
修改 `docker-compose.yml` 加载环境变量:
```yaml
services:
app:
env_file:
- .env
```
## 🔄 更新部署流程
```bash
# 1. 本地拉取最新代码
git pull origin main
# 2. 本地构建
pnpm run build
# 3. 上传构建产物
rsync -avz .output/ user@your-server:/path/to/riwa-distribute/.output/
# 4. 服务器重启
ssh user@your-server "cd /path/to/riwa-distribute && docker-compose restart app"
```
## 📈 监控和日志
```bash
# 实时日志
docker-compose logs -f app
# 查看最近 100 行日志
docker-compose logs --tail=100 app
# 查看容器状态
docker stats riwa-distribute
# 健康检查
curl http://localhost:3000/api/version
```
## 🐛 故障排查
### 容器无法启动
```bash
# 查看详细日志
docker-compose logs app
# 检查构建产物
ls -la .output/
# 重新构建镜像
docker-compose build --no-cache app
```
### 端口被占用
修改 `docker-compose.yml` 中的端口映射:
```yaml
ports:
- "8080:3000" # 改为其他端口
```
## 🔐 安全建议
1. 使用非 root 用户运行容器
2. 配置防火墙规则
3. 启用 HTTPS
4. 定期更新 Docker 镜像
5. 使用环境变量管理敏感信息
## 📦 最小部署文件
如果只想上传最少文件,只需要:
```
.output/ # 构建产物(必需)
Dockerfile # Docker 配置(必需)
docker-compose.yml # Docker Compose 配置(必需)
.dockerignore # 可选
```

View File

@@ -0,0 +1,7 @@
export default defineAppConfig({
ui: {
colors: {
primary: 'blue',
},
},
})

View File

@@ -0,0 +1,123 @@
/* 渐入动画 */
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* 渐入上移动画 */
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 渐入下移动画 */
@keyframes fade-in-down {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 缩放动画 */
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 应用动画类 */
.animate-fade-in {
animation: fade-in 0.6s ease-out forwards;
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out forwards;
opacity: 0;
}
.animate-fade-in-down {
animation: fade-in-down 0.6s ease-out forwards;
}
.animate-scale-in {
animation: scale-in 0.6s ease-out forwards;
}
/* 发光脉冲效果 */
@keyframes glow-pulse {
0%, 100% {
box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);
}
50% {
box-shadow: 0 0 40px rgba(99, 102, 241, 0.6);
}
}
.animate-glow-pulse {
animation: glow-pulse 2s ease-in-out infinite;
}
/* 渐变背景动画 */
@keyframes gradient-shift {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.animate-gradient {
background-size: 200% 200%;
animation: gradient-shift 8s ease infinite;
}
/* 悬浮动画 */
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
/* 旋转发光效果 */
@keyframes rotate-glow {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-rotate-glow {
animation: rotate-glow 10s linear infinite;
}

View File

@@ -1,36 +0,0 @@
#!/bin/bash
# Riwa App 分发页部署脚本
set -e
echo "🚀 开始构建 Riwa App 分发页..."
# 1. 安装依赖(如果需要)
if [ ! -d "node_modules" ]; then
echo "📦 安装依赖..."
pnpm install
fi
# 2. 构建应用
echo "🔨 构建生产版本..."
pnpm run build
# 3. 检查构建产物
if [ ! -d ".output" ]; then
echo "❌ 构建失败:.output 目录不存在"
exit 1
fi
echo "✅ 构建完成!"
echo ""
echo "📦 构建产物位于: .output/"
echo ""
echo "🚢 部署步骤:"
echo "1. 将整个项目文件夹上传到服务器"
echo "2. 在服务器上运行: docker-compose up -d"
echo ""
echo "或者只上传必要文件:"
echo "1. 上传 .output/ 目录"
echo "2. 上传 Dockerfile 和 docker-compose.yml"
echo "3. 在服务器上运行: docker-compose up -d"

View File

@@ -8,10 +8,13 @@ export default defineNuxtConfig({
devtools: { enabled: true }, devtools: { enabled: true },
css: ['~/assets/css/main.css'], css: [
'~/assets/css/main.css',
'~/assets/css/animations.css',
],
colorMode: { colorMode: {
preference: 'system', preference: 'dark',
}, },
i18n: { i18n: {

View File

@@ -56,8 +56,19 @@ useHead({
</script> </script>
<template> <template>
<div v-if="app" class="min-h-screen bg-gray-50 dark:bg-gray-900"> <div v-if="app" class="min-h-screen relative overflow-hidden bg-gradient-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">
<UContainer> <!-- 科技感网格背景 -->
<div class="fixed inset-0 opacity-30 dark:opacity-20 pointer-events-none">
<div class="absolute inset-0" 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>
<!-- 动态发光球体背景 -->
<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-pulse"></div>
<div class="absolute bottom-1/4 -right-48 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-pulse" style="animation-delay: 1s;"></div>
</div>
<UContainer class="relative z-10">
<!-- Header --> <!-- Header -->
<header class="sticky top-0 z-50"> <header class="sticky top-0 z-50">
<div class="flex items-center gap-4 py-4"> <div class="flex items-center gap-4 py-4">
@@ -65,20 +76,25 @@ useHead({
icon="i-heroicons-arrow-left" icon="i-heroicons-arrow-left"
color="neutral" color="neutral"
variant="ghost" variant="ghost"
class="hover:scale-110 transition-transform duration-300"
@click="goBack" @click="goBack"
/> />
<h1 class="text-xl font-bold text-gray-900 dark:text-white"> <h1 class="text-xl font-bold bg-gradient-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">
{{ app.name }} {{ app.name }}
</h1> </h1>
</div> </div>
</header> </header>
<!-- Main Content --> <!-- Main Content -->
<div class="py-8"> <div class="py-8 animate-fade-in">
<!-- App Header --> <!-- App Header -->
<div class="flex items-start gap-6 mb-8"> <div class="flex items-start gap-6 mb-8 group">
<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 shrink-0 shadow-xl"> <div class="relative">
{{ app.name.charAt(0) }} <div class="absolute -inset-2 bg-gradient-to-r from-primary-500 via-purple-500 to-blue-500 rounded-3xl opacity-50 blur-xl group-hover:opacity-100 transition-all duration-500 animate-pulse"></div>
<div class="size-24 rounded-3xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-4xl shrink-0 shadow-2xl shadow-blue-500/50 relative overflow-hidden group-hover:scale-110 transition-all duration-500">
<div class="absolute inset-0 bg-gradient-to-tr from-white/0 via-white/30 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700"></div>
<span class="relative z-10">{{ app.name.charAt(0) }}</span>
</div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2"> <h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
@@ -97,120 +113,148 @@ useHead({
<!-- Download Buttons --> <!-- Download Buttons -->
<div class="grid md:grid-cols-3 gap-4 mb-8"> <div class="grid md:grid-cols-3 gap-4 mb-8">
<UButton <div v-if="app.downloads.ios" class="relative group">
v-if="app.downloads.ios" <div class="absolute -inset-1 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl opacity-0 group-hover:opacity-100 blur transition-all duration-500"></div>
icon="i-heroicons-device-phone-mobile" <UButton
size="xl" icon="i-heroicons-device-phone-mobile"
block size="xl"
@click="handleDownload('ios')" block
> class="relative transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/50 hover:-translate-y-2"
@click="handleDownload('ios')"
>
<div class="text-left w-full"> <div class="text-left w-full">
<div class="font-semibold text-base">iOS</div> <div class="font-semibold text-base">iOS</div>
<div v-if="app.size?.ios" class="text-xs opacity-80"> <div v-if="app.size?.ios" class="text-xs opacity-80">
{{ app.size.ios }} {{ app.size.ios }}
</div> </div>
</div> </div>
</UButton> </UButton>
<UButton </div>
v-if="app.downloads.android" <div v-if="app.downloads.android" class="relative group">
icon="i-heroicons-device-tablet" <div class="absolute -inset-1 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl opacity-0 group-hover:opacity-100 blur transition-all duration-500"></div>
size="xl" <UButton
block icon="i-heroicons-device-tablet"
@click="handleDownload('android')" size="xl"
> block
class="relative transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/50 hover:-translate-y-2"
@click="handleDownload('android')"
>
<div class="text-left w-full"> <div class="text-left w-full">
<div class="font-semibold text-base">Android</div> <div class="font-semibold text-base">Android</div>
<div v-if="app.size?.android" class="text-xs opacity-80"> <div v-if="app.size?.android" class="text-xs opacity-80">
{{ app.size.android }} {{ app.size.android }}
</div> </div>
</div> </div>
</UButton> </UButton>
<UButton </div>
v-if="app.downloads.h5" <div v-if="app.downloads.h5" class="relative group">
icon="i-heroicons-globe-alt" <div class="absolute -inset-1 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl opacity-0 group-hover:opacity-100 blur transition-all duration-500"></div>
size="xl" <UButton
block icon="i-heroicons-globe-alt"
@click="handleDownload('h5')" size="xl"
> block
<div class="text-left w-full"> class="relative transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/50 hover:-translate-y-2"
<div class="font-semibold text-base">Web</div> @click="handleDownload('h5')"
<div class="text-xs opacity-80"> >
PWA <div class="text-left w-full">
<div class="font-semibold text-base">Web</div>
<div class="text-xs opacity-80">
PWA
</div>
</div> </div>
</div> </UButton>
</UButton> </div>
</div> </div>
<!-- Stats --> <!-- Stats -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<UCard> <div class="relative group">
<div class="text-center"> <div class="absolute -inset-0.5 bg-gradient-to-r from-primary-500 to-purple-500 rounded-xl opacity-0 group-hover:opacity-100 blur transition-all duration-500"></div>
<div class="text-3xl font-bold text-primary-500"> <UCard class="relative backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 hover:shadow-xl hover:shadow-primary-500/20 transition-all duration-500 hover:-translate-y-1">
{{ app.stats.total.toLocaleString() }} <div class="text-center">
<div class="text-3xl font-bold bg-gradient-to-r from-primary-500 to-purple-500 bg-clip-text text-transparent">
{{ 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> </div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2"> </UCard>
{{ locale === 'zh-CN' ? '总下载' : 'Total Downloads' }} </div>
<div class="relative group">
<div class="absolute -inset-0.5 bg-gradient-to-r from-purple-500 to-pink-500 rounded-xl opacity-0 group-hover:opacity-100 blur transition-all duration-500"></div>
<UCard class="relative backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 hover:shadow-xl hover:shadow-purple-500/20 transition-all duration-500 hover:-translate-y-1">
<div class="text-center">
<div class="text-3xl font-bold bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent">
{{ app.stats.today.toLocaleString() }}
</div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2">
{{ locale === 'zh-CN' ? '今日下载' : 'Today' }}
</div>
</div> </div>
</div> </UCard>
</UCard> </div>
<UCard> <div class="relative group">
<div class="text-center"> <div class="absolute -inset-0.5 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl opacity-0 group-hover:opacity-100 blur transition-all duration-500"></div>
<div class="text-3xl font-bold text-primary-500"> <UCard class="relative backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 hover:shadow-xl hover:shadow-blue-500/20 transition-all duration-500 hover:-translate-y-1">
{{ app.stats.today.toLocaleString() }} <div class="text-center">
<div class="text-3xl font-bold bg-gradient-to-r from-blue-500 to-cyan-500 bg-clip-text text-transparent">
{{ app.stats.ios.toLocaleString() }}
</div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2">
iOS
</div>
</div> </div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2"> </UCard>
{{ locale === 'zh-CN' ? '今日下载' : 'Today' }} </div>
<div class="relative group">
<div class="absolute -inset-0.5 bg-gradient-to-r from-indigo-500 to-purple-500 rounded-xl opacity-0 group-hover:opacity-100 blur transition-all duration-500"></div>
<UCard class="relative backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 hover:shadow-xl hover:shadow-indigo-500/20 transition-all duration-500 hover:-translate-y-1">
<div class="text-center">
<div class="text-3xl font-bold bg-gradient-to-r from-indigo-500 to-purple-500 bg-clip-text text-transparent">
{{ app.stats.android.toLocaleString() }}
</div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-2">
Android
</div>
</div> </div>
</div> </UCard>
</UCard> </div>
<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> </div>
<!-- Description --> <!-- Description -->
<UCard class="mb-8"> <div class="relative group mb-8">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4"> <div class="absolute -inset-0.5 bg-gradient-to-r from-primary-500 via-purple-500 to-blue-500 rounded-xl opacity-0 group-hover:opacity-30 blur transition-all duration-500"></div>
{{ locale === 'zh-CN' ? '应用介绍' : 'Description' }} <UCard class="relative backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 hover:shadow-xl hover:shadow-primary-500/10 transition-all duration-500">
</h3> <h3 class="text-xl font-semibold bg-gradient-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 mb-4">
<p class="text-gray-700 dark:text-gray-300 leading-relaxed"> {{ locale === 'zh-CN' ? '应用介绍' : 'Description' }}
{{ app.description[locale as 'zh-CN' | 'en-US'] }} </h3>
</p> <p class="text-gray-700 dark:text-gray-300 leading-relaxed">
</UCard> {{ app.description[locale as 'zh-CN' | 'en-US'] }}
</p>
</UCard>
</div>
<!-- What's New --> <!-- What's New -->
<UCard> <div class="relative group">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4"> <div class="absolute -inset-0.5 bg-gradient-to-r from-purple-500 via-pink-500 to-rose-500 rounded-xl opacity-0 group-hover:opacity-30 blur transition-all duration-500"></div>
{{ locale === 'zh-CN' ? '更新内容' : "What's New" }} <UCard class="relative backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 hover:shadow-xl hover:shadow-purple-500/10 transition-all duration-500">
</h3> <h3 class="text-xl font-semibold bg-gradient-to-r from-gray-900 via-purple-600 to-pink-600 dark:from-white dark:via-purple-400 dark:to-pink-400 bg-clip-text text-transparent mb-4">
<ul class="space-y-3"> {{ locale === 'zh-CN' ? '更新内容' : "What's New" }}
<li </h3>
v-for="(note, index) in app.releaseNotes[locale as 'zh-CN' | 'en-US']" <ul class="space-y-3">
:key="index" <li
class="flex items-start gap-3 text-gray-700 dark:text-gray-300" v-for="(note, index) in app.releaseNotes[locale as 'zh-CN' | 'en-US']"
> :key="index"
<span class="text-primary-500 mt-1 text-lg"></span> class="flex items-start gap-3 text-gray-700 dark:text-gray-300 animate-fade-in-up"
<span>{{ note }}</span> :style="`animation-delay: ${index * 0.1}s`"
</li> >
</ul> <span class="text-primary-500 mt-1 text-lg animate-pulse"></span>
</UCard> <span>{{ note }}</span>
</li>
</ul>
</UCard>
</div>
</div> </div>
</UContainer> </UContainer>
</div> </div>

View File

@@ -84,16 +84,29 @@ useHead({
</script> </script>
<template> <template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900"> <div class="min-h-screen relative overflow-hidden bg-gradient-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" 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>
<!-- 动态发光球体背景 -->
<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-pulse"></div>
<div class="absolute bottom-1/4 -right-48 w-96 h-96 bg-purple-500/20 rounded-full blur-3xl animate-pulse" style="animation-delay: 1s;"></div>
<div class="absolute top-1/2 left-1/2 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl animate-pulse" style="animation-delay: 2s;"></div>
</div>
<!-- Header --> <!-- Header -->
<UContainer> <UContainer class="relative z-10">
<header class="sticky top-0 z-50"> <header class="sticky top-0 z-50">
<div class="flex items-center justify-between py-4"> <div class="flex items-center justify-between py-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3 group">
<div class="size-10 rounded-xl bg-linear-to-br from-primary-500 to-primary-600 flex items-center justify-center text-white font-bold text-xl"> <div class="size-10 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-xl relative overflow-hidden transition-all duration-300 group-hover:scale-110">
R <div class="absolute inset-0 bg-gradient-to-tr from-white/0 via-white/20 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700"></div>
<span class="relative z-10">R</span>
</div> </div>
<h1 class="text-xl font-bold text-gray-900 dark:text-white"> <h1 class="text-xl font-bold bg-gradient-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">
{{ t('appName') }} {{ t('appName') }}
</h1> </h1>
</div> </div>
@@ -117,17 +130,18 @@ useHead({
</UContainer> </UContainer>
<!-- Main Content --> <!-- Main Content -->
<UContainer class="py-8"> <UContainer class="py-8 relative z-10">
<!-- Search and Filter --> <!-- Search and Filter -->
<div class="mb-8 space-y-4"> <div class="mb-8 space-y-4 animate-fade-in">
<!-- Search --> <!-- Search -->
<div class="relative max-w-2xl mx-auto"> <div class="relative max-w-2xl mx-auto group">
<div class="absolute -inset-1 bg-gradient-to-r from-primary-500 via-purple-500 to-blue-500 rounded-xl opacity-0 group-hover:opacity-30 blur transition-all duration-500"></div>
<UInput <UInput
v-model="searchKeyword" v-model="searchKeyword"
icon="i-heroicons-magnifying-glass" icon="i-heroicons-magnifying-glass"
size="xl" size="xl"
:placeholder="locale === 'zh-CN' ? '搜索应用...' : 'Search apps...'" :placeholder="locale === 'zh-CN' ? '搜索应用...' : 'Search apps...'"
class="w-full" class="w-full relative"
/> />
</div> </div>
@@ -146,17 +160,28 @@ useHead({
<!-- Apps Grid --> <!-- Apps Grid -->
<div v-if="filteredApps.length > 0" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div v-if="filteredApps.length > 0" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<UCard <div
v-for="app in filteredApps" v-for="(app, index) in filteredApps"
:key="app.id" :key="app.id"
class="cursor-pointer hover:shadow-xl transition-all hover:scale-[1.02]" class="group relative animate-fade-in-up"
@click="openAppDetail(app)" :style="`animation-delay: ${index * 0.05}s`"
> >
<div class="flex items-start gap-4"> <!-- 发光边框效果 -->
<!-- App Icon --> <div class="absolute -inset-0.5 bg-gradient-to-r from-primary-500 via-purple-500 to-blue-500 rounded-2xl opacity-0 group-hover:opacity-100 blur transition-all duration-500 group-hover:blur-md"></div>
<div class="size-16 rounded-2xl bg-linear-to-br from-primary-500 to-primary-600 flex items-center justify-center text-white font-bold text-2xl shrink-0">
{{ app.name.charAt(0) }} <UCard
</div> class="cursor-pointer backdrop-blur-sm bg-white/90 dark:bg-gray-900/90 border border-gray-200/50 dark:border-gray-800/50 transition-all duration-500 hover:shadow-2xl hover:shadow-primary-500/20 hover:-translate-y-2 relative overflow-hidden"
@click="openAppDetail(app)"
>
<!-- 内部发光效果 -->
<div class="absolute inset-0 bg-gradient-to-br from-primary-500/5 via-transparent to-purple-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
<div class="flex items-start gap-4 relative z-10">
<!-- App Icon -->
<div class="size-16 rounded-2xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white font-bold text-2xl shrink-0 shadow-lg shadow-blue-500/50 relative overflow-hidden group-hover:shadow-2xl group-hover:shadow-blue-500/60 transition-all duration-500 group-hover:scale-110 group-hover:rotate-3">
<div class="absolute inset-0 bg-gradient-to-tr from-white/0 via-white/30 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700"></div>
<span class="relative z-10">{{ app.name.charAt(0) }}</span>
</div>
<!-- App Info --> <!-- App Info -->
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
@@ -174,34 +199,38 @@ useHead({
</div> </div>
</div> </div>
<!-- Download Buttons --> <!-- Download Buttons -->
<div class="grid grid-cols-3 gap-2 mt-4"> <div class="grid grid-cols-3 gap-2 mt-4 relative z-10">
<UButton <UButton
v-if="app.downloads.ios" v-if="app.downloads.ios"
icon="i-heroicons-device-phone-mobile" icon="i-heroicons-device-phone-mobile"
label="iOS" label="iOS"
size="sm" size="sm"
block block
@click.stop="handleDownload(app, 'ios')" class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1"
/> @click.stop="handleDownload(app, 'ios')"
<UButton />
v-if="app.downloads.android" <UButton
icon="i-heroicons-device-tablet" v-if="app.downloads.android"
label="Android" icon="i-heroicons-device-tablet"
size="sm" label="Android"
block size="sm"
@click.stop="handleDownload(app, 'android')" block
/> class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1"
<UButton @click.stop="handleDownload(app, 'android')"
v-if="app.downloads.h5" />
icon="i-heroicons-globe-alt" <UButton
label="Web" v-if="app.downloads.h5"
size="sm" icon="i-heroicons-globe-alt"
block label="Web"
@click.stop="handleDownload(app, 'h5')" size="sm"
/> block
</div> class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1"
</UCard> @click.stop="handleDownload(app, 'h5')"
/>
</div>
</UCard>
</div>
</div> </div>
<!-- Empty State --> <!-- Empty State -->