const { useState, useEffect, useRef } = React; const cx = (...a) => a.filter(Boolean).join(' '); const q = (k) => new URLSearchParams(location.search).get(k); const S2 = q('s') === '2'; // 评审:直接进结果 const G = q('g') === '1'; // 评审:冻结在合成中 const REDUCE = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); function Btn({ v = 'glass', size, icon, block, children, ...rest }) { return ( ); } function Phone({ children }) { return (
{children}
); } /* ============ 在我家试电视:AR 量房 → 实时摆放 → AI 精细效果 ============ */ const MODELS = [ { id: 'e8s', name: '海信 85 E8S', short: '海信 E8S', panel: 'RGB-MiniLED' }, { id: 'g5', name: 'LG 65 G5', short: 'LG G5', panel: 'OLED evo' }, { id: 'sny', name: 'Sony BRAVIA 8 II', short: 'Sony B8 II', panel: 'QD-OLED' }, { id: 'tcl', name: 'TCL 85 Q10K', short: 'TCL Q10K', panel: 'Mini LED' }, ]; const REF = 85; // 预览样片里那台电视的实际尺寸(英寸) const SIZES = [55, 65, 75, 85, 98]; // 尺寸档位(place 阶段的可选项,deep link sz 吸附到最近档) const DISTS = [240, 280, 320]; // 沙发距离候选(cm),默认 280 // 电视在照片卡中的位置(卡与照片同为 4:3,坐标即百分比) const TVRECT = { wall: { cx: 62, cy: 40.5, w: 33.5, h: 28 }, stand: { cx: 61.5, cy: 45.5, w: 34, h: 28 } }; const viewAngle = (size, dist) => Math.round(2 * Math.atan((size * 2.54 * 0.8716 / 2) / dist) * 180 / Math.PI); const fitLabel = (a) => a < 28 ? { t: '偏小', cls: 'warn' } : a <= 40 ? { t: '推荐', cls: 'ok' } : { t: '偏大', cls: 'warn' }; const T = { toResult: 1300 }; // 需 ≥ 所有合成动画收尾(bloom .3+.9=1.2s / scan .1+1.15=1.25s);reduced 时快进 const ROOM_WALL_CM = 226; const ROOM_DIST_CM = 280; function RoomApp({ onBack, onComplete, initialTv, initialMount, standSelection, initialPhase: phaseOverride }) { const phaseParam = q('rp'); const initialPhase = phaseOverride || (G ? 'gen' : S2 ? 'result' : ['scan', 'place', 'gen', 'result'].includes(phaseParam) ? phaseParam : 'capture'); const roomModels = initialTv ? [{ id: `agent-${initialTv.id}`, name: `${initialTv.brand} ${initialTv.model}`, short: initialTv.model, panel: initialTv.panel }, ...MODELS.filter((m) => !`${initialTv.brand} ${initialTv.model}`.includes(m.short))] : MODELS; const sizeChoices = initialTv && !SIZES.includes(initialTv.size) ? [...SIZES, initialTv.size].sort((a, b) => a - b) : SIZES; const [phase, setPhase] = useState(initialPhase); // capture | scan | place | gen | result const [size, setSize] = useState(() => { if (initialTv && Number.isFinite(initialTv.size)) return initialTv.size; const sz = parseFloat(q('sz')); // 深链尺寸吸附到最近档位:无效值回 REF,避免 NaN 传遍派生值 return Number.isFinite(sz) ? SIZES.reduce((a, b) => Math.abs(b - sz) < Math.abs(a - sz) ? b : a) : REF; }); const [midx, setMidx] = useState(0); const [mount, setMount] = useState(initialMount === 'floor' || initialMount === 'stand' || q('m') === 'stand' ? 'stand' : 'wall'); const [dist, setDist] = useState(ROOM_DIST_CM); const timers = useRef([]); const clear = () => { timers.current.forEach(clearTimeout); timers.current = []; }; useEffect(() => clear, []); /* 深链直入 scan 只用于展示扫描态:补上 scan→place 推进定时器,否则永久停在无按钮的扫描屏 */ useEffect(() => { if (initialPhase === 'scan') timers.current.push(setTimeout(() => setPhase('place'), REDUCE ? 360 : 1550)); }, []); const startScan = () => { if (phase !== 'capture') return; clear(); setPhase('scan'); timers.current.push(setTimeout(() => setPhase('place'), REDUCE ? 360 : 1550)); }; const makeAI = () => { if (phase !== 'place') return; clear(); setPhase('gen'); if (selectedRoomSrc) { const startedAt = Date.now(); let settled = false; const finish = () => { if (settled) return; settled = true; const minWait = REDUCE ? 320 : T.toResult; timers.current.push(setTimeout(() => setPhase('result'), Math.max(0, minWait - (Date.now() - startedAt)))); }; const preloader = new Image(); preloader.onload = finish; preloader.onerror = finish; preloader.src = selectedRoomSrc; timers.current.push(setTimeout(finish, 5000)); } else { timers.current.push(setTimeout(() => setPhase('result'), REDUCE ? 320 : T.toResult)); } }; const reset = () => { clear(); setSize(initialTv && Number.isFinite(initialTv.size) ? initialTv.size : REF); setDist(ROOM_DIST_CM); setPhase('capture'); }; const model = roomModels[midx]; const angle = viewAngle(size, dist), angleFit = fitLabel(angle); const wCm = Math.round(size * 2.54 * 0.8716), hCm = Math.round(wCm * 9 / 16); const clearance = ROOM_WALL_CM - wCm; const fit = clearance < 16 ? { t: '墙面过满', cls: 'warn' } : angleFit; const aiShown = phase === 'gen' || phase === 'result'; const arShown = phase === 'place'; /* 预生成图只代表 85″ 与提示词里指定的支架颜色;其他组合回退到可缩放、可着色的实时摆放,避免图像与选择不一致。 */ const selectedRoomSrc = standSelection && size === REF && standSelection.style && standSelection.style.roomImage && standSelection.style.roomColor === standSelection.color.v ? standSelection.style.roomImage : null; const resultSrc = selectedRoomSrc || (standSelection ? IMG.orig : mount === 'wall' ? IMG.wall : IMG.stand); const arSource = mount === 'wall' ? IMG.wall : IMG.stand; // AR 电视:以样片实际尺寸(REF)为基准,按所选尺寸等比缩放;样片裁切只显示电视模型区域 const tvr = TVRECT[mount], k = size / REF; const fp = { left: tvr.cx - tvr.w * k / 2, top: tvr.cy - tvr.h * k / 2, width: tvr.w * k, height: tvr.h * k }; const crop = { backgroundImage: `url(${arSource})`, backgroundSize: `${10000 / tvr.w}% ${10000 / tvr.h}%`, backgroundPosition: `${((tvr.cx - tvr.w / 2) / (100 - tvr.w)) * 100}% ${((tvr.cy - tvr.h / 2) / (100 - tvr.h)) * 100}%`, }; // 回传 Agent 的结构化载荷(chip 只是它的可视摘要) const payload = { wall_width_cm: ROOM_WALL_CM, size_in: size, width_cm: wCm, mount, model_id: model.id, panel: model.panel, distance_cm: dist, view_angle: angle, fit: fit.t, mount_selection: standSelection ? { style: standSelection.style.name, lift: standSelection.lift, power: standSelection.power, color: standSelection.color.label } : null, }; const mountName = standSelection ? standSelection.style.name : mount === 'wall' ? '挂墙' : '底座'; const chips = [`墙宽 ${ROOM_WALL_CM}cm`, `${size}″`, model.panel, mountName, `${(dist / 100).toFixed(1)}m`, fit.t]; const resultTitle = clearance < 0 ? `${size}″ 已超出电视墙宽度` : clearance < 16 ? `${size}″ 能放,但安装余量太小` : `${size}″ 放得下,观看尺寸${fit.t === '推荐' ? '正合适' : '需要再斟酌'}`; const step = phase === 'capture' || phase === 'scan' ? 0 : phase === 'place' ? 1 : 2; const phaseLabel = { capture: '量空间', scan: '扫描中', place: '看大小', gen: '生成中', result: '看效果', }[phase]; return (
{/* 顶栏 */}
{onBack && }
在我家试电视
{phaseLabel}
{['量空间', '看大小', '看效果'].map((label, i) => (
{i < step ? : i + 1}{label}
))}
{/* 照片卡 */}
客厅原图 {standSelection ? aiShown && {standSelection.style.name : 挂墙 AI 效果 底座 AI 效果 } {phase === 'capture' && (
镜头对准电视墙 · 缓慢移动
)} {phase === 'scan' && (
电视墙净宽 {ROOM_WALL_CM} cm 观看距离 {(ROOM_DIST_CM / 100).toFixed(1)} m
)} {phase === 'gen' && ( )} {(arShown || (phase === 'result' && standSelection && !selectedRoomSrc)) && ( {arShown &&
墙宽 {ROOM_WALL_CM}cm视距 {(dist / 100).toFixed(1)}m
}
{arShown && } {mount === 'stand' && } {arShown && {size}″ · {wCm}×{hCm}cm}
{arShown &&
已吸附电视墙
}
)} {phase === 'result' && ( {/* AI 结果仍保留实测尺寸框,确保精细效果不丢尺度可信度 */} {!selectedRoomSrc &&
{size}″ · {wCm}×{hCm}cm
}
{size}″ ≈ 视角 {angle}° {fit.t}
)} {standSelection && (arShown || (phase === 'result' && !selectedRoomSrc)) && mount === 'stand' && ( )} {standSelection && (arShown || phase === 'result') && (
{standSelection.style.name} · {standSelection.color.label}
)}
正在精细合成 {model.short}
{/* 扫描条:AR 量房成为主流程,不再是旁路入口 */}
{phase === 'capture' ? (
先量一下,后面看到的大小才是真的
) : (
正在建立客厅空间ARKit
墙面平面 已识别空间尺度 校准中
)}
{/* 实时摆放:便宜环,尺寸/机型/安装方式都即时变化 */}
电视墙净宽{ROOM_WALL_CM} cm 观看距离{(dist / 100).toFixed(1)} m 当前适配{fit.t}
{initialTv ? (
电视已锁定{model.short} 支架已锁定{mountName}
) :
{[['wall', '挂墙', 'monitor'], ['stand', '底座', 'layers']].map(([kk, l, ic]) => ( ))}
{roomModels.map((m, i) => ( ))}
}
目标尺寸{size}″ · {wCm}×{hCm}cm
{initialTv ?
已按当前候选锁定 {size}″
:
{sizeChoices.map((s) => )}
}
沙发距离
{DISTS.map((d) => )}
视角 {angle}° {fit.t}
回传 Agent
{chips.map((c) => {c})}
重新量 生成精细效果
{/* AI 精细结果:昂贵环只在用户确认 AR 摆放后触发 */}
{resultTitle}{model.name} · {standSelection ? standSelection.style.name : mount === 'wall' ? '挂墙' : '底座摆放'}方案
墙面余量{Math.max(0, clearance)} cm 观看视角{angle}° 尺度来源AR 实测
{standSelection &&
{standSelection.style.name
本次合成支架{standSelection.style.name} · {standSelection.color.label}{standSelection.lift} · {standSelection.power}
}
回传 Agent
{chips.map((c) => {c})}
setPhase('place')}>调整摆放 onComplete && onComplete(payload)}>{onComplete ? '保存并回到对话' : '保存这套方案'}
); } /* 旧名称保留给历史深链/评审页,产品入口统一使用 RoomApp。 */ const ComposeApp = RoomApp; /* ============ 采光 · 测光(前台工具 #3) ============ 两轴分离:照度轴 = 锁曝光 EV 反推 lux(快测=估算档,补齐三时段=可信档); 反光轴 = AI 识别光源位置(对屏/侧向/顶部)。两轴合成抗反与亮度建议。 */ const LMIN = 30, LMAX = 3000; const logT = (v) => Math.min(1, Math.max(0, (Math.log(v) - Math.log(LMIN)) / (Math.log(LMAX) - Math.log(LMIN)))); const lux = (ev) => Math.round(2.5 * Math.pow(2, ev)); // ISO 2720:E ≈ 2.5·2^EV(ISO100) const SLOTS = [ { k: 'day', l: '白天', ic: 'sun', ev: 7.0 }, { k: 'dusk', l: '傍晚', ic: 'sunset', ev: 4.5 }, { k: 'night', l: '夜晚', ic: 'moon', ev: 3.8 }, ]; /* agent 识别出的反光源(box 为照片内百分比,按真实样片标注) */ const LSRC = [ { id: 'win2', ph: 2, n: '对屏侧窗', risk: 'hi', box: { x: 66, y: 10, w: 27, h: 52 } }, { id: 'lamp2', ph: 2, n: '落地灯', risk: 'mid', box: { x: 1, y: 25, w: 11.5, h: 38 } }, { id: 'spot2', ph: 2, n: '筒灯×2', risk: 'mid', box: { x: 12.5, y: 1.5, w: 11.5, h: 8.5 } }, { id: 'win1', ph: 1, n: '侧窗', risk: 'mid', box: { x: 0.6, y: 8, w: 20, h: 50 } }, { id: 'spot1', ph: 1, n: '筒灯×3', risk: 'mid', box: { x: 43, y: 1.5, w: 21, h: 11.5 } }, { id: 'cove1', ph: 1, n: '灯带', risk: 'lo', box: { x: 26, y: 6.5, w: 71, h: 6 } }, ]; const RISKL = { hi: '高', mid: '中', lo: '低' }; function ABox({ s, i }) { const b = s.box; return (
{s.n} · {RISKL[s.risk]}
); } function LightApp({ onBack }) { const LPS = ['turn', 'cap2', 'ana', 'result']; const lp0 = LPS.includes(q('lp')) ? q('lp') : 'cap1'; const [phase, setPhase] = useState(lp0); // cap1 | m1 | turn | cap2 | m2 | ana | result const [ev, setEv] = useState(null); const [pv, setPv] = useState(q('pv') === '1' ? 1 : 2); // 结果页当前照片 const [busySlot, setBusySlot] = useState(null); const [meas, setMeas] = useState(() => { const m = { day: null, dusk: null, night: null }; if (lp0 !== 'cap1') m.day = lux(SLOTS[0].ev); if (q('slots') === '3') SLOTS.forEach((s) => { m[s.k] = lux(s.ev); }); return m; }); const timers = useRef([]); const T2 = (fn, ms) => timers.current.push(setTimeout(fn, REDUCE ? Math.min(ms, 240) : ms)); useEffect(() => () => timers.current.forEach(clearTimeout), []); // 锁曝光测光:EV 读数 ease-out 逼近目标,落点回调(clearTimeout 亦可清 interval) const runMeter = (target, done) => { let i = 0; const N = REDUCE ? 4 : 18; setEv(Math.round((target - 3.6) * 10) / 10); const id = setInterval(() => { i++; const k = 1 - i / N; setEv(Math.round((target - 3.6 * k * k) * 10) / 10); if (i >= N) { clearInterval(id); done(); } }, 46); timers.current.push(id); }; const shoot1 = () => { if (phase !== 'cap1') return; setPhase('m1'); runMeter(SLOTS[0].ev, () => { setMeas((m) => ({ ...m, day: lux(SLOTS[0].ev) })); T2(() => { setEv(null); setPhase('turn'); }, 460); }); }; const shoot2 = () => { if (phase !== 'cap2') return; setPhase('m2'); runMeter(7.4, () => T2(() => { setEv(null); setPhase('ana'); T2(() => setPhase('result'), REDUCE ? 320 : 1450); }, 400)); }; const reset = () => { timers.current.forEach(clearTimeout); timers.current = []; setMeas({ day: null, dusk: null, night: null }); setEv(null); setPv(2); setBusySlot(null); setPhase('cap1'); }; const fillSlot = (k) => { if (meas[k] != null || busySlot) return; setBusySlot(k); T2(() => { setMeas((m) => ({ ...m, [k]: lux(SLOTS.find((s) => s.k === k).ev) })); setBusySlot(null); }, 900); }; const filled = SLOTS.filter((s) => meas[s.k] != null); const cred = filled.length === 3; // 置信阶梯:3 时段 → 可信档 const dayLux = meas.day ?? lux(SLOTS[0].ev); const band = [Math.round(dayLux / 1.414), Math.round(dayLux * 1.414)]; // ±半档 EV const vmin = Math.min(...filled.map((s) => meas[s.k])), vmax = Math.max(...filled.map((s) => meas[s.k])); // 二维结论:反光轴(光源位置)× 照度轴(白天上限) const hiG = LSRC.some((s) => s.risk === 'hi'), midG = LSRC.some((s) => s.risk === 'mid'); const arV = hiG ? '必要' : midG ? '建议' : '非必需'; const brV = dayLux > 200 ? '峰值 ≥1000 nits' : dayLux > 100 ? '峰值 ≥600 nits' : '无高亮需求'; // 回传 Agent 的结构化摘要(工具核心契约:data.jsx TOOLS_FRONT.ret 承诺的返回项) const chips = [cred ? `全天 ${vmin}–${vmax}lx·可信档` : `白天 ${dayLux}lx·估算档`, '对屏侧窗·高', `抗反·${arV}`, dayLux > 200 ? '≥1000nits' : brV]; const capturing = ['cap1', 'm1', 'turn', 'cap2', 'm2'].includes(phase); const img2on = phase === 'result' ? pv === 2 : ['cap2', 'm2', 'ana'].includes(phase); const bg = img2on ? (IMG.rev || IMG.orig) : IMG.orig; const wrapCls = phase === 'result' ? 'p-lresult' : phase === 'ana' ? 'p-gen' : 'p-capture'; const stepT = { cap1: '测光①', m1: '测光①', turn: '转身', cap2: '测光②', m2: '测光②', ana: '识别中', result: '结果' }[phase]; return (
{onBack && }
采光 · 环境光检测
{stepT}
朝电视墙
{IMG.rev ? 朝沙发方向 :
「朝沙发方向」样片待生成prompts/01-scene-reverse-sofa.md
生成后放 aiimage/朝沙发.png,跑 python _mkimg.py
}
{(phase === 'cap1' || phase === 'cap2') && (
{phase === 'cap1' ? '① 朝电视墙 · 含吸顶灯' : '② 对屏方向 · 含窗与灯'}
)} {(phase === 'm1' || phase === 'm2') && (
AE-L · EV {(ev ?? 0).toFixed(1)} ≈ {lux(ev ?? 0)} lx
)} {phase === 'turn' && (
setPhase('cap2')}>
转身 180°
屏幕反射的是它对面的东西——窗、落地灯都在你身后这一侧,拍下来交给 AI 认
{ e.stopPropagation(); setPhase('cap2'); }}>已转身 · 拍对屏方向
)} {phase === 'ana' && ( )} {phase === 'result' && LSRC.filter((s) => s.ph === pv).map((s, i) => )} {phase === 'result' && (
{[[1, '① 电视墙'], [2, '② 对屏']].map(([n, l]) => ( ))}
)}
AI 正在识别反光源与位置…
{/* 拍摄条 */}
{/* 结果面板 */}
{cred ? `${vmin}–${vmax} lx` : `${dayLux} lx`}
{cred ? '全天照度跨度 · 三时段实测' : `白天快测 · 约 ${band[0]}–${band[1]} lx(±半档 EV)`}
{cred ? '可信档 · 3/3' : `估算档 · ${filled.length}/3 时段`}
{!cred &&
} {[50, 200].map((v) => )} {[['暗', 40], ['中', 100], ['亮', 700]].map(([l, v]) => {l} {l === '暗' ? '<50' : l === '中' ? '50–200' : '>200'})} {filled.map((s) => )}
{SLOTS.map((s) => ( ))}
反光源 · AI 识别 {LSRC.length} 处
{LSRC.map((s) => ( {s.n}照片{s.ph === 1 ? '①' : '②'}·{RISKL[s.risk]} ))}
抗反涂层{arV}
亮度需求{brV}
白天 >200 lx,且对屏方向有大面积侧窗直射入屏——光面屏会出现清晰镜面反射{cred ? `;夜晚 ${meas.night} lx 属暗房,OLED 黑位受益` : ''}
海信 E8S · 高峰值亮度 LG G5 · 抗反膜 Sony B8 II · 光面屏慎选
照度=锁曝光 EV 反推(估算,非照度计);反光=光源位置判定,非实测眩光
回传 Agent
{chips.map((c) => {c})}
重测 满意 · 存入清单
); } /* ============ 一键验机:五步陪验 + 双屏测试 + 证据化结论 ============ */ const INSPECT_STEPS = [ { n: '型号与包装', icon: 'scan' }, { n: '外观取证', icon: 'camera' }, { n: '屏幕检测', icon: 'grid' }, { n: '开机与遥控', icon: 'bolt' }, { n: '签收结论', icon: 'check' }, ]; const SCREEN_TESTS = [ { n: '白', c: '#f7f9ff' }, { n: '黑', c: '#08090c' }, { n: '红', c: '#e51d2a' }, { n: '绿', c: '#31bd3e' }, { n: '蓝', c: '#1458e8' }, { n: '灰', c: '#a5a9b0' }, ]; const CAST_PROTOCOLS = [ { id: 'airplay', n: 'AirPlay', icon: 'broadcast' }, { id: 'dlna', n: 'DLNA', icon: 'wifi' }, ]; const APPEARANCE_EVIDENCE = [ { n: '正面整机', image: 'inspect-screen.webp', alt: '电视正面整机与边框取证' }, { n: '背板与壁挂孔', image: 'inspect-back-panel.webp', alt: '电视背板与壁挂孔取证' }, { n: '背板接口', image: 'inspect-ports.webp', alt: '电视 HDMI USB 网口等背板接口取证' }, { n: '附件清点', image: 'inspect-accessories.webp', alt: '遥控器底座电源线与配件清点' }, ]; const BOOT_CHECKS = [ { n: '冷启动正常', icon: 'bolt' }, { n: '遥控器响应', icon: 'turn' }, { n: '扬声器有声', icon: 'activity' }, { n: 'HDMI 1 可用', icon: 'plug' }, ]; function InspectApp({ onBack }) { const shortcut = q('ip'); const initialStep = shortcut === 'result' ? 4 : shortcut === 'boot' ? 3 : shortcut === 'screen' ? 2 : shortcut === 'appearance' ? 1 : 0; const [step, setStep] = useState(initialStep); const [labelScanned, setLabelScanned] = useState(initialStep > 0); const [appearanceShots, setAppearanceShots] = useState(initialStep > 1 ? APPEARANCE_EVIDENCE.length : 0); const [screenConnected, setScreenConnected] = useState(initialStep > 2); const [castProtocol, setCastProtocol] = useState('airplay'); const [castConnecting, setCastConnecting] = useState(false); const [screenTest, setScreenTest] = useState(initialStep > 2 ? SCREEN_TESTS.length : 0); const [bootChecks, setBootChecks] = useState(initialStep > 3 ? BOOT_CHECKS.map(() => true) : BOOT_CHECKS.map(() => false)); const [issues, setIssues] = useState([]); // 多处异常并存:外观磕碰与开机故障可同时写入报告 const [reportReached, setReportReached] = useState(initialStep === 4); // 报告一旦生成就保持可回看,不随离开步骤 5 失效 const [busy, setBusy] = useState(false); const [saved, setSaved] = useState(false); const [cloudBackup, setCloudBackup] = useState(true); const timer = useRef(null); useEffect(() => () => clearTimeout(timer.current), []); useEffect(() => { if (step === 4) setReportReached(true); }, [step]); const stepDone = [labelScanned, appearanceShots >= APPEARANCE_EVIDENCE.length, screenTest >= SCREEN_TESTS.length, bootChecks.every(Boolean), reportReached || step === 4]; const completed = stepDone.filter(Boolean).length; const remaining = ['约 5 分钟', '约 4 分钟', '约 3 分钟', '约 1 分钟', '已完成'][step]; const scanLabel = () => { setBusy(true); clearTimeout(timer.current); timer.current = setTimeout(() => { setLabelScanned(true); setBusy(false); }, REDUCE ? 120 : 650); }; const nextAppearance = () => setAppearanceShots((n) => Math.min(APPEARANCE_EVIDENCE.length, n + 1)); const toggleBoot = (i) => setBootChecks((old) => old.map((v, j) => j === i ? !v : v)); const recordIssue = (text) => setIssues((old) => old.includes(text) ? old.filter((t) => t !== text) : [...old, text]); const addIssue = (text) => setIssues((old) => old.includes(text) ? old : [...old, text]); const connectScreen = () => { setCastConnecting(true); clearTimeout(timer.current); timer.current = setTimeout(() => { setScreenConnected(true); setCastConnecting(false); }, REDUCE ? 120 : 720); }; const reset = () => { clearTimeout(timer.current); setStep(0); setLabelScanned(false); setAppearanceShots(0); setScreenConnected(false); setCastProtocol('airplay'); setCastConnecting(false); setScreenTest(0); setBootChecks(BOOT_CHECKS.map(() => false)); setIssues([]); setReportReached(false); setBusy(false); setSaved(false); setCloudBackup(true); }; const chooseBackup = (value) => { setCloudBackup(value); setSaved(false); }; const PackageStep = () => (
海信电视包装标签 {busy ? '正在识别标签' : '请对准包装标签'} {busy && }
{labelScanned && (
海信 85 E8S 85 英寸 H85E8S2407 与订单一致
)} {!labelScanned ? {busy ? '识别中…' : '扫描包装标签'} : setStep(1)}>确认一致,下一步 }
); const AppearanceStep = () => { const current = APPEARANCE_EVIDENCE[Math.min(appearanceShots, APPEARANCE_EVIDENCE.length - 1)]; return (
{current.alt} {current.n}入镜
{APPEARANCE_EVIDENCE.map((item, i) => ( {item.n}{i < appearanceShots ? '已保存' : i === appearanceShots ? '待拍摄' : '稍后'} ))}
{appearanceShots < APPEARANCE_EVIDENCE.length ? 拍摄{APPEARANCE_EVIDENCE[appearanceShots].n} : setStep(2)}>完成外观取证,下一步}
); }; const ScreenStep = () => { const current = SCREEN_TESTS[Math.min(screenTest, SCREEN_TESTS.length - 1)]; const cast = CAST_PROTOCOLS.find((item) => item.id === castProtocol); return (
{!screenConnected ? (
1
在电视打开无线投屏停留在投屏接收界面
本机网页 {cast.n} 客厅电视
{CAST_PROTOCOLS.map((item) => )}
客厅电视 · 海信 85 E8S同一 Wi-Fi
已发现
{castConnecting ? `正在通过 ${cast.n} 连接…` : '投到客厅电视'}
) : (
客厅电视 · 海信 85 E8S{cast.n} 已连接
网页信号发生器{screenTest < SCREEN_TESTS.length ? `${current.n}色测试图正在电视全屏显示` : '六色测试信号已完成'}
{screenTest < SCREEN_TESTS.length ? '投放中' : '已完成'}
电视纯色屏幕检测 {screenTest < SCREEN_TESTS.length && } 坏点 0亮点 0 {screenTest > 0 && 已保存原图}
{screenTest < SCREEN_TESTS.length ? `${screenTest + 1} / 6 ${current.n}色检测` : '6 / 6 检测完成'}逐张留证
{SCREEN_TESTS.map((test, i) => )}
{screenTest < SCREEN_TESTS.length ? setScreenTest((n) => Math.min(SCREEN_TESTS.length, n + 1))}>拍摄{current.n}色画面 : setStep(3)}>完成屏幕检测,下一步}
)}
); }; const BootStep = () => (
电视开机并使用遥控器测试音量与响应 按遥控,确认电视响应 开机留证
请逐项实际确认只有你点过的项目才会写入报告
{BOOT_CHECKS.map((item, i) => ( ))}
{/* 已记录异常也能生成结论(走"建议现场复查"),不逼用户违心把故障项点成正常 */} setStep(4)}>生成签收结论
); const ResultStep = () => { const hasIssue = issues.length > 0; return (
签收建议{hasIssue ? '建议现场复查' : '可以签收'}
{hasIssue &&
{issues.join(';')}异常已写入报告,请让送装人员现场确认
}
型号与包装{issues.includes('型号与订单不一致') ? '不一致 · 1 张证据' : '一致 · 1 张证据'} 外观取证{appearanceShots ? `${appearanceShots} 张原图` : '未执行'} 屏幕检测{screenTest ? `${screenTest} 组测试` : '未执行'} 开机与遥控{bootChecks.some(Boolean) ? `1 张证据 · ${bootChecks.filter(Boolean).length} 项确认` : '未执行'}
海信 85 E8SH85E8S2407
验机数据是否上传先看数据库进行云备份?
setSaved(true)}>{saved ? (cloudBackup ? '已上传并保存' : '已保存到本机') : (cloudBackup ? '上传并保存报告' : '保存报告到本机')} {saved && cloudBackup && (
端到端非对称加密已启用

先看将对序列号等敏感信息采用端到端非对称加密,哪怕是我们也无法获取具体内容

)}
); }; /* 步骤视图是组件体内定义的函数:以函数调用渲染(而非 元素), 否则每次 setState 都生成新组件类型,React 会整棵卸载重建当前步骤(焦点丢失、动画重播、图片重解码) */ const renderStep = () => step === 0 ? PackageStep() : step === 1 ? AppearanceStep() : step === 2 ? ScreenStep() : step === 3 ? BootStep() : ResultStep(); return (
{onBack && }
一键验机
{remaining}
0 ? 'verified' : '')}> 海信 85 E8S
{labelScanned || step > 0 ? '海信 85 E8S' : '待核对电视型号'}{labelScanned || step > 0 ? '序列号 H85E8S2407' : '先扫描包装标签'}
0 ? 'check' : 'scan'} size={18} />
{completed}/ 5 已完成
{INSPECT_STEPS.map((item, i) => )}
{INSPECT_STEPS.map((item, i) => { const done = stepDone[i]; const active = i === step; const reviewable = done && !active; return (
{active &&
{renderStep()}
}
); })}
); } /* ============ 一键最佳设置:机型 + 设备 + 场景 → 设置卡 + 线材购买 ============ */ const SETUP_SOURCES = [ { id: 'native', n: '内置应用', icon: 'tv', verdict: '不用买线', cable: '电视直接播放', buy: false }, { id: 'console', n: 'PS5 / Xbox', icon: 'bolt', verdict: '先别买', cable: '优先用主机原装 HDMI 线', buy: false }, { id: 'box', n: '电视盒子', icon: 'cube', verdict: '先别买', cable: '优先用盒内 HDMI 线', buy: false }, { id: 'pc', n: '游戏电脑', icon: 'monitor', verdict: '买 1 根', cable: '超高速 HDMI 2.1 · 2米', buy: true }, ]; const SETUP_SCENES = { movie: { n: '电影', light: '自动', motion: '关闭' }, game: { n: '游戏', light: '自动 HDR', motion: 'VRR 开' }, sport: { n: '体育', light: '65', motion: '中' }, day: { n: '白天', light: '75', motion: '低' }, }; const SETUP_TUNING = { e8s: { modes: ['电影', '游戏', '体育', '标准'], warm: '暖 2' }, g5: { modes: ['FILMMAKER', '游戏优化器', '标准', '影院主页'], warm: '暖 50' }, sny: { modes: ['专业', '游戏', '标准', '自定义'], warm: '专家 1' }, tcl: { modes: ['电影', '游戏大师', '体育', '标准'], warm: '暖色' }, }; function SetupApp({ onBack }) { const [phase, setPhase] = useState(q('sp') === 'result' ? 'result' : 'pick'); const [midx, setMidx] = useState(0); /* 深链值先校验再入 state:未知值直接回默认,避免选中态失联;hasOwnProperty 挡掉原型链键(?scene=constructor) */ const [sourceId, setSourceId] = useState(() => { const v = q('src'); return SETUP_SOURCES.some((s) => s.id === v) ? v : 'console'; }); const [sceneId, setSceneId] = useState(() => { const v = q('scene'); return v && Object.prototype.hasOwnProperty.call(SETUP_SCENES, v) ? v : 'game'; }); const [added, setAdded] = useState(false); const [saved, setSaved] = useState(false); const source = SETUP_SOURCES.find((s) => s.id === sourceId) || SETUP_SOURCES[1]; const scene = SETUP_SCENES[sceneId] || SETUP_SCENES.game; const model = MODELS[midx]; const tuning = SETUP_TUNING[model.id]; const sceneIndex = ['movie', 'game', 'sport', 'day'].indexOf(sceneId); const settings = [ ['图像模式', tuning.modes[sceneIndex < 0 ? 1 : sceneIndex]], ['屏幕亮度', scene.light], ['色温', tuning.warm], ['动态', scene.motion], ]; const make = () => { setAdded(false); setSaved(false); setPhase('result'); }; const reset = () => { setAdded(false); setSaved(false); setPhase('pick'); }; return (
{onBack && }
一键最佳设置
{phase === 'pick' ? '选择场景' : '设置卡'}
{scene.n}
{model.short}
{phase === 'pick' ? (
电视型号
{MODELS.map((m, i) => )}
连接设备
{SETUP_SOURCES.map((s) => ( ))}
主要场景
{Object.entries(SETUP_SCENES).map(([id, s]) => )}
生成最佳设置
) : (
{model.short} · {scene.n}
{settings.map(([k, v]) => {k}{v})}
线材购买{source.verdict}{source.cable}
{source.buy && }
重选 setSaved(true)}>{saved ? '设置卡已保存' : '保存设置卡'}
)}
); } /* ============ stage ============ */ const APPMETA = { room: { t: '在我家试电视 · 移动端', p: '一条任务链完成 AR 量房、真实比例 3D 摆放与 AI 精细合成;房间是持久锚点,尺寸和机型可即时替换。', foot: '评审深链:?app=room&rp=place 实时摆放 · &rp=result 结果 · &m=stand 底座 · &sz=75 指定尺寸', }, light: { t: '采光 · 环境光检测 · 移动端', p: '两拍测两轴:朝电视墙锁曝光反推照度(快测=估算档,补齐傍晚 / 夜晚升可信档);转身拍对屏方向,AI 识别反光源。合成抗反与亮度建议,回传 Agent。', foot: '评审深链:?app=light&lp=result(&slots=3 补满时段 · &pv=1 电视墙标注)', }, inspect: { t: '一键验机 · 移动端', p: '一次拍摄完成外观、屏幕、接口与开机检查,直接给出是否签收。', foot: '评审深链:?app=inspect&ip=result', }, setup: { t: '一键最佳设置 · 移动端', p: '按机型、连接设备和观看场景生成设置卡,同时判断线材是否需要购买。', foot: '评审深链:?app=setup&sp=result&src=pc&scene=game', }, }; function Stage() { const app0 = ['room', 'light', 'inspect', 'setup'].includes(q('app')) ? q('app') : 'room'; const [app, setApp] = useState(app0); const pick = (a) => { if (a === app) return; const u = new URL(location); ['app', 'lp', 'slots', 'pv', 's', 'g', 'rp', 'm', 'sz', 'ip', 'sp', 'src', 'scene'].forEach((k) => u.searchParams.delete(k)); u.searchParams.set('app', a); history.replaceState(null, '', u); setApp(a); }; const M = APPMETA[app]; return (
{q('from') === 'home' && ( 主页 )}

{M.t}

{M.p}

Liquid Glass 一镜到底动效 前台交互工具
{app === 'light' ? : app === 'inspect' ? : app === 'setup' ? : }
{M.foot}
); } /* 供 电视数据库主页(home.jsx) 壳内嵌入调用;ComposeApp 只保留历史兼容 */ Object.assign(window, { RoomApp, ComposeApp, LightApp, InspectApp, SetupApp }); if (!window.__TV_EMBED) ReactDOM.createRoot(document.getElementById('root')).render();