跳过正文
  1. 工作笔记/
  2. E10 EB Ecode/

E10 EB:根据浏览框显示名称动态修改页面元素文本

·1868 字·9 分钟
泛微 E10 EB Ecode 代码片段 浏览框 DOM 刷新
作者
molefool
目录

场景
#

E10 EB 表单里,有些页面元素的显示文本需要跟着浏览框选择结果变化。例如浏览框选择“施工许可证”后,把页面上的默认标题改成“施工许可证编号”。

配置方式是让目标元素自己带映射关系:

<span class="666" data-施工许可证="施工许可证编号" data-营业执照="统一社会信用代码">默认名称</span>

代码读取浏览框显示名称,再找目标元素上的 data-浏览框显示名称。找到后,用该属性值替换元素显示文本。

关键点
#

  • BROWSER_FIELD_DATA_KEY 填浏览框字段的数据 key,不是 CSS id。
  • TARGET_CLASS 填需要动态改文案的元素 class。
  • 浏览框支持多选时,先匹配完整显示名,再匹配逗号拆分后的单项显示名。
  • E10 保存后可能只刷新表单 DOM 或表单实例,不一定整页刷新。
  • 每次刷新都尽量重新获取最新 formSdk,不要长期依赖旧实例。
  • 用字段变化、保存动作、MutationObserver 和定时轮询一起兜底,解决保存后元素回到默认名称的问题。
  • 脚本再次加载时先销毁旧实例,避免多个定时器和监听器重复运行。

可复用代码
#

/**
 * E10 EB 表单 ecode:根据浏览框显示名称动态修改页面元素显示文本。
 */
