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 },
|
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: {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user