# 表单提交与 COMMITBOX

新版自定义增加了新的物品框 COMMITBOX，可以与服务端直接交互，支持从客户端提交多种表单数据。

<div class="callout info"><p class="callout-title">使用条件</p><p>需要支持表单提交的配套 M2、RunGate、客户端与 Lua 包，GameCenter 选择 Int64 模式。仅支持 CUSUI 布局，可用于 JSON 自定义面板和 NPC 自定义对话。</p></div>

## 表单配置方式

自定义增加参数位 `form`，用于绑定服务端接收函数，自定义控件增加参数 `field`用于设置空间名称，方便服务端获取数据。再为按钮指定 `action` 与 `target`，action参数为confirm或preview，target参数为helper或npc表示通讯方式。

```cusui
1:Input-name:x=100,y=270,width=330,height=30,text="",form="commitDemo",field="name"
1:COMMITBOX-equipment:dir="formDemo",pic="box",x=24,y=105,form="commitDemo",field="equipment",count="1"
1:Btn-confirm:dir="formDemo",pic="button",x=180,y=22,cmdText="确认",form="commitDemo",action="confirm",target="helper"
```

以上为自定义的表单片段，可以根据自己的实际需求编写。

| 配置 | 作用 | 示例 |
| --- | --- | --- |
| `form` | 表单名，对应一个服务端回调 | `commitDemo` → `FormSubmit_commitDemo` |
| `field` | 服务端读取的字段名 | `name`、`equipment` |
| `action` | 本次提交的操作名 | `preview`、`confirm` |
| `target` | 接收表单的脚本入口 | `helper` 或 `npc` |

`form` 使用英文字母、数字或下划线，不能以数字开头，最多 32 字符。同一表单内字段名不能重复；不同面板可以使用相同表单名与字段名。字段名和操作名区分大小写。

## 物品框 COMMITBOX

```cusui
1:COMMITBOX-equipment:dir="formDemo",pic="box",x=24,y=105,form="commitDemo",field="equipment",count="1"
1:COMMITBOX-material:dir="formDemo",pic="box",x=180,y=105,form="commitDemo",field="material",countField="count"
```

| 参数 | 说明 |
| --- | --- |
| `COMMITBOX-控件ID` | ID 必填，同一面板内保持唯一 |
| `form`、`field` | 所属表单和物品字段名 |
| `count` | 固定的提交数量，默认 `"1"` |
| `countField` | 用同一表单的 Input 字段作为数量来源，与 `count` 二选一 |
| `items` | 可选物品名称白名单，用 `$` 分隔 |
| `itemScale` | 框内图标缩放，默认 `1.2` |
| `dir`、`pic` | 图片目录与文件名，沿用 CUSUI 图片规则 |
| `x`、`y`、`anchor`、`z`、`scale`、`touch` | 沿用 CUSUI 的布局、层级、缩放与触摸设置 |

点击空框打开背包，拖入本人背包物品，点击已选框清除选择，物品拖入和删除不会出发服务端提交。

背包变化后会重新查询物品，已删除的物品会清除选择。关闭面板清理本次选择。

<div class="callout note"><p class="callout-title">数量与库存</p><p><code>count</code> 和 <code>countField</code> 表示请求数量，服务端仍需核对实际库存。数量必须为正整数；固定数量较大时使用带引号的十进制字符串。</p></div>

## 输入框与选项框

```cusui
1:Input-name:x=100,y=270,width=330,height=30,text="",form="commitDemo",field="name"
1:Input-count:x=120,y=225,width=160,height=30,text="20",form="commitDemo",field="count"
1:CheckBox-protect:dir="formDemo",pic="check",checkPic="checked",x=310,y=230,isCheck=false,form="commitDemo",field="protect"
```

编辑时不提交，点击按钮时读取当前值。空文本和 `false` 都是有效值。

配置 `countField="count"` 时，数量来自 `field="count"` 的输入框，引用的是字段名。

表单控件不要同时配置 `cmd`、`putCmd` 或 `ok`框。

## 按钮

```cusui
1:Btn-preview:dir="formDemo",pic="button",x=24,y=22,cmdText="预览",form="commitDemo",action="preview",target="helper"
1:Btn-confirm:dir="formDemo",pic="button",x=180,y=22,cmdText="确认",form="commitDemo",action="confirm",target="helper"
```

