11 KiB
11 KiB
🏗️ 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():
// ✅ 正确
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:
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
实现方式:<page>/layout.tsx
无侧边导航(全宽)
┌────────────────────────────────────────────┐
│ 顶部导航 │
├────────────────────────────────────────────┤
│ │
│ 居中内容区 │
│ │
└────────────────────────────────────────────┘
适用:/、/publish
实现方式:<page>/layout.tsx 或直接在 page 中 max-w-* mx-auto
十一、FastAPI 端点避免冲突
suite-market 和 auth-center 后端都是 FastAPI,默认暴露 /docs(Swagger UI)、/redoc、/openapi.json。
如果前端有同名路由(如文档模块的 /docs),会与 FastAPI 默认端点冲突。
必须:
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)
不允许:
- 页面内部额外加「登录」按钮或链接
- 手动拼写
Authorizationheader - 直接调用
fetch
七、样式
- 使用 Tailwind CSS
- 基础样式在
globals.css中定义 - 组件样式使用 Tailwind class,不单独写 CSS 文件
- 不使用 CSS Modules 或 styled-components
八、落地方式
- 此文档纳入
SuiteHub/agent-profiles/suites-help/作为平台规范 - 新建前端页面时,开发者对照此文档逐一检查
- Code Review 时以此文档为标准
- 后续如有不合理之处,更新此文档(不改代码,改规矩)