# Day 12 - iOS 兼容与移动端 UI 优化

**日期**：2026-01-28

---

## 🔐 Axios 全局拦截器优化

### 背景
统一处理 API 请求的认证失败场景，避免各页面重复处理 401/403 错误。

### 实现 (`frontend/src/shared/api/axios.ts`)

```typescript
import axios from 'axios';

// 动态获取 API 地址：服务端使用 localhost，客户端使用当前域名
const API_BASE = typeof window === 'undefined'
    ? 'http://localhost:8006'
    : '';

// 防止重复跳转
let isRedirecting = false;

const api = axios.create({
    baseURL: API_BASE,
    withCredentials: true, // 自动携带 HttpOnly cookie
    headers: { 'Content-Type': 'application/json' },
});

// 响应拦截器 - 全局处理 401/403
api.interceptors.response.use(
    (response) => response,
    async (error) => {
        const status = error.response?.status;

        if ((status === 401 || status === 403) && !isRedirecting) {
            isRedirecting = true;

            // 调用 logout API 清除 HttpOnly cookie
            try {
                await fetch('/api/auth/logout', { method: 'POST' });
            } catch (e) { /* 忽略 */ }

            // 跳转登录页
            if (typeof window !== 'undefined') {
                window.location.replace('/login');
            }
        }
        return Promise.reject(error);
    }
);

export default api;
```

### 关键特性
- ✅ **自动携带 Cookie**: `withCredentials: true` 确保 HttpOnly JWT cookie 被发送
- ✅ **401/403 自动跳转**: 认证失败时自动清理并跳转登录页
- ✅ **防重复跳转**: `isRedirecting` 标志避免多个请求同时触发跳转
- ✅ **SSR 兼容**: 服务端渲染时使用 `localhost`，客户端使用相对路径

---

## 🔧 iOS Safari 安全区域白边修复

### 问题描述
iPhone Safari 浏览器底部和顶部显示白色区域，安卓正常。原因是 iOS Safari 有安全区域 (Safe Area)，页面背景没有延伸到该区域。

### 根本原因
1. 缺少 `viewport-fit=cover` 配置
2. `min-h-screen` (100vh) 在 iOS Safari 中不包含安全区域
3. 背景渐变在页面 div 上，而非 body 上，导致安全区域显示纯色

### 解决方案

#### 1. 添加 viewport 配置 (`layout.tsx`)
```typescript
export const viewport: Viewport = {
  width: 'device-width',
  initialScale: 1,
  viewportFit: 'cover',    // 允许内容延伸到安全区域
  themeColor: '#0f172a',   // 顶部状态栏颜色
};
```

#### 2. 统一渐变背景到 body (`layout.tsx`)
```tsx
<html lang="en" style={{ backgroundColor: '#0f172a' }}>
  <body
    style={{
      margin: 0,
      minHeight: '100dvh',
      background: 'linear-gradient(to bottom, #0f172a 0%, #0f172a 5%, #581c87 50%, #0f172a 95%, #0f172a 100%)',
    }}
  >
    {children}
  </body>
</html>
```

#### 3. CSS 安全区域支持 (`globals.css`)
```css
html {
  background-color: #0f172a !important;
  min-height: 100%;
}

body {
  margin: 0 !important;
  min-height: 100dvh;
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}
```

#### 4. 移除页面独立渐变背景
各页面的根 div 移除 `bg-gradient-to-br` 类，统一使用 body 渐变：
- `page.tsx`
- `login/page.tsx`
- `publish/page.tsx`
- `admin/page.tsx`
- `register/page.tsx`

### 结果
- ✅ 顶部状态栏颜色与页面一致 (themeColor)
- ✅ 底部安全区域颜色与渐变边缘一致
- ✅ 消除分层感，背景统一

---

## 📱 移动端 Header 响应式优化

### 问题描述
移动端顶部导航按钮（视频生成、发布管理、退出）过于拥挤，文字换行显示。

### 解决方案

#### 首页 Header (`page.tsx`)
```tsx
<header className="border-b border-white/10 bg-black/20 backdrop-blur-sm">
  <div className="max-w-6xl mx-auto px-4 sm:px-6 py-3 sm:py-4 flex items-center justify-between">
    <Link href="/" className="text-xl sm:text-2xl font-bold ...">
      <span className="text-3xl sm:text-4xl">🎬</span>
      ViGent
    </Link>
    <div className="flex items-center gap-1 sm:gap-4">
      <span className="px-2 sm:px-4 py-1 sm:py-2 text-sm sm:text-base ...">
        视频生成
      </span>
      <!-- 其他按钮同样处理 -->
    </div>
  </div>
</header>
```

#### 发布管理页 Header (`publish/page.tsx`)
同步应用相同的响应式类名。

### 关键改动
| 属性 | 移动端 | 桌面端 |
|------|--------|--------|
| 容器内边距 | `px-4 py-3` | `px-6 py-4` |
| 按钮间距 | `gap-1` | `gap-4` |
| 按钮内边距 | `px-2 py-1` | `px-4 py-2` |
| 字体大小 | `text-sm` | `text-base` |
| Logo 大小 | `text-xl` + `text-3xl` | `text-2xl` + `text-4xl` |

### 结果
- ✅ 移动端按钮紧凑排列，不再换行
- ✅ 桌面端保持原有宽松布局

---

## 🚀 发布页面 UI 重构

### 问题描述
原有设计将"发布时间"选项放在表单中，用户可能误选"定时发布"但忘记设置时间。

### 解决方案
将"一键发布"按钮改为两个独立按钮：
- **立即发布** (绿色，占 3/4 宽度) - 主要操作
- **定时** (占 1/4 宽度) - 点击展开时间选择器

