# mm_os Vue 前端 API 调用规范

## 一、概述

在 mm_os 框架中，Vue 前端页面通过 AJAX 调用 **type:client** 的 API 获取数据。框架内置了 `mm_vue.js`，在 Vue 原型上扩展了 `$get`、`$post`、`$getList`、`$getObj` 四个方法，开发者无需引入 axios 等第三方库即可完成 API 调用。

Vue 前端数据流遵循：**Vue 组件通过内置方法调用 API → 接收标准 JSON 响应 → 更新组件状态 → 渲染 DOM**。

### 1.1 跨平台通用说明

mm_os 的 API 接口是**前端无关**的，无论使用 Vue、微信小程序还是 uni-app，后端 API 的路径、响应格式、鉴权方式完全一致。以下内容是**所有前端平台通用的**：

| 通用项 | Vue | 微信小程序 | uni-app |
|:-------|:----|:-----------|:--------|
| **API 路径** | `/api/{app}/{name}` | 相同 | 相同 |
| **API 发现** | `/api/dev_api` 三级发现 | 相同 | 相同 |
| **响应格式** | `{ result: {...} }` / `{ error: {...} }` | 相同 | 相同 |
| **鉴权方式** | `x-auth-token` 请求头 | 相同 | 相同 |
| **错误码** | 10000~20000 | 相同 | 相同 |
| **调用方法** | **`this.$get()/$post()`** | **`wx.request()`** | **`uni.request()`** |

Vue 开发者可直接使用框架内置的 `this.$get/$post` 方法。小程序和 uni-app 开发者需要使用平台的网络请求 API，并在请求头中手动注入 `x-auth-token`（从本地存储中读取 token）。

> 本章后续内容以 Vue 的 `this.$get/$post` 为例讲解，但 API 路径、响应格式、发现方式等内容适用于所有前端平台。

## 二、Vue 内置 API 调用方法

`mm_vue.js` 在 Vue 原型上挂载了以下方法，所有 Vue 组件中可通过 `this.xxx` 直接调用：

### 2.1 `$get(url, query, func)` — GET 请求

```javascript
// 回调模式
this.$get('/api/cms/article_list', { page: 1, size: 10 }, function(json) {
  if (json.result) {
    this.list = json.result.list;
    this.count = json.result.count;
  }
}.bind(this));

// Promise 模式（不传第三个参数）
var json = await this.$get('/api/cms/article_list', { page: 1 });
if (json.result) {
  this.list = json.result.list;
}
```

### 2.2 `$post(url, param, func, type)` — POST 请求

```javascript
// 回调模式
this.$post('/api/cms/article_add', {
  title: '新文章',
  content: '内容...'
}, function(json) {
  if (json.result && json.result.bl) {
    this.$toast('添加成功');
  }
});

// Promise 模式
var json = await this.$post('/api/cms/article_add', { title: '...' });
```

| 参数 | 类型 | 必填 | 说明 |
|:-----|:-----|:-----|:------|
| `url` | string | ✅ | API 路径，如 `/api/cms/article_list` |
| `param` | object | ✅ | 请求参数（GET 时为 query，POST 时为 body） |
| `func` | function | | 回调函数，传入响应 json。不传则返回 Promise |
| `type` | string | | POST 时指定 Content-Type，默认 `application/json` |

### 2.3 `$getList(key, url, func)` — 快捷获取列表

```javascript
this.$getList('articles', '/api/cms/article_list', function(json) {
  // 回调可选；默认行为：this.articles = json.result.list
});
// 等价于：自动将 json.result.list 赋值给 this[key]
```

### 2.4 `$getObj(key, url, func)` — 快捷获取对象

```javascript
this.$getObj('article', '/api/cms/article_detail?id=123');
// 等价于：自动将 json.result.obj 赋值给 this[key]
```

### 2.5 自动处理机制

| 机制 | 说明 |
|:-----|:------|
| **Token 注入** | 自动在请求头中注入 `x-auth-token`，无需手动处理 |
| **未登录拦截** | 若接口返回未登录状态，自动跳转到登录页 |
| **错误提示** | 网络异常或返回 error 时，自动弹出 toast 提示 |
| **加载状态** | 请求期间自动设置 `this.loading = true/false` |

