# $runFrontLogic 使用指南

## 概述

`$runFrontLogic` 是前端业务逻辑的统一调用入口，由 `FrontLogicPlugin` 注册为 Vue 全局方法。它从配置中心按名称拉取 Logic 脚本，在浏览器中执行，支持操作界面组件、发起 HTTP 请求等操作。

---

## 函数签名

```javascript
this.$runFrontLogic(logicName, param, mode?)
// 等价于
LogicRunner.run(logicName, param, mode?)
```

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `logicName` | `string` | ✅ | Logic 配置名称 |
| `param` | `object` | ✅ | 传入 Logic 脚本的参数，脚本中通过 `data.xxx` 访问 |
| `mode` | `string` | ❌ | 并发控制模式，见下方说明 |

**返回值**：`Promise`，resolve 值为 Logic 脚本最后一条表达式的结果。

---

## 运行模式（mode）

### 普通模式（默认）

不传 `mode` 参数，每次调用都独立执行，互不干扰。

```javascript
// 适用场景：普通按钮点击、页面初始化加载
await this.$runFrontLogic('loadPatientInfo', { patientId: 123 })
```

---

### ignore 模式

若同名 Logic **已在运行中**，忽略本次调用，直接返回 `null`。

```javascript
// 适用场景：防止重复提交、防止重复初始化
this.$runFrontLogic('submitForm', { form: formData }, 'ignore')
```

**行为**：
- 第一次调用：正常执行
- 第一次未结束时触发第二次：直接返回 `null`，不执行
- 第一次结束后触发第二次：正常执行

```
用户第1次点击 → 执行中...
用户第2次点击（第1次未结束）→ 忽略，返回 null
用户第3次点击（第1次已结束）→ 正常执行
```

---

### cancel 模式

若同名 Logic **已在运行中**，中止前一次的 HTTP 请求，立即执行本次调用。

```javascript
// 适用场景：搜索框输入联想、筛选条件变化后重新查询
this.$runFrontLogic('searchGroupLogic', { keyword: value }, 'cancel')
```

**行为**：
- 第一次调用：正常执行
- 第一次未结束时触发第二次：**中止第一次的 HTTP 请求**，同时启动第二次执行
- 第二次正常完成，第一次中途抛出 `AbortError`

```
用户第1次触发 → 执行中（delay + http.post）...
用户第2次触发 → abort 第1次的 http 请求，同时开始第2次执行
                第1次：http.post 抛出 AbortError，Logic 中止
                第2次：正常完成，返回结果
```

> **注意**：cancel 只能中止**未完成的 HTTP 请求**，不能撤回已执行的 JS 代码。
> 若 Logic 脚本中 `http.post` 之前有 `log.info("结束")` 等语句，这些语句仍会执行。

---

## Logic 脚本内可用的插件

通过 `LogicRunner.run` 执行时，脚本内自动注入以下对象：

| 名称 | 说明 | 示例 |
|------|------|------|
| `data` | 本次调用的 `param` 参数 | `data.proxy`、`data.form` |
| `log` | 日志工具 | `log.info("消息")`、`log.debug(value)` |
| `ENV` | 全局环境变量（来自配置的 `$globalProp`） | `ENV.baseUrl` |
| `logic` | LogicRunner 自身，可嵌套调用其他 Logic | `logic.run('otherLogic', data)` |
| `dateTools` | 日期工具 | `dateTools.getNow2()`、`dateTools.delay(1000)` |
| `vueTools` | Vue 组件工具 | `vueTools.getComponent(data.proxy, 'refName')` |
| `http` | HTTP 请求（自动绑定 abortSignal） | `http.post(url, body)`、`http.get(url, params)` |
| `abortSignal` | 当前执行实例的取消信号 | `abortSignal.aborted` |

### http 插件说明

`http.post` / `http.get` 返回 `Promise`，表达式执行器会自动等待结果，可直接赋值：

```
// Logic 脚本语法（表达式，逗号分隔）
res = http.post("/api/his/logic/searchResult", { form: data.form }),
log.info("result={res}")
```

- **自动携带认证 Token**（走统一的 axios 拦截器）
- **cancel 模式下自动绑定 abortSignal**，中止时 http 请求会立即取消
- **禁用请求去重**（Logic 中的请求是有意发起的，不应被去重拦截）

### dateTools.delay 说明

`dateTools.delay(ms)` 支持 cancel 模式中断：

```
// Logic 脚本：delay 期间如果被 cancel，会立即抛出 AbortError
log.info("开始"),
dateTools.delay(3000),   // cancel 时这里会中断
http.post("/api/xxx", {}),
log.info("完成")
```

---

## 调用方式

### 方式一：Vue 组件内（推荐）

```javascript
// 组件方法或配置脚本中
await this.$runFrontLogic('logicName', { proxy: this })

// 带模式
this.$runFrontLogic('searchLogic', { keyword: val }, 'cancel')
```

### 方式二：XButtons 按钮配置

在按钮配置 JSON 中指定 `frontLogicName` 和 `frontLogicMode`，XButtons 组件会自动调用：

```json
{
  "key": "search",
  "title": "搜索",
  "frontLogicName": "searchGroupLogic",
  "frontLogicMode": "cancel"
}
```

XButtons 会将当前组件实例（`proxy`）作为参数传入：

```javascript
// XButtons 内部等价于：
await this.$runFrontLogic(item.frontLogicName, { proxy: this }, item.frontLogicMode)
```

**XButtons 的错误处理**：
- abort/cancel 导致的取消错误：**静默忽略**，不弹出提示
- 其他业务错误：弹出 `$message.error` 提示

### 方式三：直接使用 LogicRunner

```javascript
import LogicRunner from '@vue2-client/logic/LogicRunner'

const result = await LogicRunner.run('logicName', { param1: value }, 'cancel')
```

---

## 错误处理

```javascript
try {
  const result = await this.$runFrontLogic('logicName', params)
  // 处理结果
} catch (e) {
  // cancel 模式下被中止时，e.cause 为 AbortError / CanceledError
  const cause = e?.cause || e
  if (cause?.name === 'AbortError' || cause?.name === 'CanceledError' || cause?.code === 'ERR_CANCELED') {
    // 被取消，可忽略
    return
  }
  // 其他业务错误
  this.$message.error('执行失败：' + e.message)
}
```

> 通过 XButtons 触发时，cancel 取消错误由 XButtons 自动静默处理，业务代码无需关心。

---

## 各模式对比

| 场景 | 推荐模式 | 原因 |
|------|----------|------|
| 普通按钮触发、页面初始化 | 默认（不传） | 每次独立执行 |
| 表单提交、防止重复操作 | `'ignore'` | 执行中时忽略重复点击 |
| 搜索框、筛选条件实时查询 | `'cancel'` | 新条件触发时取消旧请求 |

---

## 相关文件

| 文件 | 说明 |
|------|------|
| `src/base-client/plugins/FrontLogicPlugin.js` | 注册 `$runFrontLogic` 全局方法 |
| `src/logic/LogicRunner.js` | 核心运行器，实现三种模式 |
| `src/logic/plugins/index.js` | 插件注册（dateTools、vueTools、http） |
| `src/logic/plugins/common/HttpTools.js` | HTTP 插件（兜底单例） |
| `src/logic/plugins/common/DateTools.js` | 日期工具，delay 支持 abort |
| `src/logic/plugins/common/VueTools.js` | Vue 组件工具 |
| `src/base-client/components/common/XButtons/XButtons.vue` | 按钮配置驱动调用 |
| `docs/前端业务逻辑使用说明.md` | 表达式语法与插件详细说明 |
