# 🏗️ AgentGIS 前端约定 > **目的**: 减少每个子系统在前端上的反复调整,一次定规矩,后续照做。 > **适用**: suite-market、auth-center、discussions 等所有 Next.js 前端。 > **版本**: v1.0 (2026-07-21) --- ## 一、页面骨架 所有页面使用统一的三段式骨架布局,不另起炉灶: ``` ┌──────────────────────────────────────────────┐ │ 顶部导航(Navbar) │ │ 套件 | 发布 | 管理 | 文档 [登录/用户头像] │ ├────────┬─────────────────────────────────────┤ │ 左侧 │ 右侧内容区 │ │ 导航 │ (各页面自定) │ │ │ │ │ (各页面 │ │ │ 自定) │ │ └────────┴─────────────────────────────────────┘ └────────────── 页脚(Footer) ──────────────────┘ ``` **规则:** - 顶部导航统一在 `Navbar.tsx` 维护,不单独定制 - 左侧导航通过页面级 `layout.tsx` 注入(Next.js App Router) - 页脚全局统一 - 不出现面包屑(用左侧导航替代) --- ## 二、未认证状态 所有需要登录的页面,未认证时**必须**做到: ``` ┌─────────────────────────────────────┐ │ 🔒 请登录后浏览 │ │ │ │ 登录后可查看套件、文档、管理后台等 │ │ │ │ (不显示登录按钮 — header 右上角有) │ └─────────────────────────────────────┘ ``` **规则:** - 使用统一 `LoginPrompt.tsx` 组件,不自制 - 不显示登录按钮/链接(header 右上角已有「登录」按钮) - API 调用返回 401 时,前端必须捕获并切换到 LoginPrompt,**不能卡在加载中** - 公开页面(首页等)不需登录,正常渲染 --- ## 三、数据加载与错误处理 所有页面按此顺序处理状态: ``` 加载中 → 出错 → 空数据 → 正常渲染 ``` **规则:** - 加载中:显示统一的「加载中...」骨架屏 - 出错(含 401):捕获错误,切换 LoginPrompt 或显示错误信息 - 空数据:显示「暂无内容」+ 引导文案 - 正常渲染:展示数据 - 不允许出现「永远卡在加载中」的情况 **所有 API 调用都必须有 `.catch()`:** ```typescript // ✅ 正确 fetchData().then(setData).catch(handleError) // ❌ 错误 fetchData().then(setData) // 无 catch = 卡在加载中 ``` --- ## 四、目录结构 每个 Next.js 前端项目统一按以下结构组织: ``` frontend/ ├── app/ # App Router 页面 │ ├── layout.tsx # 全局布局(Navbar + Footer) │ ├── page.tsx # 首页 │ ├── suites/ # 套件 │ │ ├── layout.tsx # 套件左侧导航 │ │ ├── page.tsx # 列表 │ │ └── [id]/page.tsx # 详情 │ ├── docs/ # 文档 │ │ ├── layout.tsx # 文档树形导航 │ │ ├── page.tsx │ │ └── [slug]/page.tsx │ ├── admin/ # 管理后台 │ │ ├── layout.tsx # 管理侧边栏 │ │ ├── applications/ │ │ ├── suites/ │ │ └── docs/ │ └── publish/ │ ├── components/ # 共享组件 │ ├── Navbar.tsx # 顶部导航 │ ├── LayoutShell.tsx # 骨架布局 │ ├── LoginPrompt.tsx # 未登录提示 │ └── ... # 其他通用组件 │ ├── lib/ # 工具库 │ ├── api.ts # API 封装(含统一 401 处理) │ ├── auth.ts # 认证 │ └── auth-context.tsx # 认证上下文 │ ├── public/ ├── package.json └── next.config.ts ``` **规则:** - `app/` 下只放路由文件,不放组件 - 业务组件放 `components/` - 工具函数放 `lib/` --- ## 五、API 401 拦截器 `lib/api.ts` 中的 axios 实例统一处理 401: ```typescript api.interceptors.response.use( (res) => res, async (err) => { if (err.response?.status === 401 && typeof window !== "undefined") { // 公开页面:静默返回错误,让页面组件自己处理 const path = window.location.pathname; if (path === "/" || path.startsWith("/suites") || path.startsWith("/docs")) { return Promise.reject(err); } // 管理类页面:尝试刷新 token try { const refreshRes = await axios.post( "https://auth.mercator.cn/api/v1/public/refresh/", {}, { withCredentials: true } ); if (refreshRes.data?.access_token) { setAccessToken(refreshRes.data.access_token); err.config.headers["Authorization"] = "Bearer " + refreshRes.data.access_token; return axios(err.config); } } catch {} // 刷新失败:跳转登录 window.location.href = "https://auth.mercator.cn/login?return_url=" + encodeURIComponent(path); } return Promise.reject(err); } ); ``` **规则:** - 公开页面(suites、docs)的 401 → 静默返回,页面组件切换到 LoginPrompt - 管理页面(admin、publish)的 401 → 尝试刷新 token,刷新失败跳转登录 - 不拦截器里弹 alert 或直接修改 DOM --- ## 六、深色/浅色主题 - 使用 Tailwind `dark:` 变体控制深色模式 - 通过 `ThemeToggle` 组件切换(位于 `components/`) - 默认跟随系统 `prefers-color-scheme` - 切换结果存入 localStorage,下次加载优先读取 - 所有新组件必须同时测试浅色和深色模式 --- ## 七、页面宽度与容器尺寸 统一使用以下预设宽度: | 用途 | Tailwind class | 说明 | |------|---------------|------| | 最宽布局 | `max-w-7xl mx-auto` | 列表页、管理后台 | | 内容页(窄) | `max-w-3xl mx-auto` | 详情页、阅读页 | | 内容页(中) | `max-w-5xl mx-auto` | 文档列表、套件详情 | | 表单 | `max-w-lg mx-auto` | 登录、设置 | | 全宽 | `max-w-none` | 极少使用 | **不允许:** 每个页面自定义宽度,导致用户在页面间切换时视觉跳跃。 --- ## 八、移动端适配 - 使用 Tailwind 响应式前缀:`sm:`(640px)、`md:`(768px)、`lg:`(1024px)、`xl:`(1280px) - 导航栏在 `lg` 断点以下折叠为汉堡菜单(已实现) - 左侧导航在 `md` 以下默认隐藏,通过按钮切换显示 - 表格在 `md` 以下切换为卡片视图(每行一张卡片) - 不允许仅桌面端可用的设计,所有页面必须跑通 375px 宽度 --- ## 九、通知(Toast 替代 alert/confirm) - 不使用浏览器原生的 `alert()`、`confirm()`、`prompt()` - 使用统一的 Toast 通知组件 - Toast 位置:右上角固定 - 类型:success(绿色)、error(红色)、warning(黄色)、info(蓝色) - 自动消失:success/info 3 秒,warning/error 5 秒 - 实现:用 react-hot-toast 或自建 `ToastProvider` --- ## 十、布局模板 按是否有侧边导航分两种布局: ### 带侧边导航 ``` ┌──────────┬────────────────────────────────┐ │ 顶部导航 │ │ ├──────────┤ 右侧内容区 │ │ 侧边栏 │ │ │ (页面 │ │ │ 自定) │ │ └──────────┴────────────────────────────────┘ ``` 适用:suites、docs、admin 实现方式:`/layout.tsx` ### 无侧边导航(全宽) ``` ┌────────────────────────────────────────────┐ │ 顶部导航 │ ├────────────────────────────────────────────┤ │ │ │ 居中内容区 │ │ │ └────────────────────────────────────────────┘ ``` 适用:/、/publish 实现方式:`/layout.tsx` 或直接在 page 中 `max-w-* mx-auto` --- ## 十一、FastAPI 端点避免冲突 suite-market 和 auth-center 后端都是 FastAPI,默认暴露 `/docs`(Swagger UI)、`/redoc`、`/openapi.json`。 如果前端有同名路由(如文档模块的 `/docs`),会与 FastAPI 默认端点冲突。 **必须:** ```python app = FastAPI( docs_url="/api/docs", # 改 /docs → /api/docs redoc_url="/api/redoc", # 改 /redoc → /api/redoc openapi_url="/api/openapi.json" # 改 /openapi.json → /api/openapi.json ) ``` --- ## 十二、Header/Footer 统一 - Header:统一使用 `Navbar.tsx`(components/ 下),不单独在各页面重复 - Footer:全局底部,包含 ICP 备案号、公安备案号、版权信息 - Header 和 Footer 在全局 `layout.tsx` 中加载,页面层不覆盖 --- ## 十三、组件命名 | 类型 | 命名规则 | 示例 | |------|---------|------| | 页面组件(app/) | PascalCase + Page 后缀 | `DocsPage`, `SuiteDetailPage` | | 布局(layout) | 文件名固定 `layout.tsx` | 不导出命名函数 | | 通用组件 | PascalCase | `LoginPrompt`, `SuiteCard` | | 工具函数 | camelCase | `fetchDocuments`, `formatDate` | | API 函数 | camelCase | `fetchDocuments`, `createDocument` | --- ## 六、认证 - header 右上角只有一个「登录」按钮 - 登录跳转到 `auth.mercator.cn` - 登录后通过 JWT cookie 回传 - API 调用由 `api.ts` 中的 axios 实例统一处理(interceptor 自动附带 token) **不允许:** - 页面内部额外加「登录」按钮或链接 - 手动拼写 `Authorization` header - 直接调用 `fetch` --- ## 七、样式 - 使用 Tailwind CSS - 基础样式在 `globals.css` 中定义 - 组件样式使用 Tailwind class,不单独写 CSS 文件 - 不使用 CSS Modules 或 styled-components --- ## 八、落地方式 1. 此文档纳入 `SuiteHub/agent-profiles/suites-help/` 作为平台规范 2. 新建前端页面时,开发者对照此文档逐一检查 3. Code Review 时以此文档为标准 4. 后续如有不合理之处,更新此文档(不改代码,改规矩)