(function () {
  var DEBUG = false;
  var BROWSER_FIELD_DATA_KEY = "这里改成浏览框的数据key";
  var TARGET_CLASS = "666";
  var POLL_INTERVAL = 800;
  var APPLY_DELAY = 120;
  var CHANGE_DELAY = 500;
  var GLOBAL_STATE_KEY = "__dynamic_title_666_state__";

  if (window[GLOBAL_STATE_KEY] && window[GLOBAL_STATE_KEY].destroy) {
    window[GLOBAL_STATE_KEY].destroy();
  }

  var state = {
    destroyed: false,
    formSdk: null,
    moduleKey: "",
    formId: "",
    dataId: "",
    timerId: null,
    pollId: null,
    observer: null,
    listener: null,
    lastBoundSdk: null,
    lastBoundField: "",
    lastSaveActionSdk: null
  };

  window[GLOBAL_STATE_KEY] = state;

  function trim(value) {
    return String(value || "").replace(/^\s+|\s+$/g, "");
  }

  function debugLog(message, data) {
    if (DEBUG && window.console && console.log) {
      console.log("[dynamic-title]", message, data || "");
    }
  }

  function getLatestFormSdk() {
    var sdk = null;

    try {
      if (window.WeFormSDK && window.WeFormSDK.getWeFormInstance) {
        if (state.moduleKey && state.formId && state.dataId) {
          sdk = window.WeFormSDK.getWeFormInstance(state.moduleKey, state.formId, state.dataId);
        }
        if (!sdk && state.moduleKey && state.formId) {
          sdk = window.WeFormSDK.getWeFormInstance(state.moduleKey, state.formId);
        }
        if (!sdk && state.moduleKey) {
          sdk = window.WeFormSDK.getWeFormInstance(state.moduleKey);
        }
        if (!sdk) {
          sdk = window.WeFormSDK.getWeFormInstance();
        }
      }
    } catch (e) {
      debugLog("获取最新 formSdk 失败", e.message);
    }

    if (sdk) {
      state.formSdk = sdk;
    }

    return sdk || state.formSdk;
  }

  function getFormSdkFromEvent(event) {
    var detail = event && event.detail ? event.detail : {};
    state.moduleKey = detail.module || detail.moduleKey || state.moduleKey;
    state.formId = detail.formId || state.formId;
    state.dataId = detail.dataId || state.dataId;

    if (detail.formSdk) {
      state.formSdk = detail.formSdk;
    }

    return state.formSdk || getLatestFormSdk();
  }

  function resolveBrowserField(formSdk) {
    try {
      return formSdk.convertFieldNameToId(BROWSER_FIELD_DATA_KEY, "main", true);
    } catch (e) {
      debugLog("浏览框 dataKey 转 fieldMark 失败", e.message);
      return "";
    }
  }

  function getBrowserShowName(formSdk, browserField) {
    var showName = "";

    try {
      if (formSdk.getBrowserShowName) {
        showName = trim(formSdk.getBrowserShowName(browserField, ","));
      }
      if (!showName && formSdk.getFieldValue) {
        showName = trim(formSdk.getFieldValue(browserField));
      }
    } catch (e) {
      debugLog("读取浏览框显示名失败", e.message);
    }

    return showName;
  }

  function addAttrName(list, attrName) {
    if (attrName && list.indexOf(attrName) === -1) {
      list.push(attrName);
    }
  }

  function buildAttrNames(showName) {
    var list = [];
    var cleanName = trim(showName);
    var parts = cleanName.split(",");

    addAttrName(list, "data-" + cleanName);

    for (var i = 0; i < parts.length; i++) {
      addAttrName(list, "data-" + trim(parts[i]));
    }

    return list;
  }

  function findNewTitle(node, attrNames) {
    for (var i = 0; i < attrNames.length; i++) {
      var value = node.getAttribute(attrNames[i]);

      if (value !== null && typeof value !== "undefined") {
        return value;
      }
    }

    return null;
  }

  function setNodeText(node, text) {
    if (typeof node.innerText !== "undefined") {
      if (node.innerText !== text) {
        node.innerText = text;
      }
      return;
    }

    if (node.textContent !== text) {
      node.textContent = text;
    }
  }

  function applyNames(reason) {
    if (state.destroyed) {
      return;
    }

    var formSdk = getLatestFormSdk();
    if (!formSdk) {
      debugLog("未获取到 formSdk,跳过", reason);
      return;
    }

    var browserField = resolveBrowserField(formSdk);
    if (!browserField) {
      return;
    }

    var showName = getBrowserShowName(formSdk, browserField);
    if (!showName) {
      debugLog("浏览框显示名为空,跳过", reason);
      return;
    }

    var attrNames = buildAttrNames(showName);
    var nodes = document.getElementsByClassName(TARGET_CLASS);

    debugLog("刷新标题", {
      reason: reason,
      showName: showName,
      attrNames: attrNames,
      count: nodes.length
    });

    for (var i = 0; i < nodes.length; i++) {
      var newTitle = findNewTitle(nodes[i], attrNames);
      if (newTitle !== null) {
        setNodeText(nodes[i], newTitle);
      }
    }
  }

  function scheduleApply(reason) {
    if (state.destroyed) {
      return;
    }

    if (state.timerId) {
      clearTimeout(state.timerId);
    }

    state.timerId = setTimeout(function () {
      applyNames(reason);
    }, APPLY_DELAY);
  }

  function bindFieldChange(formSdk, browserField) {
    if (!formSdk.bindFieldChangeEvent) {
      return;
    }

    if (state.lastBoundSdk === formSdk && state.lastBoundField === browserField) {
      return;
    }

    state.lastBoundSdk = formSdk;
    state.lastBoundField = browserField;

    formSdk.bindFieldChangeEvent(browserField, function () {
      scheduleApply("字段变化");
      setTimeout(function () {
        applyNames("字段变化延迟补刷");
      }, CHANGE_DELAY);
    });
  }

  function bindSaveAction(formSdk) {
    if (!formSdk.registerAction || !window.WeFormSDK || !window.WeFormSDK.ACTION_FORM_SAVE) {
      return;
    }

    if (state.lastSaveActionSdk === formSdk) {
      return;
    }

    state.lastSaveActionSdk = formSdk;

    formSdk.registerAction(window.WeFormSDK.ACTION_FORM_SAVE, function () {
      scheduleApply("保存完成");
      setTimeout(function () { applyNames("保存完成 500ms 补刷"); }, 500);
      setTimeout(function () { applyNames("保存完成 1500ms 补刷"); }, 1500);
      setTimeout(function () { applyNames("保存完成 3000ms 补刷"); }, 3000);
    });
  }

  function ensureObserver() {
    if (state.observer || !window.MutationObserver || !document.body) {
      return;
    }

    state.observer = new MutationObserver(function () {
      scheduleApply("DOM 变化");
    });

    state.observer.observe(document.body, {
      childList: true,
      subtree: true,
      characterData: true,
      attributes: true
    });
  }

  function ensurePoll() {
    if (state.pollId) {
      clearInterval(state.pollId);
    }

    state.pollId = setInterval(function () {
      applyNames("轮询");
    }, POLL_INTERVAL);
  }

  function start(formSdk) {
    if (state.destroyed) {
      return;
    }

    state.formSdk = formSdk || getLatestFormSdk();
    if (!state.formSdk) {
      return;
    }

    var browserField = resolveBrowserField(state.formSdk);
    if (!browserField) {
      return;
    }

    bindFieldChange(state.formSdk, browserField);
    bindSaveAction(state.formSdk);
    ensureObserver();
    ensurePoll();

    applyNames("启动立即刷新");
    setTimeout(function () { applyNames("启动 500ms 补刷"); }, 500);
    setTimeout(function () { applyNames("启动 1500ms 补刷"); }, 1500);
  }

  state.destroy = function () {
    state.destroyed = true;
    if (state.timerId) clearTimeout(state.timerId);
    if (state.pollId) clearInterval(state.pollId);
    if (state.observer) state.observer.disconnect();
    if (state.listener) window.removeEventListener("onFormReady", state.listener);
  };

  state.listener = function (event) {
    var formSdk = getFormSdkFromEvent(event);
    if (formSdk) {
      start(formSdk);
    }
  };

  window.addEventListener("onFormReady", state.listener);

  setTimeout(function () { start(getLatestFormSdk()); }, 300);
  setTimeout(function () { applyNames("加载后 1200ms 补刷"); }, 1200);
  setTimeout(function () { applyNames("加载后 3000ms 补刷"); }, 3000);
})();

