跳过正文
  1. 工作笔记/
  2. 通用片段/

E9 代码块:提交前调用接口校验并阻止提交

·526 字·3 分钟
泛微 E9 代码块 接口校验 提交拦截
作者
molefool
目录

核心思路
#

E9 表单提交前校验要从事件出发,不是从按钮出发。

WfForm.registerCheckEvent(WfForm.OPER_SUBMIT, fn) 拦截提交动作,在回调里异步调用接口。接口校验通过才调用 callback() 放行;接口没返回、返回结构不完整、返回业务失败,都不调用 callback(),提交就会被阻止。

关键链路是:

代码块 JS -> /api/esb/oa/execute -> 接口 -> 应用 -> 事件 -> data.responsebody -> 代码块 JS

这里最容易踩坑的是:E9 环境下如果接口在 HTTP、OA 接口层、应用层或事件层直接失败,前端代码块通常拿不到正常的 data.responsebody。所以接口设计上要尽量“永远成功返回”,把业务失败写到 responsebody 里,再让代码块按业务结果拦截提交。

前端拦截模板
#

jQuery(function () {
  var submitting = false;

  WfForm.registerCheckEvent(WfForm.OPER_SUBMIT, function (callback) {
    if (submitting) {
      WfForm.showMessage("正在校验中,请不要重复提交", 2, 5);
      return;
    }

    submitting = true;

    var payload = buildPayload();

    if (!payload.lines.length) {
      submitting = false;
      WfForm.showMessage("没有可提交的数据", 2, 5);
      return;
    }

    jQuery.ajax({
      url: "/api/esb/oa/execute",
      type: "POST",
      dataType: "json",
      timeout: 120000,
      cache: false,
      data: {
        eventkey: "EVENT_KEY",
        params: JSON.stringify(payload)
      },
      success: function (res) {
        if (!hasValidResponseBody(res)) {
          WfForm.showMessage("接口未返回有效校验结果,请联系管理员", 2, 8);
          return;
        }

        if (hasBusinessError(res)) {
          writeErrorToDetail(res);
          WfForm.showMessage("校验未通过,请查看明细备注", 2, 8);
          return;
        }

        WfForm.showMessage("校验通过,继续提交", 3, 5);
        callback();
      },
      error: function () {
        WfForm.showMessage("接口调用失败,请联系管理员", 2, 8);
      },
      complete: function () {
        submitting = false;
      }
    });
  });
});

组装请求数据
#

主表字段直接取值,明细表先取所有行标识,再拼成接口需要的数组。

function rowField(fieldId, rowIndex) {
  return fieldId + "_" + rowIndex;
}

function getValue(fieldMark) {
  return WfForm.getFieldValue(fieldMark) || "";
}

function getRows(detailMark) {
  var rowStr = WfForm.getDetailAllRowIndexStr(detailMark) || "";
  return rowStr ? rowStr.split(",").filter(Boolean) : [];
}

function buildPayload() {
  var detailMark = "detail_1";
  var mainType = WfForm.convertFieldNameToId("main_type");
  var lineNo = WfForm.convertFieldNameToId("line_no", detailMark);
  var itemCode = WfForm.convertFieldNameToId("item_code", detailMark);
  var qty = WfForm.convertFieldNameToId("qty", detailMark);

  var rows = getRows(detailMark);
  var lines = [];

  for (var i = 0; i < rows.length; i++) {
    lines.push({
      line: getValue(rowField(lineNo, rows[i])),
      itemCode: getValue(rowField(itemCode, rows[i])),
      qty: getValue(rowField(qty, rows[i]))
    });
  }

  return {
    type: getValue(mainType),
    lines: lines
  };
}

解析响应结果
#

代码块只认 data.responsebody。它可能是数组,也可能是单个对象,所以先统一成数组。

function normalizeList(value) {
  if (!value) return [];
  return jQuery.isArray(value) ? value : [value];
}

function getResponseBodyList(res) {
  if (!res || !res.data || res.data.responsebody == null) {
    return [];
  }
  return normalizeList(res.data.responsebody);
}

function hasValidResponseBody(res) {
  return getResponseBodyList(res).length > 0;
}

function getBodyLine(item) {
  return jQuery.trim(String(item.line || item.LINE_NO || item.WMS_LINE_ID || ""));
}

function getBodyMsg(item) {
  return jQuery.trim(String(item.msg || item.message || item.PROCESS_MESSAGE || ""));
}

function getBodyStatus(item) {
  return jQuery.trim(String(item.status || item.STATUS || "")).toUpperCase();
}

function isErrorItem(item) {
  var msg = getBodyMsg(item);
  var status = getBodyStatus(item);
  return !!msg || (!!status && status !== "S");
}

function hasBusinessError(res) {
  var list = getResponseBodyList(res);
  for (var i = 0; i < list.length; i++) {
    if (isErrorItem(list[i] || {})) {
      return true;
    }
  }
  return false;
}

回写明细错误
#

接口最好每条错误都带行号。前端用行号映射到当前 OA 明细行,把错误写回备注字段。

function writeErrorToDetail(res) {
  var detailMark = "detail_1";
  var lineNo = WfForm.convertFieldNameToId("line_no", detailMark);
  var remark = WfForm.convertFieldNameToId("remark", detailMark);
  var rows = getRows(detailMark);
  var rowMap = {};
  var data = {};

  for (var i = 0; i < rows.length; i++) {
    rowMap[getValue(rowField(lineNo, rows[i]))] = rows[i];
  }

  var list = getResponseBodyList(res);
  for (var j = 0; j < list.length; j++) {
    var item = list[j] || {};
    if (!isErrorItem(item)) continue;

    var rowIndex = rowMap[getBodyLine(item)];
    if (rowIndex == null) continue;

    data[rowField(remark, rowIndex)] = {
      value: getBodyMsg(item) || "校验未通过"
    };
  }

  WfForm.changeMoreField(data);
}

接口返回约定
#

不要让接口用异常、HTTP 500、事件执行失败来表达业务不通过。代码块需要的是一个能稳定进入 success 回调的结构。

推荐结构:

{
  "code": "100",
  "data": {
    "responsebody": [
      {
        "line": "OA-202607030001-1",
        "status": "S",
        "msg": ""
      },
      {
        "line": "OA-202607030001-2",
        "status": "E",
        "msg": "库存不足"
      }
    ]
  }
}

判断规则:

  • 能取到 data.responsebody,才算拿到接口校验结果。
  • status 为空或 S 且没有 msg,认为该行通过。
  • status 不是 S,或存在 msg,认为该行失败。
  • 任何结构缺失、接口错误、超时,都按失败处理,不放行提交。

注意点
#

  • registerCheckEvent 里是异步校验,只有业务通过时才能调用 callback()
  • 不要在接口失败的 error 分支里尝试放行;此时响应链路已经不可靠。
  • 接口、应用、事件三层都要尽量捕获异常并返回成功结构,把业务错误放进 responsebody
  • 明细错误要按稳定行号匹配,不要按数组顺序匹配,避免用户增删行后写错备注。
  • submitting 这类标记防重复提交,防止用户连续点击。
  • 写回明细后如依赖字段联动,可对目标字段补一次 WfForm.triggerFieldAllLinkage(fieldMark)

相关文章