89 lines
6.6 KiB
Markdown
89 lines
6.6 KiB
Markdown
# AGENTS.md
|
||
|
||
森睿智传(senmesh-zc-legacy)——内网/外网文件传输与网盘管理 Web 端(React SPA)。后端为 s-file(alita-bjy-uygur 仓库),本项目为其前端。
|
||
|
||
## 常用命令
|
||
|
||
```bash
|
||
bun install # 安装依赖(使用 bun.lock;Node >= 20,本机经 nvm 提供)
|
||
bun run dev # 启动开发服务器(Vite)
|
||
bun run build # tsc -b && vite build(类型检查 + 构建,构建目标 chrome86)
|
||
bun run build:inside # 内网包(VITE_IS_INSIDE=true)→ dist/inside
|
||
bun run build:outside # 外网包(VITE_IS_INSIDE=false)→ dist/outside
|
||
bun run lint # ESLint(存在大量存量告警,见「代码约定」)
|
||
bun run preview # 预览构建产物
|
||
```
|
||
|
||
内外网发布包构建后打 tar(解压后 index.html 在顶层,可直接作为 nginx 站点根):
|
||
|
||
```bash
|
||
tar -czf release/senmesh-zc-inside.tar.gz --exclude='.DS_Store' -C dist/inside .
|
||
tar -czf release/senmesh-zc-outside.tar.gz --exclude='.DS_Store' -C dist/outside .
|
||
```
|
||
|
||
没有测试框架,验证手段为 `build`(含 tsc 严格检查)+ 浏览器冒烟。
|
||
|
||
## 关键注意事项
|
||
|
||
- **路由树是生成的**:`src/routeTree.gen.ts` 由 `@tanstack/router-plugin` 在 dev/build 时生成。新增路由文件后直接跑 `tsc -b` 会报路由不存在 —— 先跑 `bunx vite build`(或启动 dev)再跑 `bunx tsc -b`。
|
||
- **Hash 路由**:`createHashHistory`,页面地址形如 `/#/admin/user`,直接访问不带 `#` 的路径会落到首页。
|
||
- **ESLint 基线**:存量代码大量 `no-explicit-any`、`useColumns` 在回调中调用等告警属既有风格;新代码与周围风格保持一致即可,`tsc` 严格检查(`strict`、`noUnusedLocals`、`noUnusedParameters`)才是硬门禁。
|
||
|
||
## 技术栈
|
||
|
||
React 19 · TypeScript(strict) · Vite(rolldown-vite) · TanStack Router(文件路由 + autoCodeSplitting)· TanStack Query v5(全局 `retry: false`、`refetchOnWindowFocus: false`,见 `src/main.tsx`)· TanStack Table · zustand(persist + derive-zustand)· axios · Tailwind 3 + shadcn/ui(Radix)· lucide-react · sonner · react-dropzone · react-hook-form + zod
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
src/
|
||
apis/ # 接口函数,按域分文件(user/org/file/upload-config)
|
||
lib/http/ # axios 实例与拦截器(index.ts 导出 http 与常量)
|
||
routes/
|
||
(user)/ # 用户端网盘页面(my/outside/inside/share/recycle)
|
||
admin/ # 管理后台;route.tsx 定义侧边菜单
|
||
extract/ # 抽取
|
||
components/ # 共享业务组件;ui/ 为 shadcn 基础组件
|
||
hooks/ # 共享 hooks(kebab-case 文件名,如 use-upload-config.ts)
|
||
stores/user.ts # 登录态 store(persist,key 为 `${VITE_STORE_PREFIX}_user`)
|
||
```
|
||
|
||
### 路由/页面组织约定
|
||
|
||
- 功能目录 = `index.tsx`(路由 + `useColumns` + 页面组件)+ `-hooks/` + `-components/`;`-` 前缀目录不参与路由。
|
||
- 列表页统一走 `useFetchList` 模式:`useState` 分页 `{pageIndex, pageSize}` + `useQuery`(queryKey 含 `[名称, pagination, params]`)+ `useReactTable`(`getCoreRowModel`、手动分页)+ 共享 `<DataTable>`/`<Pagination>`。
|
||
- 筛选条件采用「点击查询后生效」:草稿 state 与提交 params 分离,提交时过滤空值并重置到第一页(参考 `admin/org-account`、`admin/file-record`)。
|
||
- 管理后台菜单在 `admin/route.tsx` 的 `menus` 中维护;仅平台机构(超管)可见的菜单加入 `platformOnlyUrls`(由 `useIsPlatformOrg` 即 `org.id === 1` 判断),机构级菜单(用户管理、上传配置)不加入。
|
||
|
||
## HTTP 层与后端约定(重要)
|
||
|
||
- 响应拦截器(`src/lib/http/interceptors.ts`)resolve 的值是 **AlitaResult 本体**:`{ requestId, code, success, msg, data }`。因此 queryFn 里 `.then(res => res.data)` 拿到的才是业务数据(如分页 `Page`:`{ entityList, totalCount, pageCount }`)。
|
||
- 分页参数走 query:`pageNo`(从 1 开始)/`pageSize`。
|
||
- code `"200"` 成功;`555/666/999/401/402/700` 拦截器统一 `toast.error(msg)` 后 reject;`401/402` 清 token 并跳转 `/login?redirect=...`;`603` 换 token;非 JSON 响应按文件下载处理。
|
||
- **错误提示只依赖拦截器**:mutation 用 `toast.promise(mutateAsync(...), { loading, success })`,**不要加 error 回调**(会与拦截器重复弹错)。
|
||
- 请求自动携带 `Authorization: Bearer <token>`(取自 user store)与 X-XSRF-TOKEN。
|
||
|
||
## 代码约定
|
||
|
||
- API 函数放 `src/apis/`,形如 `export const xxx = (data: any) => http.post("/path", data)`,参数类型沿用 `any`(存量风格)。
|
||
- mutation hooks 放页面 `-hooks/` 下,仿照 `useCancelUser`/`useUserStatusActions`:`useMutation` + `toast.promise` 包装,暴露 `xxx` 与 `xxxIsPending`。
|
||
- 表单弹窗优先 react-hook-form + zod(参考 `EditUserDialog`);简单确认弹窗仿 `CancelUserConfirmDialog`(Dialog + Spinner + onSuccess 里 `refetch` + `setOpen(false)`)。
|
||
- 页面骨架:外层 `p-4 box-border min-h-full space-y-4` → 标题区(`text-lg` + `text-xs text-black/50` 副标题)→ 白卡片 `p-4 rounded-lg border bg-white space-y-2` → 工具行 → `<DataTable>` + sticky `<Pagination>`。
|
||
- 图标用 lucide-react;文案为中文;时间格式 `yyyy-MM-dd HH:mm:ss`;文件大小用 `formatFileSize`(`src/lib/utils.ts`)。
|
||
|
||
## 环境变量(.env.development / .env.production)
|
||
|
||
`VITE_API_URL`(dev 为 `/api`,由 vite `server.proxy` 转发到后端)、`VITE_STORE_PREFIX`、`VITE_BASEPATH`、`VITE_APP_TITLE`、`VITE_IS_INSIDE`(内网部署标识,影响菜单文案与路由行为)、`VITE_DOMAIN`、`VITE_INIT_PASSWORD`。
|
||
|
||
## 后端联调
|
||
|
||
- 接口文档在后端仓库 `alita-bjy-uygur/docs/` 下的 `接口文档-*.md`,动手前先读对应文档(含字段表、错误文案、权限说明)。
|
||
- 管理端接口普遍要求「平台管理员/机构管理员」身份,前端仅做菜单/入口级控制,权限由后端兜底;错误文案以文档「校验失败提示」为准,前端校验提示需与后端 msg 保持一致。
|
||
- 后端不可达时的冒烟技巧:登录态持久化在 localStorage `senmeshzc_user`,注入 `{"state":{"token":"...","user":{...},"org":{...}},"version":0}` 后刷新即可进入受限页面验证 UI(`org.id = 1` 即平台机构)。
|
||
|
||
## 验证清单
|
||
|
||
1. `bunx vite build && bunx tsc -b` 通过(新增路由时先 build 再 tsc)。
|
||
2. `bun run dev` + 浏览器过一遍改动页面:菜单显隐、空态/加载/错误重试态、弹窗交互、toast 文案。
|
||
3. 涉及上传/记录等平台差异逻辑时,分别验证内网(`VITE_IS_INSIDE=true`)口径。
|