Skip to content

Latest commit

 

History

History

readme.md

IDP 前端

基于 Next.js 15 (App Router) 的 IDP 管理后台前端。不依赖 NextAuth / tRPC / Drizzle —— 所有数据都通过 fetch 直连后端 Spring Boot REST API,登录态使用 JWT + Zustand 持久化到 localStorage

技术栈

技术
语言 TypeScript 5
框架 Next.js 15(App Router)+ React 19
样式 Tailwind CSS v4
数据请求 自封装 lib/api/http.ts (fetch 封装) + TanStack React Query
状态 Zustand(持久化登录态)
表单 React Hook Form + Zod + @hookform/resolvers
提示 sonner(Toast)
图标 lucide-react
测试 Vitest + Testing Library + jsdom
包管理 pnpm 10

目录结构

frontend/
├── src/
│   ├── app/
│   │   ├── login/page.tsx                # 登录页(账号密码 + 可选验证码 + SITE 回填)
│   │   ├── admin/
│   │   │   ├── layout.tsx                # 后台 Shell(按权限过滤侧边栏 + 顶栏 + 登出)
│   │   │   ├── page.tsx                  # 概览页
│   │   │   ├── profile/
│   │   │   │   └── page.tsx              # 个人中心:基本信息 + 安全设置(含修改密码)
│   │   │   ├── message/page.tsx          # 站内消息中心(普通用户视角)
│   │   │   ├── monitor/
│   │   │   │   ├── online/page.tsx       # 在线用户
│   │   │   │   └── log/page.tsx          # 系统日志(登录日志 / 操作日志)
│   │   │   └── system/
│   │   │       ├── user/page.tsx         # 用户管理
│   │   │       ├── role/page.tsx         # 角色管理 + 分配菜单
│   │   │       ├── menu/page.tsx         # 菜单管理(树形表格 + type 联动弹窗)
│   │   │       ├── config/page.tsx       # 系统配置(SITE/PASSWORD/LOGIN/STORAGE Tab)
│   │   │       ├── dict/page.tsx         # 字典管理(字典 + 明细)
│   │   │       ├── file/page.tsx         # 文件管理(FileAside + FileMain)
│   │   │       └── notice/               # 通知公告
│   │   │           ├── page.tsx          # 列表 + 搜索 + 删除
│   │   │           ├── add/page.tsx      # 新增 / 编辑(query.type=update)
│   │   │           └── view/page.tsx     # 预览
│   │   ├── layout.tsx                    # 根 Layout(QueryProvider + Toaster)
│   │   └── page.tsx                      # 入口:根据登录态跳 /login 或 /admin
│   ├── components/
│   │   ├── providers/query-provider.tsx
│   │   ├── ui/                           # 基础组件(Button/Input/Modal/Tabs/Switch/UploadImage/Breadcrumb/Dropdown/Pagination/ContextMenu/Progress/Empty 等)
│   │   ├── system/                       # 业务表单(user/role/menu-tree/notice/dict 等)
│   │   │   ├── dict-badge.tsx            # 字典明细 → 带颜色的 Badge
│   │   │   ├── notice-detail-drawer.tsx  # 通知公告右侧详情抽屉
│   │   │   ├── notice-popup.tsx          # 登录后弹窗未读公告
│   │   │   ├── notification-bell.tsx     # 顶栏未读数 + 最近消息下拉
│   │   │   ├── user-multi-select.tsx     # 多选用户(指定通知用户场景)
│   │   │   ├── file/                     # 文件管理:FileAside / FileMain / FileGrid / FileList / 各 modal / 预览 / multipart-uploader
│   │   │   └── storage/                  # 存储配置:StorageConfigTab / StorageCard / StorageAddCard / StorageFormModal
│   │   └── permission-guard.tsx          # <PermissionGuard codes=[...]>
│   ├── lib/
│   │   ├── api/                          # http 封装 + auth/user/role/option/menu/notice/dict/message/file/storage/multipart-upload API + 类型
│   │   ├── hooks/
│   │   │   ├── use-permission.ts         # 权限判断 Hook(admin 直通)
│   │   │   └── use-dict.ts               # 按 code 拉字典 + getLabel/getColor 工具
│   │   ├── store/auth-store.ts           # zustand 持久化登录态(含 menuTree 非持久字段)
│   │   └── utils.ts                      # cn / apiUrl
│   ├── styles/globals.css
│   └── env.js                            # 环境变量校验(仅 NEXT_PUBLIC_API_BASE_URL)
├── eslint.config.js
├── next.config.js
├── postcss.config.js
├── prettier.config.js
├── tsconfig.json
├── vitest.config.ts
├── vitest.setup.ts
└── package.json

