- Add environment configs for dev/staging/prod environments - Implement centralized axios request utility with standardized error handling - Add shared TypeScript types for API responses and requests - Create comprehensive API client modules for all core endpoints - Configure vue router with all application page routes - Add icon fonts, static assets, and loading animations - Set up project documentation and collaboration guidelines - Remove deprecated uni-app bridge component files
206 lines
8.5 KiB
Markdown
206 lines
8.5 KiB
Markdown
# 统一请求库封装计划(src/utils/request.ts 版)
|
||
|
||
## Summary
|
||
|
||
目标:把统一请求库集中封装在 `src/utils/request.ts`,类型集中在 `src/shared/`,实现:
|
||
|
||
- baseURL/timeout/取消请求(Axios Web)
|
||
- 默认 header 注入:Authorization(Bearer)、clientId、X-Latitude、X-Longitude、language
|
||
- 业务错误/网络错误/HTTP 错误的统一归一化(只抛错,不做 Toast/跳转)
|
||
- 输出说明文档到 `docs/request.md`
|
||
|
||
## Current State Analysis(以实际仓库为准)
|
||
|
||
已确认的事实:
|
||
|
||
- 依赖中已包含 `axios`,但 `src/` 内暂无 axios 的实际封装与使用。
|
||
- `src/utils/request.ts` 存在但为空文件,且命名疑似历史遗留。
|
||
- 代码中存在大量“缺失模块引用”(详见文末“历史遗留问题清单”),其中 `@/request/api/*` 属于最集中、最影响后续迁移的一类。
|
||
|
||
结论:
|
||
|
||
- 本次统一请求库规划要做到“新增即可用、可渐进替换”,不依赖现有(缺失的)API 模块结构。
|
||
|
||
## Assumptions & Decisions(你已确认)
|
||
|
||
- 底层实现:Axios(Web)
|
||
- baseURL:从 `import.meta.env` 的环境变量读取(本计划固定变量名为 `VITE_API_BASE_URL`)
|
||
- header 值来源:全局上下文注入(内存态),由登录/定位/语言切换时写入
|
||
- Authorization:`Bearer <token>`(token 为空则不带 Authorization)
|
||
- 错误处理:请求库只做错误标准化并抛错,不做 Toast/跳转
|
||
- header 字段:`Authorization`、`clientId`、`X-Latitude`、`X-Longitude`、`language`
|
||
|
||
## 为什么不拆得更细(回答你的问题)
|
||
|
||
可以都放在 `src/utils/request.ts` 里完成,且更适合你现在“先落地一个可用的统一入口”的诉求:
|
||
|
||
- 单文件落地成本低:少目录、少入口、少导出点,替换/回滚都更直接
|
||
- 不牺牲可维护性:在单文件里用“内部分区函数 + 类型从 shared 引入”的方式,同样能保持边界清晰
|
||
- 何时再拆分:当出现“单文件 > 300~500 行、多人同时改动频繁、需要独立单测/Mock”的情况,再把 errors/context/http 拆出去更合理
|
||
|
||
因此本计划采用:**实现集中在 request.ts,类型抽到 shared** 的折中方案。
|
||
|
||
## Proposed Changes(全新规划)
|
||
|
||
### 1) 新增类型目录:`src/shared/`
|
||
|
||
新增目录:`src/shared/`
|
||
|
||
新增文件(建议):
|
||
|
||
- `src/shared/request-types.ts`
|
||
|
||
包含的类型定义(稳定、可复用):
|
||
|
||
- `export interface ApiResponse<T> { code: number; message?: string; data: T }`
|
||
- `export interface RequestOptions { signal?: AbortSignal; headers?: Record<string, string>; skipAuth?: boolean }`
|
||
- `export type RequestContext = { token: string | null; clientId: string | null; latitude: number | null; longitude: number | null; language: string | null }`
|
||
- `export type NormalizedErrorKind = "business" | "http" | "network" | "unknown"`
|
||
- `export interface NormalizedError extends Error { kind: NormalizedErrorKind; code?: number; httpStatus?: number; response?: unknown }`
|
||
|
||
### 2) 统一请求实现:`src/utils/request.ts`
|
||
|
||
新增文件:`src/utils/request.ts`
|
||
|
||
职责(全部在一个文件内完成):
|
||
|
||
#### 2.1 上下文容器(内存态)
|
||
|
||
提供以下导出函数(由业务在合适时机调用):
|
||
|
||
- `setAuthToken(token: string | null): void`
|
||
- `setClientId(clientId: string | null): void`
|
||
- `setLocation(latitude: number | null, longitude: number | null): void`
|
||
- `setLanguage(language: string | null): void`
|
||
- `getRequestContext(): RequestContext`
|
||
|
||
默认值策略:
|
||
|
||
- `language`:如果未显式设置,尝试读取 `src/i18n/index.ts` 的 `getCurrentLocale()`(读取失败则不注入 language header)
|
||
|
||
#### 2.2 Axios 实例创建与默认配置
|
||
|
||
- `baseURL`:`import.meta.env.VITE_API_BASE_URL`
|
||
- 若缺失:直接抛出清晰错误(提示需要配置 baseURL)
|
||
- `timeout`:可选 `import.meta.env.VITE_API_TIMEOUT_MS`,缺失则默认 15000
|
||
|
||
#### 2.3 请求拦截器(header 注入)
|
||
|
||
组装 headers 规则:
|
||
|
||
- 合并顺序:`默认headers < 上下文headers < 调用方options.headers`
|
||
- `Authorization`:仅当 token 存在时注入 `Bearer <token>`;当 `options.skipAuth === true` 时跳过
|
||
- `clientId`:存在则注入
|
||
- `X-Latitude` / `X-Longitude`:存在则注入(数值转字符串)
|
||
- `language`:存在则注入
|
||
|
||
#### 2.4 响应处理(业务码 + 错误归一化)
|
||
|
||
1. 正常响应:
|
||
- 若响应体符合 `ApiResponse<T>` 且 `code === 0`:返回整个 `ApiResponse<T>`
|
||
- 若响应体符合 `ApiResponse<T>` 且 `code !== 0`:抛出 `NormalizedError(kind="business", code, message, response)`
|
||
2. 异常响应(axios error):
|
||
- 有 `response.status`:抛 `NormalizedError(kind="http", httpStatus, response)`
|
||
- 无 `response`:抛 `NormalizedError(kind="network")`
|
||
|
||
对外导出(建议最小集合):
|
||
|
||
- `export async function request<T>(config, options?: RequestOptions): Promise<ApiResponse<T>>`
|
||
- 可选:`export async function requestData<T>(...): Promise<T>`(内部调用 request 并返回 `data`,用于未来更干净的调用风格)
|
||
|
||
### 3) 文档输出到 `docs/`
|
||
|
||
新增目录:`docs/`
|
||
|
||
新增文档:`docs/request.md`(必须包含):
|
||
|
||
- 环境变量约定:`VITE_API_BASE_URL`、`VITE_API_TIMEOUT_MS`(可选)
|
||
- 上下文写入时机:
|
||
- 登录成功:`setAuthToken(token)`
|
||
- 应用初始化:`setClientId(clientId)`
|
||
- 定位成功:`setLocation(lat, lng)`
|
||
- 语言切换:`setLanguage(locale)`(或依赖默认读取 i18n)
|
||
- 使用方式:
|
||
- 旧代码风格兼容:`const res = await request<T>(); res.data...`
|
||
- 新代码推荐:`const data = await requestData<T>()`
|
||
- 错误处理示例:区分 business/http/network/unknown 并给出上层建议策略
|
||
|
||
## 历史遗留问题清单(独立梳理,方便逐个替换)
|
||
|
||
说明:以下均为“当前仓库扫描可证实”的问题点,按模块引用位置列出,便于你后续按优先级逐个替换/补齐。
|
||
|
||
### A. `@/request/api/*` 悬空引用(13 个文件)
|
||
|
||
- `src/pages/quick/index.vue`
|
||
- `src/components/Feedback/index.vue`
|
||
- `src/components/CreateServiceOrder/index.vue`
|
||
- `src/pages/service/order/components/OrderCard/index.vue`
|
||
- `src/pages/booking/components/FooterSection/index.vue`
|
||
- `src/pages/goods/index.vue`
|
||
- `src/pages/order/order/components/FooterSection/index.vue`
|
||
- `src/pages/quick/components/Tabs/index.vue`
|
||
- `src/pages/order/order/list.vue`
|
||
- `src/pages/service/order/index.vue`
|
||
- `src/pages/booking/index.vue`
|
||
- `src/pages/login/index.vue`
|
||
- `src/pages/order/order/detail.vue`
|
||
|
||
建议后续替换策略:
|
||
|
||
1. 先确定“这些 API 模块的真实来源”(是否在其它分支/其它仓库/尚未迁入)。
|
||
2. 如果确认不会迁回,则逐文件把 `@/request/api/*` 替换为基于 `src/utils/request.ts` 的真实调用(需要你提供具体 URL/参数约定)。
|
||
|
||
### B. `@/utils` 目录级导入缺失(6 个文件)
|
||
|
||
当前仓库没有 `src/utils/index.*`,但以下文件在导入 `@/utils`:
|
||
|
||
- `src/pages/quick/index.vue`
|
||
- `src/pages/booking/components/FooterSection/index.vue`
|
||
- `src/pages/goods/index.vue`
|
||
- `src/pages/order/order/components/FooterSection/index.vue`
|
||
- `src/pages/booking/index.vue`
|
||
- `src/pages/home/index.vue`
|
||
|
||
### C. `@/store` 与实际目录 `src/stores/` 命名不一致(6 个文件)
|
||
|
||
- `src/components/ImageSwiper/index.vue`
|
||
- `src/pages/quick/components/Card/index.vue`
|
||
- `src/pages/goods/album/index.vue`
|
||
- `src/pages/goods/index.vue`
|
||
- `src/pages/booking/index.vue`
|
||
- `src/pages/home/index.vue`
|
||
|
||
### D. `@/hooks/*` 缺失(3 个文件)
|
||
|
||
- `src/components/SwipeCards/index.vue`
|
||
- `src/pages/login/index.vue`
|
||
- `src/pages/home/index.vue`
|
||
|
||
### E. `@/constant/*` 缺失(6 个文件)
|
||
|
||
- `src/components/ModuleTitle/index.vue`
|
||
- `src/components/Feedback/index.vue`
|
||
- `src/components/SurveyQuestionnaire/index.vue`
|
||
- `src/components/CreateServiceOrder/index.vue`
|
||
- `src/pages/booking/index.vue`
|
||
- `src/pages/login/index.vue`
|
||
|
||
### F. `src/utils/request.ts`(空文件 + 疑似拼写问题)
|
||
|
||
- 该文件当前为空,且命名疑似应为 `requests` 或 `request`
|
||
- 建议后续统一策略:保留/替换/删除前先全仓搜索确认是否在其它分支被引用
|
||
|
||
## Verification(执行阶段你手动确认运行)
|
||
|
||
你在实现完成后手动执行:
|
||
|
||
- `yarn typecheck`
|
||
- `yarn build`
|
||
- 如有用例:`yarn test`
|
||
|
||
验收标准:
|
||
|
||
- `src/utils/request.ts` + `src/shared/request-types.ts` 编译通过
|
||
- 发起请求时能注入 headers(Authorization/clientId/X-Latitude/X-Longitude/language)
|
||
- `code !== 0` 时抛出稳定结构的错误对象(上层可根据 kind 做统一处理)
|