Day 17 / 共 20 天 · 第 4 周 前端/部署

前端结构与页面

今天看前端"长什么样、怎么组织":目录结构、整体布局与菜单、API 调用层,最后精读最典型的路由管理页 CRUD。理解了这套模式,前端所有页面都是同一套路。

📍 你在整门课的位置 · 第 4 周 前端/部署(Day 16-20)
D16 前端技术栈 D17 前端结构 D18 动态表单 D19 部署 D20 收官串讲
L01

目录结构

🤔 痛点:文件一多就找不到东西 昨天认识了技术栈,今天进代码。可一个前端工程动辄几百个文件——页面、组件、请求、类型、翻译混在一起,改个功能得翻半天。
💡 本质:按"职责"分抽屉,各就各位 这套目录就是"给代码分抽屉":pages 放页面、components 放复用组件、services 放 API 调用、interfaces 放类型、models 放全局状态、locales 放翻译。想改哪类东西直接开对应抽屉。理解了这套分层,前端所有页面都是同一套路。
📝 举个例子(前后端对应) 后端有 /v1/routes 接口 → 前端 services/route.ts 封装它 → interfaces/ 里的 TS 类型对应后端 Java DTO → pages/route/ 是它的页面。一个资源,一条竖线贯穿前后端。
frontend/src/
  app.ts        应用配置(Day 16)
  i18n.ts       国际化
  store.ts      全局 store
  switches.ts   功能开关(SHOW_INTERNAL_ROUTES=false)
  pages/        页面(约定式路由,每个子目录 = 一个路由段)
  components/   公共组件(CodeEditor/Navbar/ChatRobot/AvatarDropdown…)
  services/     API 层(按领域拆分)
  interfaces/   TS 类型(与后端 DTO 对应)
  models/       全局 store model
  locales/      中英翻译
读法:典型的 React 项目分层:pages 放页面、components 放复用组件、services 放 API 调用、interfaces 放类型、models 放状态interfaces/ 里的 TS 类型和后端的 Java DTO 一一对应——前后端用同一套数据形状对话。
L02

ProLayout 布局

pages/layout.tsx:39-88@ant-design/pro-layoutProLayout 当整体外壳(layout="mix"):右上角 Navbar + LanguageDropdown + AvatarDropdown,内容区 <Outlet/>(子路由出口)+ 全局 ChatRobot

  • menuDataRender:60-77):按 hideFromMenuvisiblePredicate(configData) 过滤,再 t() 翻译——菜单可按后端 config 动态显隐。
  • :27-34 若被同源 iframe 嵌套则显示 500(防嵌套攻击)。
<Outlet/> 是什么? 布局是"固定的框"(顶栏、侧边菜单),内容区随路由变化。<Outlet/> 就是"内容区的占位坑"——你点不同菜单,对应页面就渲染进这个坑里,外框不动。好比 App 底部导航栏不变、上面内容切换。
L03

路由 / 菜单定义

pages/_defaultProps.tsx:14-117 静态定义菜单/路由树,路径 → pages/ 目录约定映射:/dashboard/service-source/service/route、AI 分组(/ai/provider/ai/route/ai/dashboard/mcp/list)、/domain/tls-certificate/consumer/plugin/system

读法:这张表和第 1-3 周后端的控制器几乎一一对应——每个后端资源都有一个前端页面。/ai/dashboardvisiblePredicate:76)演示条件显隐;/init/loginhideFromMenu + usePureLayout(无外框)。
L04

request 拦截器

页面调用 请求拦截器加 token / GET 加 ts 后端 响应拦截器(成功)解包 data.data → 页面 错误拦截器401 → 跳 /login
拦截器 = 前端的"前台统一接待":进门自动盖 token 章,出门自动拆包裹、遇到 401 统一送去登录页。
🧭 第一人称:你是一个 GET /v1/routes 请求 页面调 getRoutes(),你诞生了。先过请求拦截器:它从 localStorage 摸出 token 塞进你的 Authorization 头,又给你尾巴加了个 ts=时间戳 防缓存。你飞到后端拿回一坨 {data:{...}}响应拦截器替你拆掉外壳,只把里面的业务对象交给页面——所以页面代码从不写 .data.data。要是后端回你 401,错误拦截器直接把用户送去 /login?redirect=...。这些"每次都做的事",你一次都不用自己操心。