## 三、API 路径与接口类型

### 3.1 三种 API 目录类型

mm_os 中 API 按目录分为三种类型，Vue 开发者主要关注 `api_{app}_client/`：

| 目录 | 类型 | 响应格式 | 用途 |
|:-----|:-----|:---------|:------|
| `api_{app}_web/` | Web 页面 API | HTML（`db.tpl.view()`） | 渲染完整页面 |
| `api_{app}_client/` | 客户端数据 API | JSON（`$.ret.obj()` / `$.ret.list()`） | **Vue 前端数据交互** |
| `api_{app}_manage/` | 管理端 API | JSON（需管理员权限） | 后台管理接口 |

### 3.2 API 路由命名约定

API 路由由 `api.json` 中的 `path` 字段定义：

```json
{
  "path": "/api/{app}/{name}",
  "type": "client",
  "method": "ALL"
}
```

调用时直接使用 path 作为 URL：

```javascript
this.$get('/api/cms/article_list', { page: 1 });
this.$post('/api/cms/article_add', { title: '...' });
```

### 3.3 method 参数

部分 API 支持通过 `?method=` 参数切换操作：

```javascript
// 同一个 path，不同 method 执行不同操作
this.$get('/api/cms/article?method=get_list');   // 获取列表
this.$get('/api/cms/article?method=get_obj');    // 获取单条
this.$post('/api/cms/article?method=add');       // 新增
this.$post('/api/cms/article?method=set');       // 修改
this.$post('/api/cms/article?method=del');       // 删除
```

## 四、API 响应格式（mm_ret 标准）

### 4.1 成功响应

```javascript
// 对象数据
{ "result": { "obj": { "article_id": 1, "title": "..." } } }

// 列表数据
{ "result": { "list": [...], "count": 100 } }

// 操作结果
{ "result": { "bl": true, "tip": "操作成功" } }

// 简单数据
{ "result": { "data": "任意值" } }
```

### 4.2 错误响应

```javascript
{
  "error": {
    "code": 10000,
    "message": "参数错误"
  }
}
```

### 4.3 响应判断模式

```javascript
// 模式一：回调中判断
this.$get('/api/xxx', {}, function(json) {
  if (json.result) {
    var data = json.result;
    // 处理成功
  } else if (json.error) {
    console.error(json.error.code, json.error.message);
    // 处理错误
  }
});

// 模式二：async/await
var json = await this.$get('/api/xxx', {});
if (json.result) {
  var data = json.result;
} else {
  // json.error
}
```

### 4.4 result 常见字段

| 字段 | 类型 | 说明 |
|:-----|:-----|:------|
| `result.obj` | Object | 单条数据对象 |
| `result.list` | Array | 列表数据 |
| `result.count` | number | 数据总条数（分页用） |
| `result.bl` | boolean | 操作是否成功 |
| `result.tip` | string | 操作提示信息 |
| `result.data` | any | 通用数据载体 |

## 五、Vue mixin CRUD 模式

mm_os 管理后台使用 `mixins/page.js` 中的 `mixin_page` 混入，配置 URL 即可获得自动 CRUD 能力。

### 5.1 mixin_page 配置

```javascript
// 在 Vue 组件中
import mixin_page from '../mixins/page.js';

export default {
  mixins: [mixin_page],
  data() {
    return {
      url: '/api/cms/client/article',
      url_add: '/api/cms/client/article?method=add',
      url_del: '/api/cms/client/article?method=del',
      url_set: '/api/cms/client/article?method=set',
      url_get_obj: '/api/cms/client/article?method=get_obj',
      url_get_list: '/api/cms/client/article?method=get_list',
      url_submit: '/api/cms/client/article?method=submit',
      field: 'article_id',       // 主键字段名
      query: { page: 1, size: 15 }
    };
  }
};
```

### 5.2 mixin 自动提供的方法