#### 新布局 (`publish/page.tsx`)
```tsx
{/* 发布按钮区域 */}
<div className="space-y-3">
  <div className="flex gap-3">
    {/* 立即发布 - 占 3/4 */}
    <button
      onClick={() => { setScheduleMode("now"); handlePublish(); }}
      className="flex-[3] py-4 rounded-xl font-bold text-lg bg-gradient-to-r from-green-600 to-teal-600 ..."
    >
      🚀 立即发布
    </button>

    {/* 定时发布 - 占 1/4 */}
    <button
      onClick={() => setScheduleMode(scheduleMode === "scheduled" ? "now" : "scheduled")}
      className="flex-1 py-4 rounded-xl font-bold text-base ..."
    >
      ⏰ 定时
    </button>
  </div>

  {/* 定时发布时间选择器 (展开时显示) */}
  {scheduleMode === "scheduled" && (
    <div className="flex gap-3 items-center">
      <input type="datetime-local" ... />
      <button>确认定时</button>
    </div>
  )}
</div>
```

### 结果
- ✅ 主操作（立即发布）更醒目
- ✅ 定时发布需要二次确认，防止误触
- ✅ 从表单区域移除发布时间选项，界面更简洁

---

## 🛤️ 后续优化项

### 后端定时发布 (待实现)
**当前状态**：定时发布使用平台端定时（在各平台设置发布时间）

**优化方向**：改为后端定时任务
- 使用 APScheduler 实现任务调度
- 存储定时任务到数据库
- 到时间后端自动触发发布 API
- 支持查看/取消定时任务

**优势**：
- 统一逻辑，不依赖平台定时 UI
- 更灵活，可管理定时任务
- 平台页面更新不影响功能

---

## 🤖 Qwen3-TTS 0.6B 声音克隆部署

### 背景
为实现用户自定义声音克隆功能，部署 Qwen3-TTS 0.6B-Base 模型，支持 3 秒参考音频快速克隆。

### GPU 分配
| GPU | 服务 | 模型 |
|-----|------|------|
| GPU0 | Qwen3-TTS | 0.6B-Base (声音克隆) |
| GPU1 | LatentSync | 1.6 (唇形同步) |

### 部署步骤

#### 1. 克隆仓库
```bash
cd /home/rongye/ProgramFiles/ViGent2/models
git clone https://github.com/QwenLM/Qwen3-TTS.git
```

#### 2. 创建 conda 环境
```bash
conda create -n qwen-tts python=3.10 -y
conda activate qwen-tts
```

#### 3. 安装依赖
```bash
cd Qwen3-TTS
pip install -e .
conda install -y -c conda-forge sox  # 音频处理依赖
```

#### 4. 下载模型权重 (使用 ModelScope，国内更快)
```bash
pip install modelscope
# Tokenizer (651MB)
modelscope download --model Qwen/Qwen3-TTS-Tokenizer-12Hz --local_dir ./checkpoints/Tokenizer
# 0.6B-Base 模型 (2.4GB)
modelscope download --model Qwen/Qwen3-TTS-12Hz-0.6B-Base --local_dir ./checkpoints/0.6B-Base
```

#### 5. 测试推理
```python
# test_inference.py
import torch
import soundfile as sf
from qwen_tts import Qwen3TTSModel

model = Qwen3TTSModel.from_pretrained(
    "./checkpoints/0.6B-Base",
    device_map="cuda:0",
    dtype=torch.bfloat16,
)

wavs, sr = model.generate_voice_clone(
    text="测试文本",
    language="Chinese",
    ref_audio="./examples/myvoice.wav",
    ref_text="参考音频的文字内容",
)
sf.write("output.wav", wavs[0], sr)
```

### 测试结果
- ✅ 模型加载成功 (GPU0)
- ✅ 声音克隆推理成功
- ✅ 输出音频 24000Hz，质量良好

### 目录结构
```
models/Qwen3-TTS/
├── checkpoints/
│   ├── Tokenizer/     # 651MB
│   └── 0.6B-Base/     # 2.4GB
├── qwen_tts/          # 源码
├── examples/
│   └── myvoice.wav    # 参考音频
└── test_inference.py  # 测试脚本
```

---

## 📁 今日修改文件清单

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `frontend/src/shared/api/axios.ts` | 修改 | Axios 全局拦截器 (401/403 自动跳转) |
| `frontend/src/app/layout.tsx` | 修改 | viewport 配置 + body 渐变背景 |
| `frontend/src/app/globals.css` | 修改 | 安全区域 CSS 支持 |
| `frontend/src/app/page.tsx` | 修改 | 移除独立渐变 + Header 响应式 |
| `frontend/src/app/login/page.tsx` | 修改 | 移除独立渐变 |
| `frontend/src/app/publish/page.tsx` | 修改 | Header 响应式 + 发布按钮重构 |
| `frontend/src/app/admin/page.tsx` | 修改 | 移除独立渐变 |
| `frontend/src/app/register/page.tsx` | 修改 | 移除独立渐变 |
| `README.md` | 修改 | 添加 "iOS/Android 移动端适配" 功能说明 |
| `Docs/FRONTEND_DEV.md` | 修改 | iOS Safari 安全区域兼容规范 + 移动端响应式规则 |
| `models/Qwen3-TTS/` | 新增 | Qwen3-TTS 声音克隆模型部署 |
| `Docs/QWEN3_TTS_DEPLOY.md` | 新增 | Qwen3-TTS 部署指南 |

---

## 🔗 相关文档

- [TASK_COMPLETE.md](../TASK_COMPLETE.md) - 任务总览
- [Day11.md](./Day11.md) - 上传架构重构
- [QWEN3_TTS_DEPLOY.md](../QWEN3_TTS_DEPLOY.md) - Qwen3-TTS 部署指南
