From b2db988834e19fb9c5651f86692f66bab7b4a1ad Mon Sep 17 00:00:00 2001 From: "jif2.zhang" Date: Mon, 14 Sep 2026 09:49:39 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E9=A6=96=E9=A1=B5=E5=8F=AA?= =?UTF-8?q?=E7=95=99=E4=B8=8B=E8=BD=BD=E3=80=81=E5=8E=BB=E7=99=BB=E5=BD=95?= =?UTF-8?q?/=E5=9C=A8=E7=BA=BF=E4=BD=93=E9=AA=8C=E5=85=A5=E5=8F=A3;=20?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=8C=BA=E6=94=B9=E4=BA=8C=E7=BB=B4=E7=A0=81?= =?UTF-8?q?;=20=E4=BF=AE=E5=A4=8D=E7=BD=91=E7=BB=9C=E7=89=88=E4=B8=80?= =?UTF-8?q?=E7=9B=B4=E6=8F=90=E7=A4=BA=E6=96=B0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Landing: 移除 header 登录/进入应用按钮、hero 在线体验入口 - 下载区: 复制链接行改为 qrcode.react 二维码 (扫码下载 APK) - 修复 '持续提示新版本可用' bug: vite 构建时把 BUILD_ID 写入 dist/.build-id, manifest-web 返回当前部署 dist 的 build_id, 与浏览器内置值同源比对; 此前返回 OTA 包 build_id (seq-) 与页面 BUILD_ID 永不一致 → 横幅常驻 --- package-lock.json | 10 ++++++++ package.json | 3 ++- server/routes/ota.js | 11 +++++++- src/pages/Landing.tsx | 58 +++++++++++++------------------------------ vite.config.ts | 18 +++++++++++--- 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index db077aa..b0f4381 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.2", + "qrcode.react": "^4.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.0", @@ -3473,6 +3474,15 @@ "node": ">=6" } }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/qs": { "version": "6.15.3", "license": "BSD-3-Clause", diff --git a/package.json b/package.json index eee80ba..507b33e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.2", + "qrcode.react": "^4.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.0", @@ -41,4 +42,4 @@ "vite": "^5.3.3", "vite-plugin-singlefile": "^2.0.1" } -} \ No newline at end of file +} diff --git a/server/routes/ota.js b/server/routes/ota.js index 19afa4f..95ccf76 100644 --- a/server/routes/ota.js +++ b/server/routes/ota.js @@ -83,13 +83,22 @@ router.get('/ota/bundle/:filename', (req, res) => { // GET /api/ota/manifest-web - Web 浏览器用的简化版 // 客户端会拿这个跟当前 build_id 比较, 有新版本就提示用户刷新 +// 比对依据: 当前部署的 dist/.build-id (vite 构建时写入), 与浏览器内置 BUILD_ID 同源; +// 当服务端换了新 dist, 老标签页拿到的 build_id 变化 → 提示刷新 router.get('/ota/manifest-web', (_req, res) => { const latest = readLatest() if (!latest) return res.json({ available: false }) + // 优先用"当前托管 dist"的 build_id (部署时 dist/.build-id 与浏览器页面同一次构建) + let buildId = null + try { + buildId = fs + .readFileSync(path.join(path.resolve(process.cwd(), 'dist'), '.build-id'), 'utf8') + .trim() + } catch { /* dist 未构建或无该文件时忽略 */ } res.json({ available: true, version: latest.version, - build_id: latest.build_id || latest.version, + build_id: buildId || latest.build_id || latest.version, note: latest.notes || '', size: latest.bundle_size || 0, }) diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx index 3e9e99c..474f919 100644 --- a/src/pages/Landing.tsx +++ b/src/pages/Landing.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from "react"; -import { useNavigate, Link } from "react-router-dom"; -import { useAppStore } from "../stores/app"; +import { QRCodeSVG } from "qrcode.react"; import { APP_VERSION, BUILD_ID } from "../version"; import { checkOtaUpdate, reloadApp } from "../api/ota"; @@ -23,17 +22,6 @@ const STATS = [ ]; function DownloadSection() { - const [copied, setCopied] = useState(false); - const copy = async () => { - try { - await navigator.clipboard.writeText(APK_URL); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } catch { - setCopied(false); - } - }; - return (

下载 Android 版

@@ -45,13 +33,10 @@ function DownloadSection() {

版本 {APP_VERSION} · Android 8.0+ · 约 30 MB

-
-

或复制下载链接(发给手机打开):

-
- {APK_URL} - +
+

手机扫码下载:

+
+

@@ -93,23 +78,13 @@ function WebUpdateBanner() { } export default function Landing() { - const navigate = useNavigate(); - const user = useAppStore((s) => s.user); - return (

- +
👗 Wardrobe - - +
@@ -127,9 +102,6 @@ export default function Landing() { ⬇️ 下载 Android APK -
@@ -182,14 +154,13 @@ export default function Landing() { const styles: Record = { header: { - display: "flex", - alignItems: "center", - justifyContent: "space-between", maxWidth: 960, margin: "0 auto", padding: "16px 20px", + display: "flex", + alignItems: "center", }, - logo: { display: "flex", alignItems: "center", gap: 8, textDecoration: "none", color: "var(--text-primary)" }, + logo: { display: "flex", alignItems: "center", gap: 8 }, logoEmoji: { fontSize: 24 }, container: { maxWidth: 960, margin: "0 auto", padding: "0 20px 60px" }, hero: { textAlign: "center", padding: "64px 0 48px" }, @@ -211,8 +182,13 @@ const styles: Record = { margin: "0 auto", }, downloadBtn: { padding: "14px 32px", fontSize: 16 }, - linkRow: { display: "flex", alignItems: "center", gap: 8, justifyContent: "center", flexWrap: "wrap" }, - linkCode: { fontSize: 11, color: "var(--text-secondary)", wordBreak: "break-all" }, + qrWrap: { + display: "inline-flex", + padding: 10, + background: "#fff", + border: "1px solid var(--border)", + borderRadius: "var(--radius)", + }, techRow: { display: "flex", gap: 10, justifyContent: "center", flexWrap: "wrap" }, footer: { textAlign: "center", padding: "24px 16px", fontSize: 12, color: "var(--text-tertiary)" }, }; \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 3ddbe86..ef9e0e6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,7 @@ -import { defineConfig } from "vite"; +import { defineConfig, type Plugin } from "vite"; import react from "@vitejs/plugin-react"; -import { readFileSync } from "node:fs"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; const host = process.env.TAURI_DEV_HOST; @@ -18,8 +19,19 @@ function buildId() { export default defineConfig(async () => { const pkg = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")); + // 构建完成后把 BUILD_ID 写进 dist/.build-id, 供 server 的 /api/ota/manifest-web + // 返回"当前部署版本"的 build_id, 让 web 端升级提示只在真是新部署时出现 + const writeBuildId: Plugin = { + name: "wardrobe:write-build-id", + closeBundle() { + const dir = path.resolve(process.cwd(), "dist"); + mkdirSync(dir, { recursive: true }); + writeFileSync(path.join(dir, ".build-id"), buildId(), "utf8"); + }, + }; + return { - plugins: [react()], + plugins: [react(), writeBuildId], clearScreen: false, server: { port: 1420,