`Btn`、`RBtn`、`DRBtn`、`Cmd`、`RCmd` 均支持用于表单提交。

| 参数 | 说明 |
| --- | --- |
| `form` | 要提交的表单，必填 |
| `action` | 操作名，必填，由服务端 `Context.Action` 读取 |
| `target="helper"` | 交给 HelperQuest；无需初始化 helperID |
| `target="npc"` | 交给当前 NPC；须由 NPC 自定义对话创建 |
| `fields={"name","equipment"}` | 可选，只提交指定字段；省略时提交表单全部已绑定字段 |

自定义面板使用 `target="helper"`。NPC面板可使用 `target="npc"`，客户端会保留该对话的会话ID。

`countField` 可以引用未包含在 `fields` 列表中的数量输入，但两者须属于同一表单。提交按钮不要同时配置 `cmd`。

每次点击都重新采集当前数据。发送失败会提示，不自动重试。客户端发送成功表示请求已发出，业务是否成功以服务端反馈为准。

## 服务端接收

以下为示例，详细配置请以GOW-Next 64服务端文档为准

```pascal
procedure FormSubmit_commitDemo(Context: TFormSubmitContext);
var
  DisplayName: string;
  Equipment: TBaseItem;
begin
  if Context.Action <> 'confirm' then Exit;
  if not Context.TryGetText('name', DisplayName) then Exit;
  if not Context.TryGetItem('equipment', Equipment) then
  begin
    This_Player.PlayerNotice('请重新选择装备', 2);
    Exit;
  end;
  // 在这里核对玩家权限、物品类型和真实库存，再调用本服业务。
  This_Player.PlayerNotice('已读取表单；示例未扣除或发放物品', 2);
end;
```

### 读取字段

| 接口 | 用途 |
| --- | --- |
| `Context.FormName` | 只读表单名 |
| `Context.Action` | 只读操作名，例如 `preview`、`confirm` |
| `HasField(Name)` | 判断是否提交字段 |
| `TryGetText(Name, Value)` | 读取文本，空字符串有效 |
| `TryGetInt64(Name, Value)` | 将文本严格解析为十进制整数 |
| `TryGetBoolean(Name, Value)` | 读取开关，`false` 有效 |
| `TryGetItem(Name, Item)` | 核验当前背包引用，取得 `TBaseItem` |
| `TryGetItemCount(Name, Count)` | 读取客户端请求数量 |

除前两项属性外，以上方法均从 `Context` 调用并返回 Boolean。字段缺失、类型不符或整数溢出时返回 `false`，输出清空为 `''`、`0`、`false` 或 `nil`。

`TryGetInt64` 接受正负号和前导零，不接受空白、小数或指数。判断开关时，先检查 `TryGetBoolean` 的返回值，再使用读出的开关值。

### 玩家与物品

`This_Player` 是提交玩家；表单不会自动设置 `This_Item`。`Context` 和读出的物品对象仅在本次同步回调中使用，不要保存到全局变量或定时任务。

`TBaseItem` 提供 `ClientItemID`、`UID`、`ItemName`。按编号操作的接口使用 `Item.ClientItemID`，物品属性接口使用物品对象；永久身份使用 `UID`。物品失效时 `TryGetItem` 返回 `false`，回调仍会执行。

普通 NPC 要求玩家同图且横纵距离各不超过 15 格；HelperQuest 可作为全局入口。

## 字段限制

| 项目 | 限制 |
| --- | --- |
| 单次字段总数 | 文本、开关、物品合计最多 32 个 |
| 表单名 | 最多 32 个英文字符，符合前述命名规则 |
| 操作名、字段名 | 最多 32 个英文字符或 16 个常用汉字 |
| 单个文本 | 最多 128 个英文字符或 64 个常用汉字 |
| 物品数量 | 正整数，最大 `9223372036854775807` |

中英文混合时，一个常用汉字按两个英文字符计算。超长或包含不支持字符时，修改后再提交，内容不会自动截断。

## 结算约定

预览与确认分别提交当前数据。预览成功不会保留物品，确认时须重新检查权限、装备类型、材料种类、实际库存、保护材料及消耗规则。

重复确认、扣除和发放的一致性由本服结算过程处理；脚本异常不会自动撤销已执行操作。示例只演示读取和校验，正式使用时接入本服结算函数。

接口详情见 [GOW-Next 服务端表单接口](https://engine-doc.gow-next.com/server/custom-panels/form-submit.html)。