使用步骤
#

  1. BROWSER_FIELD_DATA_KEY 改成浏览框字段的数据 key。
  2. TARGET_CLASS 改成页面元素使用的 class。
  3. 在目标元素上配置 data-浏览框显示名称="要显示的新文本"
  4. 如果是多选浏览框,同时配置完整显示名或单项显示名。
  5. 保存后测试字段变化、保存、刷新 DOM 三种场景。

注意点
#

  • class 名 666 能用,但不够语义化,后续项目建议改成类似 dynamic-title
  • MutationObserver 观察范围是整个 document.body,复杂页面可能触发频繁;如果页面很重,可以把观察范围缩小到表单容器。
  • 轮询是兜底方案,主要处理保存后 DOM 被泛微重置但事件没完整触发的情况。
  • GLOBAL_STATE_KEY 要保持唯一,避免同一页面多个 ecode 片段互相销毁。
  • 如果只想在一个表单实例内生效,优先保留 moduleKey/formId/dataId 的精确实例获取逻辑。

续集:ESB Mapping 驱动版本
#

前面的版本靠浏览框显示名称去匹配元素上的 data-* 属性,适合快速验证,但配置会分散在页面元素里。后续更稳定的方向是把映射关系放到 ESB 动作流里维护,前端只负责拉取 mapping、读取证照类型 ID、修改标题文本。

这版先只做动态改名,不合并隐藏行、空白行清理或布局重排。隐藏和清理布局风险更高,应该拆成单独 ecode 调试。

Mapping 接口
#

POST /api/esb/server/event/triggerActionFlow

补充理解:这个接口本质上是在页面内调用 E10 内部接口,去触发指定的动作流。动作流可以用“自定义触发”组件作为开始,用默认结束组件作为返回;这两个组件基本对应一次调用里的请求 body 和响应 body。其他字段结构不要硬猜,可以通过动作流触发调试观察实际入参和出参。

前端 ecode 只负责传入 customParamsesbFlowId,mapping 的组装、查询和返回建议放在动作流里处理。内部参考文档需要登录查看:E10 动作流触发参考

请求体保留最小参数:

{
  customParams: {
    mainTable: {}
  },
  moduleSource: "#optional",
  esbFlowId: "ESB_FLOW_ID"
}

实际读取路径:

response.actionData.responseData.customData.mainTable.detail3

返回行示例:

[
  {
    "fieldname": ["varcharField1"],
    "showname": "新的字段标题",
    "certtype": "CERT_TYPE_OPTION_ID"
  }
]

字段含义:

字段作用
certtype证照类型浏览框选项 ID。
fieldname页面标题原始文本或字段 dataKey,可为数组。
showname当前证照类型下要显示的新标题。