services/request.tsx 是 axios 封装,核心是拦截器:

// :14-26 请求拦截器:从 localStorage 取 token 塞进 Authorization 头;
//         GET 追加 ts=Date.now() 防缓存
// :28-41 响应拦截器(成功):解包 data.data(后端统一包 {data:...})
// :42-77 错误拦截器:401 且非 login → 跳 /login?redirect=...;
//         其它错误按 i18n 文案弹 Modal
"拦截器"帮你自动做什么? 每个 API 请求都要带 token、每个响应都要解包、每个 401 都要跳登录——拦截器让你把这些"每次都做的事"写一次,所有请求自动享用。回想后端 Day 05 的 AOP 切面——前端拦截器就是前端版的"切面",一进一出统一处理。响应拦截器自动解包 data.data,所以各 service 直接拿到业务对象(对应后端 Day 04 的 Response 三段式)。
L05

service 按领域拆分

services/ 每个文件对应一组后端端点,都 import request from './request'。例如 services/route.ts

GET    /v1/routes           :4-6
GET    /v1/routes/{name}     :9-11
POST   /v1/routes           :13-15
DELETE /v1/routes/{name}    :17-19
PUT    /v1/routes/{name}    :21-23
读法:前端 service 和后端 Controller 端点一一对应(Day 04 的表)。llm-provider.ts/ai-route.ts/dashboard.ts/system.ts/plugin.ts 各管一块。前端 service 层的职责就是"把后端 REST 封装成 JS 函数",页面调函数即可,不用关心 URL。
L06

精读路由管理页

👶 小白 vs 👨‍🏫 老师 👶:权重之和必须=100,后端 Day 09 不是已经校验过了吗?前端为什么还要再查一遍?
👨‍🏫:为了体验。前端先拦一道,用户点保存立刻看到红字提示,不用等一个来回的网络请求再报错。
👶:那后端那道能省掉吗?
👨‍🏫:绝不能。前端校验只是"礼貌提醒",绕过前端直接调 API 的请求它拦不住——后端校验才是最后防线。前后端各校验一遍是常规操作,不是重复劳动。

pages/route/index.tsx(670 行)是最典型的"列表 + 抽屉表单 + 删除确认"CRUD 页:

  • 表格列(:53-139):name/domains/routePredicates/services/auth/action 六列。服务列(:91-105)展示服务名 + 权重百分比,权重 0 标黄。
  • 数据加载用 ahooks 的 useRequest:181-209)。
  • 保存 handleDrawerOK:241+):前端也校验"服务权重之和必须 = 100":250-254),呼应后端 Day 09。
  • 渲染骨架(:568-665):PageContainer + 顶部筛选 + 主 Table + 删除 Modal + 编辑 Drawer(内含 <RouteForm/>)。
"抽屉表单"是什么体验? 点"新建/编辑"时从屏幕右侧滑出一个面板(Drawer 抽屉)填表单,不用跳页。列表页保持在原位,抽屉里编辑,保存后关闭抽屉刷新列表。这是管理后台最常见的交互,所有资源页(域名/证书/消费者…)都是这个套路。
L07

能力探测降级

route/index.tsx:50-51:从 system model 读 capabilities,判断是否支持 config.ingress.v1,不支持就禁用"新建路由"按钮。

读法:回想后端 Day 08 的"能力探测"——后端探测集群支持哪个 Ingress 版本,把结果放进 system 信息;前端读到这个能力位,若集群不支持就把按钮灰掉,避免用户点了却失败。前后端配合的优雅降级。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 前端目录怎么分层?interfaces 和后端什么关系?
  • ProLayout + <Outlet/> 怎么组织布局?
  • request 拦截器帮你自动做了哪三件事?
  • 路由页的"列表 + 抽屉 + 删除"套路?权重校验前后端都做?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/higress-console/frontend/src
ls pages/ components/ services/
sed -n '39,88p' pages/layout.tsx
sed -n '14,77p' services/request.tsx
sed -n '241,254p' pages/route/index.tsx
明天预告 · Day 18动态插件配置表单——前端最精华的部分:从后端返回的 JSON Schema 递归渲染出表单,还能在"表单"和"YAML 源码"两个 tab 间双向同步。这是整个前端的技术高峰。
← Day 16 Day 18 · 动态表单 →