style(appShell): 优化应用外壳及底部导航样式与结构

- 在 tailwind 配置中添加应用外壳相关的 safelist 类名
- 替换 App.tsx 中应用外壳容器的类名,去除多余的内边距容器
- 在现代和遗留样式文件中新增移动端全屏、桌面居中列的样式规则
- 新增底部导航外层和内层的样式及响应式布局支持
- appShell.ts 中将样式类改为统一的 x8- 前缀类名
- BottomNav 组件使用新的样式类名以匹配样式调整
- Home 页面 ListRow 组件支持可选缩略图展示,增加界面展示丰富度
This commit is contained in:
eafonyang
2026-06-03 11:52:57 +08:00
parent 28aa90296f
commit ccc404b75a
7 changed files with 156 additions and 15 deletions
+3 -5
View File
@@ -22,7 +22,7 @@ import SelectPage from "./pages/SelectPage";
import CommunityPage from "./pages/CommunityPage"; import CommunityPage from "./pages/CommunityPage";
import { AIDrawerProvider } from "./contexts/AIDrawerContext"; import { AIDrawerProvider } from "./contexts/AIDrawerContext";
import DesktopAIShell from "./components/DesktopAIShell"; import DesktopAIShell from "./components/DesktopAIShell";
import { appShellColumnClass, appShellGutterClass } from "./lib/appShell"; import { appShellColumnClass, appShellOuterClass } from "./lib/appShell";
const EQPage = lazy(() => import("./pages/EQPage")); const EQPage = lazy(() => import("./pages/EQPage"));
const AIPage = lazy(() => import("./pages/AIPage")); const AIPage = lazy(() => import("./pages/AIPage"));
@@ -75,10 +75,8 @@ function App() {
<Toaster /> <Toaster />
<Router hook={useHashLocation}> <Router hook={useHashLocation}>
<AIDrawerProvider> <AIDrawerProvider>
<div <div className={appShellOuterClass}>
className={`min-h-dvh bg-[#0d1117] flex justify-center ${appShellGutterClass}`} <div className={appShellColumnClass}>
>
<div className={`min-h-dvh ${appShellColumnClass}`}>
<DesktopAIShell> <DesktopAIShell>
<AppRouter /> <AppRouter />
</DesktopAIShell> </DesktopAIShell>
+3 -3
View File
@@ -22,7 +22,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { Link, useLocation } from "wouter"; import { Link, useLocation } from "wouter";
import { useMemo } from "react"; import { useMemo } from "react";
import { appShellColumnClass, appShellGutterClass } from "@/lib/appShell"; import { bottomNavInnerClass, bottomNavOuterClass } from "@/lib/appShell";
type BottomNavLocale = NonNullable<(typeof localeZh)["bottomNav"]>; type BottomNavLocale = NonNullable<(typeof localeZh)["bottomNav"]>;
@@ -57,10 +57,10 @@ export default function BottomNav() {
return ( return (
<nav <nav
className={`fixed inset-x-0 bottom-0 z-50 flex justify-center pointer-events-none ${appShellGutterClass}`} className={bottomNavOuterClass}
> >
<div <div
className={`pointer-events-auto ${appShellColumnClass}`} className={bottomNavInnerClass}
style={{ style={{
background: "rgba(10, 10, 12, 0.96)", background: "rgba(10, 10, 12, 0.96)",
backdropFilter: "blur(40px) saturate(200%)", backdropFilter: "blur(40px) saturate(200%)",
+57
View File
@@ -119,6 +119,8 @@
html, html,
body, body,
#root { #root {
width: 100%;
max-width: 100%;
min-height: 100%; min-height: 100%;
} }
@@ -176,6 +178,61 @@
min-height: 0; min-height: 0;
min-width: 0; min-width: 0;
} }
/* App shell: mobile full-bleed, desktop centered column (modern + legacy share class names). */
.x8-app-shell-outer {
min-height: 100dvh;
width: 100%;
min-width: 0;
display: flex;
justify-content: center;
background-color: #0d1117;
padding-left: 0;
padding-right: 0;
}
.x8-app-shell-column {
min-height: 100dvh;
width: 100%;
min-width: 0;
max-width: none;
}
.x8-bottom-nav-outer {
pointer-events: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
width: 100%;
min-width: 0;
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.x8-bottom-nav-inner {
pointer-events: auto;
width: 100%;
min-width: 0;
max-width: none;
}
@media (min-width: 1024px) {
.x8-app-shell-outer,
.x8-bottom-nav-outer {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.x8-app-shell-column,
.x8-bottom-nav-inner {
max-width: 1180px;
}
}
@media (min-width: 640px) { @media (min-width: 640px) {
.container { .container {
padding-left: 1.5rem; padding-left: 1.5rem;
+69
View File
@@ -6,9 +6,22 @@
html, html,
body, body,
#root { #root {
width: 100%;
max-width: 100%;
height: 100%; height: 100%;
} }
@media (max-width: 1023px) {
html,
body {
overflow-x: hidden;
overscroll-behavior-x: none;
}
#root {
overflow-x: hidden;
}
}
body { body {
background: #000; background: #000;
color: #f2f2f2; color: #f2f2f2;
@@ -44,6 +57,62 @@ body {
} }
@layer components { @layer components {
/* App shell: mobile full-bleed, desktop centered column (plain CSS for Chrome 91). */
.x8-app-shell-outer {
min-height: 100vh;
min-height: -webkit-fill-available;
width: 100%;
min-width: 0;
display: flex;
justify-content: center;
background-color: #0d1117;
padding-left: 0;
padding-right: 0;
}
.x8-app-shell-column {
min-height: 100vh;
min-height: -webkit-fill-available;
width: 100%;
min-width: 0;
max-width: none;
}
.x8-bottom-nav-outer {
pointer-events: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
width: 100%;
min-width: 0;
justify-content: center;
padding-left: 0;
padding-right: 0;
}
.x8-bottom-nav-inner {
pointer-events: auto;
width: 100%;
min-width: 0;
max-width: none;
}
@media (min-width: 1024px) {
.x8-app-shell-outer,
.x8-bottom-nav-outer {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.x8-app-shell-column,
.x8-bottom-nav-inner {
max-width: 1180px;
}
}
/* Match modern .cyan-slider styling (input[type=range]) for legacy build. */ /* Match modern .cyan-slider styling (input[type=range]) for legacy build. */
.cyan-slider { .cyan-slider {
-webkit-appearance: none; -webkit-appearance: none;
+5 -5
View File
@@ -1,8 +1,8 @@
/** Centered phone-style shell width (desktop / ultrawide). */ /** Centered phone-style shell width (desktop / ultrawide). */
export const APP_SHELL_MAX_PX = 1180; export const APP_SHELL_MAX_PX = 1180;
/** Inner column inside horizontal gutters — use with a flex/grid centering parent. */ /** Responsive rules live in `.x8-app-shell-*` (index.css + index.legacy.css). */
export const appShellColumnClass = export const appShellOuterClass = "x8-app-shell-outer";
"w-full min-w-0 max-w-[min(1180px,100%)]"; export const appShellColumnClass = "x8-app-shell-column";
export const bottomNavOuterClass = "x8-bottom-nav-outer";
export const appShellGutterClass = "px-4 sm:px-6"; export const bottomNavInnerClass = "x8-bottom-nav-inner";
+13 -2
View File
@@ -135,7 +135,7 @@ function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: bool
// ── List row ── // ── List row ──
function ListRow({ function ListRow({
icon, label, value, onClick, toggle, checked, onToggle, icon, label, value, onClick, toggle, checked, onToggle, thumbnailSrc,
}: { }: {
icon: React.ReactNode; icon: React.ReactNode;
label: string; label: string;
@@ -144,6 +144,8 @@ function ListRow({
toggle?: boolean; toggle?: boolean;
checked?: boolean; checked?: boolean;
onToggle?: (v: boolean) => void; onToggle?: (v: boolean) => void;
/** Optional small thumbnail shown between value text and chevron. */
thumbnailSrc?: string;
}) { }) {
return ( return (
<div className="ios-list-row cursor-pointer" onClick={onClick}> <div className="ios-list-row cursor-pointer" onClick={onClick}>
@@ -157,7 +159,15 @@ function ListRow({
{toggle ? ( {toggle ? (
<IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} /> <IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} />
) : ( ) : (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1.5">
{thumbnailSrc && (
<img
src={thumbnailSrc}
alt=""
className="w-16 h-12 rounded-[8px] object-cover flex-shrink-0"
style={{ border: "1px solid rgba(255,255,255,0.12)" }}
/>
)}
{value && <span className="ios-row-value">{value}</span>} {value && <span className="ios-row-value">{value}</span>}
<ChevronRight size={16} className="ios-chevron" /> <ChevronRight size={16} className="ios-chevron" />
</div> </div>
@@ -873,6 +883,7 @@ export default function Home() {
icon={<Gauge size={16} />} icon={<Gauge size={16} />}
label={homeText.vu ?? "VU表"} label={homeText.vu ?? "VU表"}
value={vuLabel} value={vuLabel}
thumbnailSrc={`${import.meta.env.BASE_URL}vu/vu${(ds.vu ?? 0) + 1}.png`}
onClick={() => setLocation("/vu")} onClick={() => setLocation("/vu")}
/> />
<ListRow <ListRow
+6
View File
@@ -29,5 +29,11 @@ module.exports = {
}, },
}, },
plugins: [], plugins: [],
safelist: [
"x8-app-shell-outer",
"x8-app-shell-column",
"x8-bottom-nav-outer",
"x8-bottom-nav-inner",
],
}; };