跳转至

常见二次开发实例

以下示例描述当前前端推荐模式。示例中的 /reports 是占位业务路径,不代表后端已经提供该接口。

1. 新增页面

1.1 创建路由入口

src/app/(main)/reports/page.tsx
import type { Metadata } from 'next';

import ReportsPageContent from '@/components/reports/ReportsPageContent';

export const metadata: Metadata = {
  title: '报表中心',
};

export default function ReportsPage() {
  return <ReportsPageContent />;
}

1.2 创建业务组件

src/components/reports/ReportsPageContent/index.tsx

组件负责交互和业务展示。需要 Hooks 时添加 'use client'

1.3 登录与功能控制

  • 纯客户端动作使用 useRequireLogin()
  • 整个页面必须登录才能访问时,增加服务端用户检查;
  • 可选功能在页面入口使用运行时开关;
  • 权限不能只靠“隐藏菜单”,服务端接口仍需做授权检查。

1.4 路径兼容

链接、跳转和回调地址应使用项目提供的应用路径函数,不能直接硬编码 /reports,否则部署到 APP_BASE_PATH 后可能跳转错误。

2. 新增后端 API 方法

2.1 在领域 Service 中定义类型和方法

import { apiClient } from '@/lib/api/axios';

interface ReportListParams {
  keyword?: string;
}

interface ReportListItem {
  id: string;
  name: string;
}

interface ReportListResponse {
  code: number;
  message: string;
  data: ReportListItem[];
}

export const reportService = {
  fetchReports: (params?: ReportListParams) =>
    apiClient.get<unknown, ReportListResponse>('/reports', { params }),
};

浏览器实际请求 /server/reports,通用 Route Handler 会转发到配置的上游 /reports。普通 CRUD 通常不需要新增代理 Route 文件。

2.2 选择调用方式

  • 共享列表:在 Zustand Store 中调用并缓存;
  • 表单保存:在组件中使用 useServiceRequest
  • Server Component 首屏:创建 .server.ts Service 并使用 serverApiClient
  • 长连接:使用显式 SSE Route 和 apiClient.stream()

2.3 检查业务成功

修改类请求不能只检查 HTTP 状态,应使用项目统一业务响应检查,再进行:

  • success message;
  • 关闭弹窗;
  • 刷新 Store;
  • 记录成功日志。

3. 新增前端自有 Route Handler

仅当逻辑属于 Next.js 前端服务时使用 /api,例如签名、健康检查、前端日志或预览编排。

src/app/api/reports/export/route.ts

必须处理:

  • 请求方法和 Content-Type;
  • 身份和权限;
  • 参数校验;
  • server-only 密钥;
  • 超时与外部请求限制;
  • 结构化日志;
  • 受控错误响应。

不要把 /api 变成第二套后端 CRUD 代理。

4. 新增弹窗

可参考:

  • src/components/layout/UserAvatarPopover/RedeemCodeModal.tsx
  • src/components/layout/UserAvatarPopover/index.tsx

推荐模式:

  1. 父组件管理 open 状态;
  2. 弹窗定义清晰的 Props 接口;
  3. 使用 Ant Design Modal 和 Form;
  4. 使用 useServiceRequest 提交;
  5. 关闭时重置表单;
  6. 成功后调用 onSuccess 通知父组件刷新;
  7. 日志只记录事件和错误码,不记录表单敏感值。
interface ExampleModalProps {
  open: boolean;
  onClose: () => void;
  onSuccess?: () => void;
}

5. 新增 Zustand Store

适用于跨页面或跨组件共享的数据:

  1. src/stores/ 定义 state、actions 和创建函数;
  2. src/providers/StoresProvider/ 创建 Provider;
  3. 将 Provider 加入总组合;
  4. 如需首屏数据,在服务端布局读取并注入 initial state;
  5. 组件使用 selector 订阅最小状态片段;
  6. 对并发列表请求增加去重或缓存策略。

不要把一次按钮提交也放进全局 Store;也不要在多个组件中分别 useEffect 请求同一份共享数据。

6. 新增运行时配置

浏览器可见配置

  1. src/lib/runtime-config.ts 增加类型和默认值;
  2. 在根布局构建注入值;
  3. src/lib/consts/env.client.ts 通过统一函数读取;
  4. 更新 .env.example 和环境变量文档;
  5. 验证容器重启后生效且不需要重新构建。

浏览器可见配置不能包含密钥。

服务端私密配置

  1. src/lib/consts/env.server.ts 增加 getter;
  2. 保留 server-only 防护;
  3. 明确必填或可选;
  4. 只在 Server Component、Route Handler 或 .server.ts 中调用;
  5. 不注入 window.__RUNTIME_CONFIG__

7. 新增功能开关

功能开关适合控制不同部署环境的功能差异,但必须明确:

  • 前端隐藏不等于权限控制;
  • 页面路由也要检查开关;
  • 后端接口必须独立授权;
  • 默认值要符合安全和兼容预期;
  • 用户手册需要标记“按部署配置显示”。

8. 修改品牌和主题

品牌名称、Logo 和 Favicon 优先使用运行时配置。颜色和 Token 可通过:

  • src/styles/globals.css
  • src/theme/antd-theme.ts
  • public/branding/theme.css
  • Compose 的可选品牌文件挂载。

不要在多个业务组件中分散硬编码项目品牌名称和颜色。

9. 新增长连接接口

参考现有 src/app/server/user/events/route.ts 和 Agent run Route:

  • 使用 Node.js runtime;
  • 设置动态模式和合理的 maxDuration
  • 委托统一代理;
  • 传递取消信号;
  • 记录流建立、结束和失败;
  • 在 Nginx 配置足够长的读写超时;
  • 明确重试是否安全。

10. 二开完成定义

一项前端二开至少满足:

  • 路由在根路径和项目部署前缀下都可用;
  • 未登录、无权限和功能关闭状态正确;
  • loading、empty、error、success 状态完整;
  • 深色与浅色主题可读;
  • 浏览器无新增未处理异常;
  • 日志不包含敏感信息;
  • pnpm build 成功;
  • 用户操作文档和项目配置说明同步更新。