关键经验
#

  • 证照类型字段固定按 certType 取值。
  • 浏览框优先用 getBrowserOptionId 获取 optionId;不要再依赖显示名称匹配。
  • 真正要改的是 .weapp-form-widget-internal-title--text span 里的标题文本,不是输入框 placeholder
  • 每次改名前先恢复上一轮改过的标题,避免切换证照类型后旧标题残留。
  • getFieldInfo 更适合定位业务字段输入框位置,不适合直接定位标题文本。
  • E10 保存后可能局部刷新 DOM 或 form 实例,所以需要 formReady、延迟补刷和轻量 watcher。
  • 多卡片场景里 getWeFormInstance() 可能拿到当前活动表单;当前单表单主流程可先用简化方式。

最小稳定代码
#

(function () {
  var FLOW_ID = "ESB_FLOW_ID";
  var CERT_TYPE_DATA_KEY = "certType";
  var STATE_KEY = "__hz_cert_mapping_rename_only__";

  if (window[STATE_KEY] && window[STATE_KEY].destroy) {
    window[STATE_KEY].destroy();
  }

  var state = {
    destroyed: false,
    formSdk: null,
    mappings: [],
    mappingLoaded: false,
    timer: null,
    watcher: null,
    lastCertId: ""
  };

  window[STATE_KEY] = state;

  function trim(value) {
    return String(value || "").replace(/\u00a0/g, " ").replace(/^\s+|\s+$/g, "");
  }

  function sdk() {
    var formSdk = null;

    try {
      if (window.WeFormSDK && window.WeFormSDK.getWeFormInstance) {
        formSdk = window.WeFormSDK.getWeFormInstance();
      }
    } catch (e) {}

    if (formSdk) {
      state.formSdk = formSdk;
    }

    return formSdk || state.formSdk;
  }

  function fieldMark(dataKey) {
    var formSdk = sdk();

    if (!formSdk || !dataKey) {
      return "";
    }

    try {
      return formSdk.convertFieldNameToId(dataKey, "main", true);
    } catch (e) {
      return "";
    }
  }

  function certId() {
    var formSdk = sdk();
    var mark = fieldMark(CERT_TYPE_DATA_KEY);

    if (!formSdk || !mark) {
      return "";
    }

    try {
      return trim(formSdk.getBrowserOptionId(mark, ","));
    } catch (e1) {
      try {
        return trim(formSdk.getFieldValue(mark));
      } catch (e2) {
        return "";
      }
    }
  }

  function normalizeMappings(rows) {
    var list = [];
    var i;
    var j;
    var row;
    var names;

    for (i = 0; rows && i < rows.length; i++) {
      row = rows[i] || {};
      names = row.fieldname;

      if (typeof names === "string") {
        names = [names];
      }

      for (j = 0; names && j < names.length; j++) {
        if (trim(row.certtype) && trim(names[j])) {
          list.push({
            certtype: trim(row.certtype),
            fieldname: trim(names[j]),
            showname: trim(row.showname) || trim(names[j])
          });
        }
      }
    }

    return list;
  }

  function loadMappings(callback) {
    var xhr = new XMLHttpRequest();

    xhr.open("POST", "/api/esb/server/event/triggerActionFlow", true);
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");

    xhr.onreadystatechange = function () {
      var json;
      var rows;

      if (xhr.readyState !== 4) {
        return;
      }

      try {
        json = JSON.parse(xhr.responseText || "{}");
      } catch (e) {
        json = {};
      }

      rows =
        json &&
        json.actionData &&
        json.actionData.responseData &&
        json.actionData.responseData.customData &&
        json.actionData.responseData.customData.mainTable &&
        json.actionData.responseData.customData.mainTable.detail3;

      state.mappings = normalizeMappings(rows || []);
      state.mappingLoaded = true;

      if (callback) {
        callback();
      }
    };

    xhr.send(JSON.stringify({
      customParams: {
        mainTable: {}
      },
      moduleSource: "#optional",
      esbFlowId: FLOW_ID
    }));
  }

  function originTitle(node) {
    var text;

    if (!node) {
      return "";
    }

    text = trim(node.getAttribute("data-hz-origin-title"));

    if (!text) {
      text = trim(node.innerText || node.textContent);
      node.setAttribute("data-hz-origin-title", text);
    }

    return text;
  }

  function restoreTitle() {
    var nodes = document.querySelectorAll("[data-hz-renamed-title='1']");
    var i;
    var old;

    for (i = 0; i < nodes.length; i++) {
      old = nodes[i].getAttribute("data-hz-origin-title");

      if (old) {
        nodes[i].innerText = old;
      }

      nodes[i].removeAttribute("data-hz-renamed-title");
    }
  }

  function setTitle(node, text) {
    if (!node) {
      return;
    }

    if (!node.getAttribute("data-hz-origin-title")) {
      node.setAttribute("data-hz-origin-title", trim(node.innerText || node.textContent));
    }

    node.innerText = text;
    node.setAttribute("data-hz-renamed-title", "1");
  }

  function applyRename() {
    var id = certId();
    var nodes = document.querySelectorAll(".weapp-form-widget-internal-title--text span");
    var i;
    var j;
    var key;
    var mapping;

    restoreTitle();

    if (!state.mappingLoaded || !id) {
      return;
    }

    for (i = 0; i < nodes.length; i++) {
      key = originTitle(nodes[i]);

      for (j = 0; j < state.mappings.length; j++) {
        mapping = state.mappings[j];

        if (mapping.certtype === id && mapping.fieldname === key) {
          setTitle(nodes[i], mapping.showname);
        }
      }
    }
  }

  function schedule(delay) {
    if (state.destroyed) {
      return;
    }

    clearTimeout(state.timer);

    state.timer = setTimeout(function () {
      applyRename();
    }, delay || 100);
  }

  function startWatcher() {
    state.watcher = setInterval(function () {
      var id;

      if (state.destroyed) {
        return;
      }

      id = certId();

      if (id !== state.lastCertId) {
        state.lastCertId = id;
        schedule(80);
      }
    }, 300);
  }

  function boot() {
    loadMappings(function () {
      state.lastCertId = certId();
      schedule(100);

      setTimeout(function () {
        schedule(0);
      }, 600);
    });

    startWatcher();
  }

  state.destroy = function () {
    state.destroyed = true;
    clearTimeout(state.timer);
    clearInterval(state.watcher);
    restoreTitle();
  };

  try {
    if (window.ebuilderSDK && window.ebuilderSDK.getPageSDK) {
      window.ebuilderSDK.getPageSDK().on("formReady", function () {
        boot();
      });
    } else {
      boot();
    }
  } catch (e) {
    boot();
  }
})();

