# 静态展示页托管 pattern：登录可见 + 全屏无壳 + iframe 内嵌

**日期**：2026-05-12
**来源**：Issue #340（Allen EAI Robotics 组织架构 Netlify 页迁移）
**适用**：员工自己做完的成品 HTML 想挂到公司域名下，#332 内部 PaaS 上线前的临时承载

## 问题

外部协作者（Allen 这类非前端 dev）做了自带 CSS reset + 完整样式的单页 HTML，想挂到 FFAI Workspace 域名下。直接转成 JSX 会和 `globals.css` 串台（`*{box-sizing:border-box;margin:0;padding:0}` 这种全局重置会污染整站），且 Allen 后续改样式不会 React。

## 项目里现成的三件套

把外部静态页接进来的固定路径，照搬即可：

1. **静态文件放 `frontend/public/showcase-pages/<page-slug>.html`**
   - 路径用 `showcase-pages` 而不是 `showcase`，避免和下一步的 Next.js 路由 `/showcase/*` 撞 URL
   - 单文件 + 外部 CDN 字体最理想；带 asset 的话整个目录拷进去

2. **新建 Next.js 路由 `frontend/src/app/showcase/<page-slug>/page.tsx`**
   - `'use client'` + `useAuthGuard()` → 仅校验登录，不查权限
   - 渲染 fullscreen `<iframe src="/showcase-pages/<page-slug>.html" className="fixed inset-0 w-screen h-screen border-0" />`
   - iframe 同源 → 没有 CSP/X-Frame-Options 问题；隔离 CSS scope；Allen 后续可以直接 push HTML 文件不用懂 React

3. **在 [`frontend/src/components/layout/LayoutWrapper.tsx`](../frontend/src/components/layout/LayoutWrapper.tsx) 把路由加进 `isFullscreenPage` 判断**
   - 已有先例：`scan-inventory` / `siteattendance/c/*` 全屏页
   - 加一句 `const isShowcase = pathname.startsWith('/showcase/');` 然后塞进 `isFullscreenPage` 的 `||` 链

## 为什么不用 JSX 转译

- 外部页一般带 `*{margin:0}` 这种 CSS reset，进 React 树会污染整站
- 外部页用 `<style>` 内联自己的 CSS 变量（`--bg`、`--border` 等），跟项目的 Tailwind/shadcn 体系正交
- iframe 提供天然 scope 隔离，零代码改动
- 维护权交还给原作者：Allen 改 HTML 直接 push 就行

## 一致性验证（重要）

如果是从公网/Netlify 镜像过来的，必须做**字节级 diff**确认没漏内容：

```bash
curl -sS -L <source-url> -o /tmp/source.html
diff /tmp/source.html frontend/public/showcase-pages/<page-slug>.html
md5sum /tmp/source.html frontend/public/showcase-pages/<page-slug>.html
```

视觉验证用 Playwright MCP 各截一张同尺寸 viewport 截图，比 MD5：

```bash
md5sum netlify-orig.png local-static.png  # 应一致
```

`diff exit=0` + 两端 MD5 相同 = "数据完整一致" 的硬证据。

## 不适用场景

- 需要动态从后端拉数据 → 走正常 Next.js 页面 + API
- 需要 RBAC 权限粒度（不只是"登录"） → 走 `(modules)` 路由组用 layout 里的 `hasPermission()` 检查
- 长期归属 + 频繁迭代的产品页 → 跟 PM 对齐后建正式模块（`docs/modules/<module>/`）走完整文档流程，不要用 showcase
