Skip to main content
  1. Work Notes/
  2. E9 Ecode/

Nested Second-level Detail for PC and Mobile

·8135 words·39 mins
Ecology E9 Ecode Nested Detail Code Snippets PC Mobile
Author
molefool
Table of Contents

Notes
#

  • Original comments were removed; the code now keeps only a small number of key block notes.
  • Workflow IDs, node IDs, field names, mapping keys, and business labels were sanitized.
  • Real second-level rows still live in the standard hidden detail table; the page only renders them inside first-level rows.
  • PC and mobile share the same configuration and data handling flow.

Sanitized Code
#



(function () {
  'use strict';

  // 配置区:命中指定流程、节点和端类型后,把隐藏二级明细渲染到一级明细行内。
  var NESTED_DETAIL_ECODE_CONFIG = window.NS_NESTED_DETAIL_ECODE_CONFIG || {
    enable: true,
    debug: false,
    desktopHashPrefixList: ['#/main/workflow/req'],
    mobileHashPrefixList: ['#/main/workflow/req', '#/workflow/req', '#/workflow/request', '#/req'],
    mobilePathPrefixList: ['/spa/workflow/', '/workflow/', '/mobile/', '/emobile/'],
    workflowRules: [
      {
        workflowId: 'WORKFLOW_1',
        nodeIdList: [],
        pageTypeList: ['desktop', 'mobile'],
        mappings: [
          {
            mappingKey: 'nested_detail',
            parentDetailTableMark: 'detail_1',
            childDetailTableMark: 'detail_2',
            renderFieldName: 'render_field',
            parentLinkFieldName: 'parent_id',
            childLinkFieldName: 'parent_id',
            childRowKeyFieldName: 'child_id',
            parentTitleFieldName: 'parent_title',
            summaryFieldName: 'weight',
            hideChildTableHeader: true,
            selectionColumnWidth: '2.5%',
            childSelectionEnabledNodeIdList: ['NODE_1','NODE_2','NODE_3'],
            childActionEnabledNodeIdList: ['NODE_1','NODE_2','NODE_3'],
            editableSuffixNodeIdList: ['NODE_1'],
            expectedTotal: 100,
            summaryTolerance: 0.0001,
            validateOnSubmit: true,
            hideRenderFieldEditor: true,
            autoCleanupOrphanChildren: false,
            childFields: [
              {
                fieldName: 'action_name',
                label: '行动项',
                type: 'text',
                required: true,
                columnWidth: '22.5%',
              },
              {
                fieldName: 'weight',
                label: '权重(%)',
                type: 'number',
                required: true,
                decimalScale: 2,
                suffixText: '%',
                inputWidth: '56px',
                columnWidth: '25%',

              },
              {
                fieldName: 'finish_date',
                label: '完成日期',
                type: 'date',
                required: true,
                columnWidth: '25%'
              },

              {
                fieldName: 'measure',
                label: '衡量标准',
                type: 'textarea',
                required: true,
                columnWidth: '25%'
              }
            ],
            formulaRules: [

            ]
          }
        ]
      },
      {
        workflowId: 'WORKFLOW_2',
        nodeIdList: [],
        pageTypeList: ['desktop', 'mobile'],
        mappings: [
          {
            mappingKey: 'nested_detail',
            parentDetailTableMark: 'detail_1',
            childDetailTableMark: 'detail_2',
            renderFieldName: 'render_field',
            parentLinkFieldName: 'parent_id',
            childLinkFieldName: 'parent_id',
            childRowKeyFieldName: 'child_id',
            parentTitleFieldName: 'parent_title',
            summaryFieldName: 'changed_weight',
            hideChildTableHeader: true,
            selectionColumnWidth: '2%',
            childSelectionEnabledNodeIdList: ['NODE_4','NODE_5'],
            childActionEnabledNodeIdList: ['NODE_4','NODE_5'],
            editableSuffixNodeIdList: ['NODE_4'],
            expectedTotal: 100,
            summaryTolerance: 0.0001,
            validateOnSubmit: true,
            hideRenderFieldEditor: true,
            autoCleanupOrphanChildren: false,
            childFields: [
              {
                fieldName: 'action_name',
                label: '行动项',
                type: 'text',
                required: true,
                columnWidth: '12.7%',
              },
              {
                fieldName: 'changed_action_name',
                label: '变更后行动项',
                type: 'text',
                required: true,
                columnWidth: '17.3%',
                style: 'color: red;'
              },
              {
                fieldName: 'weight',
                label: '权重(%)',
                type: 'number',
                required: true,
                decimalScale: 2,
                suffixText: '%',
                inputWidth: '56px',
                columnWidth: '10%',

              },
              {
                fieldName: 'changed_weight',
                label: '变更后权重(%)',
                type: 'number',
                required: true,
                decimalScale: 2,
                suffixText: '%',
                inputWidth: '60px',
                columnWidth: '11.2%',
                style: 'color: red;'
              },
              {
                fieldName: 'finish_date',
                label: '完成日期',
                type: 'date',
                required: true,
                columnWidth: '10.5%'
              },
              {
                fieldName: 'changed_finish_date',
                label: '变更后完成日期',
                type: 'date',
                required: true,
                columnWidth: '11.5%',
                style: 'color: red;'
              },
              {
                fieldName: 'measure',
                label: '衡量标准',
                type: 'textarea',
                required: true,
                columnWidth: '13.5%'
              },
              {
                fieldName: 'changed_measure',
                label: '变更后衡量标准',
                type: 'textarea',
                required: true,
                columnWidth: '13%',
                style: 'color: red;'
              }
            ],
            formulaRules: [

            ]
          }
        ]
      },
      {
        workflowId: 'WORKFLOW_3',
        nodeIdList: [],
        pageTypeList: ['desktop', 'mobile'],
        mappings: [
          {
            mappingKey: 'nested_detail',
            parentDetailTableMark: 'detail_1',
            childDetailTableMark: 'detail_2',
            renderFieldName: 'render_field',
            parentLinkFieldName: 'parent_id',
            childLinkFieldName: 'parent_id',
            childRowKeyFieldName: 'child_id',
            parentTitleFieldName: 'parent_title',
            summaryFieldName: 'weight',
            hideChildTableHeader: true,
            selectionColumnWidth: '2.5%',
            childSelectionEnabledNodeIdList: [],
            childActionEnabledNodeIdList: [],
            editableSuffixNodeIdList: [],
            expectedTotal: 100,
            summaryTolerance: 0.0001,
            validateOnSubmit: true,
            hideRenderFieldEditor: true,
            autoCleanupOrphanChildren: false,
            childFields: [
              {
                fieldName: 'action_name',
                label: '行动项',
                type: 'text',
                required: true,
                columnWidth: '22.5%',
              },
              {
                fieldName: 'weight',
                label: '权重(%)',
                type: 'number',
                required: true,
                decimalScale: 2,
                suffixText: '%',
                inputWidth: '56px',
                columnWidth: '25%',

              },
              {
                fieldName: 'finish_date',
                label: '完成日期',
                type: 'date',
                required: true,
                columnWidth: '25%'
              },

              {
                fieldName: 'measure',
                label: '衡量标准',
                type: 'textarea',
                required: true,
                columnWidth: '25%'
              }
            ],
            formulaRules: [

            ]
          }
        ]
      },
      {
        workflowId: 'WORKFLOW_4',
        nodeIdList: [],
        pageTypeList: ['desktop', 'mobile'],
        mappings: [
          {
            mappingKey: 'nested_detail',
            parentDetailTableMark: 'detail_1',
            childDetailTableMark: 'detail_2',
            renderFieldName: 'render_field',
            parentLinkFieldName: 'parent_id',
            childLinkFieldName: 'parent_id',
            childRowKeyFieldName: 'child_id',
            parentTitleFieldName: 'parent_title',
            summaryFieldName: 'weight',
            hideChildTableHeader: true,
            selectionColumnWidth: '2.5%',
            childSelectionEnabledNodeIdList: [],
            childActionEnabledNodeIdList: [],
            editableSuffixNodeIdList: [],
            expectedTotal: 100,
            summaryTolerance: 0.0001,
            validateOnSubmit: true,
            hideRenderFieldEditor: true,
            autoCleanupOrphanChildren: false,
            childFields: [
              {
                fieldName: 'action_name',
                label: '行动项',
                type: 'text',
                required: true,
                columnWidth: '22.5%',
              },
              {
                fieldName: 'weight',
                label: '权重(%)',
                type: 'number',
                required: true,
                decimalScale: 2,
                suffixText: '%',
                inputWidth: '56px',
                columnWidth: '25%',

              },
              {
                fieldName: 'finish_date',
                label: '完成日期',
                type: 'date',
                required: true,
                columnWidth: '25%'
              },

              {
                fieldName: 'measure',
                label: '衡量标准',
                type: 'textarea',
                required: true,
                columnWidth: '25%'
              }
            ],
            formulaRules: [

            ]
          }
        ]
      }
    ]
  };

  // 运行时状态:缓存运行实例、刷新定时器、字段绑定和选中行,避免脚本重复加载后状态丢失。
  var GLOBAL_STATE_KEY = '__NS_NESTED_DETAIL_RUNTIME__';
  var STYLE_ID = 'ns-nested-detail-style';
  var ROOT_ATTR = 'data-ns-nested-root';
  var ROOT_KEY_ATTR = 'data-ns-runtime-key';
  var INLINE_VALIDATION_MESSAGE = '__NS_NESTED_INLINE_VALIDATION__';

  function getGlobalState() {
    if (!window[GLOBAL_STATE_KEY]) {
      window[GLOBAL_STATE_KEY] = {
        styleInjected: false,
        runtimeMap: {},
        bindingMap: {},
        observerMap: {},
        refreshTimerMap: {},
        initTimerMap: {},
        submitGuardMap: {},
        uiBound: false,
        internalUpdateDepth: 0,
        modalContext: null
      };
    }
    return window[GLOBAL_STATE_KEY];
  }

  function log() {
    if (!NESTED_DETAIL_ECODE_CONFIG.debug || !window.console || !console.log) {
      return;
    }
    var args = Array.prototype.slice.call(arguments || []);
    args.unshift('[nested-detail]');
    console.log.apply(console, args);
  }

  // 基础工具:统一处理空值、数组、数字、CSS 尺寸和安全调用,后续读写字段都会复用。
  function normalizeValue(value) {
    if (value === undefined || value === null) {
      return '';
    }
    return String(value).trim();
  }

  function toArray(value) {
    if (Array.isArray(value)) {
      return value;
    }
    if (value === undefined || value === null || value === '') {
      return [];
    }
    return [value];
  }

  function uniqueArray(list) {
    var result = [];
    var cache = {};
    var index;
    var key;
    for (index = 0; index < list.length; index += 1) {
      key = normalizeValue(list[index]);
      if (!key || cache[key]) {
        continue;
      }
      cache[key] = true;
      result.push(key);
    }
    return result;
  }

  function hasOwnPropertySafe(target, key) {
    return !!target && Object.prototype.hasOwnProperty.call(target, key);
  }

  function resolveNodeAccessEnabled(config, keyList, currentNodeId, defaultEnabled) {
    var index;
    var key;
    var list;
    for (index = 0; index < keyList.length; index += 1) {
      key = keyList[index];
      if (!hasOwnPropertySafe(config, key)) {
        continue;
      }
      list = uniqueArray(toArray(config[key]).map(function (item) {
        return normalizeValue(item);
      }).filter(function (item) {
        return !!item;
      }));
      if (!list.length) {
        return false;
      }
      return list.indexOf(normalizeValue(currentNodeId)) >= 0;
    }
    return defaultEnabled;
  }

  function parseNumber(value) {
    var text = normalizeValue(value).replace(/,/g, '');
    if (!text) {
      return 0;
    }
    var result = Number(text);
    return isFiniteNumber(result) ? result : 0;
  }

  function isFiniteNumber(value) {
    return typeof value === 'number' && isFinite(value);
  }

  function formatNumber(value, decimalScale) {
    if (value === '' || value === undefined || value === null) {
      return '';
    }
    var num = Number(value);
    if (!isFiniteNumber(num)) {
      return normalizeValue(value);
    }
    if (decimalScale === undefined || decimalScale === null || decimalScale === '') {
      return String(num);
    }
    return num.toFixed(Number(decimalScale));
  }

  function appendSuffixText(value, suffixText) {
    var normalizedText = normalizeValue(value);
    var normalizedSuffix = normalizeValue(suffixText);
    if (!normalizedText || !normalizedSuffix) {
      return normalizedText;
    }
    return normalizedText + normalizedSuffix;
  }

  function normalizeCssSize(value) {
    if (value === undefined || value === null || value === '') {
      return '';
    }
    if (typeof value === 'number' && isFinite(value)) {
      return String(value) + 'px';
    }
    return normalizeValue(value);
  }

  function escapeSelectorValue(value) {
    return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
  }

  function safeCall(fn, fallback) {
    try {
      return fn();
    } catch (e) {
      return fallback;
    }
  }

  // 页面识别:读取流程基础信息、当前节点、路由和端类型,只在目标流程页面启用。
  function getBaseInfoSafe() {
    if (!window.WfForm || typeof window.WfForm.getBaseInfo !== 'function') {
      return null;
    }
    return safeCall(function () {
      return window.WfForm.getBaseInfo() || null;
    }, null);
  }

  function getLayoutStoreSafe() {
    if (!window.WfForm || typeof window.WfForm.getLayoutStore !== 'function') {
      return null;
    }
    return safeCall(function () {
      return window.WfForm.getLayoutStore() || null;
    }, null);
  }

  function getCurrentNodeIdSafe(baseInfo) {
    var nodeId = safeCall(function () {
      var globalStore = window.WfForm && typeof window.WfForm.getGlobalStore === 'function'
        ? window.WfForm.getGlobalStore()
        : null;
      return globalStore && globalStore.commonParam ? globalStore.commonParam.currentnodeid : '';
    }, '');
    return normalizeValue(nodeId || (baseInfo && baseInfo.nodeid));
  }

  function isHashMatched(prefixList) {
    var hash = window.location && window.location.hash ? window.location.hash : '';
    var list = toArray(prefixList);
    var index;
    if (!hash || !list.length) {
      return false;
    }
    for (index = 0; index < list.length; index += 1) {
      if (hash.indexOf(list[index]) === 0) {
        return true;
      }
    }
    return false;
  }

  function isPathMatched(prefixList) {
    var pathname = window.location && window.location.pathname ? window.location.pathname.toLowerCase() : '';
    var list = toArray(prefixList);
    var index;
    var prefix;
    if (!pathname || !list.length) {
      return false;
    }
    for (index = 0; index < list.length; index += 1) {
      prefix = normalizeValue(list[index]).toLowerCase();
      if (prefix && pathname.indexOf(prefix) === 0) {
        return true;
      }
    }
    return false;
  }

  function isMobileUserAgent() {
    var ua = window.navigator && window.navigator.userAgent ? window.navigator.userAgent.toLowerCase() : '';
    return /mobile|android|iphone|ipad|ipod|micromessenger|wxwork|emobile/.test(ua);
  }

  function detectPageType() {
    if (isPathMatched(NESTED_DETAIL_ECODE_CONFIG.mobilePathPrefixList) || isMobileUserAgent()) {
      return 'mobile';
    }
    return 'desktop';
  }

  function isPageLocationMatched(pageType, config) {
    if (pageType === 'mobile') {
      return isHashMatched(config.mobileHashPrefixList) ||
        isPathMatched(config.mobilePathPrefixList) ||
        isMobileUserAgent() ||
        !!(getBaseInfoSafe() && getBaseInfoSafe().workflowid);
    }
    return isHashMatched(config.desktopHashPrefixList);
  }

  function isWorkflowMatched(rule, baseInfo, pageType) {
    var workflowId = normalizeValue(baseInfo && baseInfo.workflowid);
    var nodeId = getCurrentNodeIdSafe(baseInfo);
    var workflowRuleId = normalizeValue(rule && rule.workflowId);
    var nodeIdList = toArray(rule && rule.nodeIdList);
    var pageTypeList = toArray(rule && rule.pageTypeList);
    var matchedNode = !nodeIdList.length;
    var index;

    if (!workflowId || !workflowRuleId || workflowId !== workflowRuleId) {
      return false;
    }
    if (pageTypeList.length && pageTypeList.indexOf(pageType) < 0) {
      return false;
    }
    for (index = 0; index < nodeIdList.length; index += 1) {
      if (normalizeValue(nodeIdList[index]) === nodeId) {
        matchedNode = true;
        break;
      }
    }
    return matchedNode;
  }

  // 字段读写:把配置字段名转换成运行时字段 ID,并封装 WfForm 的安全读写。
  function toDetailFieldId(fieldName, detailTableMark) {
    if (!window.WfForm || typeof window.WfForm.convertFieldNameToId !== 'function') {
      return '';
    }
    return safeCall(function () {
      return window.WfForm.convertFieldNameToId(fieldName, detailTableMark) || '';
    }, '');
  }

  function getFieldValueSafe(fieldMark) {
    if (!window.WfForm || typeof window.WfForm.getFieldValue !== 'function') {
      return '';
    }
    return safeCall(function () {
      return window.WfForm.getFieldValue(fieldMark) || '';
    }, '');
  }

  function getBrowserShowNameSafe(fieldMark) {
    if (!window.WfForm || typeof window.WfForm.getBrowserShowName !== 'function') {
      return '';
    }
    return safeCall(function () {
      return window.WfForm.getBrowserShowName(fieldMark) || '';
    }, '');
  }

  function withInternalUpdate(fn) {
    var state = getGlobalState();
    state.internalUpdateDepth += 1;
    try {
      return fn();
    } finally {
      state.internalUpdateDepth -= 1;
    }
  }

  function isInternalUpdating() {
    return getGlobalState().internalUpdateDepth > 0;
  }

  function setFieldValueSafe(fieldMark, value) {
    var normalized = value === undefined || value === null ? '' : value;
    var currentValue = normalizeValue(getFieldValueSafe(fieldMark));
    var nextValue = normalizeValue(normalized);
    var success = false;

    if (!window.WfForm || typeof window.WfForm.changeFieldValue !== 'function') {
      return false;
    }
    if (currentValue === nextValue) {
      return true;
    }

    withInternalUpdate(function () {
      success = safeCall(function () {
        window.WfForm.changeFieldValue(fieldMark, { value: normalized });
        return true;
      }, false);
      if (!success) {
        success = safeCall(function () {
          window.WfForm.changeFieldValue(fieldMark, normalized);
          return true;
        }, false);
      }
    });
    return success;
  }

  function changeFieldAttrSafe(fieldMark, viewAttr) {
    if (!window.WfForm || typeof window.WfForm.changeFieldAttr !== 'function') {
      return false;
    }
    return safeCall(function () {
      window.WfForm.changeFieldAttr(fieldMark, viewAttr);
      return true;
    }, false);
  }

  function disableNativeRequiredForRuntime(runtime) {
    var rowIndexList;
    if (!runtime || runtime.pageType !== 'mobile' || !runtime.childFieldList || !runtime.childFieldList.length) {
      return;
    }
    rowIndexList = getAllDetailRowIndexList(runtime.childDetailTableMark);
    rowIndexList.forEach(function (rowIndex) {
      runtime.childFieldList.forEach(function (fieldMeta) {
        if (!fieldMeta.required || !fieldMeta.fieldId) {
          return;
        }
        changeFieldAttrSafe(fieldMeta.fieldId, 2);
        changeFieldAttrSafe(getChildRowFieldMark(runtime, fieldMeta.fieldId, rowIndex), 2);
      });
    });
  }

  // 明细行操作:读取、新增、删除真实明细行,确保数据仍落在标准明细表里。
  function getAllDetailRowIndexList(detailTableMark) {
    if (!window.WfForm || typeof window.WfForm.getDetailAllRowIndexStr !== 'function') {
      return [];
    }
    return safeCall(function () {
      var rowIndexStr = window.WfForm.getDetailAllRowIndexStr(detailTableMark);
      if (!rowIndexStr) {
        return [];
      }
      return rowIndexStr.split(',').map(function (item) {
        return normalizeValue(item);
      }).filter(function (item) {
        return !!item;
      });
    }, []);
  }

  function addDetailRowSafe(detailTableMark) {
    var beforeList = getAllDetailRowIndexList(detailTableMark);
    var index;
    var result;

    if (!window.WfForm || typeof window.WfForm.addDetailRow !== 'function') {
      return '';
    }

    result = safeCall(function () {
      return window.WfForm.addDetailRow(detailTableMark);
    }, '');
    if (normalizeValue(result)) {
      return normalizeValue(result);
    }

    safeCall(function () {
      return window.WfForm.addDetailRow(detailTableMark, {});
    }, null);

    var afterList = getAllDetailRowIndexList(detailTableMark);
    for (index = 0; index < afterList.length; index += 1) {
      if (beforeList.indexOf(afterList[index]) < 0) {
        return afterList[index];
      }
    }
    return afterList.length ? afterList[afterList.length - 1] : '';
  }

  function findNewDetailRowIndex(detailTableMark, beforeList) {
    var afterList = getAllDetailRowIndexList(detailTableMark);
    var index;
    for (index = 0; index < afterList.length; index += 1) {
      if (beforeList.indexOf(afterList[index]) < 0) {
        return afterList[index];
      }
    }
    return '';
  }

  function delDetailRowSafe(detailTableMark, rowIndex) {
    if (!window.WfForm || typeof window.WfForm.delDetailRow !== 'function') {
      return false;
    }
    return safeCall(function () {
      withInternalUpdate(function () {
        window.WfForm.delDetailRow(detailTableMark, rowIndex);
      });
      return true;
    }, false);
  }

  // DOM 工具:定位字段宿主、嵌套面板根节点和输入控件,兼容 PC 与移动端页面结构。
  function queryAllSafe(selector, root) {
    var baseNode = root || document;
    return safeCall(function () {
      return Array.prototype.slice.call(baseNode.querySelectorAll(selector) || []);
    }, []);
  }

  function isElementNode(node) {
    return !!node && node.nodeType === 1;
  }

  function getNestedRootFromNode(node) {
    var currentNode = node;
    if (!currentNode) {
      return null;
    }
    if (!isElementNode(currentNode)) {
      currentNode = currentNode.parentElement || currentNode.parentNode;
    }
    if (!isElementNode(currentNode) || !currentNode.closest) {
      return null;
    }
    return currentNode.closest('[' + ROOT_ATTR + ']');
  }

  function isNodeInsideNestedRoot(node) {
    return !!getNestedRootFromNode(node);
  }

  function nodeContainsNestedRoot(node) {
    if (!isElementNode(node)) {
      return false;
    }
    if (node.getAttribute && node.getAttribute(ROOT_ATTR)) {
      return true;
    }
    return !!(node.querySelector && node.querySelector('[' + ROOT_ATTR + ']'));
  }

  function isEditableFieldNode(node) {
    var tagName;
    var typeName;
    if (!isElementNode(node)) {
      return false;
    }
    if (node.isContentEditable) {
      return true;
    }
    tagName = (node.tagName || '').toUpperCase();
    if (tagName === 'TEXTAREA') {
      return true;
    }
    if (tagName !== 'INPUT') {
      return false;
    }
    typeName = normalizeValue(node.getAttribute('type')).toLowerCase();
    return ['button', 'submit', 'reset', 'checkbox', 'radio', 'file'].indexOf(typeName) === -1;
  }

  function isEditingInsideRuntime(runtime) {
    var activeNode = document.activeElement;
    var rootNode = getNestedRootFromNode(activeNode);
    if (!rootNode) {
      return false;
    }
    if (normalizeValue(rootNode.getAttribute(ROOT_ATTR)) !== runtime.runtimeKey) {
      return false;
    }
    return isEditableFieldNode(activeNode);
  }

  function findFieldHostNode(node) {
    if (!node || !node.closest) {
      return null;
    }
    return node.closest('td') ||
      node.closest('.wea-form-cell') ||
      node.closest('.wea-form-item') ||
      node.closest('.wea-mobile-form-item') ||
      node.closest('.mobile-wf-form-item') ||
      node.closest('.am-list-item') ||
      node.closest('[data-fieldname]') ||
      node.closest('[data-fieldmark]') ||
      node.parentElement ||
      null;
  }

  function findFieldValueTd(fieldName, fieldMarkWithRow) {
    var escapedFieldName = escapeSelectorValue(fieldName);
    var escapedFieldMark = escapeSelectorValue(fieldMarkWithRow);
    var selectorList = [
      '[data-fieldname="' + escapedFieldName + '"][data-fieldmark="' + escapedFieldMark + '"]',
      '[data-fieldmark="' + escapedFieldMark + '"]',
      '#' + escapedFieldMark,
      '[name="' + escapedFieldMark + '"]'
    ];
    var selectorIndex;
    var nodeList;
    var nodeIndex;
    var hostNode;

    for (selectorIndex = 0; selectorIndex < selectorList.length; selectorIndex += 1) {
      nodeList = queryAllSafe(selectorList[selectorIndex]);
      for (nodeIndex = 0; nodeIndex < nodeList.length; nodeIndex += 1) {
        hostNode = findFieldHostNode(nodeList[nodeIndex]);
        if (hostNode) {
          return hostNode;
        }
      }
    }
    return null;
  }

  function hideOriginalHostChildren(hostCell, rootNode) {
    if (!hostCell) {
      return;
    }
    Array.prototype.slice.call(hostCell.children || []).forEach(function (child) {
      if (child === rootNode) {
        return;
      }
      if (child.nodeType !== 1) {
        return;
      }
      child.style.display = 'none';
    });
  }

  function setNodeWidth(node, widthPx) {
    if (!node || !widthPx) {
      return;
    }
    node.style.width = widthPx;
    node.style.minWidth = widthPx;
    node.style.maxWidth = widthPx;
    node.setAttribute('width', widthPx.replace(/px$/i, ''));
  }

  function applyChildSelectionColumnWidth(runtime, rootNode) {
    var widthValue;
    if (!runtime || !rootNode) {
      return;
    }
    widthValue = runtime.selectionColumnWidth;
    queryAllSafe('th.ant-table-selection-column,td.ant-table-selection-column', rootNode).forEach(function (node) {
      setNodeWidth(node, widthValue);
      node.style.paddingLeft = '0';
      node.style.paddingRight = '0';
      node.style.textAlign = 'center';
    });
    queryAllSafe('table', rootNode).forEach(function (tableNode) {
      var firstCol = tableNode.querySelector('colgroup col:first-child');
      if (firstCol) {
        setNodeWidth(firstCol, widthValue);
      }
    });
  }

  // 样式区:注入嵌套面板所需的 PC 表格、移动端列表、校验提示和字段后缀样式。
  function injectStyleOnce() {
    var styleNode;
    var cssText;
    var state = getGlobalState();

    if (state.styleInjected || document.getElementById(STYLE_ID)) {
      state.styleInjected = true;
      return;
    }

    cssText = [
      '.wf-input-required{right:1px}',
      '.ns-nested-panel{border:1.2px solid #dee6fe;border-radius:0;background:#fff;padding:0;margin:0;box-shadow:none;}',
      '.ns-nested-panel--embedded{padding:0;}',
      '.ns-nested-panel__toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;padding:2px 6px 2px 0;background:#ffffff;border-bottom:1.2px solid #dee6fe;}',
      '.ns-nested-panel__toolbar--plain{justify-content:flex-end;margin:0;}',
      '.ns-nested-panel__toolbar-actions{display:flex;align-items:center;gap:8px;}',
      '.ns-nested-panel__title{font-size:13px;font-weight:600;color:#0f335c;}',
      '.ns-nested-panel__button{border:0;border-radius:2px;background:#7a6cf0;color:#fff;padding:6px 12px;cursor:pointer;font-size:12px;line-height:1.2;}',
      '.ns-nested-panel__button--ghost{background:#9aa3b2;color:#fff;}',
      '.ns-nested-panel__button--icon{width:20px;height:20px;padding:0;display:inline-flex;align-items:center;justify-content:center;border-radius:2px;font-size:12px;}',
      '.ns-nested-panel__button:disabled{opacity:.45;cursor:not-allowed;}',
      '.ns-nested-panel__empty{padding:12px 8px;text-align:center;color:#7c8ba0;background:#fff;border:1px dashed #dee6fe;border-radius:0;}',
      '.ns-nested-panel__table{width:100%;border-collapse:collapse;background:#fff;border-radius:0;overflow:hidden;table-layout:fixed;}',
      '.ns-nested-panel__table-wrap{background:#fff;border-radius:0;overflow:hidden;}',
      '.ns-nested-panel__table-wrap .ant-table{border:0;border-radius:0;background:#fff;}',
      '.ns-nested-panel__table-wrap .ant-table-small{border-radius:0;}',
      '.ns-nested-panel__table-wrap .ant-table-content,.ns-nested-panel__table-wrap .ant-table-body,.ns-nested-panel__table-wrap .ant-table-body-outer{margin:0 !important;}',
      '.ns-nested-panel__table-wrap .ant-table table{table-layout:fixed;border-collapse:collapse;}',
      '.ns-nested-panel__table-wrap .ant-table-thead > tr > th,.ns-nested-panel__table-wrap .ant-table-tbody > tr > td{border-right:1.2px solid #dee6fe;border-bottom:1.2px solid #dee6fe;padding:4px 6px;font-size:12px;vertical-align:middle;background:#fff;}',
      '.ns-nested-panel__table-wrap .ant-table-thead > tr > th{background:#eef3ff;color:#32557e;font-weight:600;}',
      '.ns-nested-panel__table-wrap .ant-table-thead > tr > th:last-child,.ns-nested-panel__table-wrap .ant-table-tbody > tr > td:last-child{border-right:0;}',
      '.ns-nested-panel__table-wrap .ant-table-tbody > tr:last-child > td{border-bottom:0;}',
      '.ns-nested-panel__table-wrap .ant-table-selection-column{width:var(--ns-selection-column-width,22px) !important;min-width:var(--ns-selection-column-width,22px) !important;max-width:var(--ns-selection-column-width,22px) !important;padding-left:0 !important;padding-right:0 !important;text-align:center;}',
      '.ns-nested-panel__table-wrap .ant-table-thead > tr > .ant-table-selection-column,.ns-nested-panel__table-wrap .ant-table-tbody > tr > .ant-table-selection-column{width:var(--ns-selection-column-width,22px) !important;min-width:var(--ns-selection-column-width,22px) !important;max-width:var(--ns-selection-column-width,22px) !important;}',
      '.ns-nested-panel__table-wrap .ant-table-selection-column .ant-checkbox-wrapper{display:flex;align-items:center;justify-content:center;width:100%;margin:0;}',
      '.ns-nested-panel__table-wrap .ant-table-selection-column .ant-checkbox{transform:scale(.9);transform-origin:center;}',
      '.ns-nested-panel__table th,.ns-nested-panel__table td{border-right:1.2px solid #dee6fe;border-bottom:1.2px solid #dee6fe;padding:4px 6px;text-align:left;font-size:12px;vertical-align:middle;}',
      '.ns-nested-panel__table th{background:#eef3ff;color:#32557e;font-weight:600;}',
      '.ns-nested-panel__table th:last-child,.ns-nested-panel__table td:last-child{border-right:0;}',
      '.ns-nested-panel__table tr:last-child td{border-bottom:0;}',
      '.ns-nested-panel__field{min-width:0;min-height:100%;align-items:center;}',
      '.ns-nested-panel__field--colored{color:var(--ns-field-text-color);}',
      '.ns-nested-panel__field--colored input,.ns-nested-panel__field--colored textarea,.ns-nested-panel__field--colored .ant-input,.ns-nested-panel__field--colored .ant-select-selection-selected-value,.ns-nested-panel__field--colored .browserName,.ns-nested-panel__field--colored .wea-browser-span,.ns-nested-panel__field--colored .wf-form-control,.ns-nested-panel__field--colored span{color:var(--ns-field-text-color) !important;}',
      '.ns-nested-panel__field--suffix{min-width:0;}',
      '.ns-suffix-field-host{display:inline-flex !important;align-items:center;gap:4px;vertical-align:middle;padding:0 !important;}',
      '.ns-suffix-field-host input,.ns-suffix-field-host textarea,.ns-suffix-field-host .ant-input{width:var(--ns-field-input-width,auto) !important;padding:0 !important;}',
      '.ns-suffix-input-text{color:#667085;font-size:12px;line-height:1;pointer-events:none;flex:0 0 auto;}',
      '.ns-nested-panel__required-mark.wf-input-required{position:absolute !important;right:0 !important;top:50% !important;transform:translateY(-50%) !important;color:#ff4d4f !important;font-size:20px !important;font-weight:600 !important;line-height:1 !important;margin-left:0 !important;z-index:2;}',
      '.ns-nested-panel__op{display:flex;gap:8px;align-items:center;white-space:nowrap;}',
      '.ns-nested-panel__link{color:#0f6cbd;cursor:pointer;background:none;border:0;padding:0;font-size:12px;}',
      '.ns-nested-panel__summary{margin-top:0;padding:4px 8px;display:flex;justify-content:flex-end;gap:10px;align-items:center;color:#334e68;font-size:12px;border-top:1.2px solid #dee6fe;}',
      '.ns-nested-panel__badge{display:inline-flex;align-items:center;padding:0;border-radius:0;font-weight:600;background:none;}',
      '.ns-nested-panel__badge--ok{color:#111111;}',
      '.ns-nested-panel__badge--error{color:#d92d20;}',
      '.ns-nested-mobile-panel{border:1px solid #dee6fe;background:#fff;margin:0;padding:0;}',
      '.ns-nested-mobile-panel__toolbar{display:flex;justify-content:flex-end;gap:8px;padding:4px 6px;border-bottom:1px solid #dee6fe;background:#f7f9ff;}',
      '.ns-nested-mobile-panel__list{display:flex;flex-direction:column;padding:0;}',
      '.ns-nested-mobile-panel__row{display:grid;align-items:center;min-height:46px;border-bottom:1px solid #dee6fe;background:#fff;}',
      '.ns-nested-mobile-panel__row:last-child{border-bottom:0;}',
      '.ns-nested-mobile-panel__select-cell{display:flex;align-items:center;justify-content:center;min-width:0;height:100%;border-right:1px solid #edf1f5;}',
      '.ns-nested-mobile-panel__cell{min-width:0;height:100%;display:flex;align-items:center;padding:4px 8px;border-right:1px solid #edf1f5;box-sizing:border-box;}',
      '.ns-nested-mobile-panel__cell--error{align-items:flex-start;justify-content:center;flex-direction:column;}',
      '.ns-nested-mobile-panel__cell:last-child{border-right:0;}',
      '.ns-nested-mobile-panel__control{min-width:0;width:100%;}',
      '.ns-nested-mobile-panel__control .ns-nested-panel__field{width:100%;min-height:0;display:block;position:relative;padding-right:16px;box-sizing:border-box;}',
      '.ns-nested-mobile-panel__control input,.ns-nested-mobile-panel__control textarea,.ns-nested-mobile-panel__control .ant-input{width:100%;max-width:100%;min-height:26px;box-sizing:border-box;border:1px solid #cfd8e3;border-radius:2px;padding:2px 4px;font-size:12px;background:#fff;}',
      '.ns-nested-mobile-panel__control textarea{min-height:34px;resize:vertical;}',
      '.ns-nested-mobile-panel__error{display:inline-flex;align-items:center;margin-top:4px;padding:2px 8px;border:1px solid #ff9b9b;border-radius:8px;background:#fff4f4;color:#e5484d;font-size:12px;line-height:1.4;}',
      '.ns-nested-mobile-panel__empty{padding:14px 8px;text-align:center;color:#7c8ba0;font-size:12px;}',
      '.ns-nested-mobile-panel__summary{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;padding:7px 8px;border-top:1px solid #dee6fe;font-size:12px;color:#334e68;}',
      '.ns-nested-modal{position:fixed;inset:0;display:none;align-items:center;justify-content:center;z-index:100000;background:rgba(15,35,59,.32);}',
      '.ns-nested-modal--show{display:flex;}',
      '.ns-nested-modal__dialog{width:min(760px,92vw);max-height:86vh;overflow:auto;background:#fff;border-radius:14px;box-shadow:0 16px 44px rgba(15,35,59,.22);}',
      '.ns-nested-modal__header{padding:18px 20px 12px;border-bottom:1.2px solid #edf1f5;font-size:16px;font-weight:700;color:#0f335c;}',
      '.ns-nested-modal__body{padding:18px 20px;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px 16px;}',
      '.ns-nested-modal__field{display:flex;flex-direction:column;gap:6px;}',
      '.ns-nested-modal__label{font-size:12px;color:#334e68;font-weight:600;}',
      '.ns-nested-modal__required{color:#d92d20;margin-left:4px;}',
      '.ns-nested-modal__input,.ns-nested-modal__textarea{width:100%;border:1.2px solid #c9d6e5;border-radius:8px;padding:9px 10px;font-size:13px;box-sizing:border-box;}',
      '.ns-nested-modal__textarea{min-height:96px;resize:vertical;}',
      '.ns-nested-modal__footer{padding:14px 20px 20px;display:flex;justify-content:flex-end;gap:10px;}',
      '.ns-nested-modal__error{grid-column:1/-1;font-size:12px;color:#b42318;display:none;}',
      '.ns-nested-modal__error--show{display:block;}'
    ].join('');

    styleNode = document.createElement('style');
    styleNode.id = STYLE_ID;
    styleNode.type = 'text/css';
    styleNode.appendChild(document.createTextNode(cssText));
    document.head.appendChild(styleNode);
    state.styleInjected = true;
  }

  // 字段配置解析:从 childFields 中读取标题、类型、列宽、后缀、颜色和输入框尺寸。
  function findChildFieldLabel(childFieldList, fieldName) {
    var fieldMeta = findChildFieldMeta(childFieldList, fieldName);
    return fieldMeta ? normalizeValue(fieldMeta.label || fieldMeta.fieldName) : '';
  }

  function findChildFieldMeta(childFieldList, fieldName) {
    var normalizedFieldName = normalizeValue(fieldName);
    var index;
    var fieldMeta;
    for (index = 0; index < childFieldList.length; index += 1) {
      fieldMeta = childFieldList[index];
      if (normalizeValue(fieldMeta.fieldName) === normalizedFieldName) {
        return fieldMeta;
      }
    }
    return null;
  }

  function findChildFieldSuffixText(childFieldList, fieldName) {
    var fieldMeta = findChildFieldMeta(childFieldList, fieldName);
    return fieldMeta ? normalizeValue(fieldMeta.suffixText) : '';
  }

  function parseStyleColor(styleConfig) {
    var matched;
    if (!styleConfig) {
      return '';
    }
    if (typeof styleConfig === 'string') {
      matched = styleConfig.match(/(?:^|;)\s*color\s*:\s*([^;]+)/i);
      return matched && matched[1] ? normalizeValue(matched[1]) : '';
    }
    if (typeof styleConfig === 'object' && styleConfig.color !== undefined && styleConfig.color !== null) {
      return normalizeValue(styleConfig.color);
    }
    return '';
  }

  function resolveChildFieldTextColor(fieldConfig) {
    return normalizeValue(fieldConfig.textColor || fieldConfig.color || parseStyleColor(fieldConfig.style));
  }

  function resolveFieldSuffixText(fieldConfig) {
    var suffixText = normalizeValue(fieldConfig.suffixText);
    if (suffixText) {
      return suffixText;
    }
    return fieldConfig && fieldConfig.showPercent ? '%' : '';
  }

  function resolveFieldInputWidth(fieldConfig) {
    return normalizeCssSize(fieldConfig && (fieldConfig.inputWidth || fieldConfig.width));
  }

  function resolveFieldColumnWidth(fieldConfig) {
    return normalizeCssSize(fieldConfig && fieldConfig.columnWidth);
  }

  function resolveSelectionColumnWidth(config) {
    var widthValue = config && (
      config.selectionCheckboxColumnWidth !== undefined && config.selectionCheckboxColumnWidth !== null && config.selectionCheckboxColumnWidth !== ''
        ? config.selectionCheckboxColumnWidth
        : config.selectionColumnWidth !== undefined && config.selectionColumnWidth !== null && config.selectionColumnWidth !== ''
          ? config.selectionColumnWidth
          : config.rowSelectionWidth
    );
    return normalizeCssSize(widthValue || 22);
  }

  // 运行时编译:把命中的配置整理成字段 ID、权限、公式和渲染参数。
  function buildRuntime(baseInfo, pageType, workflowRule, mapping) {
    var state = getGlobalState();
    var prevRuntime;
    var childFieldList = [];
    var formulaRules = [];
    var summaryFieldName = normalizeValue(mapping.summaryFieldName);
    var summaryFieldLabel = normalizeValue(mapping.summaryFieldLabel);
    var summaryFieldSuffixText = '';
    var runtimeKey = [
      normalizeValue(baseInfo.workflowid),
      normalizeValue(baseInfo.requestid),
      normalizeValue(mapping.mappingKey || mapping.parentDetailTableMark || 'mapping')
    ].join('|');
    prevRuntime = state.runtimeMap[runtimeKey];
    var childFieldConfigs = toArray(mapping.childFields);
    var formulaConfigs = toArray(mapping.formulaRules);
    var index;
    var fieldConfig;
    var fieldId;
    var formulaRule;
    var targetDetailMark;
    var targetFieldId;

    if (!mapping || !mapping.parentDetailTableMark || !mapping.childDetailTableMark) {
      return null;
    }

    for (index = 0; index < childFieldConfigs.length; index += 1) {
      fieldConfig = childFieldConfigs[index];
      fieldId = toDetailFieldId(fieldConfig.fieldName, mapping.childDetailTableMark);
      if (!fieldId) {
        continue;
      }
      childFieldList.push({
        fieldName: normalizeValue(fieldConfig.fieldName),
        fieldId: fieldId,
        label: normalizeValue(fieldConfig.label || fieldConfig.fieldName),
        type: normalizeValue(fieldConfig.type || 'text'),
        required: !!fieldConfig.required,
        decimalScale: fieldConfig.decimalScale,
        placeholder: normalizeValue(fieldConfig.placeholder),
        useBrowserShowName: !!fieldConfig.useBrowserShowName,
        defaultValue: fieldConfig.defaultValue,
        textColor: resolveChildFieldTextColor(fieldConfig),
        suffixText: resolveFieldSuffixText(fieldConfig),
        inputWidth: resolveFieldInputWidth(fieldConfig),
        columnWidth: resolveFieldColumnWidth(fieldConfig)
      });
    }

    if (!summaryFieldLabel) {
      summaryFieldLabel = findChildFieldLabel(childFieldList, summaryFieldName);
    }
    summaryFieldSuffixText = findChildFieldSuffixText(childFieldList, summaryFieldName);

    for (index = 0; index < formulaConfigs.length; index += 1) {
      formulaRule = formulaConfigs[index];
      targetDetailMark = normalizeValue(formulaRule.scope) === 'parent'
        ? mapping.parentDetailTableMark
        : mapping.childDetailTableMark;
      targetFieldId = toDetailFieldId(formulaRule.targetFieldName, targetDetailMark);
      if (!targetFieldId) {
        continue;
      }
      formulaRules.push({
        scope: normalizeValue(formulaRule.scope || 'child') === 'parent' ? 'parent' : 'child',
        targetFieldName: normalizeValue(formulaRule.targetFieldName),
        targetFieldId: targetFieldId,
        expression: normalizeValue(formulaRule.expression),
        decimalScale: formulaRule.decimalScale
      });
    }

    var runtime = {
      runtimeKey: runtimeKey,
      pageType: pageType,
      workflowId: normalizeValue(baseInfo.workflowid),
      requestId: normalizeValue(baseInfo.requestid),
      nodeId: getCurrentNodeIdSafe(baseInfo),
      workflowRule: workflowRule,
      mapping: mapping,
      layoutStore: getLayoutStoreSafe(),
      parentDetailTableMark: normalizeValue(mapping.parentDetailTableMark),
      childDetailTableMark: normalizeValue(mapping.childDetailTableMark),
      renderFieldName: normalizeValue(mapping.renderFieldName),
      renderFieldId: toDetailFieldId(mapping.renderFieldName, mapping.parentDetailTableMark),
      parentLinkFieldName: normalizeValue(mapping.parentLinkFieldName),
      parentLinkFieldId: toDetailFieldId(mapping.parentLinkFieldName, mapping.parentDetailTableMark),
      childLinkFieldName: normalizeValue(mapping.childLinkFieldName),
      childLinkFieldId: toDetailFieldId(mapping.childLinkFieldName, mapping.childDetailTableMark),
      childRowKeyFieldName: normalizeValue(mapping.childRowKeyFieldName),
      childRowKeyFieldId: toDetailFieldId(mapping.childRowKeyFieldName, mapping.childDetailTableMark),
      parentTitleFieldName: normalizeValue(mapping.parentTitleFieldName),
      parentTitleFieldId: toDetailFieldId(mapping.parentTitleFieldName, mapping.parentDetailTableMark),
      summaryFieldName: summaryFieldName,
      summaryFieldLabel: summaryFieldLabel || summaryFieldName,
      summaryFieldSuffixText: summaryFieldSuffixText,
      summaryFieldId: toDetailFieldId(summaryFieldName, mapping.childDetailTableMark),
      hideChildTableHeader: mapping.hideChildTableHeader !== false,
      selectionColumnWidth: resolveSelectionColumnWidth(mapping),
      selectionEnabled: resolveNodeAccessEnabled(
        mapping,
        ['childSelectionEnabledNodeIdList', 'selectionEnabledNodeIdList', 'selectableNodeIdList'],
        getCurrentNodeIdSafe(baseInfo),
        true
      ),
      actionEnabled: resolveNodeAccessEnabled(
        mapping,
        ['childActionEnabledNodeIdList', 'actionEnabledNodeIdList', 'actionNodeIdList', 'toolbarEnabledNodeIdList'],
        getCurrentNodeIdSafe(baseInfo),
        true
      ),
      editableSuffixEnabled: resolveNodeAccessEnabled(
        mapping,
        ['editableSuffixNodeIdList', 'createNodeIdList'],
        getCurrentNodeIdSafe(baseInfo),
        false
      ),
      lastObservedParentRowCount: prevRuntime && isFiniteNumber(prevRuntime.lastObservedParentRowCount)
        ? prevRuntime.lastObservedParentRowCount
        : getAllDetailRowIndexList(mapping.parentDetailTableMark).length,
      lastObservedChildRowCount: prevRuntime && isFiniteNumber(prevRuntime.lastObservedChildRowCount)
        ? prevRuntime.lastObservedChildRowCount
        : getAllDetailRowIndexList(mapping.childDetailTableMark).length,
      expectedTotal: mapping.expectedTotal === undefined ? null : Number(mapping.expectedTotal),
      summaryTolerance: mapping.summaryTolerance === undefined ? 0.0001 : Number(mapping.summaryTolerance),
      validateOnSubmit: mapping.validateOnSubmit !== false,
      hideRenderFieldEditor: mapping.hideRenderFieldEditor !== false,
      autoCleanupOrphanChildren: !!mapping.autoCleanupOrphanChildren,
      selectedRowMap: prevRuntime && prevRuntime.selectedRowMap ? prevRuntime.selectedRowMap : {},
      childFieldList: childFieldList,
      formulaRules: formulaRules
    };

    if (!runtime.renderFieldId || !runtime.parentLinkFieldId || !runtime.childLinkFieldId) {
      log('runtime skipped because required fields are missing', runtime.runtimeKey, runtime);
      return null;
    }
    return runtime;
  }

  // 父子关联:给一级行和二级行补稳定 key,用隐藏字段维护从属关系。
  function generateRowKey(prefix) {
    return [
      prefix || 'NK',
      new Date().getTime(),
      Math.floor(Math.random() * 1000000)
    ].join('_');
  }

  function getParentRowFieldMark(runtime, fieldId, rowIndex) {
    return fieldId + '_' + rowIndex;
  }

  function getChildRowFieldMark(runtime, fieldId, rowIndex) {
    return fieldId + '_' + rowIndex;
  }

  function ensureParentLinkValue(runtime, parentRowIndex) {
    var fieldMark = getParentRowFieldMark(runtime, runtime.parentLinkFieldId, parentRowIndex);
    var currentValue = normalizeValue(getFieldValueSafe(fieldMark));
    if (currentValue) {
      return currentValue;
    }
    currentValue = generateRowKey('P');
    setFieldValueSafe(fieldMark, currentValue);
    return currentValue;
  }

  function ensureChildRowKeyValue(runtime, childRowIndex) {
    if (!runtime.childRowKeyFieldId) {
      return '';
    }
    var fieldMark = getChildRowFieldMark(runtime, runtime.childRowKeyFieldId, childRowIndex);
    var currentValue = normalizeValue(getFieldValueSafe(fieldMark));
    if (currentValue) {
      return currentValue;
    }
    currentValue = generateRowKey('C');
    setFieldValueSafe(fieldMark, currentValue);
    return currentValue;
  }

  function getParentRowTitle(runtime, parentRowIndex) {
    if (!runtime.parentTitleFieldId) {
      return '';
    }
    return normalizeValue(
      getFieldValueSafe(getParentRowFieldMark(runtime, runtime.parentTitleFieldId, parentRowIndex))
    );
  }

  function getFieldDisplayValue(fieldMeta, fieldMark) {
    var showName;
    var rawValue;
    if (fieldMeta.useBrowserShowName) {
      showName = normalizeValue(getBrowserShowNameSafe(fieldMark));
      if (showName) {
        return showName;
      }
    }
    rawValue = getFieldValueSafe(fieldMark);
    if (fieldMeta.type === 'number' && rawValue !== '') {
      return formatNumber(rawValue, fieldMeta.decimalScale);
    }
    return normalizeValue(rawValue);
  }

  // 数据建模:读取真实二级明细行,转换成面板渲染和校验需要的数据。
  function buildChildRowData(runtime, childRowIndex) {
    var data = {
      rowIndex: childRowIndex,
      rowKey: runtime.childRowKeyFieldId ? ensureChildRowKeyValue(runtime, childRowIndex) : '',
      parentLinkValue: normalizeValue(
        getFieldValueSafe(getChildRowFieldMark(runtime, runtime.childLinkFieldId, childRowIndex))
      ),
      fieldValueMap: {},
      fieldDisplayMap: {}
    };
    runtime.childFieldList.forEach(function (fieldMeta) {
      var fieldMark = getChildRowFieldMark(runtime, fieldMeta.fieldId, childRowIndex);
      data.fieldValueMap[fieldMeta.fieldName] = normalizeValue(getFieldValueSafe(fieldMark));
      data.fieldDisplayMap[fieldMeta.fieldName] = getFieldDisplayValue(fieldMeta, fieldMark);
    });
    if (runtime.summaryFieldId) {
      data.summaryValue = normalizeValue(
        getFieldValueSafe(getChildRowFieldMark(runtime, runtime.summaryFieldId, childRowIndex))
      );
    } else {
      data.summaryValue = '';
    }
    return data;
  }

  function buildChildRowsByParent(runtime) {
    var childRowsByParent = {};
    var rowIndexList = getAllDetailRowIndexList(runtime.childDetailTableMark);
    rowIndexList.forEach(function (rowIndex) {
      var rowData = buildChildRowData(runtime, rowIndex);
      if (!rowData.parentLinkValue) {
        return;
      }
      if (!childRowsByParent[rowData.parentLinkValue]) {
        childRowsByParent[rowData.parentLinkValue] = [];
      }
      childRowsByParent[rowData.parentLinkValue].push(rowData);
    });
    return childRowsByParent;
  }

  // 公式处理:解析表达式里的父子字段 token,并把计算结果写回真实字段。
  function parseFormulaTokens(expression) {
    var regexp = /\{([^}]+)\}/g;
    var match;
    var result = [];
    while ((match = regexp.exec(expression))) {
      result.push(normalizeValue(match[1]));
    }
    return uniqueArray(result);
  }

  function getFormulaTokenValue(runtime, token, parentRowIndex, childRowIndex) {
    var fieldName = token;
    var scope = 'auto';
    var fieldId;
    var fieldMark;

    if (token.indexOf('parent.') === 0) {
      scope = 'parent';
      fieldName = token.substring(7);
    } else if (token.indexOf('child.') === 0) {
      scope = 'child';
      fieldName = token.substring(6);
    }

    if ((scope === 'auto' || scope === 'child') && childRowIndex !== '' && childRowIndex !== undefined && childRowIndex !== null) {
      fieldId = toDetailFieldId(fieldName, runtime.childDetailTableMark);
      if (fieldId) {
        fieldMark = getChildRowFieldMark(runtime, fieldId, childRowIndex);
        return getFieldValueSafe(fieldMark);
      }
    }

    fieldId = toDetailFieldId(fieldName, runtime.parentDetailTableMark);
    if (fieldId && parentRowIndex !== '' && parentRowIndex !== undefined && parentRowIndex !== null) {
      fieldMark = getParentRowFieldMark(runtime, fieldId, parentRowIndex);
      return getFieldValueSafe(fieldMark);
    }
    return '';
  }

  function evaluateFormulaExpression(runtime, expression, parentRowIndex, childRowIndex) {
    var compiledExpression = String(expression || '').replace(/\{([^}]+)\}/g, function (all, token) {
      var value = getFormulaTokenValue(runtime, normalizeValue(token), parentRowIndex, childRowIndex);
      var num = parseNumber(value);
      return isFiniteNumber(num) ? String(num) : '0';
    });
    return safeCall(function () {
      return Function('"use strict";return (' + compiledExpression + ');')();
    }, '');
  }

  function applyFormulaRules(runtime) {
    var parentRows = getAllDetailRowIndexList(runtime.parentDetailTableMark);
    var childRowsByParent = buildChildRowsByParent(runtime);

    runtime.formulaRules.forEach(function (formulaRule) {
      if (!formulaRule.expression) {
        return;
      }
      if (formulaRule.scope === 'parent') {
        parentRows.forEach(function (parentRowIndex) {
          var nextValue = evaluateFormulaExpression(runtime, formulaRule.expression, parentRowIndex, '');
          var output = nextValue === '' ? '' : formatNumber(nextValue, formulaRule.decimalScale);
          setFieldValueSafe(
            getParentRowFieldMark(runtime, formulaRule.targetFieldId, parentRowIndex),
            output
          );
        });
        return;
      }

      parentRows.forEach(function (parentRowIndex) {
        var parentLinkValue = ensureParentLinkValue(runtime, parentRowIndex);
        var childRows = childRowsByParent[parentLinkValue] || [];
        childRows.forEach(function (childRow) {
          var nextValue = evaluateFormulaExpression(
            runtime,
            formulaRule.expression,
            parentRowIndex,
            childRow.rowIndex
          );
          var output = nextValue === '' ? '' : formatNumber(nextValue, formulaRule.decimalScale);
          setFieldValueSafe(
            getChildRowFieldMark(runtime, formulaRule.targetFieldId, childRow.rowIndex),
            output
          );
        });
      });
    });
  }

  // 数据清理与汇总:可清理孤儿子行,并按父行统计二级明细合计。
  function cleanupOrphanChildren(runtime) {
    var parentRows = getAllDetailRowIndexList(runtime.parentDetailTableMark);
    var parentKeyMap = {};
    var childRows = getAllDetailRowIndexList(runtime.childDetailTableMark);
    var deletedRowMap = {};
    var parentRowIndex;
    var childRowIndex;
    var parentKey;
    if (!runtime.autoCleanupOrphanChildren) {
      return;
    }
    parentRows.forEach(function (parentRowIndex) {
      parentKeyMap[ensureParentLinkValue(runtime, parentRowIndex)] = true;
    });
    childRows.forEach(function (childRowIndex) {
      parentKey = normalizeValue(
        getFieldValueSafe(getChildRowFieldMark(runtime, runtime.childLinkFieldId, childRowIndex))
      );
      if (parentKey && !parentKeyMap[parentKey]) {
        delDetailRowSafe(runtime.childDetailTableMark, childRowIndex);
        deletedRowMap[String(childRowIndex)] = true;
      }
    });
    Object.keys(runtime.selectedRowMap || {}).forEach(function (rowKey) {
      runtime.selectedRowMap[rowKey] = toArray(runtime.selectedRowMap[rowKey]).filter(function (selectedRowIndex) {
        return !deletedRowMap[String(selectedRowIndex)];
      });
    });
  }

  function calculateSummary(childRows, runtime) {
    var sum = 0;
    childRows.forEach(function (childRow) {
      sum += parseNumber(childRow.summaryValue);
    });
    var expected = runtime.expectedTotal;
    var passed = true;
    if (expected !== null && expected !== undefined && expected !== '' && isFiniteNumber(expected)) {
      passed = Math.abs(sum - expected) <= runtime.summaryTolerance;
    }
    return {
      sum: sum,
      expected: expected,
      passed: passed
    };
  }

  // 渲染环境:复用泛微页面已有 React、ReactDOM、组件库和 store 上下文。
  function getReactEnv() {
    return {
      React: window.React,
      ReactDOM: window.ReactDOM,
      Provider: window.mobxReact && window.mobxReact.Provider,
      WeaTable: window.ecCom && window.ecCom.WeaTable,
      Button: window.antd && window.antd.Button
    };
  }

  function buildProviderStoreProps(globalStore, layoutStore) {
    var props = {};
    if (globalStore) {
      props.globalStore = globalStore;
      props.wfGlobalStore = globalStore;
    }
    if (layoutStore) {
      props.layoutStore = layoutStore;
      props.wfLayoutStore = layoutStore;
    }
    return props;
  }

  // 选择状态:记录每个一级行下勾选的二级行,供批量删除使用。
  function getSelectedChildRows(runtime, parentRowIndex) {
    return toArray(runtime.selectedRowMap[String(parentRowIndex)]).map(function (rowIndex) {
      return normalizeValue(rowIndex);
    }).filter(function (rowIndex) {
      return !!rowIndex;
    });
  }

  function setSelectedChildRows(runtime, parentRowIndex, rowIndexList) {
    runtime.selectedRowMap[String(parentRowIndex)] = uniqueArray(
      toArray(rowIndexList).map(function (rowIndex) {
        return normalizeValue(rowIndex);
      }).filter(function (rowIndex) {
        return !!rowIndex;
      })
    );
  }

  function buildChildDataSource(runtime, childRows) {
    return childRows.map(function (childRow) {
      var fieldMarkMap = {};
      runtime.childFieldList.forEach(function (fieldMeta) {
        fieldMarkMap[fieldMeta.fieldName] = getChildRowFieldMark(runtime, fieldMeta.fieldId, childRow.rowIndex);
      });
      return {
        key: String(childRow.rowIndex),
        __rowIndex: String(childRow.rowIndex),
        __fieldMarkMap: fieldMarkMap
      };
    });
  }

  // 移动端辅助:生成移动端网格布局,刷新合计行并展示字段级校验错误。
  function buildMobileGridTemplate(runtime) {
    var columnList = [];
    if (runtime.selectionEnabled) {
      columnList.push(normalizeCssSize(runtime.selectionColumnWidth || 28) || '28px');
    }
    runtime.childFieldList.forEach(function (fieldMeta) {
      columnList.push(fieldMeta.columnWidth || 'minmax(96px,1fr)');
    });
    return columnList.join(' ');
  }

  function updateMobileSummaryNode(runtime, parentRowIndex) {
    var rootNode;
    var parentKey;
    var childRows;
    var summaryInfo;
    var sumNode;
    var expectedNode;
    var statusNode;
    if (!runtime || runtime.pageType !== 'mobile') {
      return;
    }
    rootNode = document.querySelector(
      '[' + ROOT_ATTR + '="' + escapeSelectorValue(runtime.runtimeKey) + '"][data-parent-row="' + escapeSelectorValue(parentRowIndex) + '"]'
    );
    if (!rootNode) {
      return;
    }
    parentKey = ensureParentLinkValue(runtime, parentRowIndex);
    childRows = buildChildRowsByParent(runtime)[parentKey] || [];
    summaryInfo = calculateSummary(childRows, runtime);
    sumNode = rootNode.querySelector('[data-ns-mobile-summary="sum"]');
    expectedNode = rootNode.querySelector('[data-ns-mobile-summary="expected"]');
    statusNode = rootNode.querySelector('[data-ns-mobile-summary="status"]');
    if (sumNode) {
      sumNode.textContent = '当前汇总:' + appendSuffixText(formatNumber(summaryInfo.sum, 2), runtime.summaryFieldSuffixText);
    }
    if (expectedNode) {
      expectedNode.textContent = '目标值:' + appendSuffixText(formatNumber(summaryInfo.expected, 2), runtime.summaryFieldSuffixText);
    }
    if (statusNode) {
      statusNode.textContent = summaryInfo.passed ? '校验通过' : '校验未通过';
      statusNode.className = summaryInfo.passed
        ? 'ns-nested-panel__badge ns-nested-panel__badge--ok'
        : 'ns-nested-panel__badge ns-nested-panel__badge--error';
    }
  }

  function scheduleMobileSummaryUpdate(runtime, parentRowIndex) {
    if (!runtime || runtime.pageType !== 'mobile') {
      return;
    }
    [0, 80].forEach(function (delay) {
      setTimeout(function () {
        updateMobileSummaryNode(runtime, parentRowIndex);
      }, delay);
    });
  }

  function getMobileFieldCell(runtime, parentRowIndex, childRowIndex, fieldName) {
    var rootNode;
    if (!runtime || runtime.pageType !== 'mobile') {
      return null;
    }
    rootNode = document.querySelector(
      '[' + ROOT_ATTR + '="' + escapeSelectorValue(runtime.runtimeKey) + '"][data-parent-row="' + escapeSelectorValue(parentRowIndex) + '"]'
    );
    if (!rootNode) {
      return null;
    }
    return rootNode.querySelector(
      '[data-ns-child-row-index="' + escapeSelectorValue(childRowIndex) + '"][data-ns-child-field-name="' + escapeSelectorValue(fieldName) + '"]'
    );
  }

  function clearMobileValidationErrors(runtime) {
    var selector;
    if (!runtime || runtime.pageType !== 'mobile') {
      return;
    }
    selector = '[' + ROOT_ATTR + '="' + escapeSelectorValue(runtime.runtimeKey) + '"]';
    queryAllSafe(selector).forEach(function (rootNode) {
      queryAllSafe('.ns-nested-mobile-panel__cell--error', rootNode).forEach(function (cellNode) {
        cellNode.classList.remove('ns-nested-mobile-panel__cell--error');
      });
      queryAllSafe('.ns-nested-mobile-panel__error', rootNode).forEach(function (errorNode) {
        if (errorNode.parentNode) {
          errorNode.parentNode.removeChild(errorNode);
        }
      });
    });
  }

  function clearMobileFieldValidationError(runtime, parentRowIndex, childRowIndex, fieldName) {
    var cellNode = getMobileFieldCell(runtime, parentRowIndex, childRowIndex, fieldName);
    if (!cellNode) {
      return;
    }
    cellNode.classList.remove('ns-nested-mobile-panel__cell--error');
    queryAllSafe('.ns-nested-mobile-panel__error', cellNode).forEach(function (errorNode) {
      if (errorNode.parentNode) {
        errorNode.parentNode.removeChild(errorNode);
      }
    });
  }

  function updateMobileRequiredMark(runtime, parentRowIndex, childRowIndex, fieldName, value) {
    var cellNode = getMobileFieldCell(runtime, parentRowIndex, childRowIndex, fieldName);
    var markNode;
    if (!cellNode) {
      return;
    }
    markNode = cellNode.querySelector('.ns-nested-panel__required-mark');
    if (!markNode) {
      return;
    }
    markNode.style.display = normalizeValue(value) ? 'none' : 'inline-block';
  }

  function showMobileFieldValidationError(runtime, parentRowIndex, childRowIndex, fieldName, message) {
    var cellNode = getMobileFieldCell(runtime, parentRowIndex, childRowIndex, fieldName);
    var errorNode;
    var inputNode;
    if (!cellNode) {
      return false;
    }
    clearMobileFieldValidationError(runtime, parentRowIndex, childRowIndex, fieldName);
    cellNode.classList.add('ns-nested-mobile-panel__cell--error');
    errorNode = document.createElement('div');
    errorNode.className = 'ns-nested-mobile-panel__error';
    errorNode.textContent = message;
    cellNode.appendChild(errorNode);
    safeCall(function () {
      cellNode.scrollIntoView({
        block: 'center',
        inline: 'center',
        behavior: 'smooth'
      });
    }, null);
    inputNode = cellNode.querySelector('input,textarea,select');
    if (inputNode && typeof inputNode.focus === 'function') {
      setTimeout(function () {
        safeCall(function () {
          inputNode.focus();
        }, null);
      }, 260);
    }
    return true;
  }

  // 新增与删除:新增子行后补关联字段、默认值和唯一 key,删除时同步真实明细表。
  function initializeChildRow(runtime, parentRowIndex, childRowIndex) {
    var parentLinkValue;
    if (!childRowIndex) {
      return false;
    }
    parentLinkValue = ensureParentLinkValue(runtime, parentRowIndex);
    setFieldValueSafe(
      getChildRowFieldMark(runtime, runtime.childLinkFieldId, childRowIndex),
      parentLinkValue
    );
    if (runtime.childRowKeyFieldId) {
      ensureChildRowKeyValue(runtime, childRowIndex);
    }
    runtime.childFieldList.forEach(function (fieldMeta) {
      if (fieldMeta.defaultValue !== undefined && fieldMeta.defaultValue !== null && fieldMeta.defaultValue !== '') {
        setFieldValueSafe(
          getChildRowFieldMark(runtime, fieldMeta.fieldId, childRowIndex),
          fieldMeta.defaultValue
        );
      }
    });
    setSelectedChildRows(runtime, parentRowIndex, []);
    applyFormulaRules(runtime);
    log('child row initialized', {
      runtimeKey: runtime.runtimeKey,
      nodeId: runtime.nodeId,
      parentRowIndex: parentRowIndex,
      childRowIndex: childRowIndex,
      childRowCount: getAllDetailRowIndexList(runtime.childDetailTableMark).length
    });
    renderRuntime(runtime);
    scheduleRefresh(runtime, 20);
    scheduleMobileRenderRecovery(runtime);
    return true;
  }

  function finalizePendingChildRow(runtime, parentRowIndex, beforeList, attempt) {
    var childRowIndex = findNewDetailRowIndex(runtime.childDetailTableMark, beforeList);
    if (childRowIndex) {
      log('child row finalized', {
        runtimeKey: runtime.runtimeKey,
        nodeId: runtime.nodeId,
        parentRowIndex: parentRowIndex,
        childRowIndex: childRowIndex,
        attempt: attempt
      });
      initializeChildRow(runtime, parentRowIndex, childRowIndex);
      return;
    }
    if (attempt >= 6) {
      log('child row finalize failed', {
        runtimeKey: runtime.runtimeKey,
        nodeId: runtime.nodeId,
        parentRowIndex: parentRowIndex,
        beforeList: beforeList,
        currentList: getAllDetailRowIndexList(runtime.childDetailTableMark)
      });
      showBlockMessage('新增隐藏二级明细行失败,请检查当前流程页面是否已完成加载。');
      return;
    }
    setTimeout(function () {
      finalizePendingChildRow(runtime, parentRowIndex, beforeList, attempt + 1);
    }, [30, 60, 120, 200, 320, 500][attempt] || 200);
  }

  function deleteSelectedChildRows(runtime, parentRowIndex) {
    var selectedRows = getSelectedChildRows(runtime, parentRowIndex);
    if (!selectedRows.length) {
      return;
    }
    log('child delete click', {
      runtimeKey: runtime.runtimeKey,
      nodeId: runtime.nodeId,
      parentRowIndex: parentRowIndex,
      selectedRows: selectedRows
    });
    selectedRows.forEach(function (childRowIndex) {
      delDetailRowSafe(runtime.childDetailTableMark, childRowIndex);
    });
    setSelectedChildRows(runtime, parentRowIndex, []);
    renderRuntime(runtime);
    scheduleRefresh(runtime, 20);
    scheduleMobileRenderRecovery(runtime);
  }

  function addChildRowForParent(runtime, parentRowIndex) {
    var beforeList = getAllDetailRowIndexList(runtime.childDetailTableMark);
    var childRowIndex = addDetailRowSafe(runtime.childDetailTableMark);
    var currentList = getAllDetailRowIndexList(runtime.childDetailTableMark);

    log('child add click', {
      runtimeKey: runtime.runtimeKey,
      nodeId: runtime.nodeId,
      parentRowIndex: parentRowIndex,
      beforeList: beforeList,
      addDetailRowResult: childRowIndex,
      currentList: currentList
    });

    if (!childRowIndex && !currentList.length) {
      showBlockMessage('新增隐藏二级明细行失败,请检查当前流程页面是否已完成加载。');
      return;
    }

    if (childRowIndex && beforeList.indexOf(String(childRowIndex)) < 0) {
      initializeChildRow(runtime, parentRowIndex, childRowIndex);
      return;
    }

    finalizePendingChildRow(runtime, parentRowIndex, beforeList, 0);
  }

  function getChildFieldWrapperProps(fieldMeta, fieldMark) {
    var style = {};
    var className = 'ns-nested-panel__field';
    var suffixText = normalizeValue(fieldMeta && fieldMeta.suffixText);
    var inputWidth = normalizeCssSize(fieldMeta && fieldMeta.inputWidth);
    var result;
    if (fieldMeta && fieldMeta.textColor) {
      className += ' ns-nested-panel__field--colored';
      style['--ns-field-text-color'] = fieldMeta.textColor;
    }
    if (inputWidth) {
      style['--ns-field-input-width'] = inputWidth;
    }
    if (suffixText) {
      className += ' ns-nested-panel__field--suffix';
    }
    result = {
      className: className,
      style: style
    };
    if (fieldMark) {
      result['data-ns-field-mark'] = fieldMark;
    }
    if (suffixText) {
      result['data-ns-suffix-text'] = suffixText;
    }
    return result;
  }

  // 字段渲染:把真实表单字段搬进自定义面板,必要时使用降级输入控件。
  function buildChildFieldNode(fieldMeta, fieldMark, React, runtime, parentRowIndex, childRowIndex) {
    var wrapperProps = getChildFieldWrapperProps(fieldMeta, fieldMark);
    var fieldContent;
    if (runtime && runtime.pageType === 'mobile') {
      fieldContent = buildFallbackFieldNode(fieldMeta, fieldMark, React, runtime, parentRowIndex, childRowIndex);
    } else if (window.WfForm && typeof window.WfForm.generateFieldContentComp === 'function') {
      fieldContent = window.WfForm.generateFieldContentComp(fieldMark);
    } else {
      fieldContent = buildFallbackFieldNode(fieldMeta, fieldMark, React, runtime, parentRowIndex, childRowIndex);
    }
    return React.createElement(
      'div',
      wrapperProps,
      fieldContent,
      runtime && runtime.pageType === 'mobile' && fieldMeta.required
        ? React.createElement('span', {
          className: 'wf-input-required ns-nested-panel__required-mark',
          style: {
            display: normalizeValue(getFieldValueSafe(fieldMark)) ? 'none' : 'inline-block'
          }
        }, '*')
        : null
    );
  }

  function shouldRenderFallbackTextarea(fieldMeta, runtime) {
    var fieldName = normalizeValue(fieldMeta && fieldMeta.fieldName).toLowerCase();
    if (fieldMeta && fieldMeta.type === 'textarea') {
      return true;
    }
    return !!(runtime && runtime.pageType === 'mobile' && (fieldName === 'action_name' || fieldName === 'changed_action_name'));
  }

  function buildFallbackFieldNode(fieldMeta, fieldMark, React, runtime, parentRowIndex, childRowIndex) {
    var value = getFieldValueSafe(fieldMark);
    var inputType = fieldMeta.type === 'number' ? 'number' : fieldMeta.type === 'date' ? 'date' : 'text';
    var commonProps = {
      defaultValue: value,
      placeholder: fieldMeta.placeholder || '',
      required: !!fieldMeta.required,
      onChange: function onFallbackChange(event) {
        var value = event && event.target ? event.target.value : '';
        setFieldValueSafe(fieldMark, value);
        updateMobileRequiredMark(runtime, parentRowIndex, childRowIndex, fieldMeta.fieldName, value);
        if (normalizeValue(value)) {
          clearMobileFieldValidationError(runtime, parentRowIndex, childRowIndex, fieldMeta.fieldName);
        }
        if (runtime) {
          applyFormulaRules(runtime);
          scheduleMobileSummaryUpdate(runtime, parentRowIndex);
        }
      },
      onBlur: function onFallbackBlur(event) {
        var value = event && event.target ? event.target.value : '';
        setFieldValueSafe(fieldMark, value);
        updateMobileRequiredMark(runtime, parentRowIndex, childRowIndex, fieldMeta.fieldName, value);
        if (normalizeValue(value)) {
          clearMobileFieldValidationError(runtime, parentRowIndex, childRowIndex, fieldMeta.fieldName);
        }
        if (runtime) {
          applyFormulaRules(runtime);
          scheduleMobileSummaryUpdate(runtime, parentRowIndex);
          scheduleRefresh(runtime, 30, true);
        }
      }
    };
    if (shouldRenderFallbackTextarea(fieldMeta, runtime)) {
      return React.createElement('textarea', commonProps);
    }
    commonProps.type = inputType;
    if (fieldMeta.type === 'date') {
      commonProps.onClick = function onFallbackDateClick(event) {
        if (event && typeof event.stopPropagation === 'function') {
          event.stopPropagation();
        }
        openDatePickerSafe(event && event.currentTarget);
      };
    }
    if (fieldMeta.type === 'number') {
      commonProps.step = fieldMeta.decimalScale ? String(1 / Math.pow(10, Number(fieldMeta.decimalScale))) : 'any';
    }
    return React.createElement('input', commonProps);
  }

  // 字段装饰:给只读和编辑态字段补单位后缀,处理泛微异步重渲染后的样式恢复。
  function getReadonlyTextNodeByFieldMark(fieldMark, hostCell) {
    var candidateIdList;
    var index;
    var targetNode;
    var editableDescendant;
    if (!fieldMark) {
      return null;
    }
    candidateIdList = [
      fieldMark + 'span',
      fieldMark + '_Ospan'
    ];
    for (index = 0; index < candidateIdList.length; index += 1) {
      targetNode = safeCall(function () {
        return document.getElementById(candidateIdList[index]);
      }, null);
      if (!targetNode) {
        continue;
      }
      if (hostCell && hostCell.contains && !hostCell.contains(targetNode)) {
        continue;
      }
      editableDescendant = safeCall(function () {
        return targetNode.querySelector('input:not([type="hidden"]), textarea, select');
      }, null);
      if (editableDescendant) {
        continue;
      }
      return targetNode;
    }
    return null;
  }

  function findFieldInputHost(hostCell, fieldMark) {
    var targetNode;
    var readonlyNode;
    if (!hostCell) {
      return null;
    }
    if (fieldMark) {
      readonlyNode = getReadonlyTextNodeByFieldMark(fieldMark, hostCell);
      if (readonlyNode && hostCell.contains(readonlyNode)) {
        return readonlyNode;
      }
      readonlyNode = safeCall(function () {
        return document.getElementById(fieldMark);
      }, null);
      if (readonlyNode && hostCell.contains(readonlyNode) && isEditableInputNode(readonlyNode)) {
        return readonlyNode;
      }
    }
    targetNode = hostCell.querySelector('input:not([type="hidden"])') ||
      hostCell.querySelector('textarea') ||
      hostCell.querySelector('span[id$="_Ospan"]') ||
      hostCell.querySelector('.browserName') ||
      hostCell.querySelector('.wea-browser-span') ||
      hostCell.querySelector('.wf-form-control') ||
      hostCell.querySelector('.ant-select-selection-selected-value');
    if (!targetNode) {
      return null;
    }
    return targetNode;
  }

  function isEditableInputNode(node) {
    var tagName;
    var typeName;
    if (!node || !node.tagName) {
      return false;
    }
    tagName = String(node.tagName).toUpperCase();
    if (tagName === 'TEXTAREA') {
      return true;
    }
    if (tagName !== 'INPUT') {
      return false;
    }
    typeName = normalizeValue(node.getAttribute('type') || node.type).toLowerCase();
    return typeName !== 'hidden';
  }

  function openDatePickerSafe(inputNode) {
    if (!inputNode) {
      return;
    }
    safeCall(function () {
      if (typeof inputNode.focus === 'function') {
        inputNode.focus();
      }
      if (typeof inputNode.showPicker === 'function') {
        inputNode.showPicker();
      }
    }, null);
  }

  function getSuffixHostNode(inputNode) {
    if (!inputNode) {
      return null;
    }
    return inputNode.parentElement || inputNode;
  }

  function ensureSuffixTextNode(hostNode, inputNode, suffixText) {
    var suffixNode;
    var nextNode;
    if (!hostNode || !inputNode) {
      return null;
    }
    suffixNode = hostNode.querySelector('.ns-suffix-input-text[data-ns-generated="1"]');
    if (!suffixNode) {
      suffixNode = document.createElement('span');
      suffixNode.className = 'ns-suffix-input-text';
      suffixNode.setAttribute('data-ns-generated', '1');
    }
    suffixNode.textContent = suffixText;
    nextNode = inputNode.nextSibling;
    if (suffixNode.parentNode !== hostNode || suffixNode.previousSibling !== inputNode) {
      if (suffixNode.parentNode) {
        suffixNode.parentNode.removeChild(suffixNode);
      }
      hostNode.insertBefore(suffixNode, nextNode);
    }
    return suffixNode;
  }

  function applyReadonlyTextSuffix(hostCell, fieldMark, suffixText, runtime) {
    var readonlyNode;
    var hostNode;
    var textValue;
    if (!hostCell || !fieldMark || !suffixText) {
      return false;
    }
    if (runtime && runtime.editableSuffixEnabled) {
      return false;
    }
    readonlyNode = getReadonlyTextNodeByFieldMark(fieldMark, hostCell);
    if (!readonlyNode) {
      return false;
    }
    hostNode = getSuffixHostNode(readonlyNode);
    if (hostNode) {
      queryAllSafe('.ns-suffix-input-text[data-ns-generated="1"]', hostNode).forEach(function (suffixNode) {
        if (suffixNode.parentNode) {
          suffixNode.parentNode.removeChild(suffixNode);
        }
      });
    }
    textValue = normalizeValue(readonlyNode.textContent || readonlyNode.innerText);
    if (!textValue) {
      return true;
    }
    if (textValue.slice(-suffixText.length) !== suffixText) {
      readonlyNode.textContent = textValue + suffixText;
    }
    return true;
  }

  function applyConfiguredReadonlyTextSuffixes(runtime, rootNode) {
    var rowIndexList;
    if (!runtime || runtime.editableSuffixEnabled) {
      return;
    }
    rowIndexList = getAllDetailRowIndexList(runtime.childDetailTableMark);
    runtime.childFieldList.forEach(function (fieldMeta) {
      var suffixText = normalizeValue(fieldMeta && fieldMeta.suffixText);
      if (!suffixText || !fieldMeta.fieldId) {
        return;
      }
      rowIndexList.forEach(function (rowIndex) {
        var fieldMark = getChildRowFieldMark(runtime, fieldMeta.fieldId, rowIndex);
        var readonlyNode = getReadonlyTextNodeByFieldMark(fieldMark, rootNode);
        var textValue;
        var hostNode;
        if (!readonlyNode) {
          return;
        }
        hostNode = getSuffixHostNode(readonlyNode);
        if (hostNode) {
          queryAllSafe('.ns-suffix-input-text[data-ns-generated="1"]', hostNode).forEach(function (suffixNode) {
            if (suffixNode.parentNode) {
              suffixNode.parentNode.removeChild(suffixNode);
            }
          });
        }
        textValue = normalizeValue(readonlyNode.textContent || readonlyNode.innerText);
        if (!textValue) {
          return;
        }
        if (textValue.slice(-suffixText.length) !== suffixText) {
          readonlyNode.textContent = textValue + suffixText;
        }
      });
    });
  }

  function decorateFieldHostWithSuffix(hostCell, suffixText, inputWidth, fieldMark, runtime) {
    var normalizedSuffix = normalizeValue(suffixText);
    var normalizedWidth = normalizeCssSize(inputWidth);
    var valueNode;
    var hostNode;
    if (!hostCell || !normalizedSuffix) {
      return;
    }
    if (applyReadonlyTextSuffix(hostCell, fieldMark, normalizedSuffix, runtime)) {
      return;
    }
    valueNode = findFieldInputHost(hostCell, fieldMark);
    hostNode = getSuffixHostNode(valueNode);
    if (!valueNode || !hostNode || !hostNode.classList) {
      return;
    }
    hostNode.classList.add('ns-suffix-field-host');
    if (normalizedWidth && isEditableInputNode(valueNode)) {
      hostNode.style.setProperty('--ns-field-input-width', normalizedWidth);
      valueNode.style.width = normalizedWidth;
    }
    if (isEditableInputNode(valueNode)) {
      valueNode.style.padding = '0';
    }
    hostNode.style.padding = '0';
    ensureSuffixTextNode(hostNode, valueNode, normalizedSuffix);
  }

  function applyChildFieldSuffixDecorators(rootNode, runtime) {
    if (!rootNode) {
      return;
    }
    queryAllSafe('.ns-nested-panel__field[data-ns-suffix-text]', rootNode).forEach(function (fieldNode) {
      decorateFieldHostWithSuffix(
        fieldNode,
        fieldNode.getAttribute('data-ns-suffix-text'),
        fieldNode.style.getPropertyValue('--ns-field-input-width'),
        fieldNode.getAttribute('data-ns-field-mark'),
        runtime
      );
    });
  }

  function scheduleChildFieldDecorators(rootNode, runtime) {
    [0, 80, 180, 360, 700, 1200].forEach(function (delay) {
      setTimeout(function () {
        if (!rootNode || !rootNode.parentNode) {
          return;
        }
        applyChildSelectionColumnWidth(runtime, rootNode);
        applyChildFieldSuffixDecorators(rootNode, runtime);
        applyConfiguredReadonlyTextSuffixes(runtime, rootNode);
      }, delay);
    });
  }

  function ensureChildDecoratorObserver(rootNode, runtime) {
    if (!rootNode || !window.MutationObserver || rootNode.__nsChildDecoratorObserver) {
      return;
    }
    rootNode.__nsChildDecoratorTimer = null;
    rootNode.__nsChildDecoratorObserver = new MutationObserver(function () {
      clearTimeout(rootNode.__nsChildDecoratorTimer);
      rootNode.__nsChildDecoratorTimer = setTimeout(function () {
        if (!rootNode || !rootNode.parentNode) {
          return;
        }
        applyChildSelectionColumnWidth(runtime, rootNode);
        applyChildFieldSuffixDecorators(rootNode, runtime);
        applyConfiguredReadonlyTextSuffixes(runtime, rootNode);
      }, 30);
    });
    rootNode.__nsChildDecoratorObserver.observe(rootNode, {
      childList: true,
      subtree: true
    });
  }

  function destroyChildDecoratorObserver(rootNode) {
    if (!rootNode) {
      return;
    }
    clearTimeout(rootNode.__nsChildDecoratorTimer);
    rootNode.__nsChildDecoratorTimer = null;
    if (rootNode.__nsChildDecoratorObserver) {
      rootNode.__nsChildDecoratorObserver.disconnect();
      rootNode.__nsChildDecoratorObserver = null;
    }
  }

  function cleanupSuffixHostNode(hostNode) {
    var inputNode;
    if (!hostNode || !hostNode.classList) {
      return;
    }
    hostNode.classList.remove('ns-suffix-field-host');
    hostNode.style.removeProperty('--ns-field-input-width');
    hostNode.style.padding = '';
    inputNode = hostNode.querySelector('input,textarea,.ant-input');
    if (inputNode) {
      inputNode.style.width = '';
      inputNode.style.padding = '';
    }
  }

  function cleanupNonRuntimeSuffixDecorators() {
    queryAllSafe('.ns-suffix-input-text[data-ns-generated="1"]').forEach(function (suffixNode) {
      var nestedRoot = suffixNode.closest('[' + ROOT_ATTR + ']');
      var hostNode = suffixNode.parentElement;
      if (nestedRoot) {
        return;
      }
      if (hostNode) {
        hostNode.removeChild(suffixNode);
        cleanupSuffixHostNode(hostNode);
      }
    });
  }

  // 降级渲染:组件库不可用时仍提供可操作的简易表格。
  function renderFallbackTable(runtime, dataSource, React) {
    var headerNode = runtime.hideChildTableHeader
      ? null
      : React.createElement(
        'thead',
        null,
        React.createElement(
          'tr',
          null,
          runtime.childFieldList.map(function (fieldMeta) {
            return React.createElement(
              'th',
              {
                key: fieldMeta.fieldName,
                style: fieldMeta.columnWidth ? { width: fieldMeta.columnWidth } : null
              },
              fieldMeta.label
            );
          })
        )
      );
    return React.createElement(
      'table',
      { className: 'ns-nested-panel__table' },
      headerNode,
      React.createElement(
        'tbody',
        null,
        dataSource.map(function (record) {
          return React.createElement(
            'tr',
            { key: record.key },
            runtime.childFieldList.map(function (fieldMeta) {
              return React.createElement(
                'td',
                {
                  key: fieldMeta.fieldName,
                  style: fieldMeta.columnWidth ? { width: fieldMeta.columnWidth } : null
                },
                buildChildFieldNode(fieldMeta, record.__fieldMarkMap[fieldMeta.fieldName], React)
              );
            })
          );
        })
      )
    );
  }

  // 移动端渲染:用列表面板展示二级明细,包含新增、删除、勾选、字段编辑和汇总。
  function renderMobileRuntimePanel(runtime, parentRowIndex, childRows, summaryInfo, rootNode) {
    var reactEnv = getReactEnv();
    var React = reactEnv.React;
    var ReactDOM = reactEnv.ReactDOM;
    var Provider = reactEnv.Provider;
    var globalStore;
    var layoutStore;
    var selectedRows;
    var dataSource;
    var toolbarNode;
    var contentNode;
    var summaryNode;
    var panelNode;

    if (!React || !ReactDOM || !window.WfForm) {
      return;
    }

    globalStore = safeCall(function () {
      return window.WfForm.getGlobalStore ? window.WfForm.getGlobalStore() : null;
    }, null);
    layoutStore = getLayoutStoreSafe();
    selectedRows = runtime.selectionEnabled ? getSelectedChildRows(runtime, parentRowIndex) : [];
    dataSource = buildChildDataSource(runtime, childRows);

    toolbarNode = React.createElement(
      'div',
      { className: 'ns-nested-mobile-panel__toolbar' },
      React.createElement(
        'button',
        {
          type: 'button',
          className: 'ns-nested-panel__button ns-nested-panel__button--icon',
          disabled: !runtime.actionEnabled,
          onClick: function onMobileAdd() {
            if (!runtime.actionEnabled) {
              return;
            }
            addChildRowForParent(runtime, parentRowIndex);
          }
        },
        '+'
      ),
      React.createElement(
        'button',
        {
          type: 'button',
          className: 'ns-nested-panel__button ns-nested-panel__button--ghost ns-nested-panel__button--icon',
          disabled: !runtime.actionEnabled || !selectedRows.length,
          onClick: function onMobileDelete() {
            if (!runtime.actionEnabled) {
              return;
            }
            deleteSelectedChildRows(runtime, parentRowIndex);
          }
        },
        '-'
      )
    );

    if (dataSource.length) {
      contentNode = React.createElement(
        'div',
        { className: 'ns-nested-mobile-panel__list' },
        dataSource.map(function (record) {
          var rowIndex = record.__rowIndex;
          var checked = selectedRows.indexOf(rowIndex) >= 0;
          return React.createElement(
            'div',
            {
              key: record.key,
              className: 'ns-nested-mobile-panel__row',
              style: {
                gridTemplateColumns: buildMobileGridTemplate(runtime)
              }
            },
            runtime.selectionEnabled
              ? React.createElement(
                'div',
                { className: 'ns-nested-mobile-panel__select-cell' },
                React.createElement('input', {
                  type: 'checkbox',
                  disabled: !runtime.selectionEnabled,
                  checked: checked,
                  onChange: function onMobileSelect(event) {
                    var nextSelectedRows = selectedRows.slice();
                    var selectedIndex = nextSelectedRows.indexOf(rowIndex);
                    if (event && event.target && event.target.checked) {
                      if (selectedIndex < 0) {
                        nextSelectedRows.push(rowIndex);
                      }
                    } else if (selectedIndex >= 0) {
                      nextSelectedRows.splice(selectedIndex, 1);
                    }
                    setSelectedChildRows(runtime, parentRowIndex, nextSelectedRows);
                    renderRuntime(runtime);
                    scheduleRefresh(runtime, 0);
                  }
                })
              )
              : null,
            runtime.childFieldList.map(function (fieldMeta) {
              return React.createElement(
                'div',
                {
                  key: fieldMeta.fieldName,
                  className: 'ns-nested-mobile-panel__cell',
                  'data-ns-child-row-index': rowIndex,
                  'data-ns-child-field-name': fieldMeta.fieldName,
                  onClick: function onMobileFieldCellClick(event) {
                    var inputNode;
                    if (fieldMeta.type !== 'date') {
                      return;
                    }
                    inputNode = event.currentTarget.querySelector('input[type="date"]');
                    openDatePickerSafe(inputNode);
                  }
                },
                React.createElement(
                  'div',
                  { className: 'ns-nested-mobile-panel__control' },
                  buildChildFieldNode(fieldMeta, record.__fieldMarkMap[fieldMeta.fieldName], React, runtime, parentRowIndex, rowIndex)
                )
              );
            })
          );
        })
      );
    } else {
      contentNode = React.createElement(
        'div',
        { className: 'ns-nested-mobile-panel__empty' },
        '当前主行下还没有子明细,点击右上角加号直接新增。'
      );
    }

    summaryNode = runtime.summaryFieldId && summaryInfo.expected !== null && summaryInfo.expected !== undefined && summaryInfo.expected !== ''
      ? React.createElement(
        'div',
        { className: 'ns-nested-mobile-panel__summary' },
        React.createElement('span', { 'data-ns-mobile-summary': 'sum' }, '当前汇总:' + appendSuffixText(formatNumber(summaryInfo.sum, 2), runtime.summaryFieldSuffixText)),
        React.createElement('span', { 'data-ns-mobile-summary': 'expected' }, '目标值:' + appendSuffixText(formatNumber(summaryInfo.expected, 2), runtime.summaryFieldSuffixText)),
        React.createElement(
          'span',
          {
            'data-ns-mobile-summary': 'status',
            className: summaryInfo.passed
              ? 'ns-nested-panel__badge ns-nested-panel__badge--ok'
              : 'ns-nested-panel__badge ns-nested-panel__badge--error'
          },
          summaryInfo.passed ? '校验通过' : '校验未通过'
        )
      )
      : null;

    panelNode = React.createElement(
      'div',
      { className: 'ns-nested-mobile-panel' },
      toolbarNode,
      contentNode,
      summaryNode
    );

    if (Provider && globalStore) {
      panelNode = React.createElement(
        Provider,
        buildProviderStoreProps(globalStore, layoutStore),
        panelNode
      );
    }

    ReactDOM.render(panelNode, rootNode);
    ensureChildDecoratorObserver(rootNode, runtime);
    scheduleChildFieldDecorators(rootNode, runtime);
  }

  // PC 端渲染:用表格面板展示二级明细,保留真实字段承载数据。
  function renderRuntimePanel(runtime, parentRowIndex, childRows, summaryInfo, rootNode) {
    if (runtime && runtime.pageType === 'mobile') {
      renderMobileRuntimePanel(runtime, parentRowIndex, childRows, summaryInfo, rootNode);
      return;
    }

    var reactEnv = getReactEnv();
    var React = reactEnv.React;
    var ReactDOM = reactEnv.ReactDOM;
    var Provider = reactEnv.Provider;
    var WeaTable = reactEnv.WeaTable;
    var Button = reactEnv.Button;
    var globalStore;
    var layoutStore;
    var selectedRows;
    var dataSource;
    var columns;
    var rowSelection;
    var tableNode;
    var toolbarNode;
    var summaryNode;
    var contentNode;
    var panelNode;
    var tableWrapStyle;

    if (!React || !ReactDOM || !window.WfForm) {
      return;
    }

    globalStore = safeCall(function () {
      return window.WfForm.getGlobalStore ? window.WfForm.getGlobalStore() : null;
    }, null);
    layoutStore = getLayoutStoreSafe();
    selectedRows = runtime.selectionEnabled ? getSelectedChildRows(runtime, parentRowIndex) : [];
    dataSource = buildChildDataSource(runtime, childRows);
    tableWrapStyle = {
      '--ns-selection-column-width': runtime.selectionColumnWidth
    };
    columns = runtime.childFieldList.map(function (fieldMeta) {
      return {
        title: fieldMeta.label,
        dataIndex: fieldMeta.fieldName,
        key: fieldMeta.fieldName,
        width: fieldMeta.columnWidth || undefined,
        render: function renderFieldCell(value, record) {
          return buildChildFieldNode(fieldMeta, record.__fieldMarkMap[fieldMeta.fieldName], React);
        }
      };
    });
    rowSelection = childRows.length ? {
      columnWidth: runtime.selectionColumnWidth,
      selectedRowKeys: selectedRows,
      getCheckboxProps: function getCheckboxProps() {
        return {
          disabled: !runtime.selectionEnabled
        };
      },
      onChange: function onSelectionChange(selectedRowKeys) {
        log('child selection change', {
          runtimeKey: runtime.runtimeKey,
          nodeId: runtime.nodeId,
          parentRowIndex: parentRowIndex,
          selectionEnabled: runtime.selectionEnabled,
          selectedRowKeys: selectedRowKeys
        });
        if (!runtime.selectionEnabled) {
          log('child selection blocked', {
            runtimeKey: runtime.runtimeKey,
            nodeId: runtime.nodeId,
            parentRowIndex: parentRowIndex
          });
          return;
        }
        setSelectedChildRows(runtime, parentRowIndex, selectedRowKeys);
        renderRuntime(runtime);
        scheduleRefresh(runtime, 0);
      }
    } : null;
    if (Button) {
      toolbarNode = React.createElement(
        'div',
        { className: 'ns-nested-panel__toolbar ns-nested-panel__toolbar--plain' },
        React.createElement(
          'div',
          { className: 'ns-nested-panel__toolbar-actions' },
          React.createElement(Button, {
            type: 'primary',
            size: 'small',
            icon: 'plus',
            className: 'ns-nested-panel__button--icon',
            disabled: !runtime.actionEnabled,
            onClick: function onAdd() {
              if (!runtime.actionEnabled) {
                return;
              }
              addChildRowForParent(runtime, parentRowIndex);
            }
          }),
          React.createElement(Button, {
            size: 'small',
            icon: 'minus',
            className: 'ns-nested-panel__button--icon',
            disabled: !runtime.actionEnabled || !selectedRows.length,
            onClick: function onDelete() {
              if (!runtime.actionEnabled) {
                return;
              }
              deleteSelectedChildRows(runtime, parentRowIndex);
            }
          })
        )
      );
    } else {
      toolbarNode = React.createElement(
        'div',
        { className: 'ns-nested-panel__toolbar ns-nested-panel__toolbar--plain' },
        React.createElement(
          'div',
          { className: 'ns-nested-panel__toolbar-actions' },
          React.createElement(
            'button',
            {
              type: 'button',
              className: 'ns-nested-panel__button ns-nested-panel__button--icon',
              disabled: !runtime.actionEnabled,
              onClick: function onAddFallback() {
                if (!runtime.actionEnabled) {
                  return;
                }
                addChildRowForParent(runtime, parentRowIndex);
              }
            },
            '+'
          ),
          React.createElement(
            'button',
            {
              type: 'button',
              className: 'ns-nested-panel__button ns-nested-panel__button--ghost ns-nested-panel__button--icon',
              disabled: !runtime.actionEnabled || !selectedRows.length,
              onClick: function onDeleteFallback() {
                if (!runtime.actionEnabled) {
                  return;
                }
                deleteSelectedChildRows(runtime, parentRowIndex);
              }
            },
            '-'
          )
        )
      );
    }

    if (childRows.length) {
      tableNode = WeaTable
        ? React.createElement(WeaTable, {
          columns: columns,
          dataSource: dataSource,
          rowKey: 'key',
          pagination: false,
          rowSelection: rowSelection,
          showHeader: !runtime.hideChildTableHeader,
          bordered: true,
          size: 'small'
        })
        : renderFallbackTable(runtime, dataSource, React);
      contentNode = React.createElement(
        'div',
        {
          className: 'ns-nested-panel__table-wrap',
          style: tableWrapStyle
        },
        tableNode
      );
    } else {
      contentNode = React.createElement(
        'div',
        { className: 'ns-nested-panel__empty' },
        '当前主行下还没有子明细,点击右上角加号直接新增。'
      );
    }

    summaryNode = runtime.summaryFieldId && summaryInfo.expected !== null && summaryInfo.expected !== undefined && summaryInfo.expected !== ''
      ? React.createElement(
        'div',
        { className: 'ns-nested-panel__summary' },
        React.createElement('span', null, '当前汇总:' + appendSuffixText(formatNumber(summaryInfo.sum, 2), runtime.summaryFieldSuffixText)),
        React.createElement('span', null, '目标值:' + appendSuffixText(formatNumber(summaryInfo.expected, 2), runtime.summaryFieldSuffixText)),
        React.createElement(
          'span',
          {
            className: summaryInfo.passed
              ? 'ns-nested-panel__badge ns-nested-panel__badge--ok'
              : 'ns-nested-panel__badge ns-nested-panel__badge--error'
          },
          summaryInfo.passed ? '校验通过' : '校验未通过'
        )
      )
      : null;

    panelNode = React.createElement(
      'div',
      { className: 'ns-nested-panel ns-nested-panel--embedded' },
      toolbarNode,
      contentNode,
      summaryNode
    );

    if (Provider && globalStore) {
      panelNode = React.createElement(
        Provider,
        buildProviderStoreProps(globalStore, layoutStore),
        panelNode
      );
    }

    ReactDOM.render(panelNode, rootNode);
    ensureChildDecoratorObserver(rootNode, runtime);
    scheduleChildFieldDecorators(rootNode, runtime);
  }

  // 面板挂载:在一级明细占位字段中创建根节点,并清理已经失效的面板。
  function cleanupStaleRoots(runtime, currentParentRowList) {
    var currentMap = {};
    var selector = '[' + ROOT_ATTR + '="' + escapeSelectorValue(runtime.runtimeKey) + '"]';
    var reactDOM = window.ReactDOM;

    currentParentRowList.forEach(function (rowIndex) {
      currentMap[String(rowIndex)] = true;
    });

    queryAllSafe(selector).forEach(function (rootNode) {
      var rowIndex = normalizeValue(rootNode.getAttribute('data-parent-row'));
      if (!currentMap[rowIndex] && rootNode.parentNode) {
        destroyChildDecoratorObserver(rootNode);
        if (reactDOM && typeof reactDOM.unmountComponentAtNode === 'function') {
          safeCall(function () {
            reactDOM.unmountComponentAtNode(rootNode);
          }, null);
        }
        rootNode.parentNode.removeChild(rootNode);
      }
    });
    Object.keys(runtime.selectedRowMap || {}).forEach(function (rowIndex) {
      if (!currentMap[String(rowIndex)]) {
        delete runtime.selectedRowMap[rowIndex];
      }
    });
  }

  function ensurePanelRoot(runtime, parentRowIndex) {
    var fieldMarkWithRow = runtime.renderFieldId + '_' + parentRowIndex;
    var hostCell = findFieldValueTd(runtime.renderFieldName, fieldMarkWithRow);
    var selector;
    var rootNode;

    if (!hostCell) {
      return null;
    }
    selector = '[' + ROOT_ATTR + '="' + escapeSelectorValue(runtime.runtimeKey) + '"][data-parent-row="' + escapeSelectorValue(parentRowIndex) + '"]';
    rootNode = hostCell.querySelector(selector);
    if (!rootNode) {
      rootNode = document.createElement('div');
      rootNode.setAttribute(ROOT_ATTR, runtime.runtimeKey);
      rootNode.setAttribute(ROOT_KEY_ATTR, runtime.runtimeKey);
      rootNode.setAttribute('data-parent-row', parentRowIndex);
      hostCell.appendChild(rootNode);
    }
    if (runtime.hideRenderFieldEditor) {
      hideOriginalHostChildren(hostCell, rootNode);
    }
    return rootNode;
  }

  // 主刷新流程:同步数据、执行公式、按父行分组二级明细,然后重新渲染面板。
  function renderRuntime(runtime) {
    var parentRowList = getAllDetailRowIndexList(runtime.parentDetailTableMark);
    var childRowsByParent;
    var missingRootCount = 0;

    cleanupNonRuntimeSuffixDecorators();
    cleanupOrphanChildren(runtime);
    applyFormulaRules(runtime);
    childRowsByParent = buildChildRowsByParent(runtime);
    disableNativeRequiredForRuntime(runtime);
    log('render runtime', {
      runtimeKey: runtime.runtimeKey,
      nodeId: runtime.nodeId,
      parentRowCount: parentRowList.length,
      childRowCount: getAllDetailRowIndexList(runtime.childDetailTableMark).length,
      selectedRowMap: runtime.selectedRowMap
    });

    parentRowList.forEach(function (parentRowIndex) {
      var parentKey = ensureParentLinkValue(runtime, parentRowIndex);
      var childRows = childRowsByParent[parentKey] || [];
      var summaryInfo = calculateSummary(childRows, runtime);
      var rootNode = ensurePanelRoot(runtime, parentRowIndex);
      if (!rootNode) {
        missingRootCount += 1;
        log('panel root missing', {
          runtimeKey: runtime.runtimeKey,
          nodeId: runtime.nodeId,
          parentRowIndex: parentRowIndex,
          renderFieldId: runtime.renderFieldId
        });
        return;
      }
      withInternalUpdate(function () {
        renderRuntimePanel(runtime, parentRowIndex, childRows, summaryInfo, rootNode);
      });
    });

    cleanupStaleRoots(runtime, parentRowList);
    if (missingRootCount > 0) {
      scheduleRefresh(runtime, 150, true);
    }
  }

  // 刷新调度:防抖并延迟刷新,避免字段连续变化或移动端异步加载导致重复渲染。
  function scheduleRefresh(runtime, delay, forceRender) {
    var state = getGlobalState();
    var timerKey = runtime.runtimeKey;
    clearTimeout(state.refreshTimerMap[timerKey]);
    state.refreshTimerMap[timerKey] = setTimeout(function () {
      if (!forceRender && isEditingInsideRuntime(runtime)) {
        log('refresh deferred while editing', {
          runtimeKey: runtime.runtimeKey,
          nodeId: runtime.nodeId,
          delay: delay || 60
        });
        scheduleRefresh(runtime, 120, false);
        return;
      }
      log('refresh execute', {
        runtimeKey: runtime.runtimeKey,
        nodeId: runtime.nodeId,
        delay: delay || 60,
        forceRender: !!forceRender
      });
      renderRuntime(runtime);
    }, delay || 60);
  }

  function scheduleMobileRenderRecovery(runtime) {
    if (!runtime || runtime.pageType !== 'mobile') {
      return;
    }
    [80, 220, 520, 1000].forEach(function (delay) {
      setTimeout(function () {
        if (isEditingInsideRuntime(runtime)) {
          return;
        }
        renderRuntime(runtime);
      }, delay);
    });
  }

  function validateRuntimeBeforeSubmit(runtime) {
    var parentRows = getAllDetailRowIndexList(runtime.parentDetailTableMark);
    var childRowsByParent = buildChildRowsByParent(runtime);
    var index;
    var childIndex;
    var fieldIndex;
    var parentRowIndex;
    var parentKey;
    var childRows;
    var childRow;
    var fieldMeta;
    var summaryInfo;
    var parentTitle;

    if (!runtime.validateOnSubmit) {
      return '';
    }
    clearMobileValidationErrors(runtime);

    for (index = 0; index < parentRows.length; index += 1) {
      parentRowIndex = parentRows[index];
      parentKey = ensureParentLinkValue(runtime, parentRowIndex);
      childRows = childRowsByParent[parentKey] || [];

      for (childIndex = 0; childIndex < childRows.length; childIndex += 1) {
        childRow = childRows[childIndex];
        for (fieldIndex = 0; fieldIndex < runtime.childFieldList.length; fieldIndex += 1) {
          fieldMeta = runtime.childFieldList[fieldIndex];
          if (!fieldMeta.required) {
            continue;
          }
          if (!normalizeValue(childRow.fieldValueMap[fieldMeta.fieldName])) {
            parentTitle = getParentRowTitle(runtime, parentRowIndex) || ('一级明细行 #' + parentRowIndex);
            if (showMobileFieldValidationError(
              runtime,
              parentRowIndex,
              childRow.rowIndex,
              fieldMeta.fieldName,
              '“' + fieldMeta.label + '”未填写'
            )) {
              return INLINE_VALIDATION_MESSAGE;
            }
            return parentTitle + ' 的子明细第 ' + (childIndex + 1) + ' 行“' + fieldMeta.label + '”不能为空';
          }
        }
      }

      if (runtime.summaryFieldId) {
        summaryInfo = calculateSummary(childRows, runtime);
        if (!summaryInfo.passed) {
          parentTitle = getParentRowTitle(runtime, parentRowIndex) || ('一级明细行 #' + parentRowIndex);
        return parentTitle + ' 的“' + (runtime.summaryFieldLabel || runtime.summaryFieldName) + '”汇总必须等于 ' +
          appendSuffixText(formatNumber(summaryInfo.expected, 2), runtime.summaryFieldSuffixText) +
          ',当前为 ' + appendSuffixText(formatNumber(summaryInfo.sum, 2), runtime.summaryFieldSuffixText);
        }
      }
    }
    return '';
  }

  function showBlockMessage(message) {
    if (window.WfForm && typeof window.WfForm.showConfirm === 'function') {
      window.WfForm.showConfirm(message, function () {});
      return;
    }
    window.alert(message);
  }

  function bindSubmitCheckOnce(baseInfo) {
    var state = getGlobalState();
    var guardKey = [normalizeValue(baseInfo.workflowid), normalizeValue(baseInfo.requestid), getCurrentNodeIdSafe(baseInfo)].join('|');
    var registered = false;

    if (state.submitGuardMap[guardKey] || !window.WfForm || typeof window.WfForm.registerCheckEvent !== 'function') {
      return;
    }

    function createHandler() {
      return function (callback) {
        var runtimeKeys = Object.keys(state.runtimeMap);
        var index;
        var runtime;
        var message;
        for (index = 0; index < runtimeKeys.length; index += 1) {
          runtime = state.runtimeMap[runtimeKeys[index]];
          if (
            normalizeValue(runtime.workflowId) !== normalizeValue(baseInfo.workflowid) ||
            normalizeValue(runtime.requestId) !== normalizeValue(baseInfo.requestid)
          ) {
            continue;
          }
          message = validateRuntimeBeforeSubmit(runtime);
          if (message) {
            if (message !== INLINE_VALIDATION_MESSAGE) {
              showBlockMessage(message);
            }
            return;
          }
        }
        callback();
      };
    }

    if (window.WfForm.OPER_SAVE !== undefined && window.WfForm.OPER_SAVE !== null) {
      window.WfForm.registerCheckEvent(window.WfForm.OPER_SAVE, createHandler());
      registered = true;
    }
    if (window.WfForm.OPER_SUBMIT !== undefined && window.WfForm.OPER_SUBMIT !== null) {
      window.WfForm.registerCheckEvent(window.WfForm.OPER_SUBMIT, createHandler());
      registered = true;
    }
    if (!registered) {
      return;
    }
    state.submitGuardMap[guardKey] = true;
  }

  function bindRuntimeEvents(runtime) {
    var state = getGlobalState();
    var bindingKey = runtime.runtimeKey;
    var boundFieldIdList = [];
    var childFormulaTokens = [];

    if (state.bindingMap[bindingKey]) {
      return;
    }

    if (runtime.summaryFieldId) {
      boundFieldIdList.push(runtime.summaryFieldId);
    }
    if (runtime.parentTitleFieldId) {
      boundFieldIdList.push(runtime.parentTitleFieldId);
    }
    boundFieldIdList.push(runtime.parentLinkFieldId);
    boundFieldIdList.push(runtime.childLinkFieldId);
    if (runtime.childRowKeyFieldId) {
      boundFieldIdList.push(runtime.childRowKeyFieldId);
    }
    runtime.formulaRules.forEach(function (formulaRule) {
      boundFieldIdList.push(formulaRule.targetFieldId);
      childFormulaTokens = childFormulaTokens.concat(parseFormulaTokens(formulaRule.expression));
    });

    childFormulaTokens.forEach(function (token) {
      var fieldName = token;
      if (token.indexOf('parent.') === 0) {
        fieldName = token.substring(7);
        boundFieldIdList.push(toDetailFieldId(fieldName, runtime.parentDetailTableMark));
        return;
      }
      if (token.indexOf('child.') === 0) {
        fieldName = token.substring(6);
      }
      boundFieldIdList.push(toDetailFieldId(fieldName, runtime.childDetailTableMark));
      boundFieldIdList.push(toDetailFieldId(fieldName, runtime.parentDetailTableMark));
    });

    uniqueArray(boundFieldIdList).forEach(function (fieldId) {
      if (!fieldId || !window.WfForm || typeof window.WfForm.bindDetailFieldChangeEvent !== 'function') {
        return;
      }
      window.WfForm.bindDetailFieldChangeEvent(fieldId, function () {
        if (isInternalUpdating()) {
          return;
        }
        scheduleRefresh(runtime, 40);
      });
    });

    state.bindingMap[bindingKey] = true;
  }

  function bindMutationObserver(runtime) {
    var state = getGlobalState();
    if (state.observerMap[runtime.runtimeKey] || !window.MutationObserver || !document.body) {
      return;
    }
    state.observerMap[runtime.runtimeKey] = new MutationObserver(function (mutationList) {
      var shouldRefresh = false;
      var nestedRootTouched = false;
      var parentRowCount;
      var childRowCount;
      if (isInternalUpdating()) {
        return;
      }
      mutationList.forEach(function (mutation) {
        var targetNode = mutation.target;
        var addedNodes = Array.prototype.slice.call(mutation.addedNodes || []);
        var removedNodes = Array.prototype.slice.call(mutation.removedNodes || []);
        var allInsideNestedRoot = isNodeInsideNestedRoot(targetNode);

        if (nodeContainsNestedRoot(targetNode)) {
          nestedRootTouched = true;
        }
        if (!allInsideNestedRoot) {
          shouldRefresh = true;
        }

        addedNodes.concat(removedNodes).forEach(function (node) {
          if (nodeContainsNestedRoot(node)) {
            nestedRootTouched = true;
          }
          if (!isNodeInsideNestedRoot(node)) {
            allInsideNestedRoot = false;
          }
        });
        if (!allInsideNestedRoot) {
          shouldRefresh = true;
        }
      });
      if (shouldRefresh) {
        if (nestedRootTouched) {
          log('nested root touched, force refresh requested', {
            runtimeKey: runtime.runtimeKey,
            nodeId: runtime.nodeId
          });
          scheduleRefresh(runtime, 80, true);
          scheduleMobileRenderRecovery(runtime);
          return;
        }
        parentRowCount = getAllDetailRowIndexList(runtime.parentDetailTableMark).length;
        childRowCount = getAllDetailRowIndexList(runtime.childDetailTableMark).length;
        if (
          parentRowCount === runtime.lastObservedParentRowCount &&
          childRowCount === runtime.lastObservedChildRowCount
        ) {
          return;
        }
        runtime.lastObservedParentRowCount = parentRowCount;
        runtime.lastObservedChildRowCount = childRowCount;
        log('mutation refresh requested', {
          runtimeKey: runtime.runtimeKey,
          nodeId: runtime.nodeId,
          parentRowCount: parentRowCount,
          childRowCount: childRowCount
        });
        scheduleRefresh(runtime, 80, true);
      }
    });
    state.observerMap[runtime.runtimeKey].observe(document.body, {
      childList: true,
      subtree: true
    });
  }

  function initPage(pageType, forceLocation) {
    var config = NESTED_DETAIL_ECODE_CONFIG;
    var baseInfo;
    var ruleIndex;
    var workflowRuleList = toArray(config.workflowRules);
    var workflowRule;
    var mappingIndex;
    var mappingList;
    var mapping;
    var runtime;
    var state = getGlobalState();

    if (!config || !config.enable || !window.WfForm) {
      return;
    }

    if (!forceLocation && !isPageLocationMatched(pageType, config)) {
      return;
    }

    baseInfo = getBaseInfoSafe();
    if (!baseInfo || !baseInfo.workflowid) {
      return;
    }

    injectStyleOnce();
    bindSubmitCheckOnce(baseInfo);

    for (ruleIndex = 0; ruleIndex < workflowRuleList.length; ruleIndex += 1) {
      workflowRule = workflowRuleList[ruleIndex];
      if (!isWorkflowMatched(workflowRule, baseInfo, pageType)) {
        continue;
      }
      mappingList = toArray(workflowRule.mappings);
      for (mappingIndex = 0; mappingIndex < mappingList.length; mappingIndex += 1) {
        mapping = mappingList[mappingIndex];
        runtime = buildRuntime(baseInfo, pageType, workflowRule, mapping);
        if (!runtime) {
          continue;
        }
        state.runtimeMap[runtime.runtimeKey] = runtime;
        bindRuntimeEvents(runtime);
        bindMutationObserver(runtime);
        scheduleRefresh(runtime, 20);
      }
    }
  }

  function scheduleInitPage(pageType, delay, attempt, forceLocation) {
    var state = getGlobalState();
    var normalizedPageType = pageType || detectPageType();
    var timerKey = 'init|' + normalizedPageType;
    var nextAttempt = attempt || 0;

    clearTimeout(state.initTimerMap[timerKey]);
    state.initTimerMap[timerKey] = setTimeout(function () {
      if (!forceLocation && !isPageLocationMatched(normalizedPageType, NESTED_DETAIL_ECODE_CONFIG)) {
        return;
      }
      initPage(normalizedPageType, forceLocation);
      if ((!window.WfForm || !getBaseInfoSafe()) && nextAttempt < 12) {
        scheduleInitPage(normalizedPageType, Math.min(200 + nextAttempt * 150, 1500), nextAttempt + 1, forceLocation);
      }
    }, delay || 30);
  }

  function bindGlobalInitListenersOnce() {
    var state = getGlobalState();
    if (state.uiBound) {
      return;
    }
    state.uiBound = true;

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', function () {
        scheduleInitPage(detectPageType(), 80, 0);
      });
    } else {
      scheduleInitPage(detectPageType(), 80, 0);
    }

    window.addEventListener('hashchange', function () {
      scheduleInitPage(detectPageType(), 80, 0);
    });
    window.addEventListener('popstate', function () {
      scheduleInitPage(detectPageType(), 80, 0);
    });
  }

  function registerMobileMount(comName) {
    if (!window.ecodeSDK) {
      return;
    }
    if (typeof window.ecodeSDK.overwriteMobilePropsFnQueueMapSet === 'function') {
      window.ecodeSDK.overwriteMobilePropsFnQueueMapSet(comName, {
        fn: function (newProps) {
          scheduleInitPage('mobile', 30, 0, true);
          return newProps;
        },
        order: 1999,
        desc: '一级明细嵌套二级明细 - 移动端流程页挂载'
      });
    }
    if (typeof window.ecodeSDK.overwriteMobileClassFnQueueMapSet === 'function') {
      window.ecodeSDK.overwriteMobileClassFnQueueMapSet(comName, {
        fn: function (Com, newProps) {
          scheduleInitPage('mobile', 30, 0, true);
          return {
            com: Com,
            props: newProps
          };
        },
        order: 1999,
        desc: '一级明细嵌套二级明细 - 移动端流程页挂载'
      });
    }
  }

  window.NS_NESTED_DETAIL_DEBUG_INFO = function () {
    var state = getGlobalState();
    var baseInfo = getBaseInfoSafe();
    var runtimeSnapshots = {};
    Object.keys(state.runtimeMap || {}).forEach(function (runtimeKey) {
      var runtime = state.runtimeMap[runtimeKey];
      runtimeSnapshots[runtimeKey] = {
        pageType: runtime.pageType,
        workflowId: runtime.workflowId,
        requestId: runtime.requestId,
        nodeId: runtime.nodeId,
        parentDetailTableMark: runtime.parentDetailTableMark,
        childDetailTableMark: runtime.childDetailTableMark,
        renderFieldId: runtime.renderFieldId,
        parentRows: getAllDetailRowIndexList(runtime.parentDetailTableMark),
        childRows: getAllDetailRowIndexList(runtime.childDetailTableMark),
        rootCount: queryAllSafe('[' + ROOT_ATTR + '="' + escapeSelectorValue(runtime.runtimeKey) + '"]').length
      };
    });
    return {
      href: window.location.href,
      hash: window.location.hash,
      pathname: window.location.pathname,
      userAgent: window.navigator && window.navigator.userAgent,
      detectedPageType: detectPageType(),
      desktopLocationMatched: isPageLocationMatched('desktop', NESTED_DETAIL_ECODE_CONFIG),
      mobileLocationMatched: isPageLocationMatched('mobile', NESTED_DETAIL_ECODE_CONFIG),
      hasEcodeSDK: !!window.ecodeSDK,
      hasMobilePropsHook: !!(window.ecodeSDK && window.ecodeSDK.overwriteMobilePropsFnQueueMapSet),
      hasMobileClassHook: !!(window.ecodeSDK && window.ecodeSDK.overwriteMobileClassFnQueueMapSet),
      hasWfForm: !!window.WfForm,
      baseInfo: baseInfo,
      runtimeKeys: Object.keys(state.runtimeMap || {}),
      bindingKeys: Object.keys(state.bindingMap || {}),
      observerKeys: Object.keys(state.observerMap || {}),
      runtimeSnapshots: runtimeSnapshots
    };
  };

  if (window.ecodeSDK && typeof window.ecodeSDK.overwritePropsFnQueueMapSet === 'function') {
    window.ecodeSDK.overwritePropsFnQueueMapSet('WeaReqTop', {
      fn: function (newProps) {
        scheduleInitPage('desktop', 30, 0);
        return newProps;
      },
      order: 1999,
      desc: '一级明细嵌套二级明细 - PC 流程页挂载'
    });
  }

  [
    'WeaReqTop',
    'WorkflowReq',
    'WorkflowForm',
    'ReqForm',
    'RequestForm',
    'RequestDetail',
    'TabPage',
    'ListView',
    'DetailList',
    'Form'
  ].forEach(registerMobileMount);

  bindGlobalInitListenersOnce();

})();

Related