环境变量

.env

NEXT_PUBLIC_API_BASE_URL=http://localhost:8080
变量 说明
NEXT_PUBLIC_API_BASE_URL 后端 Spring Boot REST 地址,前端所有请求基地址

API 客户端约定

src/lib/api/http.ts 提供统一的 fetch 封装:

  • 自动注入 Authorization: Bearer <token>(来自 zustand auth-store)。
  • 自动反序列化后端 R<T> = {code, msg, data} 结构,仅当 code === 0 时返回 data,否则抛出 HttpError
  • 401 自动调用注册的 unauthorized 回调(默认会清空登录态并跳转 /login)。

API 客户端文件 → 后端接口对应:

文件 后端接口
lib/api/auth.ts POST /auth/loginPOST /auth/logoutGET /auth/user/infoGET /auth/captchaPOST /system/user/passwordPUT /system/user/profile
lib/api/user.ts GET/POST/PUT/DELETE /system/user 系列 + 重置密码 / 分配角色
lib/api/role.ts GET/POST/PUT/DELETE /system/role 系列
lib/api/option.ts GET/PUT/PATCH /system/optionPOST /system/option/image、公开 GET /system/option/site/system/option/login
lib/api/menu.ts GET/POST/PUT/DELETE /system/menu 系列 + GET/PUT /system/role/{id}/menu + GET /auth/user/route(前端动态侧边栏数据源)
lib/api/dict.ts GET /system/dict/listGET/POST/PUT/DELETE /system/dict/system/dict/{dictId}/item 系列、公开 GET /system/dict/{code}/item
lib/api/notice.ts GET/POST/PUT/DELETE /system/notice + /popup/{id}/read/dashboard
lib/api/message.ts GET /system/message 分页、/unread-countPOST /system/message/{id}/read/read-all
lib/api/monitor.ts GET/DELETE /monitor/onlineGET /system/logGET /system/log/{id} 与日志导出
lib/api/file.ts GET/POST/PUT/DELETE /system/file 系列 + 回收站 /system/file/recycle/*
lib/api/storage.ts GET/POST/PUT/DELETE /system/storage 系列 + /status + /default
lib/api/multipart-upload.ts 分片上传 init / part / complete / cancel

http.ts 额外提供 http.upload<T>(path, formData, { onProgress }),使用 XMLHttpRequest 以支持上传进度回调,普通文件 / 分片上传均通过它发起。

常用脚本

脚本 作用
pnpm dev 启动开发服务器(Turbopack)
pnpm build / pnpm start 构建并启动生产模式
pnpm typecheck TypeScript 严格类型检查
pnpm lint / pnpm lint:fix ESLint 检查 / 自动修复
pnpm format:write / format:check Prettier 格式化
pnpm test 运行 Vitest 单测(一次性)
pnpm test:watch 监听模式
pnpm test:ui 启动 Vitest UI
pnpm test:coverage 生成覆盖率报告(v8)
pnpm check lint + typecheck 组合

测试约定

  • 测试文件位置:与被测代码同目录,命名为 *.test.ts / *.test.tsx
  • 组件测试统一使用 @testing-library/react + @testing-library/jest-dom
  • 任何新增/修改的函数、组件、API 客户端都必须附带至少一个测试,并在 PR 中保证 pnpm test 通过。

开发约定

  1. 新增功能 = 新增测试 + 更新文档:详见 .cursor/rules/feature-workflow.mdc
  2. 统一通过 ~/ 别名引用 src/:见 tsconfig.json
  3. 环境变量必须在 src/env.js 中声明,不要直接读取 process.env(仅 lib/api/http.ts 直接读,因 server / client 同时使用)。
  4. 登录态、JWT、用户信息只放在 auth-store,禁止再放到其它 store 或 cookie。
  5. 所有 export 的函数 / Hook / 组件 / 类型必须有中文 JSDoc;组件 Props 接口的每个字段都要 /** ... */ 行内注释;详见 .cursor/rules/api-doc-comments.mdc
  6. JSDoc 中如包含 */ 序列(如 **/model/* 这种 glob)必须改写规避,否则 tsc 会报伪类型错误。

侧边栏:动态菜单驱动

admin/layout.tsx 在登录后调用 GET /auth/user/route 拉取用户可见菜单({@code type=1} 目录、{@code type=2} 菜单按 sort 升序的树),按以下规则渲染:

  • 顶级目录({@code type=1})渲染为可展开 / 折叠的分组;
  • 菜单({@code type=2})渲染为 Link;当 path 命中当前路由时高亮;
  • isExternal=true 渲染为 <a target="_blank">
  • isHidden=truestatus=0 的节点会被过滤;
  • icon 字段按 ICON_MAP 映射到 lucide-react 图标,未匹配时退化为 Folder

/admin(概览)与 /admin/profile(个人中心:基本信息 + 修改密码)保持硬编码(不属于菜单管理的内容)。详细的菜单数据模型见 ../docs/menu.md

布局与滚动容器

admin/layout.tsx 的最外层使用 h-screen + overflow-hidden 固定为视口高度,左侧 <aside> 内的菜单 <nav> 与右侧主内容 <main> 各自带 min-h-0 + overflow-(y-)auto,因此:

  • 主内容超长时只在 <main> 内部滚动,侧边栏保持不动
  • 菜单超长时只在 <nav> 内部滚动,不会撑高页面;
  • 浏览器页面级(<html> / <body>)不会出现滚动条。

如果以后改回 min-h-screen 或去掉 overflow-hidden,主内容会撑高最外层并触发页面级滚动,侧边栏就会跟着一起滚 —— 这是已有过的回归,已在 src/app/admin/layout.test.tsx 中通过断言关键 className 锁定,不要轻易改动。

个人中心 /admin/profile

参考 continew-admin 的个人中心页布局,由 app/admin/profile/page.tsx 实现:

  • 左侧 “基本信息” 卡:展示 ID / 用户名 / 昵称 / 邮箱 / 手机 / 性别 / 角色,并提供 “编辑” 按钮触发 Modal 修改昵称、邮箱、手机、性别;
  • 右侧 “安全设置” 卡:展示登录密码(始终已设置)、安全邮箱、安全手机三行;点击 “修改” 调起对应 Modal(密码使用专门的 “原密码 + 新密码 + 确认” 表单,邮箱 / 手机复用基本信息 Modal);
  • 修改基本信息成功后会立即 getUserInfo 重拉并更新 zustand store,顶栏 / 侧边栏即时反映新的昵称;
  • 修改密码成功后会自动登出并跳回 /login,强制使用新密码重新登录。

后端配套接口:

方法 路径 说明
PUT /system/user/profile 当前登录用户自助修改昵称 / 邮箱 / 手机 / 性别(任意登录用户可调用,无需 system:user:* 权限)
POST /system/user/password 当前登录用户自助修改密码
GET /auth/user/info 拉取当前用户信息(含 gender

默认登录账号

后端会自动初始化默认管理员账号 admin / 123456,登录后请尽快通过用户管理页重置密码。

文件管理与存储配置

新增 /admin/system/file 与系统配置页 STORAGE Tab,覆盖企业后台的文件管理需求:

入口 说明
/admin/system/file 双列布局:左 FileAside(分类切换 + 资源统计),右 FileMain(面包屑 / 操作栏 / grid·list 视图 / 分页 / 右键菜单 / 多选 / 批量删除 / 回收站)
/admin/system/config?tab=STORAGE 本地 / 对象存储分组卡片网格,支持新增 / 编辑 / 启用 / 禁用 / 设为默认 / 删除

关键能力:

  • 普通上传 + 分片上传:分片上传组件 multipart-uploader-core.ts 用 Web Crypto 计算 SHA256,按 5MB 切片、并发 3 个分片上传;已成功的分片号写入 sessionStorage,断点续传同一文件时跳过;触发 AbortSignal 自动调用后端 cancel。
  • 秒传:分片上传 init 命中已有 SHA256 时直接返回 existing 文件信息,无需再上传任何分片。
  • 预览
    • image-preview.tsx:原生 lightbox,支持缩放 / 旋转 / 重置 / 列表轮播 / 键盘 ←→Esc;
    • video-preview.tsxaudio-preview.tsx:原生 <video> / <audio>
    • office-preview.tsxpdf<iframe>docxdocx-previewxlsxxlsx(SheetJS)渲染 HTML table,ppt/pptx 等暂不支持时兜底 “下载 + 新标签页打开”;
    • getOfficePreviewKind(extension) 暴露给上层做扩展名分流;图片预览同时把当前页内所有图片传给 ImagePreview 实现轮播。
  • 回收站FileRecycleModal 提供分页 / 还原 / 物理删除 / 清空,所有写操作都会同步刷新文件列表与统计。
  • 存储表单storage-form-modal.tsx 在 LOCAL / S3 之间动态切换字段;编辑态下编码字段禁用,SecretKey 留空表示不修改。
  • 交互约定
    • 整张卡片 / 整行 单击 = 打开(文件夹进入下一级 / 文件触发对应预览);
    • 卡片左上角勾选框(hover 或选中时显示)/ 列表行复选框 = 仅切换选中,不冒泡;
    • 右键菜单:详情 / 重命名system:file:update)/ 下载(仅文件,浏览器 <a download> 触发)/ 删除system:file:delete,红色高亮)。

详细架构与流程参见 ../docs/file-storage.md

通知公告 / 字典 / 消息中心

新增的三个模块共享同一组前端基础设施:

  • 字典 Hook useDict(code, enabled?):5 分钟 staleTime 缓存,返回 { items, getLabel, getColor };写入字典后调用 queryClient.invalidateQueries({ queryKey: dictQueryKey(code) }) 即可刷新所有消费方。详见 ../docs/dict.md
  • DictBadge:把字典明细的 color 字段(primary/success/warning/danger/info)渲染成统一的 Badge tone;匹配不到时回落为灰色 + 原 value。
  • 通知公告页面:列表 / 新增编辑 / 预览三页 + 右侧详情抽屉;正文用 <textarea> + 预览端 <pre className="whitespace-pre-wrap">,零额外依赖;定时发布 / 草稿 / 立即发布的状态切换在 add/page.tsx 中按 status + isTiming 推导,详见 ../docs/notice.md
  • 登录弹窗 NoticePopup:在 /admin/layout.tsx 内首次进入时 fetch /system/notice/popup,命中则用 Modal 依次展示;会话内通过 sessionStorage 记录已展示 ID 防重复弹出。
  • 顶栏未读数 NotificationBell:拉 /system/message/unread-count + 最近 5 条消息,点击消息会跳转 message.path 并自动 read(id)。详见 ../docs/message.md
  • Dashboard 卡片/admin 概览页新增 “最新公告” 卡片,数据来自 /system/notice/dashboard

系统监控 /admin/monitor/*

监控功能参考 continew-admin-ui 的在线用户、登录日志、操作日志页面,由后端 MenuSeeder 初始化 “系统监控” 目录:

  • /admin/monitor/online:在线用户列表,支持用户名/昵称、登录时间过滤;具备 monitor:online:kickout 时可强退其他会话;
  • /admin/monitor/log:系统日志页面,包含 “登录日志 / 操作日志” 双 Tab;支持状态、时间、操作人、IP 等条件筛选;
  • 日志导出复用 http.download,当前后端返回 CSV 文件。

详细设计、接口与测试覆盖见 ../docs/monitor.md

已知 breaking change(v2 菜单改造)

  • /admin/system/permission 页面已下线,改为 /admin/system/menu;旧书签需要更新。
  • 旧权限码 system:permission:* 整体废弃,统一为 system:menu:*;自定义角色绑定的旧权限需要在 “角色管理 → 分配菜单” 中重新分配。
  • 数据库表 idp_sys_permission / idp_sys_role_permissionidp_sys_menu / idp_sys_role_menu 取代,本地需要 drop database idp; 重建。