diff --git a/AICONTEXT.md b/AICONTEXT.md deleted file mode 100644 index 01511a6..0000000 --- a/AICONTEXT.md +++ /dev/null @@ -1,166 +0,0 @@ -# @simcu/simapi — AI 开发指南 - -> 面向 AI Agent 的代码结构说明,帮助理解、修改和扩展本库。 - ---- - -## 项目结构 - -``` -simapi-vue/ -├── src/ -│ ├── types.ts # 类型定义,全部导出 -│ ├── simapi.core.ts # 核心类 SimApiCore,零框架依赖 -│ └── simapi.pinia.ts # Pinia Store,Vue3 适配层 -├── dist/ # 构建产物 -├── package.json # exports: "." → core, "/pinia" → vue -├── vite.config.ts # 统一构建配置,生成 .mjs 和 .cjs -└── tsconfig.build.json # tsc 生成类型声明 -``` - ---- - -## 核心类型(types.ts) - -```typescript -SimApiBaseResponse // 标准响应 { code, message, data? } -SimApiOptions // configure() 入参 { debug?, auth?, api? } -SimApiAuthConfig // auth: { token_name, check_url, logout_url, login_url } -SimApiApiConfig // api: { endpoints, defaultEndpoint, businessCallback, responseCallback, timeout? } -SimApiBusinessCallback // { [code]: (data) => void },支持数字码或 'common' -``` - ---- - -## SimApiCore(simapi.core.ts) - -**职责**:纯 TS HTTP 客户端,基于原生 fetch API,不依赖任何框架。 - -**关键设计**: - -- **零依赖**:使用原生 fetch,无 axios 或其他 HTTP 库 -- **无 Cookie**:所有请求使用 `credentials: 'omit'`,避免 CORS 问题 -- **Token 传递**:通过请求头 `Token` 传递认证信息 -- **超时控制**:通过 `fetchWithTimeout` 辅助函数实现 -- `query()` 抛出异常的只有网络/HTTP 错误,业务错误码通过回调处理 -- `isLoggedIn` 是 getter,基于 localStorage 中的 token 判断 -- **版本号**:`getVersion()` 返回的版本信息中,`uiSimApi` 由构建时注入的 `SimApiVersion` 填充,`uiApp` 由调用方的 `AppVersion` 填充(未注入时为 `0.0.0-develop`) - -**修改建议**: - -- 改请求方法(GET/PUT/DELETE):在 `fetchPost()` 内新增 `method` 参数分支,或新增 `fetchGet()`/`fetchPut()` 方法 -- 改 Token 存储:替换 `localStorage` 为 `sessionStorage` 或内存变量,修改 `getToken()`/`setToken()`/`removeToken()` -- 改登录/登出逻辑:修改 `login()`/`logout()` 方法 -- 改超时处理:修改 `fetchWithTimeout()` 函数 -- **版本管理**:版本号通过 `declare const` 声明常量,构建时通过 Vite 的 `define` 注入。未指定时默认为 `0.0.0-develop` - -**autoInit 设计约束**: - -- 仅读取 `window.simapi` 的顶级字段:`endpoints`、`defaultEndpoint`、`debug` -- 业务回调(`businessCallback`/`responseCallback`)不支持从 window 读取,必须在代码中通过 `setBusinessCallback` 注册 - ---- - -## Pinia Store(simapi.pinia.ts) - -**职责**:Vue3 适配层,SimApiCore 的纯代理,不维护任何独立状态。 - -**关键设计**: - -- **无独立状态**:state 中只有一个 `_core` 实例,不维护 `debug`、`token` 等独立数据 -- **单例 Core**:在 store state 中实例化 `SimApiCore`,整个应用共享一个实例 -- **纯代理映射**:所有 getters 直接映射到 `this._core` 的属性,所有 actions 直接调用 `this._core` 的方法 -- **响应式**:通过 Pinia 的响应式系统,当 core 状态变化时自动更新 - -**使用方式**: - -```typescript -// 任意组件 -import { useSimApi } from '@simcu/simapi' - -const api = useSimApi() - -// 初始化(二选一) -// 方式一:从 window.simapi 读取 -api.autoInit() - -// 方式二:直接传入配置 -api.configure({ - api: { endpoints: { default: 'https://api.example.com' } }, -}) - -// 所有方法与 SimApiCore 完全一致 -await api.query('/users/list', { page: 1 }) -``` - ---- - -## 构建流程 - -``` -npm run build - → vite build 输出 dist/index.mjs / index.cjs / pinia.mjs / pinia.cjs - → tsc -p tsconfig.build.json 输出 *.d.ts 类型声明 -``` - -**版本号注入:** - -版本号通过 `vite.config.ts` 的 `define` 配置注入,从 npm config 读取: - -```typescript -define: { - 'SimApiVersion': JSON.stringify(process.env.npm_config_SimApiVersion || '0.0.0-develop'), -} -``` - -**本地构建示例:** -```bash -# 通过环境变量 -# Windows PowerShell -$env:npm_config_SimApiVersion='1.0.0'; npm run build - -# Linux/Mac -npm_config_SimApiVersion=1.0.0 npm run build -``` - -**GitHub Actions 自动发布:** -```yaml -env: - SimApiVersion: ${{ github.ref_name }} -run: npm run build -``` - -**dist 输出是平铺的**,core 和 pinia 的编译产物全部在同一目录: - -``` -dist/ -├── index.mjs # core ESM -├── index.cjs # core CJS -├── pinia.mjs # pinia ESM -├── pinia.cjs # pinia CJS -├── simapi.core.d.ts -├── simapi.pinia.d.ts -└── types.d.ts -``` - ---- - -## package.json exports - -```json -".": { "import": "./dist/index.mjs", "types": "./dist/simapi.core.d.ts" } -"./pinia": { "import": "./dist/pinia.mjs", "types": "./dist/simapi.pinia.d.ts" } -``` - ---- - -## 注意事项 - -- Core 默认 `debug: true`,生产环境需手动 `configure({ debug: false })` -- `query()` 返回 `Promise>`,code !== 200 时不 reject,通过 `businessCallback` 处理 -- `login()` 成功后将 `result.data` 存入 localStorage -- **无 Cookie**:所有请求不发送 Cookie,Token 通过请求头传递 -- **零依赖**:不需要安装 axios,使用原生 fetch -- 删除了 Angular 支持,如需恢复参考 git 历史 -- **版本号管理**:使用 `declare const` + Vite `define` 注入 -- **AppVersion** 不注入,留给调用方自己管理 diff --git a/README.md b/README.md index 9c20193..441afe9 100644 --- a/README.md +++ b/README.md @@ -1,267 +1,561 @@ -# @simcu/simapi +# @simcu/simapi — SimApi Vue 前端库(AI 编码参考) -> 轻量级 HTTP 请求库,基于原生 fetch,支持任意 JS/TS 环境及 Vue3。 +> **包名**: `@simcu/simapi` | **技术栈**: TypeScript + Vue3 + Pinia + 原生 fetch +> **后端对应**: [simapi-net](../simapi-net)(`Simcu.SimApi` NuGet 包) +> **性质**: simapi-net 的官方前端 HTTP 客户端,**专为其统一响应格式设计** +> +> **使用方式**: 将本文档作为上下文提供给 AI,或粘贴到对话开头。AI 阅读本文档后应能正确编写调用 simapi-net 接口的前端代码。 -## 安装 +--- + +## 0. 一句话定位 + +simapi-vue 是 **simapi-net 的前端搭档**。后端用 `Simcu.SimApi` 写接口,前端用 `@simcu/simapi` 调接口。两者共享同一套响应格式、认证方式和错误处理约定。 + +--- + +## 1. 核心概念(必读) + +### 1.1 统一响应格式 + +simapi-net 所有接口的响应格式固定如下(HTTP 状态码始终 200): + +```json +{ "code": 200, "message": "成功", "data": { ... } } +``` + +| code 含义 | +|-----------| +| 200 成功 | 204 无数据 | 400 参数错误 | +| 401 需要登录 | 403 无权访问 | 404 不存在 | 500 服务器错误 | + +### 1.2 Token 认证 + +- 前端通过请求头 `Token: ` 传递认证令牌 +- Token 存储在 localStorage,key 默认为 `simapi-auth-token` +- **不使用 Cookie / 不使用 Authorization: Bearer** + +### 1.3 请求方式 + +- **默认全部 POST**,body 为 JSON +- 使用原生 fetch,不依赖 axios + +--- + +## 2. 项目结构 + +``` +simapi-vue/ +├── src/ +│ ├── types.ts # 所有类型定义 +│ ├── simapi.core.ts # 纯 TS 核心(SimApiCore 类) +│ └── simapi.pinia.ts # Vue3 Pinia Store 封装(useSimApi) +├── dist/ # 构建产物(ESM) +├── package.json # 包名 @simcu/simapi +└── vite.config.ts # Vite 构建配置 +``` + +### 导出路径(Subpath Exports) + +| 导入路径 | 内容 | 适用场景 | +|---------|------|---------| +| `@simcu/simapi` | SimApiCore + 类型 | 纯 TS / Node.js / 任意 JS 环境 | +| `@simcu/simapi/pinia` | useSimApi Pinia Store | **Vue3 项目(推荐)** | + +--- + +## 3. 快速开始(Vue3 项目标准用法) + +### 3.1 安装 ```bash -npm install @simcu/simapi +npm install @simcu/simapi pinia ``` -## 架构 +> `pinia` 是 peerDependency,Vue3 项目必须安装。 -``` -src/ -├── types.ts # 类型定义(SimApiBaseResponse、SimApiOptions 等) -├── simapi.core.ts # 纯 TS 核心,无框架依赖 -└── simapi.pinia.ts # Vue3 Pinia Store 封装 -``` +### 3.2 第一步:public/config.js — 外部配置文件 -## 核心层(框架无关) +在项目的 `public/` 目录下创建 `config.js`,定义 API 地址: -适用于浏览器、Node.js、小程序等任意环境。 - -```typescript -import { SimApiCore } from '@simcu/simapi' - -const api = new SimApiCore() - -// 从 window.simapi 读取配置(可选) -api.autoInit() - -// 或手动配置 -api.configure({ - api: { endpoints: { default: 'https://api.example.com' } }, +```javascript +// public/config.js +window.simapi = { debug: true, -}) - -// 发起请求 -const res = await api.query('/users/list', { page: 1 }) -// res.code === 200,res.data 为业务数据 - -// 登录 -await api.login({ phone: '13800138000', code: '123456' }) - -// 登出 -await api.logout() - -// 注册业务错误码回调 -api.setBusinessCallback(401, () => router.push('/login')) -api.setBusinessCallback('common', (data) => alert(data.message)) -``` - -## Vue3 - -安装 Pinia: - -```bash -npm install pinia -``` - -```typescript -// main.ts -import { createApp } from 'vue' -import { createPinia } from 'pinia' -import App from './App.vue' - -const app = createApp(App) -app.use(createPinia()) -app.mount('#app') -``` - -```typescript -// 任意组件 -import { useSimApi } from '@simcu/simapi' - -const api = useSimApi() - -// 响应式 -console.log(api.token) // 当前 Token -console.log(api.isLoggedIn) // 是否已登录 - -// 发起请求 -const res = await api.query('/users/list', { page: 1 }) -console.log(res.data) - -// 登录 / 登出 -await api.login({ phone: '13800138000', code: '123456' }) -await api.logout() - -// 调试模式 -api.setDebug(true) -``` - -### 多端点 - -```typescript -api.setEndpoints({ - default: 'https://api.example.com', - admin: 'https://admin.example.com', -}) - -// 指定端点发请求 -await api.query('/stats', {}, 'admin') -``` - -## autoInit — 从 window 读取配置 - -支持字段:`endpoints`、`defaultEndpoint`、`debug`。业务回调需在代码中通过 `setBusinessCallback` 处理。 - -```html - -``` - -初始化时手动调用: - -```typescript -// 方式一:从 window.simapi 读取 -const api = useSimApi() -api.autoInit() - -// 方式二:直接传入配置 -const api = useSimApi() -api.configure({ - api: { endpoints: { default: 'https://api.example.com' } }, -}) -``` - -## SimApiBaseResponse 响应格式 - -```typescript -interface SimApiBaseResponse { - code: number // 200 = 成功,其他为业务错误码 - message: string // 提示信息 - data?: T // 业务数据 } ``` -## 完整配置参考 +**为什么用 config.js 而不是硬编码?** +- 前后端分离部署时,API 地址可能变化 +- `public/` 下的文件 Vite 会直接复制到输出目录,不经过构建 +- 打包后运维人员可以直接修改 `config.js` 切换环境,无需重新构建 -### SimApiOptions — 完整配置结构 +**config.js 支持的字段:** + +| 字段 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `debug` | boolean | 是否打印请求/响应日志 | `false` | +| `endpoints` | object | 多端点地址映射 | `{ default: '' }` | +| `defaultEndpoint` | string | 默认使用的端点名称 | `'default'` | + +### 3.3 第二步:index.html — 引入 config.js + +```html + + + + + + + My App + + + + +
+ + + +``` + +> ` +``` + +**关键点说明:** + +| 要点 | 说明 | +|------|------| +| `import from '@simcu/simapi/pinia'` | Vue3 项目**必须**用 `/pinia` 子路径导入 | +| `onMounted` 中初始化 | 确保 DOM 已加载、config.js 已执行、Pinia 已就绪 | +| `autoInit()` | 读取 `window.simapi` 的 `endpoints`、`defaultEndpoint`、`debug` | +| `setBusinessCallback(401, fn)` | 当后端返回 code=401 时自动执行 | +| `setBusinessCallback('common', fn)` | 兜底回调,任何非 200 且未匹配其他回调时触发 | + +### 3.6 第五步:在组件中使用 + +```vue + + + + +``` + +--- + +## 4. 完整项目模板(可直接复制使用) + +以下是一个完整的 Vue3 + simapi-vue 项目初始化清单: + +### 文件清单 + +``` +my-project/ +├── public/ +│ └── config.js # ← API 配置(第 1 步) +├── index.html # ← 引入 config.js(第 2 步) +├── src/ +│ ├── main.ts # ← 注册 Pinia(第 3 步) +│ ├── App.vue # ← 初始化 SimApi(第 4 步) +│ ├── router/ +│ │ └── index.ts +│ └── views/ +│ └── Login.vue # ← 使用示例 +├── package.json # ← 依赖 +└── vite.config.ts +``` + +### public/config.js + +```javascript +window.simapi = { + debug: true, + endpoints: { + default: "http://localhost:5000" + } +} +``` + +### index.html + +```html + + + + + + My App + + + +
+ + + +``` + +### src/main.ts + +```typescript +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' + +createApp(App) + .use(createPinia()) + .use(router) + .mount('#app') +``` + +### src/App.vue + +```vue + + + +``` + +### src/views/Login.vue + +```vue + + + +``` + +--- + +## 5. API 参考 + +### 5.1 import 方式 + +```typescript +// ✅ Vue3 项目 — 用 Pinia Store(推荐) +import { useSimApi } from '@simcu/simapi/pinia' + +// ✅ 非 Vue 项目 / 纯 TS — 用 Core 类 +import { SimApiCore } from '@simcu/simapi' +``` + +### 5.2 useSimApi Store — 方法与属性一览 + +获取实例: + +```typescript +const api = useSimApi() // 单例模式,全局状态共享 +``` + +#### 属性(Getters) + +| 属性 | 类型 | 说明 | +|------|------|------| +| `api.token` | `string` | 当前存储的 Token(只读) | +| `api.isLoggedIn` | `boolean` | 是否已登录(Token 是否存在) | +| `api.debug` | `boolean` | 调试开关 | +| `api.api` | `SimApiApiConfig` | API 配置对象(一般不直接操作) | +| `api.auth` | `SimApiAuthConfig` | 认证配置对象(一般不直接操作) | + +#### 方法(Actions) + +| 方法 | 参数 | 返回值 | 说明 | +|------|------|--------|------| +| `autoInit()` | 无 | `void` | 从 `window.simapi` 读取 endpoints/debug 配置 | +| `configure(options)` | `SimApiOptions` | `void` | 手动配置(深合并) | +| `setDebug(bool)` | boolean | `void` | 设置调试模式 | +| `setEndpoints(map)` | `{[name]: url}` | `void` | 设置多端点映射 | +| `query(uri, params?, endpoint?, headers?)` | 见下方详解 | `Promise>` | **核心方法:发送 POST 请求** | +| `login(request)` | `{[key]: any}` | `Promise>` | 登录,成功后自动存 Token | +| `logout(url?)` | string? | `Promise` | 登出,清除 Token 并调后端接口 | +| `checkLogin(url?)` | string? | `Promise` | 检查登录态,过期则触发 401 回调 | +| `setBusinessCallback(code, fn)` | number\|string, callback | `void` | 注册业务错误码回调 | +| `getToken()` | 无 | `string` | 获取当前 Token | +| `setToken(token)` | string | `void` | 手动设置 Token | +| `removeToken()` | 无 | `void` | 清除 Token | +| `getVersion(endpoint?)` | string? | `Promise` | 获取前后端版本信息 | +| `getEndpoint(name?)` | string? | `string` | 获取某端点的 baseURL | + +### 5.3 query 方法详解 + +这是最核心的方法——几乎所有数据交互都通过它: + +```typescript +async function query( + uri: string, // 接口路径,如 '/user/list' + params?: any = {}, // 请求体(POST body),JSON 对象 + endpointKey?: string, // 可选:指定端点名(默认用 default) + extraHeaders?: Record // 可选:额外请求头 +): Promise> +``` + +**使用示例:** + +```typescript +// 基本查询 +const res = await api.query('/user/list', { page: 1, count: 20 }) +console.log(res.data) // User[] 数组 + +// 带参数 +const res = await api.query('/user/detail', { id: 'xxx' }) + +// 指定端点 +await api.query('/admin/stats', {}, 'admin') + +// 自定义请求头 +await api.query('/file/upload', formData, undefined, { 'Content-Type': 'multipart/form-data' }) + +// 错误处理 +try { + const res = await api.query('/user/list') +} catch (err: any) { + // err 是 SimApiBaseResponse 类型 + console.log(err.code) // 业务错误码,如 400/401/403/500 + console.log(err.message) // 错误消息 + console.log(err.data) // 可能携带的错误详情 +} +``` + +**⚠️ query 的行为要点:** + +1. 自动在请求头添加 `Token`(如果存在) +2. `code === 200` → 正常返回 `SimApiBaseResponse` +3. `code !== 200` → 先执行对应的 businessCallback,然后 **throw 异常** +4. 网络错误 → 执行 responseCallback.error,throw 包装后的 `{code: -1}` 异常 +5. 所以调用方只需 `try/catch` 处理异常即可 + +### 5.4 login / logout 方法 + +```typescript +// login:POST 到 auth.login_url(默认 /auth/login),自动保存返回的 Token +await api.login({ phone: '13800138000', code: '123456' }) +// 后端返回 { code: 200, data: "token-string" } +// 前端自动将 data 存入 localStorage + +// logout:清除本地 Token,可选调后端登出接口 +await api.logout() // 调 /auth/logout +await api.logout(null) // 只清本地 Token,不调后端 +``` + +### 5.5 setBusinessCallback — 业务错误处理 + +```typescript +// 针对特定错误码 +api.setBusinessCallback(401, (data) => { + console.log('未授权', data.message) + router.replace('/login') +}) + +api.setBusinessCallback(403, (data) => { + alert('无权限:' + data.message) +}) + +// 兜底:任何未单独处理的非 200 错误都会走 common +api.setBusinessCallback('common', (data) => { + console.error('请求失败:', data.code, data.message) +}) +``` + +**回调执行顺序:** 匹配具体错误码 → 未匹配则走 `'common'` → 然后 throw + +--- + +## 6. 类型定义速查 + +```typescript +/** 标准响应 */ +interface SimApiBaseResponse { + code: number // 200=成功,其他=业务错误码 + message: string // 提示信息 + data?: T // 业务数据 +} + +/** 版本信息 */ +interface SimApiVersions { + uiApp: string // 前端应用版本 + uiSimApi: string // 前端 SimApi 版本 + apiApp: string // 后端应用版本(简化版) + apiSimApi: string // 后端 SimApi 版本(简化版) + apiAppFull: string // 后端应用版本(完整版) + apiSimApiFull: string // 后端 SimApi 版本(完整版) +} + +/** 认证配置 */ +interface SimApiAuthConfig { + token_name: string // localStorage key,默认 'simapi-auth-token' + check_url: string // 登录检查接口,默认 '/auth/check' + logout_url: string // 登出接口,默认 '/auth/logout' + login_url: string // 登录接口,默认 '/auth/login' +} + +/** API 配置 */ +interface SimApiApiConfig { + endpoints: { [name]: string } // 多端点映射 + defaultEndpoint: string // 默认端点 + businessCallback: SimApiBusinessCallback // 错误码回调 + responseCallback: SimApiResponseCallback // 响应拦截器 + timeout?: number // 超时毫秒数,默认 10000 +} + +/** 完整选项 */ interface SimApiOptions { - /** 调试模式,默认 true */ debug?: boolean - - /** 认证相关配置 */ auth?: Partial - - /** API 相关配置 */ api?: Partial } ``` -### SimApiAuthConfig — 认证配置 +--- -```typescript -interface SimApiAuthConfig { - /** localStorage 中存储 Token 的 key */ - token_name: string // 默认 'simapi-auth-token' +## 7. 多端点支持 - /** 检查登录状态的接口路径 */ - check_url: string // 默认 '/auth/check' +适用于需要连接多个后端服务的场景: - /** 登出接口路径 */ - logout_url: string // 默认 '/auth/logout' - - /** 登录接口路径 */ - login_url: string // 默认 '/auth/login' -} -``` - -### SimApiApiConfig — API 配置 - -```typescript -interface SimApiApiConfig { - /** 多端点映射,key 为端点名,value 为 baseURL */ - endpoints: { [name: string]: string } - - /** 默认端点名,默认 'default' */ - defaultEndpoint: string - - /** 业务错误码回调,key 为错误码或 'common' */ - businessCallback: SimApiBusinessCallback - - /** 响应拦截回调 */ - responseCallback: SimApiResponseCallback - - /** 请求超时时间(毫秒),默认 10000 */ - timeout?: number -} -``` - -**⚠️ CORS 说明** - -- 本库使用原生 fetch API,默认不发送 Cookie(`credentials: 'omit'`) -- Token 通过请求头 `Token` 传递,无需依赖 Cookie -- 服务器返回 `Access-Control-Allow-Origin: *` 不会有问题 - -### SimApiBusinessCallback — 业务回调 - -key 支持数字错误码或 `'common'`(通用兜底回调): - -```typescript -type SimApiBusinessCallback = { - [code: number | string]: (data: SimApiBaseResponse) => void -} - -// 示例 -{ - 401: (data) => router.push('/login'), // 未授权 - 403: (data) => ElMessage.error('无权限'), // 无权限 - 500: (data) => console.error(data), // 服务器错误 - 'common': (data) => ElMessage.error(data.message) // 其他错误码兜底 -} -``` - -### SimApiResponseCallback — 响应拦截 - -```typescript -interface SimApiResponseCallback { - /** 成功响应拦截,可在此统一处理数据结构 */ - success: (response: any) => any - - /** 网络/HTTP 错误拦截 */ - error: (err: any) => void -} - -// 示例:统一脱敏处理 -{ - success: (res) => { - // fetch 返回 { code, message, data } - return res +```javascript +// config.js +window.simapi = { + debug: true, + endpoints: { + default: 'https://api.example.com', // 主服务 + admin: 'https://admin.example.com', // 管理后台服务 + cdn: 'https://cdn.example.com', // CDN/文件服务 }, - error: (err) => { - console.error('请求失败', err) - throw err - } + defaultEndpoint: 'default' } ``` -### SimApiVersions — 版本信息 - ```typescript -interface SimApiVersions { - uiApp: string // 前端应用版本 - uiSimApi: string // 前端 SimApi 版本 - apiApp: string // 后端应用版本(简化版,如 "1.2.3") - apiSimApi: string // 后端 SimApi 版本(简化版) - apiAppFull: string // 后端应用版本(完整版,如 "1.2.3+20240101") - apiSimApiFull: string // 后端 SimApi 版本(完整版) -} +// 使用默认端点 +await api.query('/user/list') // → https://api.example.com/user/list + +// 指定端点 +await api.query('/system/stats', {}, 'admin') // → https://admin.example.com/system/stats ``` -### 完整配置示例 +也可以运行时动态添加: + +```typescript +api.setEndpoints({ backup: 'https://backup-api.example.com' }) +``` + +--- + +## 8. configure — 手动完整配置 + +除了 autoInit 从 `window.simapi` 读取外,也可以手动配置一切: ```typescript api.configure({ @@ -269,130 +563,190 @@ api.configure({ auth: { token_name: 'my-app-token', - check_url: '/api/auth/check', - logout_url: '/api/auth/logout', - login_url: '/api/auth/login', + check_url: '/auth/check', + logout_url: '/auth/logout', + login_url: '/auth/login', }, api: { endpoints: { default: 'https://api.example.com', - admin: 'https://admin.example.com', }, defaultEndpoint: 'default', + timeout: 15000, businessCallback: { - 401: () => router.push('/login'), - 403: () => ElMessage.error('无权限访问'), - 500: (data) => console.error('服务器错误:', data.message), - 'common': (data) => ElMessage.error(data.message || '请求失败'), + 401: () => router.replace('/login'), + 403: (data) => alert('无权限'), + 500: (data) => console.error('服务器错误', data), + 'common': (data) => MessagePlugin.error(data.message), }, responseCallback: { - success: (res) => res.data ?? res, - error: (err) => { - console.error('网络错误', err) - }, + success: (res) => res, // 成功响应拦截(可做数据转换) + error: (err) => console.error('网络错误', err), }, }, }) ``` -## API 参考 +**configure vs autoInit 的关系:** +- `autoInit()` 只读 `window.simapi` 的 `endpoints`、`defaultEndpoint`、`debug` +- `configure()` 可以覆盖所有字段,包括 auth 和 callbacks +- 通常做法是:`autoInit()` 读基础配置 + `setBusinessCallback()` 补充回调 -### SimApiCore(核心类) +--- -| 方法/属性 | 说明 | -|-----------|------| -| `configure(options)` | 批量配置(深合并) | -| `autoInit()` | 从 `window.simapi` 读取配置(endpoints、defaultEndpoint、debug) | -| `setEndpoints(map)` | 设置端点 | -| `setBusinessCallback(code, fn)` | 注册业务错误码回调 | -| `query(uri, params?, endpointKey?, headers?)` | POST 请求,返回 `Promise>` | -| `login(request)` | 登录,自动存 Token | -| `logout(url?)` | 登出,清除 Token | -| `checkLogin(url?)` | 主动检查登录状态 | -| `getVersion(endpointName?)` | 获取版本信息,返回 `Promise` | -| `getToken()` | 获取 Token | -| `setToken(token)` | 手动设置 Token | -| `removeToken()` | 清除 Token | -| `isLoggedIn` | getter,是否已登录 | -| `debug` | boolean,调试模式 | -| `logDebug(...args)` | 日志工具(仅在 debug 模式输出) | +## 9. GOTCHAS(AI 最容易犯的错) -## 构建 +| ❌ 错误 | ✅ 正确 | +|---------|---------| +| `import { useSimApi } from '@simcu/simapi'` (Vue3) | `from '@simcu/simapi/pinia'`(必须带 `/pinia`) | +| 忘记 `app.use(createPinia())` | **必须在 `useSimApi()` 之前**注册 Pinia | +| `config.js` 放在 `src/` 下 | 必须放在 `public/` 下,Vite 才能原样复制 | +| `index.html` 中不引 config.js 或放在 main.ts 之后 | config.js 必须**同步加载**且在模块代码之前执行 | +| 用 `Authorization: Bearer xxx` | 用 `Token` 请求头(这是 simapi-net 约定) | +| 期望 HTTP 4xx/5xx 表示错误 | 所有错误都是 **HTTP 200 + JSON `code` 字段** | +| `res.data` 直接用而不判空 | `res.data` 可能是 `undefined`,用 `res.data ?? []` 或 `res.data!` | +| 在 setup 外部调用 `useSimApi()` | `useSimApi()` 只能在 **setup 上下文**(或 pinia active)中调用 | +| 用 Cookie 传 Token | Token 通过 **请求头 `Token`** + **localStorage** 存储 | +| `new SimApiCore()` 在 Vue 项目里用 | Vue 项目统一用 `useSimApi()` Pinia Store | -```bash -npm install -npm run build -npm link # 本地调试 +--- + +## 10. 与 simapi-net 后端的对接约定 + +### 10.1 通信协议 + +``` +[Vue 前端] -- POST(JSON) --> [simapi-net 后端] + Header: Token: + Body: { key: value } + +[Vue 前端] <-- JSON {code, message, data} -- [simapi-net 后端] + (HTTP Status 始终 200) ``` -### 版本号注入 +### 10.2 内置路由对照表 -库使用 `declare const` 声明版本常量,构建时通过 Vite 的 `define` 注入版本号。 +simapi-net 启用认证后,自动生成以下路由,simapi-vue 已内置对应方法: -**注意:** -- **SimApiVersion**:由 simapi 库构建时注入 -- **AppVersion**:不注入,留给调用方 APP 注入 +| simapi-net 路由 | simapi-vue 方法 | 触发条件 | +|-----------------|-----------------|----------| +| `POST /auth/login` | `api.login(request)` | `EnableSimApiAuth = true` | +| `POST /auth/check` | `api.checkLogin()` | `EnableSimApiAuth = true` | +| `POST /auth/logout` | `api.logout()` | `EnableSimApiAuth = true` | +| `POST /user/info` | `api.query('/user/info')` | `EnableSimApiAuth = true`(需登录) | +| `GET /versions` | `api.getVersion()` | `EnableVersionUrl`(默认开启) | -**支持的版本号环境变量(按优先级):** +### 10.3 登录流程示例 -1. `VITE_SimApiVersion` — Vite .env 文件 -2. `npm_config_SimApiVersion` — npm 构建时通过 `npm run build` 前设置 -3. `SimApiVersion` — 直接环境变量(如 GitHub Actions) - -未指定时默认为 `0.0.0-develop`。 - -**本地构建:** - -```bash -# Windows PowerShell -$env:npm_config_SimApiVersion='1.0.0'; npm run build - -# Windows CMD -set npm_config_SimApiVersion=1.0.0 && npm run build - -# Linux/Mac -npm_config_SimApiVersion=1.0.0 npm run build ``` - -**GitHub Actions / CI/CD:** - -```yaml -- name: Build - env: - SimApiVersion: ${{ github.ref_name }} # 或其他版本号 - run: npm run build -``` - -**调用方注入 AppVersion:** - -调用方在自己的 `vite.config.ts` 中添加: - -```typescript -define: { - 'AppVersion': JSON.stringify('1.0.0') -} +用户输入手机号+验证码 + ↓ +前端: api.login({ phone: '138xx', code: '123456' }) + ↓ POST /auth/login { phone, code } +后端: 验证通过 → 返回 { code: 200, data: "token-string" } + ↓ +前端: 自动将 token 存入 localStorage['simapi-auth-token'] + ↓ +后续请求: api.query('/user/info') + ↓ 自动带上 Header: Token: token-string +后端: SimApiAuthMiddleware 解析 Token → HttpContext.Items["LoginInfo"] + ↓ Controller 可通过 LoginInfo 获取当前用户 ``` --- -## 从旧版迁移 +## 11. 构建 -如果你之前使用的是带 axios 的版本,迁移非常简单: +```bash +# 开发模式(监听文件变化) +npm run dev + +# 生产构建 +npm run build + +# 本地 link 调试 +npm link +cd ../your-project +npm link @simcu/simapi +``` + +构建产物位于 `dist/` 目录: +- `dist/index.mjs` — 核心(SimApiCore)ESM 入口 +- `dist/pinia.mjs` — Pinia Store ESM 入口 +- `dist/*.d.ts` — TypeScript 类型声明 + +### 版本号注入 + +库使用 `declare const` 声明版本常量,构建时通过 Vite 的 `define` 注入。 + +**注意:** +- **SimApiVersion**:由 simapi 库自身构建时注入 +- **AppVersion**:由**调用方项目**在自己的 `vite.config.ts` 中注入 + +#### simapi 库自身构建 — 注入 SimApiVersion + +在 simapi-vue 的 `vite.config.ts` 中,从自身 `package.json` 读取版本号: + +```typescript +import { readFileSync } from 'node:fs' + +const pkg = JSON.parse(readFileSync('./package.json', 'utf-8')) + +export default defineConfig({ + // ... 其他配置 + define: { + SimApiVersion: JSON.stringify(pkg.version), // ← 取 simapi-vue 自身的 version + }, +}) +``` + +未配置时默认为 `0.0.0-develop`。 + +#### 调用方项目 — 注入 AppVersion(推荐方式:从 package.json 自动读取) + +在调用方项目的 `vite.config.ts` 中添加: + +```typescript +import { readFileSync } from 'node:fs' + +const pkg = JSON.parse(readFileSync('./package.json', 'utf-8')) + +export default defineConfig({ + // ... 其他配置 + define: { + AppVersion: JSON.stringify(pkg.version), // ← 自动取 package.json 的 version 字段 + }, +}) +``` + +这样每次发版只需改 `package.json` 的 `version`,无需同步修改其他地方。 + +--- + +## 12. 从 axios 迁移 + +如果你之前用的是 axios: ```diff - import axios from 'axios' -+ import { SimApiCore } from '@simcu/simapi' -- // ... 你的 axios 配置 +- const res = await axios.post('/user/list', { page: 1 }) +- console.log(res.data) -+ const api = new SimApiCore() -+ api.setEndpoints({ default: 'https://api.example.com' }) -+ const res = await api.query('/users/list', { page: 1 }) ++ import { useSimApi } from '@simcu/simapi/pinia' ++ const api = useSimApi() ++ const res = await api.query('/user/list', { page: 1 }) ++ console.log(res.data) // res.data 就是业务数据 ``` -API 完全兼容,无需其他改动。主要变化: -- 使用原生 fetch 替代 axios -- Token 通过请求头 `Token` 传递(不是 `Authorization: Bearer`) -- 默认不发送 Cookie,避免 CORS 问题 +主要区别: + +| axios | simapi-vue | +|-------|-----------| +| `axios.post()` | `api.query()` | +| `response.data` 直接是业务数据 | `SimApiBaseResponse.data` 是业务数据 | +| HTTP 4xx/5xx 表示错误 | HTTP 200 + `code` 字段表示错误 | +| `interceptors.response` | `businessCallback` + `responseCallback` | +| Authorization Bearer | Token header |