323 lines
11 KiB
Markdown
323 lines
11 KiB
Markdown
# 🏗️ 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
|
||
实现方式:`<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 默认端点冲突。
|
||
|
||
**必须:**
|
||
|
||
```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. 后续如有不合理之处,更新此文档(不改代码,改规矩)
|