feat: 版本时间戳统一 UTC+8; 适配全面屏安全区; 页面切换加过渡动画
- vite buildId 统一按 Asia/Shanghai 生成, CI(UTC) 与本地时区不再漂移 - index.html 加 viewport-fit=cover (env(safe-area-inset-*) 才生效) - app-shell 顶部 +20px+safe inset, bottom-nav 底部 +20px+safe inset 防状态栏/手势条遮内容 - 页面跳转加淡入上滑动画 (Layout 内嵌页面按 pathname 重挂载, Landing/Login 同款)
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<title>Wardrobe</title>
|
<title>Wardrobe</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+2
-2
@@ -39,7 +39,7 @@ function RootGate() {
|
|||||||
const effective = user ?? getStoredUser();
|
const effective = user ?? getStoredUser();
|
||||||
return <Navigate to={effective ? "/home" : "/login"} replace />;
|
return <Navigate to={effective ? "/home" : "/login"} replace />;
|
||||||
}
|
}
|
||||||
return <Landing />;
|
return <div className="page-enter"><Landing /></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RouterGate() {
|
function RouterGate() {
|
||||||
@@ -65,7 +65,7 @@ function RouterGate() {
|
|||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<RootGate />} />
|
<Route path="/" element={<RootGate />} />
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<div className="page-enter"><Login /></div>} />
|
||||||
<Route element={<ProtectedRoute><Layout /></ProtectedRoute>}>
|
<Route element={<ProtectedRoute><Layout /></ProtectedRoute>}>
|
||||||
<Route path="/home" element={<Home />} />
|
<Route path="/home" element={<Home />} />
|
||||||
<Route path="/closet" element={<Closet />} />
|
<Route path="/closet" element={<Closet />} />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
import { Outlet, useNavigate, useLocation } from "react-router-dom";
|
import { Outlet, useNavigate, useLocation } from "react-router-dom";
|
||||||
import { useAppStore } from "../stores/app";
|
import { useAppStore } from "../stores/app";
|
||||||
|
|
||||||
@@ -12,6 +13,12 @@ export default function Layout() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const user = useAppStore((s) => s.user);
|
const user = useAppStore((s) => s.user);
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 切换页面时回到顶部 + 触发过渡动画 (key 随路径重挂载)
|
||||||
|
useEffect(() => {
|
||||||
|
contentRef.current?.scrollTo({ top: 0 });
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
const isActive = (path: string) =>
|
const isActive = (path: string) =>
|
||||||
path === "/home" ? location.pathname === "/home" || location.pathname === "/" : location.pathname.startsWith(path);
|
path === "/home" ? location.pathname === "/home" || location.pathname === "/" : location.pathname.startsWith(path);
|
||||||
@@ -23,9 +30,11 @@ export default function Layout() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<div className="app-content">
|
<div className="app-content" ref={contentRef}>
|
||||||
|
<div key={location.pathname} className="page-enter">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<nav className="bottom-nav">
|
<nav className="bottom-nav">
|
||||||
<button className={"nav-item" + (isActive("/home") ? " active" : "")} onClick={() => navigate("/home")}>
|
<button className={"nav-item" + (isActive("/home") ? " active" : "")} onClick={() => navigate("/home")}>
|
||||||
<span className="nav-icon">{NAV_ICONS.home}</span>
|
<span className="nav-icon">{NAV_ICONS.home}</span>
|
||||||
|
|||||||
+14
-1
@@ -61,6 +61,8 @@ textarea {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
/* 顶部避开状态栏/刘海, 再多让出一个行高 (edge-to-edge 时 env() 才生效) */
|
||||||
|
padding-top: calc(env(safe-area-inset-top) + 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-content {
|
.app-content {
|
||||||
@@ -92,7 +94,8 @@ textarea {
|
|||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
padding: 6px 8px;
|
padding: 6px 8px;
|
||||||
padding-bottom: calc(6px + env(safe-area-inset-bottom));
|
/* 底部避开手势条, 再多让出一个行高, 避免文字被遮挡 */
|
||||||
|
padding-bottom: calc(20px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
@@ -271,3 +274,13 @@ textarea {
|
|||||||
from { opacity: 0; transform: translateX(-50%) translateY(-8px); }
|
from { opacity: 0; transform: translateX(-50%) translateY(-8px); }
|
||||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 页面切换动画 (Layout 内嵌页面与 Landing/Login 共用) */
|
||||||
|
.page-enter {
|
||||||
|
animation: page-in 0.26s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes page-in {
|
||||||
|
from { opacity: 0; transform: translateY(12px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
+4
-3
@@ -7,12 +7,13 @@ const host = process.env.TAURI_DEV_HOST;
|
|||||||
|
|
||||||
function buildId() {
|
function buildId() {
|
||||||
// YYYYMMDD.HHmmss: 每次构建生成新的 BUILD_ID, 用于 OTA/web 升级比对
|
// YYYYMMDD.HHmmss: 每次构建生成新的 BUILD_ID, 用于 OTA/web 升级比对
|
||||||
const d = new Date();
|
// 统一按 UTC+8 (Asia/Shanghai) 取时, 避免 CI (UTC) 与本地时区漂移导致误判升级
|
||||||
const pad = (n: number) => String(n).padStart(2, "0");
|
const pad = (n: number) => String(n).padStart(2, "0");
|
||||||
|
const cn = new Date(Date.now() + 8 * 3600 * 1000);
|
||||||
return (
|
return (
|
||||||
[d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())].join("") +
|
[cn.getUTCFullYear(), pad(cn.getUTCMonth() + 1), pad(cn.getUTCDate())].join("") +
|
||||||
"." +
|
"." +
|
||||||
[pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join("")
|
[pad(cn.getUTCHours()), pad(cn.getUTCMinutes()), pad(cn.getUTCSeconds())].join("")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user