diff --git a/docs/windows-dev-setup.md b/docs/windows-dev-setup.md new file mode 100644 index 0000000..774f1e8 --- /dev/null +++ b/docs/windows-dev-setup.md @@ -0,0 +1,192 @@ +# Windows 11 开发环境搭建指南 + +> 适用场景:全新重装系统后的 Windows 11,国内网络环境。 + +--- + +## 前置说明 + +- **WebView2**:Windows 11 自带,无需安装。 +- **WSL2**:Windows 11 默认支持,Docker Desktop 会自动启用。 +- **全程使用 cmd + 官网 .exe 安装包**,不依赖 PowerShell 脚本。 + +--- + +## 一、基础工具安装(图形界面,双击下一步) + +按顺序安装,装完一个再装下一个。 + +### 1. Git + +- 下载:https://git-scm.com/download/win +- 安装:全默认,一路 Next。 + +### 2. Node.js 22 LTS + +- 下载:https://nodejs.org/ +- 安装:勾选 **"Automatically install necessary tools"**(会自动装 Python 2.7 等构建工具)。 + +### 3. Visual Studio Build Tools 2022 + +- 下载:https://aka.ms/vs/17/release/vs_BuildTools.exe +- 安装:只勾选 **"使用 C++ 的桌面开发"**(约 8GB),其他全取消。 + +### 4. Rust + +- 下载:https://rustup.rs/ → 点击 `rustup-init.exe (64-bit)` +- 安装:选 **1) Proceed with default installation**(默认 MSVC 工具链)。 + +--- + +## 二、国内镜像配置(cmd 执行) + +打开 **cmd(Win+R → cmd)**,逐行执行: + +```cmd +:: ========== npm 镜像 ========== +npm config set registry https://registry.npmmirror.com + +:: ========== Rust 镜像 ========== +mkdir "%USERPROFILE%\.cargo" 2>nul + +echo [source.crates-io] > "%USERPROFILE%\.cargo\config.toml" +echo replace-with = 'ustc' >> "%USERPROFILE%\.cargo\config.toml" +echo [source.ustc] >> "%USERPROFILE%\.cargo\config.toml" +echo registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/" >> "%USERPROFILE%\.cargo\config.toml" + +setx RUSTUP_UPDATE_ROOT https://mirrors.ustc.edu.cn/rust-static/rustup +setx RUSTUP_DIST_SERVER https://mirrors.ustc.edu.cn/rust-static + +:: ========== Python 镜像(预留,方案 B 用到) ========== +pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple +pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn + +echo 镜像配置完成,请关闭并重新打开 cmd +``` + +**执行完后,关闭 cmd,重新打开**,再执行验证: + +```cmd +npm config get registry +cargo --version +``` + +--- + +## 三、方案 A:只跑前端(连测试环境后端) + +### 1. 拉代码 + +```cmd +git clone <你的仓库地址> +cd meijiaka-zy\tauri-app +``` + +### 2. 装依赖 + +```cmd +npm ci +``` + +### 3. 启动 + +```cmd +npm run tauri dev +``` + +前端默认连接 `https://dev.tapi.meijiaka.cn/api/v1`,无需本地后端。 + +--- + +## 四、方案 B:前后端都本地跑 + +在方案 A 基础上继续。 + +### 1. Python 3.13 + +- 下载:https://www.python.org/ftp/python/3.13.0/python-3.13.0-amd64.exe +- 安装:**务必勾选 "Add python.exe to PATH"**,然后 Install Now。 + +### 2. 安装 uv + +```cmd +pip install uv +``` + +### 3. Docker Desktop + +- 下载:https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe +- 安装:默认,装完**重启电脑**。 +- 重启后打开 Docker Desktop,等左下角状态变绿。 + +### 4. 后端启动 + +```cmd +cd meijiaka-zy\python-api + +:: 安装依赖 +uv pip install -e ".[dev]" + +:: 复制环境变量 +copy .env.example .env + +:: 启动数据库 +docker compose -f docker-compose.test.yml up -d db redis + +:: 数据库迁移 +alembic upgrade head + +:: 启动 API(终端 1) +make run +:: 或:uvicorn app.main:app --reload --port 8000 +``` + +如果需要异步调度器(脚本/TTS/字幕生成等),另开终端: + +```cmd +cd meijiaka-zy\python-api +make scheduler +:: 或:python -m app.scheduler.main +``` + +### 5. 前端启动(连本地后端) + +```cmd +cd meijiaka-zy\tauri-app +npm run tauri dev +``` + +前端 Vite 开发服务器会代理 API 请求到 `localhost:8000`。如果代理异常,检查 `tauri-app/src/api/client.ts` 中的 `PYTHON_API_BASE_URL`。 + +--- + +## 五、验证清单 + +全部装完后,在 cmd 里执行: + +```cmd +git --version +node -v +npm -v +rustc --version +cargo --version +python --version +uv --version +docker --version +``` + +每个都要有版本号输出。 + +--- + +## 六、常见问题 + +| 现象 | 原因 | 解决 | +|------|------|------| +| `npm ci` 卡住 | 镜像没配好 | 检查 `npm config get registry` 是否为 `registry.npmmirror.com` | +| `cargo build` 卡住 | Rust 镜像没生效 | 关闭 cmd 重新打开,或检查 `%USERPROFILE%\.cargo\config.toml` | +| Tauri 编译报错 `link.exe not found` | VS Build Tools 没装 C++ 桌面开发 | 重装,确保勾选了该工作负载 | +| `tauri dev` 白屏 | 前端代理地址错误 | 检查 `client.ts` 里的 base URL | +| Docker 启动失败 | WSL2 未启用 | 控制面板 → 程序和功能 → 启用 Windows 功能 → 勾选 **适用于 Linux 的 Windows 子系统** | +| `python` 命令找不到 | 安装时没勾选 Add to PATH | 重装 Python,务必勾选 | +| `alembic` 命令找不到 | 没在虚拟环境里 | 确保在 `python-api` 目录下执行,`uv pip install` 已经装了 | diff --git a/scripts/admin-ops.sql b/scripts/admin-ops.sql index 206951a..dfa587a 100644 --- a/scripts/admin-ops.sql +++ b/scripts/admin-ops.sql @@ -108,8 +108,8 @@ END $$; DO $$ DECLARE - v_mobile TEXT := '13800138000'; -- ← 修改:目标用户手机号 - v_gift_points INT := 500; -- ← 修改:赠送积分数量 + v_mobile TEXT := '13860199646'; -- ← 修改:目标用户手机号 + v_gift_points INT := 5000; -- ← 修改:赠送积分数量 v_gift_days INT := 180; -- ← 修改:有效期(天) v_reason TEXT := '运营活动赠送'; -- ← 修改:赠送原因(写入流水描述) v_user_id UUID; diff --git a/tauri-app/src-tauri/Cargo.lock b/tauri-app/src-tauri/Cargo.lock index 199a0de..cfb35ce 100644 --- a/tauri-app/src-tauri/Cargo.lock +++ b/tauri-app/src-tauri/Cargo.lock @@ -4219,7 +4219,7 @@ dependencies = [ [[package]] name = "tauri-app" -version = "1.6.1" +version = "1.6.2" dependencies = [ "base64 0.22.1", "chrono", diff --git a/tauri-app/src-tauri/src/ffmpeg_cmd.rs b/tauri-app/src-tauri/src/ffmpeg_cmd.rs index 42ac191..1160644 100644 --- a/tauri-app/src-tauri/src/ffmpeg_cmd.rs +++ b/tauri-app/src-tauri/src/ffmpeg_cmd.rs @@ -29,6 +29,16 @@ pub fn escape_ffmpeg_path(path: &str) -> String { path.replace("'", "'\\''") } +/// 去掉 Windows UNC 前缀(\\?\),使 convertFileSrc 能正确生成 asset:// URL +fn normalize_path(path: &std::path::Path) -> String { + let s = path.to_string_lossy().to_string(); + if s.starts_with(r"\\?\") { + std::path::PathBuf::from(&s[4..]).to_string_lossy().to_string() + } else { + s + } +} + /// 验证路径在允许的目录内,防止路径遍历攻击 /// 允许的目录:应用数据目录(app_local_data_dir) fn validate_safe_path(path: &str) -> Result { @@ -910,7 +920,7 @@ pub async fn transcode_for_preview(app: &AppHandle, input_path: &str) -> Result< && resolution_ok; if can_skip { - return Ok(path_str); + return Ok(normalize_path(&std::path::Path::new(&path_str))); } } } diff --git a/tauri-app/src/components/Modal/Modal.tsx b/tauri-app/src/components/Modal/Modal.tsx index 5d80a79..c7b2c08 100644 --- a/tauri-app/src/components/Modal/Modal.tsx +++ b/tauri-app/src/components/Modal/Modal.tsx @@ -8,6 +8,7 @@ interface ModalProps { children: React.ReactNode; width?: string; centerTitle?: boolean; + maxHeight?: string; } export default function Modal({ @@ -17,6 +18,7 @@ export default function Modal({ children, width = '560px', centerTitle = false, + maxHeight, }: ModalProps) { const overlayRef = useRef(null); @@ -45,7 +47,15 @@ export default function Modal({ } }} > -
+
{title && (
= { compose: '压制成片', subtitle_burn: '字幕烧录', cover_design: '封面设计', + cover_avatar: '封面人物形象', caption: '字幕生成', }; @@ -40,7 +41,7 @@ export default function PricingModal({ open, onClose }: PricingModalProps) { }, [open, rules.length]); return ( - + {loading ? (
加载中... diff --git a/tauri-app/src/components/RechargeModal/RechargeModal.css b/tauri-app/src/components/RechargeModal/RechargeModal.css index c1237f7..6b35b84 100644 --- a/tauri-app/src/components/RechargeModal/RechargeModal.css +++ b/tauri-app/src/components/RechargeModal/RechargeModal.css @@ -307,6 +307,12 @@ flex: 1; } +.recharge-modal-footer .btn.btn-ghost { + background: var(--bg-secondary); + border: 1px solid var(--border-light); + color: var(--text-secondary); +} + /* 订单信息 */ .recharge-order-info { display: flex; diff --git a/tauri-app/src/pages/Profile/UsageDetail.tsx b/tauri-app/src/pages/Profile/UsageDetail.tsx index 59c0e14..1305f90 100644 --- a/tauri-app/src/pages/Profile/UsageDetail.tsx +++ b/tauri-app/src/pages/Profile/UsageDetail.tsx @@ -208,6 +208,7 @@ const SOURCE_TYPE_LABELS: Record = { compose: '压制成片', subtitle_burn: '字幕烧录', cover_design: '封面设计', + cover_avatar: '封面人物形象', caption: '字幕生成', }; @@ -217,6 +218,7 @@ const SOURCE_TYPE_OPTIONS = [ { value: 'polish', label: '文案润色' }, { value: 'title', label: '标题生成' }, { value: 'tts', label: '配音合成' }, + { value: 'cover_avatar', label: '封面人物形象' }, { value: 'voice_clone', label: '声音复刻' }, { value: 'video', label: '视频生成' }, { value: 'compose', label: '压制成片' }, diff --git a/tauri-app/src/pages/VideoCreation/SubtitleBurning.tsx b/tauri-app/src/pages/VideoCreation/SubtitleBurning.tsx index d1f3808..a46989a 100644 --- a/tauri-app/src/pages/VideoCreation/SubtitleBurning.tsx +++ b/tauri-app/src/pages/VideoCreation/SubtitleBurning.tsx @@ -635,7 +635,7 @@ export default function SubtitleBurning() { onClick={() => setPreviewMode('result')} disabled={!burnedVideoUrl} > - {previewMode === 'result' ? '视频预览中...' : '视频预览'} + {previewMode === 'result' ? '视频预览中' : '视频预览'}
) : ( diff --git a/tauri-app/src/pages/VideoGeneration/_components/GenerationControls.tsx b/tauri-app/src/pages/VideoGeneration/_components/GenerationControls.tsx index 70ebd21..c2feb74 100644 --- a/tauri-app/src/pages/VideoGeneration/_components/GenerationControls.tsx +++ b/tauri-app/src/pages/VideoGeneration/_components/GenerationControls.tsx @@ -40,7 +40,7 @@ const GenerationControls: React.FC = ({ style={{ flex: 1, marginTop: 0, flexShrink: 0 }} onClick={onPreview} > - {isPreviewing ? '视频预览中...' : '视频预览'} + {isPreviewing ? '视频预览中' : '视频预览'}
); diff --git a/tauri-app/src/pages/VideoGeneration/_components/ShotTimeline.tsx b/tauri-app/src/pages/VideoGeneration/_components/ShotTimeline.tsx index 15a030d..6a436ba 100644 --- a/tauri-app/src/pages/VideoGeneration/_components/ShotTimeline.tsx +++ b/tauri-app/src/pages/VideoGeneration/_components/ShotTimeline.tsx @@ -14,6 +14,7 @@ interface ShotTimelineProps { userUploadedMaterials: Record; selectedAvatarMaterial: AvatarMaterial | null; isComposedPreview: boolean; + matchingShotIds?: Set; onSceneClick: (id: number, shot: ScriptShot) => void; onSwitchMaterial: (shotId: string) => void; onUploadMaterial: (shotId: string) => void; @@ -33,6 +34,7 @@ const ShotTimeline: React.FC = ({ userUploadedMaterials, selectedAvatarMaterial, isComposedPreview, + matchingShotIds, onSceneClick, onSwitchMaterial, onUploadMaterial, @@ -123,6 +125,7 @@ const ShotTimeline: React.FC = ({