| 方法 | 说明 | 调用方式 |
|:-----|:------|:---------|
| `this.get()` | 获取列表（按 `query` 条件） | `this.get()` |
| `this.add()` | 新增记录 | `this.add({ title: '...' })` |
| `this.del()` | 删除记录 | `this.del({ article_id: 1 })` |
| `this.set()` | 修改记录 | `this.set({ article_id: 1, title: '...' })` |
| `this.search()` | 搜索（重置 page 为 1 后重新 get） | `this.search({ keyword: '...' })` |
| `this.submit()` | 提交表单（自动判断新增/修改） | `this.submit()` |

### 5.3 URL 配置说明

| 字段 | 说明 |
|:-----|:------|
| `url` | 基础 API 地址，`get()` 等方法会自动拼接 `?method=` |
| `url_add` | 新增接口（POST） |
| `url_del` | 删除接口（POST） |
| `url_set` | 修改接口（POST） |
| `url_get_obj` | 获取单条详情（GET） |
| `url_get_list` | 获取列表（GET） |
| `url_submit` | 提交接口（POST，自动判断新增还是修改） |
| `field` | 主键字段名，用于判断 `field` 有值为修改，无值为新增 |
| `query` | 默认查询参数，含 page/size 等 |

## 六、通用 Vue 组件数据获取模式

mm_os 的 Vue 组件按功能分为五种类型，各有不同的数据获取模式：

### 6.1 组件分类

| 类型 | 命名约定 | 数据来源 | 数据输出 |
|:-----|:---------|:---------|:---------|
| **item** | `item_xxx` | `props` 接收父组件传入的 `query` 和 `list` | 渲染单条数据 |
| **list** | `list_xxx` | `props` 接收数据列表 | 渲染列表 |
| **bar** | `bar_xxx` | 用户操作触发 | 触发查询/操作事件 |
| **form** | `form_xxx` | 用户输入 | `$post` 提交数据 |
| **nav** | `nav_xxx` | `props` 接收配置 | `$router` / `location.href` 跳转 |

### 6.2 标准数据流

```
父组件                        子组件
  │                             │
  ├── $get('/api/xxx')          │
  │   获取数据                   │
  │                             │
  ├── :list="list" ──────────→ props.list 接收
  │   :query="query" ────────→ props.query 接收
  │                             │
  │                         渲染数据
  │                             │
  │   @events="handle" ←──── events(name, param)
  │   接收子组件回调              │
  │                             │
  ├── $post('/api/xxx')         │
  │   执行操作                   │
  │                             │
  └── this.get() 刷新列表       │
```

### 6.3 item 组件示例

```javascript
// item_article.js
export default {
  props: {
    query: { type: Object, default: () => ({}) },
    list: { type: Array, default: () => [] }
  },
  methods: {
    onEdit(o) {
      this.$emit('events', 'edit', o);
    },
    onDelete(o) {
      this.$emit('events', 'delete', o);
    }
  }
};
```

### 6.4 form 组件示例

```javascript
// form_article.js
export default {
  data() {
    return { form: { title: '', content: '' } };
  },
  methods: {
    async onSubmit() {
      var json = await this.$post('/api/cms/article?method=add', this.form);
      if (json.result && json.result.bl) {
        this.$toast('保存成功');
        this.$emit('events', 'saved');
      }
    }
  }
};
```

## 七、API 发现途径

### 7.1 🏆 首选：`/api/dev_api` — 三级 API 发现浏览器

mm_os 内置了 `/api/dev_api` 接口，让 Vue 开发者能**逐层发现**所有可用的 API，无需翻阅源码。

#### 第一级：获取全部可用 scope

不传任何参数，返回所有可用的 `scope` 值列表：

```javascript
// GET /api/dev_api
var json = await this.$get('/api/dev_api');
var scopes = json.result.scope;
// scopes = [
//   { name: "admin_web",    title: "网站-后台管理系统" },
//   { name: "cms_client",   title: "内容客户端" },
//   { name: "cms_manage",   title: "内容管理端" },
//   { name: "cms_web",      title: "网站-内容管理" },
//   { name: "iot_client",   title: "物联网客户端" },
//   { name: "iot_manage",   title: "物联网管理端" },
//   { name: "iot_web",      title: "网站-物联网" },
//   { name: "user_client",  title: "用户客户端" },
//   ...
// ]
```

