fix(web): 首页只留下载、去登录/在线体验入口; 下载区改二维码; 修复网络版一直提示新版本
- Landing: 移除 header 登录/进入应用按钮、hero 在线体验入口 - 下载区: 复制链接行改为 qrcode.react 二维码 (扫码下载 APK) - 修复 '持续提示新版本可用' bug: vite 构建时把 BUILD_ID 写入 dist/.build-id, manifest-web 返回当前部署 dist 的 build_id, 与浏览器内置值同源比对; 此前返回 OTA 包 build_id (seq-<ts>) 与页面 BUILD_ID 永不一致 → 横幅常驻
This commit is contained in:
Generated
+10
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+10
-1
@@ -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,
|
||||
})
|
||||
|
||||
+17
-41
@@ -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 (
|
||||
<section style={styles.section}>
|
||||
<h2 style={styles.sectionTitle}>下载 Android 版</h2>
|
||||
@@ -45,13 +33,10 @@ function DownloadSection() {
|
||||
<p style={{ fontSize: 12, color: "var(--text-tertiary)", marginTop: 10 }}>
|
||||
版本 {APP_VERSION} · Android 8.0+ · 约 30 MB
|
||||
</p>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<p style={{ fontSize: 12, color: "var(--text-tertiary)", marginBottom: 6 }}>或复制下载链接(发给手机打开):</p>
|
||||
<div style={styles.linkRow}>
|
||||
<code style={styles.linkCode}>{APK_URL}</code>
|
||||
<button className="btn btn-secondary" style={{ padding: "6px 12px", fontSize: 12 }} onClick={copy}>
|
||||
{copied ? "已复制 ✓" : "复制"}
|
||||
</button>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<p style={{ fontSize: 12, color: "var(--text-tertiary)", marginBottom: 10 }}>手机扫码下载:</p>
|
||||
<div style={styles.qrWrap}>
|
||||
<QRCodeSVG value={APK_URL} size={168} level="M" style={{ display: "block" }} />
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ fontSize: 12, color: "var(--text-tertiary)", marginTop: 16, lineHeight: 1.6 }}>
|
||||
@@ -93,23 +78,13 @@ function WebUpdateBanner() {
|
||||
}
|
||||
|
||||
export default function Landing() {
|
||||
const navigate = useNavigate();
|
||||
const user = useAppStore((s) => s.user);
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: "100vh", background: "var(--bg)", color: "var(--text-primary)", userSelect: "text" }}>
|
||||
<header style={styles.header}>
|
||||
<Link to="/" style={styles.logo}>
|
||||
<div style={styles.logo}>
|
||||
<span style={styles.logoEmoji}>👗</span>
|
||||
<span style={{ fontWeight: 700, fontSize: 18 }}>Wardrobe</span>
|
||||
</Link>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
style={{ padding: "8px 18px", fontSize: 14 }}
|
||||
onClick={() => (user ? navigate("/home") : navigate("/login"))}
|
||||
>
|
||||
{user ? "进入应用" : "登录"}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div style={styles.container}>
|
||||
@@ -127,9 +102,6 @@ export default function Landing() {
|
||||
<a className="btn btn-primary" href={APK_URL} download="wardrobe.apk" style={styles.heroBtn}>
|
||||
⬇️ 下载 Android APK
|
||||
</a>
|
||||
<button className="btn btn-secondary" style={styles.heroBtn} onClick={() => navigate("/login")}>
|
||||
在线体验 →
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -182,14 +154,13 @@ export default function Landing() {
|
||||
|
||||
const styles: Record<string, React.CSSProperties> = {
|
||||
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<string, React.CSSProperties> = {
|
||||
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)" },
|
||||
};
|
||||
+15
-3
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user