首次提交:脑晴测 H5 项目

包含前端源码、预构建产物(dist/)、零依赖 Node.js 服务器(server.mjs)、
Cloudflare Worker 版本(worker/)、部署文档(DEPLOY.md)和交接文档。

技术总监可直接 clone 后运行 node server.mjs 启动,无需安装 React 或 build。
DEEPSEEK_API_KEY 通过环境变量注入,未纳入仓库。
This commit is contained in:
MingNian
2026-07-28 10:24:47 +08:00
commit b0fce840a3
152 changed files with 8536 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
import { StrictMode, useState } from "react";
import { createRoot } from "react-dom/client";
import {
BottomSheet,
Carousel,
FlowStack,
KeyboardInput,
MobileRuntime,
MobileScroll,
type FlowScreen,
} from "../src/mobile";
import "../src/styles.css";
import "./runtime-fixture.css";
function CarouselFixture() {
const [tapCount, setTapCount] = useState(0);
const [sheetOpen, setSheetOpen] = useState(false);
return (
<MobileRuntime>
<MobileScroll className="fixture-screen">
<main className="fixture-content">
<h1>Runtime fixture</h1>
<button className="sheet-trigger" type="button" onClick={() => setSheetOpen(true)}>
Open sheet
</button>
<Carousel
ariaLabel="Featured cards"
className="fixture-carousel"
contentClassName="fixture-carousel-track"
>
{Array.from({ length: 7 }, (_, index) => (
<button
className="carousel-card"
type="button"
onClick={() => setTapCount((count) => count + 1)}
key={index}
>
Card {index + 1}
</button>
))}
</Carousel>
<output data-testid="tap-count">{tapCount}</output>
<div className="fixture-tall-content">Scrollable parent content</div>
</main>
</MobileScroll>
<BottomSheet
open={sheetOpen}
onOpenChange={setSheetOpen}
title="Animated sheet"
description="Exit motion regression fixture."
>
<p>Sheet content</p>
</BottomSheet>
</MobileRuntime>
);
}
function KeyboardFixture() {
const screen: FlowScreen = {
id: "keyboard",
footerHeight: 84,
footer: () => (
<div className="keyboard-footer">
<KeyboardInput aria-label="Message" placeholder="Message" />
</div>
),
render: () => (
<MobileScroll className="fixture-screen">
<main className="fixture-content fixture-tall-content">Keyboard fixture</main>
</MobileScroll>
),
};
return (
<MobileRuntime>
<FlowStack initial={screen} />
</MobileRuntime>
);
}
function stackedScreen(level: number): FlowScreen {
return {
id: `flow-level-${level}`,
headerHeight: 56,
header: (flow) => (
<div className="flow-fixture-header">
<button type="button" onClick={flow.pop}> Back</button>
<strong>Level {level}</strong>
<span />
</div>
),
footerHeight: 64,
footer: (flow) => (
<div className="flow-fixture-footer">
<button type="button" onClick={flow.pop}>Done</button>
</div>
),
render: (flow) => (
<MobileScroll className="fixture-screen">
<main className="fixture-content">
<h1>{level === 2 ? "Screen stacking works" : `Nested view level ${level}`}</h1>
{level < 4 ? (
<button type="button" onClick={() => flow.push(stackedScreen(level + 1))}>
Push level {level + 1}
</button>
) : null}
</main>
</MobileScroll>
),
};
}
function FlowFixture() {
const screen: FlowScreen = {
id: "flow-root",
footerHeight: 84,
footer: () => (
<div className="keyboard-footer">
<KeyboardInput aria-label="Flow message" placeholder="Message" />
</div>
),
render: (flow) => (
<MobileScroll className="fixture-screen">
<main className="fixture-content">
<h1>Flow root</h1>
<button type="button" onClick={() => flow.push(stackedScreen(2))}>
Push level 2
</button>
</main>
</MobileScroll>
),
};
return (
<MobileRuntime>
<FlowStack initial={screen} />
</MobileRuntime>
);
}
const fixture = new URLSearchParams(window.location.search).get("fixture");
const fixtureElement =
fixture === "keyboard"
? <KeyboardFixture />
: fixture === "flow"
? <FlowFixture />
: <CarouselFixture />;
createRoot(document.getElementById("root")!).render(
<StrictMode>{fixtureElement}</StrictMode>,
);