> `scope` 命名格式为 `{app}_{type}`，其中 `type` 为 `client`（数据接口）/ `web`（页面接口）/ `manage`（管理接口）。

#### 第二级：查看指定 scope 下的 API 列表

```javascript
// GET /api/dev_api?scope=cms_client
// 从上一级返回的 scope 列表中选一个，如 "cms_client"
var json = await this.$get('/api/dev_api', { scope: 'cms_client' });
var apiList = json.result.list;
// apiList = [
//   { name: "cms_article",        title: "文章管理",            path: "/api/cms/article", ... },
//   { name: "cms_article_type",   title: "文章分类",            path: "/api/cms/article_type", ... },
//   { name: "cms_comment",        title: "文章评论",            path: "/api/cms/comment", ... },
//   ...
// ]
```

每个 API 返回的字段说明：

| 字段 | 说明 | 用途 |
|:-----|:------|:------|
| `name` | API 标识名 | 用于第三级查询的 `name` 参数 |
| `title` | API 中文标题 | 了解接口用途 |
| `description` | API 描述 | 了解接口功能 |
| `path` | **路由路径** | **Vue 调用时用的 URL** |
| `type` | 类型（api） | - |
| `method` | 请求方法（GET/POST/ALL） | 决定用 `$get` 还是 `$post` |
| `cache` | 缓存时间（分钟） | 0 表示不缓存 |
| `oauth.sign_in` | 是否需要登录 | true 表示需登录才能调用 |
| `oauth.gm` | 管理员权限级别 | 0 不限制 |

#### 第三级：查看单个 API 的完整详情

```javascript
// GET /api/dev_api?scope=cms_client&name=cms_article
// 从第二级返回的列表中取一个 name，如 "cms_article"
var json = await this.$get('/api/dev_api', {
  scope: 'cms_client',
  name: 'cms_article'
});
// 返回完整详情，包含：
// json.result.config  — api.json 完整配置（path, oauth, method 等）
// json.result.param   — 参数校验配置（所有入参的名称、类型、必填、说明）
// json.result.sql     — 数据库配置（表名、字段、查询条件、排序等）
```

第三级返回的 `json.result.param` 中包含每个参数的详细说明，Vue 开发者可据此知道调用时需要传哪些参数、参数类型是什么：

```javascript
// param 中的参数定义示例
{
  "name": "title",          // 参数名
  "title": "文章标题",       // 参数中文说明
  "type": "string",         // 参数类型：string/number
  "string": {
    "not_empty": true,      // 是否必填
    "min": 0,
    "max": 255              // 最大长度
  },
  "description": "文章的标题" // 参数描述
}
```

#### 完整的 API 发现流程

```
浏览器访问 /api/dev_api
        │
        ▼
   返回 result.scope ← 所有可用 scope 列表
        │
  选择一个 scope，如 "cms_client"
        │
        ▼
   GET /api/dev_api?scope=cms_client
        │
   返回 result.list  ← 该 scope 下所有 API
        │
  选择一个 API，如 name = "cms_article"
        │
        ▼
   GET /api/dev_api?scope=cms_client&name=cms_article
        │
   返回完整详情（config + param + sql）
        │
        ▼
   在 Vue 中通过 this.$get(path, params) 调用
```

> 💡 `/api/dev_api` 本身就是一个 **type:client** 的 API，所以在 Vue 组件中可以用 `this.$get()` 直接调用它，实现**运行时动态发现 API**。

### 7.2 查看源码

API 定义在 `api.json` 中，可直接查看：

```
app/{模块名}/plugin/{插件名}/api_{app}_{client|web|manage}/{API名}/api.json
```

`api.json` 中的 `path` 字段就是 Vue 中调用时的 URL。

### 7.3 `/dev` 后台管理

访问 `/dev` 后台管理页面，在可视化界面中查看和管理 API 列表（管理员权限）。

### 7.4 `?method=help`

