/* 移动端主页 — 数据库为主体,Agent 买电视助手为入口,小工具页挂前台工具。
两个已上线工具(mobile.jsx 的 ComposeApp/LightApp)在壳内 push 打开,一个手机壳一站到底。
复用 icons.jsx(Icon) / data.jsx(TVS) / images.jsx(IMG)。深链:?tab=tools · ?tool=compose|light */
const { useState, useEffect, useRef } = React;
const cx = (...a) => a.filter(Boolean).join(' ');
const q = (k) => new URLSearchParams(location.search).get(k);
const REDUCE = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
function Phone({ children }) {
return (
);
}
const ASKS = ['预算 1.5 万,客厅怎么选', '白天客厅很亮,怕反光', 'OLED 会烧屏吗'];
const CMD = '我想买台客厅用的电视'; // 演示固定命令:点对话框打字机输入,点发送进入 Agent 对话
/* 前台工具:2 个已上线(壳内打开),4 个规划位 */
const LIVE_TOOLS = [
{ k: 'compose', icon: 'sparkle', n: 'AI 客厅合成', d: '拍下电视墙,AI 生成等比合成预览', img: () => IMG.wall },
{ k: 'light', icon: 'sun', n: '采光 · 测光', d: '两拍测照度与反光源,反推抗反需求', img: () => IMG.rev },
];
const PLAN_TOOLS = [
{ icon: 'scan', n: 'AR 量房 · 摆放', d: '量电视墙净宽与入户路径', tag: '规划中' },
{ icon: 'cube', n: '旧机估值变现', d: '拍照识别 → 含搬运费估价', tag: '规划中' },
{ icon: 'eye', n: '开箱验机 · 故障上报', d: '拍照验收,反哺故障率库', tag: '规划中' },
{ icon: 'crosshair', n: '交互对比看板', d: '并排对比实测画质与参数', tag: '网页版', href: '电视对比看板.html' },
];
function HomeScreen({ on, openDb, openAgent, frozen }) {
const [ask, setAsk] = useState('');
const [typing, setTyping] = useState(false);
const tRef = useRef(null);
useEffect(() => () => clearInterval(tRef.current), []);
/* 点对话框:打字机敲入固定命令 */
const typeCmd = () => {
if (typing || ask === CMD) return;
clearInterval(tRef.current); setTyping(true); setAsk('');
let i = 0;
tRef.current = setInterval(() => {
i++; setAsk(CMD.slice(0, i));
if (i >= CMD.length) { clearInterval(tRef.current); setTyping(false); }
}, REDUCE ? 4 : 52);
};
/* 点发送:从 Agent 卡片 morph 进对话(空输入时直接用固定命令) */
const send = (e) => {
if (typing) return;
e.stopPropagation();
openAgent(e.currentTarget.closest('.agent'), ask || CMD);
};
return (
电视数据库
{/* Agent 买电视助手 — 主入口 */}
{ask || (typing ? '' : '想买多大?预算多少?点我试试…')}
{typing && }
{ASKS.map((a) => )}
{/* 数据资产 — 用户视角:在库量 + 即将入库 */}
数据资产
{DB_STATS.inStock}
台电视
实测在库
{DB_STATS.incoming} 台即将入库
{DB_STATS.brands} 个品牌
{/* 机型库 — 自动滚动条 + 全部入口 */}
机型库
{REDUCE ? (
{DBTV.map((t) => )}
) : (
{/* 壳内层打开时暂停跑马灯,省掉层下每帧重绘 */}
{DBTV.map((t) => )}
{DBTV.map((t) => )}
)}
);
}
function TvCard({ t, onClick }) {
return (
{t.brand} {t.model}
{t.panel}{t.size}″
{t.hl}
¥{t.price.toLocaleString()}
);
}
/* DbScreen(详细机型库)在 dbview.jsx */
function ToolsScreen({ on, openTool }) {
return (
{LIVE_TOOLS.map((t) => (
))}
规划中
{PLAN_TOOLS.map((t) => {
const inner = (
{t.n}{t.d}
{t.tag}
);
return t.href
?
{inner}
:
{inner}
;
})}
);
}
/* 壳内层:两段式 morph(防卡顿)。
① 空面板从来源卡片 rect clip-path 张开(动画帧里没有内容挂载/重绘成本);
② 落定后再挂载内容淡入。关闭时内容先淡出、面板反向收回后卸载(fresh mount)。
rect 为 null(深链直开)时从屏幕中央小窗张开。 */
function MorphLayer({ rect, closing, children }) {
const [on, setOn] = useState(false);
const [ready, setReady] = useState(REDUCE);
useEffect(() => {
const r = requestAnimationFrame(() => setOn(true));
const t = setTimeout(() => setReady(true), REDUCE ? 0 : 460);
return () => { cancelAnimationFrame(r); clearTimeout(t); };
}, []);
const from = rect
? `inset(${rect.top}px ${rect.right}px ${rect.bottom}px ${rect.left}px round 22px)`
: 'inset(42% 24% 42% 24% round 26px)';
const open = on && !closing;
return (
);
}
function HomeApp() {
const tool0 = ['compose', 'light', 'db', 'agent'].includes(q('tool')) ? q('tool') : null;
const [tab, setTab] = useState(tool0 === 'compose' || tool0 === 'light' || q('tab') === 'tools' ? 'tools' : 'home');
const [tool, setTool] = useState(tool0);
const [agentAsk, setAgentAsk] = useState(CMD);
const [rect, setRect] = useState(null);
const [closing, setClosing] = useState(false);
const syncUrl = (fn) => { const u = new URL(location); fn(u.searchParams); history.replaceState(null, '', u); };
const pick = (t) => {
if (t === tab) return;
syncUrl((p) => { if (t === 'tools') p.set('tab', 'tools'); else p.delete('tab'); });
setTab(t);
};
/* 记录点击卡片相对手机屏的 rect(壳被缩放时按比例还原),morph 从这里张开 */
const openTool = (k, e) => {
let r = null;
const host = e && e.currentTarget.closest('.m-app');
if (host) {
const hr = host.getBoundingClientRect();
const cr = e.currentTarget.getBoundingClientRect();
const s = hr.width ? host.offsetWidth / hr.width : 1;
r = { top: (cr.top - hr.top) * s, left: (cr.left - hr.left) * s,
right: (hr.right - cr.right) * s, bottom: (hr.bottom - cr.bottom) * s };
}
setRect(r);
syncUrl((p) => p.set('tool', k)); setClosing(false); setTool(k);
};
const closeTool = () => {
syncUrl((p) => p.delete('tool')); setClosing(true);
setTimeout(() => { setTool(null); setClosing(false); }, REDUCE ? 60 : 650);
};
return (
openTool('db', e)}
openAgent={(el, text) => { setAgentAsk(text); openTool('agent', el && { currentTarget: el }); }} frozen={!!tool} />
{tool && (
{tool === 'db' ?
: tool === 'agent' ?
: tool === 'light' ?
: }
)}
{[['home', '首页', 'database'], ['tools', '小工具', 'grid']].map(([k, l, ic]) => (
))}
);
}
function StageHome() {
return (
电视数据库 · 移动端主页
数据库是主体,Agent 买电视助手是主入口;小工具页只放手机才能测的前台工具,两个已上线 demo 可直接点进。
Agent 主导
Liquid Glass
双 Tab
深链:?tab=tools 直达小工具 · ?tool=compose|light|db|agent 壳内直开工具 / 机型库 / 买电视助手
);
}
/* 真机模式:小屏(真手机/窄窗)全屏直跑,不套假手机壳;?m=1 强制真机、?m=0 强制评审舞台 */
const REAL = q('m') === '1' || (q('m') !== '0' && window.matchMedia('(max-width: 620px)').matches);
if (REAL) document.documentElement.classList.add('real');
ReactDOM.createRoot(document.getElementById('root')).render(
REAL ?
:
);