排查顺序
#

  1. ESB 接口是否返回数据。
  2. 返回路径是否仍然是 actionData.responseData.customData.mainTable.detail3
  3. certType 是否拿到了浏览框 optionId。
  4. mapping 的 certtype 是否等于当前 optionId。
  5. 标题节点原始文本是否等于 mapping 的 fieldname
  6. 是否错误修改到了输入框,而不是标题文本节点。

当前结论
#

  • 推荐保留 ESB mapping 驱动动态改名。
  • 推荐使用证照类型 optionId 匹配。
  • 推荐用标题 DOM 原始文本匹配 mapping 的 fieldname
  • 推荐用 data-hz-origin-title 保存原始标题,切换前先恢复再重命名。
  • 暂不把隐藏行、删除空白、重排布局混入这个脚本。

通用调试与排障补充
#

E10 ecode 最容易卡在执行时机、SDK 实例、字段取值、接口返回和 DOM 定位这几层。排障时不要一口气改完最终功能,先按层验证。

分层验证顺序
#

  1. 控制台是否能看到脚本启动日志。
  2. 是否触发 formReadyonFormReady
  3. 是否能拿到 window.WeFormSDK.getWeFormInstance()
  4. convertFieldNameToId("certType", "main", true) 是否返回 fieldMark。
  5. 浏览框是否能取到 getBrowserOptionId
  6. ESB 接口是否返回 mapping,返回路径是否正确。
  7. .weapp-form-widget-internal-title--text span 是否能找到标题节点。
  8. 标题原始文本是否等于 mapping 的 fieldname
  9. 保存或局部刷新后是否需要重新绑定和补刷。

语法与入口
#

E10 的 JavaScript 校验偏保守,正式片段默认使用 ES5 写法:var、普通 functiontry/catch。避免可选链、箭头函数、const / let、JSX、class 和复杂语法糖。

最小执行测试:

(function () {
  try {
    console.log("[ecode-test] script start", new Date().toLocaleString());
  } catch (e) {
    console.log("[ecode-test] script error", e);
  }
})();

表单 ready 调试:

