feat: 添加应用配置和动画效果,优化首页和应用详情页面样式
This commit is contained in:
@@ -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 # 可选
|
||||
```
|
||||
7
packages/distribute/app.config.ts
Normal file
7
packages/distribute/app.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'blue',
|
||||
},
|
||||
},
|
||||
})
|
||||
123
packages/distribute/assets/css/animations.css
Normal file
123
packages/distribute/assets/css/animations.css
Normal 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;
|
||||
}
|
||||
@@ -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"
|
||||
@@ -8,10 +8,13 @@ export default defineNuxtConfig({
|
||||
|
||||
devtools: { enabled: true },
|
||||
|
||||
css: ['~/assets/css/main.css'],
|
||||
css: [
|
||||
'~/assets/css/main.css',
|
||||
'~/assets/css/animations.css',
|
||||
],
|
||||
|
||||
colorMode: {
|
||||
preference: 'system',
|
||||
preference: 'dark',
|
||||
},
|
||||
|
||||
i18n: {
|
||||
|
||||
@@ -56,8 +56,19 @@ useHead({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="app" class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<UContainer>
|
||||
<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">
|
||||
<!-- 科技感网格背景 -->
|
||||
<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 class="sticky top-0 z-50">
|
||||
<div class="flex items-center gap-4 py-4">
|
||||
@@ -65,20 +76,25 @@ useHead({
|
||||
icon="i-heroicons-arrow-left"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
class="hover:scale-110 transition-transform duration-300"
|
||||
@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 }}
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="py-8">
|
||||
<div class="py-8 animate-fade-in">
|
||||
<!-- 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 shrink-0 shadow-xl">
|
||||
{{ app.name.charAt(0) }}
|
||||
<div class="flex items-start gap-6 mb-8 group">
|
||||
<div class="relative">
|
||||
<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 class="flex-1">
|
||||
<h2 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||
@@ -97,120 +113,148 @@ useHead({
|
||||
|
||||
<!-- 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 v-if="app.downloads.ios" class="relative group">
|
||||
<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>
|
||||
<UButton
|
||||
icon="i-heroicons-device-phone-mobile"
|
||||
size="xl"
|
||||
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="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')"
|
||||
>
|
||||
</UButton>
|
||||
</div>
|
||||
<div v-if="app.downloads.android" class="relative group">
|
||||
<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>
|
||||
<UButton
|
||||
icon="i-heroicons-device-tablet"
|
||||
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="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
|
||||
</UButton>
|
||||
</div>
|
||||
<div v-if="app.downloads.h5" class="relative group">
|
||||
<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>
|
||||
<UButton
|
||||
icon="i-heroicons-globe-alt"
|
||||
size="xl"
|
||||
block
|
||||
class="relative transition-all duration-300 hover:shadow-2xl hover:shadow-blue-500/50 hover:-translate-y-2"
|
||||
@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>
|
||||
</div>
|
||||
</UButton>
|
||||
</UButton>
|
||||
</div>
|
||||
</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 class="relative group">
|
||||
<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>
|
||||
<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">
|
||||
<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 class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
{{ locale === 'zh-CN' ? '总下载' : 'Total Downloads' }}
|
||||
</UCard>
|
||||
</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>
|
||||
</UCard>
|
||||
<UCard>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-primary-500">
|
||||
{{ app.stats.today.toLocaleString() }}
|
||||
</UCard>
|
||||
</div>
|
||||
<div class="relative group">
|
||||
<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>
|
||||
<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">
|
||||
<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 class="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
{{ locale === 'zh-CN' ? '今日下载' : 'Today' }}
|
||||
</UCard>
|
||||
</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>
|
||||
</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>
|
||||
</UCard>
|
||||
</div>
|
||||
</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>
|
||||
<div class="relative group mb-8">
|
||||
<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>
|
||||
<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 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">
|
||||
{{ 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>
|
||||
</div>
|
||||
|
||||
<!-- 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 class="relative group">
|
||||
<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>
|
||||
<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 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">
|
||||
{{ 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 animate-fade-in-up"
|
||||
:style="`animation-delay: ${index * 0.1}s`"
|
||||
>
|
||||
<span class="text-primary-500 mt-1 text-lg animate-pulse">•</span>
|
||||
<span>{{ note }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</div>
|
||||
|
||||
@@ -84,16 +84,29 @@ useHead({
|
||||
</script>
|
||||
|
||||
<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 -->
|
||||
<UContainer>
|
||||
<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">
|
||||
<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">
|
||||
R
|
||||
<div class="flex items-center gap-3 group">
|
||||
<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">
|
||||
<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>
|
||||
<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') }}
|
||||
</h1>
|
||||
</div>
|
||||
@@ -117,17 +130,18 @@ useHead({
|
||||
</UContainer>
|
||||
|
||||
<!-- Main Content -->
|
||||
<UContainer class="py-8">
|
||||
<UContainer class="py-8 relative z-10">
|
||||
<!-- Search and Filter -->
|
||||
<div class="mb-8 space-y-4">
|
||||
<div class="mb-8 space-y-4 animate-fade-in">
|
||||
<!-- 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
|
||||
v-model="searchKeyword"
|
||||
icon="i-heroicons-magnifying-glass"
|
||||
size="xl"
|
||||
:placeholder="locale === 'zh-CN' ? '搜索应用...' : 'Search apps...'"
|
||||
class="w-full"
|
||||
class="w-full relative"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -146,17 +160,28 @@ useHead({
|
||||
|
||||
<!-- Apps Grid -->
|
||||
<div v-if="filteredApps.length > 0" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<UCard
|
||||
v-for="app in filteredApps"
|
||||
<div
|
||||
v-for="(app, index) in filteredApps"
|
||||
:key="app.id"
|
||||
class="cursor-pointer hover:shadow-xl transition-all hover:scale-[1.02]"
|
||||
@click="openAppDetail(app)"
|
||||
class="group relative animate-fade-in-up"
|
||||
:style="`animation-delay: ${index * 0.05}s`"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- App Icon -->
|
||||
<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) }}
|
||||
</div>
|
||||
<!-- 发光边框效果 -->
|
||||
<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>
|
||||
|
||||
<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 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 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
@@ -174,34 +199,38 @@ useHead({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Download Buttons -->
|
||||
<div class="grid grid-cols-3 gap-2 mt-4">
|
||||
<UButton
|
||||
v-if="app.downloads.ios"
|
||||
icon="i-heroicons-device-phone-mobile"
|
||||
label="iOS"
|
||||
size="sm"
|
||||
block
|
||||
@click.stop="handleDownload(app, 'ios')"
|
||||
/>
|
||||
<UButton
|
||||
v-if="app.downloads.android"
|
||||
icon="i-heroicons-device-tablet"
|
||||
label="Android"
|
||||
size="sm"
|
||||
block
|
||||
@click.stop="handleDownload(app, 'android')"
|
||||
/>
|
||||
<UButton
|
||||
v-if="app.downloads.h5"
|
||||
icon="i-heroicons-globe-alt"
|
||||
label="Web"
|
||||
size="sm"
|
||||
block
|
||||
@click.stop="handleDownload(app, 'h5')"
|
||||
/>
|
||||
</div>
|
||||
</UCard>
|
||||
<!-- 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="sm"
|
||||
block
|
||||
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"
|
||||
icon="i-heroicons-device-tablet"
|
||||
label="Android"
|
||||
size="sm"
|
||||
block
|
||||
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1"
|
||||
@click.stop="handleDownload(app, 'android')"
|
||||
/>
|
||||
<UButton
|
||||
v-if="app.downloads.h5"
|
||||
icon="i-heroicons-globe-alt"
|
||||
label="Web"
|
||||
size="sm"
|
||||
block
|
||||
class="transition-all duration-300 hover:shadow-lg hover:shadow-blue-500/50 hover:-translate-y-1"
|
||||
@click.stop="handleDownload(app, 'h5')"
|
||||
/>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
|
||||
Reference in New Issue
Block a user