部分 API 支持传入 `method=help` 参数，返回该接口的文档说明：

```javascript
var json = await this.$get('/api/cms/article_list', { method: 'help' });
// 如果支持，会返回接口的参数说明和使用示例
```

### 7.5 按命名规律推断

API 路径有固定规律，可根据模块名和功能名推断：

```
/api/{app}/{name}                          # 通用API
/api/{app}/{name}?method=get_list           # 获取列表
/api/{app}/{name}?method=get_obj            # 获取单条
/api/{app}/{name}?method=add                # 新增
/api/{app}/{name}?method=set                # 修改
/api/{app}/{name}?method=del                # 删除
/api/{app}/{name}?method=submit             # 提交
```

### 7.6 浏览器 Network 面板

开发时打开浏览器 F12 → Network 标签，过滤 `api/` 即可看到所有实际发起的 API 请求，包含完整的请求参数和响应数据。

## 八、错误处理与异常流程

### 8.1 标准错误处理模板

```javascript
// 回调模式
this.$get('/api/xxx', query, function(json) {
  if (json.error) {
    if (json.error.code === 10001) {
      // 未登录，mm_vue.js 已自动跳转登录页
      return;
    }
    this.$toast(json.error.message || '请求失败');
    return;
  }
  // 正常处理 json.result
  this.data = json.result;
}.bind(this));
```

### 8.2 常见错误码

| 错误码 | 含义 | 自动处理 |
|:-------|:-----|:---------|
| `10000` | 参数错误 | 显示错误消息 |
| `10001` | 未登录 | 自动跳转登录页 |
| `10002` | 无权限 | 显示"无权限" |
| `10003` | 数据不存在 | 显示提示 |
| `10004` | 操作失败 | 显示错误消息 |
| `20000` | 系统异常 | 显示"系统异常" |

### 8.3 全局异常捕获

Vue 组件中可使用 `try/catch` 捕获 Promise 异常：

```javascript
async fetchData() {
  try {
    var json = await this.$get('/api/xxx', {});
    if (json.result) {
      this.list = json.result.list;
    }
  } catch (e) {
    console.error('API 调用异常:', e);
    this.$toast('网络异常，请稍后重试');
  }
}
```

### 8.4 加载状态

```javascript
data() {
  return { loading: false, list: [] };
},
async mounted() {
  this.loading = true;
  var json = await this.$get('/api/xxx', {});
  this.list = json.result ? json.result.list : [];
  this.loading = false;
}
```

## 九、与 type:web 模板渲染的模式对比

| 维度 | Vue前端 (type:client) | 模板渲染 (type:web) |
|:-----|:----------------------|:---------------------|
| **数据获取方** | 浏览器端 Vue 组件 | 服务端控制器 |
| **调用方式** | `this.$get()` / `this.$post()` | `db.tpl.view(path, model)` |
| **响应格式** | JSON `{ result: {...} }` | HTML 字符串 |
| **数据绑定** | Vue 响应式 `{{ }}` / `v-for` / `v-if` | 模板语法 `${viewBag.xxx}` / `<!--{ loop }-->` |
| **交互方式** | 客户端动态渲染，无需整页刷新 | 服务端渲染，整页刷新 |
| **SEO** | ❌ 需 SSR 支持 | ✅ 天然支持 |
| **用户体验** | SPA 式流畅交互 | 传统多页体验 |
| **适用场景** | 管理后台、复杂交互页面 | 内容展示页、需 SEO 的页面 |

## 十、禁止行为

- ❌ 禁止在 Vue 组件中直接使用 `fetch` 或引入 axios（应使用 `this.$get/$post`）
- ❌ 禁止硬编码 API 路径，应统一管理或从配置获取
- ❌ 禁止忽略 `json.error` 分支，所有 API 调用必须处理错误情况
- ❌ 禁止在 `mounted` 中同步执行多个串行 API 调用（应使用 `async/await`）
- ❌ 禁止在模板中直接调用 API（应在 methods 或生命周期中调用）
- ❌ 禁止绕过 `mm_vue.js` 的 token 机制手动处理认证