(function () {
  try {
    if (window.ebuilderSDK && window.ebuilderSDK.getPageSDK) {
      window.ebuilderSDK.getPageSDK().on("formReady", function (args) {
        console.log("[ecode-test] pageSdk formReady", args);
      });
    }

    window.addEventListener("onFormReady", function (event) {
      console.log("[ecode-test] window onFormReady", event && event.detail);
    });
  } catch (e) {
    console.log("[ecode-test] formReady error", e);
  }
})();

SDK、字段与浏览框
#

单表单场景先用简化方式取实例:

var formSdk = window.WeFormSDK.getWeFormInstance();
var mark = formSdk.convertFieldNameToId("certType", "main", true);

console.log("[ecode-test] fieldMark", mark);
console.log("[ecode-test] getFieldValue", formSdk.getFieldValue(mark));
console.log("[ecode-test] getBrowserOptionId", formSdk.getBrowserOptionId(mark, ","));
console.log("[ecode-test] getBrowserShowName", formSdk.getBrowserShowName(mark, ","));

经验:

  • dataKey 不是字段 ID,主表字段一般要先 convertFieldNameToId(dataKey, "main", true)
  • 浏览框稳定匹配优先用 getBrowserOptionId
  • 字段变化事件触发时,浏览框值可能还没完全更新,建议延迟几十到几百毫秒再读。
  • 保存、联动、局部刷新后,事件绑定可能失效,formReady 后要能重新绑定。

接口与 DOM
#

接口先单独调通,再接入改名逻辑。不要接口、DOM、隐藏布局一起调。

var xhr = new XMLHttpRequest();

xhr.open("POST", "/api/esb/server/event/triggerActionFlow", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onreadystatechange = function () {
  if (xhr.readyState !== 4) {
    return;
  }

  console.log("[ecode-test] status", xhr.status);
  console.log("[ecode-test] responseText", xhr.responseText);
};

标题节点调试:

var nodes = document.querySelectorAll(".weapp-form-widget-internal-title--text span");
var i;

console.log("[ecode-test] title node count", nodes.length);

for (i = 0; i < nodes.length; i++) {
  console.log("[ecode-test] title", i, nodes[i].innerText || nodes[i].textContent, nodes[i]);
}

经验:

  • 本需求改的是标题文本节点,不是输入框,不是 placeholder
  • getFieldInfo 更适合找业务字段输入框位置,不适合直接找标题文本。
  • 如果值正确但页面没变化,优先检查 DOM 选择器和目标节点。

保存后失效与多卡片
#

保存后标题恢复默认,通常是 E10 局部刷新了表单 DOM。处理方式是:formReady 后执行一次,字段变化后执行一次,保存或局部刷新后延迟补刷,必要时用轻量 watcher 只比较证照类型 ID。

var lastId = "";

setInterval(function () {
  var id = certId();

  if (id !== lastId) {
    lastId = id;
    applyRename();
  }
}, 300);

多卡片 / 多表单场景要谨慎:getWeFormInstance() 可能拿到当前活动表单;全局 STATE_KEY 复用也可能让新实例销毁旧实例。没有明确表单隔离参数时,先保持动态改名简单,不要把多卡片锁定、隐藏行、布局重排混在一起。

隐藏与布局不要混进来
#

隐藏布局是高风险逻辑。不要全页面扫空 td 后直接隐藏,也不要一开始就重排。若后续要做,单独开发:先用 mapping 判断保留字段,再用 getFieldInfo 和标题 DOM 定位业务区域,只处理目标表格的 td.cell_Sheet1_x_x,并确认行列边界。

日志模板
#

function log(message, data) {
  if (window.console && console.log) {
    console.log("[hz-ecode-debug] " + message, data || "");
  }
}

常用日志点:脚本启动、formSdkfieldMark、当前 optionId、接口返回、mapping 数量、标题节点数量、命中字段、准备修改节点。

上线前检查
#

  • 代码能保存,没有 JavaScript 格式错误。
  • 首次进入页面能正确改名。
  • 切换证照类型后能恢复旧标题并应用新标题。
  • 保存后、局部刷新后仍能补刷。
  • ESB 接口异常时页面不报错,只是跳过改名。
  • mapping 为空时页面不报错。
  • 同页面打开其他卡片时,没有明显误改或互相恢复。
  • 没有把隐藏行、空白行清理和动态改名混在同一个稳定版里。