apple před 18 hodinami
rodič
revize
e237fb3f37

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+# 本地 H5 转换说明文档目录不参与提交 by xu 2026-03-08
+docs/

+ 4 - 2
js/mp_request/request.js

@@ -157,19 +157,21 @@ const request = {
     if (options.loading === false) {
       loadingConfig = false;
     } else {
+      // 功能说明:H5 请求超时统一调到 1 分钟,避免审核/变动类慢接口过早超时 by xu 2026-03-09
       loadingConfig = {
         show: true,
         title: "加载中...",
         mask: true,
         delay: 300,
-        timeout: 10000,
+        timeout: 60000,
         ...(typeof options.loading === "object" ? options.loading : {}),
       };
     }
 
     // 解析请求配置
+    // 功能说明:H5 axios 请求超时统一调到 1 分钟,和 loading 超时提示保持一致 by xu 2026-03-09
     const requestConfig = {
-      timeout: 15000,
+      timeout: 60000,
       ...options.request,
     };
 

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1919 - 1608
js/mp_ss_components/mp-ss-components.js


+ 193 - 74
js/mp_utils/field-formatter.js

@@ -11,20 +11,26 @@
  * @param {Map} dictCache - 字典缓存
  * @param {Function} updateCallback - 更新回调函数
  */
-const getDictTranslation = async (cbName, value, cacheKey, dictCache, updateCallback) => {
+const getDictTranslation = async (
+  cbName,
+  value,
+  cacheKey,
+  dictCache,
+  updateCallback
+) => {
   try {
     console.log(`🔍 获取字典翻译 [${cbName}:${value}]`);
 
     // 使用 loadObjpOpt 接口调用字典
     const result = await request.post(
-      '/service?ssServ=loadObjpOpt&objectpickerdropdown1=1',
+      "/service?ssServ=loadObjpOpt&objectpickerdropdown1=1",
       {
         objectpickerparam: JSON.stringify({
           input: "false",
-          codebook: cbName
+          codebook: cbName,
         }),
-        objectpickertype: 2,      // 2表示获取要回显的一项
-        objectpickervalue: value  // 需回显的值
+        objectpickertype: 2, // 2表示获取要回显的一项
+        objectpickervalue: value, // 需回显的值
       },
       { loading: false, formData: true }
     );
@@ -60,30 +66,30 @@ const getDictTranslation = async (cbName, value, cacheKey, dictCache, updateCall
  */
 const formatFieldValue = (fieldObj, dictCache, getDictTranslationFn) => {
   if (!fieldObj || !fieldObj.field || fieldObj.value === undefined) {
-    return '';
+    return "";
   }
 
   const { field, value } = fieldObj;
-  
+
   // 空值处理
-  if (value === null || value === undefined || value === '') {
-    return '';
+  if (value === null || value === undefined || value === "") {
+    return "";
   }
 
   // 如果字段有字典配置,进行字典翻译
   if (field.cbName) {
     const cacheKey = `${field.cbName}_${value}`;
-    
+
     // 先从缓存中查找
     if (dictCache.has(cacheKey)) {
       return dictCache.get(cacheKey);
     }
-    
+
     // 缓存中没有,异步获取翻译
     if (getDictTranslationFn) {
       getDictTranslationFn(field.cbName, value, cacheKey, dictCache);
     }
-    
+
     // 返回原值作为临时显示
     return value;
   }
@@ -94,25 +100,47 @@ const formatFieldValue = (fieldObj, dictCache, getDictTranslationFn) => {
   }
 
   // 日期格式化(兼容旧逻辑)
-  if (field.type === 'date' || field.name.toLowerCase().includes('date') ||
-      field.name.toLowerCase().includes('time')) {
+  if (
+    field.type === "date" ||
+    field.name.toLowerCase().includes("date") ||
+    field.name.toLowerCase().includes("time")
+  ) {
     return h5FormatDate(value);
   }
 
   // 数字格式化
-  if (field.type === 'number' && typeof value === 'number') {
+  if (field.type === "number" && typeof value === "number") {
     return value.toLocaleString();
   }
 
   // 布尔值格式化
-  if (field.type === 'boolean' || typeof value === 'boolean') {
-    return value ? '是' : '否';
+  if (field.type === "boolean" || typeof value === "boolean") {
+    return value ? "是" : "否";
   }
 
   // 默认返回字符串
   return String(value);
 };
 
+/**
+ * 规范化日期字符串,兼容异常空白符和乱码分隔符
+ * @param {string|Date|number} value - 原始日期值
+ * @returns {string} 规范化后的日期字符串
+ */
+const normalizeDateInput = (value) => {
+  if (value === null || value === undefined) return "";
+
+  return (
+    String(value)
+      // 功能说明:兼容接口里被错误转码的窄空格(如  PM),避免标题时间无法格式化 by xu 2026-03-06
+      .replace(/ /g, " ")
+      .replace(/[\u00A0\u1680\u180E\u2000-\u200D\u202F\u205F\u3000]/g, " ")
+      .replace(/(\d)(AM|PM)$/i, "$1 $2")
+      .replace(/\s+/g, " ")
+      .trim()
+  );
+};
+
 /**
  * 解析特殊日期格式:Jul 4, 2025, 6:00:00 AM
  * @param {string} dateStr - 日期字符串
@@ -120,18 +148,32 @@ const formatFieldValue = (fieldObj, dictCache, getDictTranslationFn) => {
  */
 const parseSpecialDateFormat = (dateStr) => {
   try {
+    const normalizedDateStr = normalizeDateInput(dateStr);
+
     // 匹配格式:Jul 4, 2025, 6:00:00 AM
-    const regex = /^(\w{3})\s+(\d{1,2}),\s+(\d{4}),\s+(\d{1,2}):(\d{2}):(\d{2})\s+(AM|PM)$/;
-    const match = dateStr.match(regex);
+    const regex =
+      /^(\w{3})\s+(\d{1,2}),\s+(\d{4}),\s+(\d{1,2}):(\d{2}):(\d{2})\s+(AM|PM)$/i;
+    const match = normalizedDateStr.match(regex);
 
     if (!match) return null;
 
-    const [, monthStr, day, year, hour, minute, second, ampm] = match;
+    const [, monthStr, day, year, hour, minute, second, ampmRaw] = match;
+    const ampm = String(ampmRaw).toUpperCase();
 
     // 月份映射
     const months = {
-      'Jan': 0, 'Feb': 1, 'Mar': 2, 'Apr': 3, 'May': 4, 'Jun': 5,
-      'Jul': 6, 'Aug': 7, 'Sep': 8, 'Oct': 9, 'Nov': 10, 'Dec': 11
+      Jan: 0,
+      Feb: 1,
+      Mar: 2,
+      Apr: 3,
+      May: 4,
+      Jun: 5,
+      Jul: 6,
+      Aug: 7,
+      Sep: 8,
+      Oct: 9,
+      Nov: 10,
+      Dec: 11,
     };
 
     const month = months[monthStr];
@@ -139,9 +181,9 @@ const parseSpecialDateFormat = (dateStr) => {
 
     // 处理12小时制
     let hour24 = parseInt(hour);
-    if (ampm === 'PM' && hour24 !== 12) {
+    if (ampm === "PM" && hour24 !== 12) {
       hour24 += 12;
-    } else if (ampm === 'AM' && hour24 === 12) {
+    } else if (ampm === "AM" && hour24 === 12) {
       hour24 = 0;
     }
 
@@ -151,10 +193,10 @@ const parseSpecialDateFormat = (dateStr) => {
       day: parseInt(day),
       hour: hour24,
       minute: parseInt(minute),
-      second: parseInt(second)
+      second: parseInt(second),
     };
   } catch (error) {
-    console.warn('解析特殊日期格式失败:', dateStr, error);
+    console.warn("解析特殊日期格式失败:", dateStr, error);
     return null;
   }
 };
@@ -171,13 +213,13 @@ const formatDateObject = (dateObj, format) => {
   return format
     .replace(/YYYY/g, year)
     .replace(/yyyy/g, year)
-    .replace(/MM/g, String(month + 1).padStart(2, '0'))
-    .replace(/DD/g, String(day).padStart(2, '0'))
-    .replace(/dd/g, String(day).padStart(2, '0'))
-    .replace(/HH/g, String(hour).padStart(2, '0'))
-    .replace(/hh/g, String(hour).padStart(2, '0'))
-    .replace(/mm/g, String(minute).padStart(2, '0'))
-    .replace(/ss/g, String(second).padStart(2, '0'));
+    .replace(/MM/g, String(month + 1).padStart(2, "0"))
+    .replace(/DD/g, String(day).padStart(2, "0"))
+    .replace(/dd/g, String(day).padStart(2, "0"))
+    .replace(/HH/g, String(hour).padStart(2, "0"))
+    .replace(/hh/g, String(hour).padStart(2, "0"))
+    .replace(/mm/g, String(minute).padStart(2, "0"))
+    .replace(/ss/g, String(second).padStart(2, "0"));
 };
 
 /**
@@ -186,14 +228,20 @@ const formatDateObject = (dateObj, format) => {
  * @param {string} format - 格式,如 'YYYY-MM-DD HH:mm:ss'
  * @returns {string} 格式化后的日期
  */
-const h5FormatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
-  if (!date) return '';
+const h5FormatDate = (date, format = "YYYY-MM-DD HH:mm:ss") => {
+  if (!date) return "";
 
   try {
-    console.log(`🔧 h5FormatDate 开始处理:`, date, '目标格式:', format);
+    const normalizedDate = normalizeDateInput(date);
+    console.log(
+      `🔧 h5FormatDate 开始处理:`,
+      normalizedDate,
+      "目标格式:",
+      format
+    );
 
     // 手动解析特殊格式:Jul 4, 2025, 6:00:00 AM
-    const parseResult = parseSpecialDateFormat(date);
+    const parseResult = parseSpecialDateFormat(normalizedDate);
     if (parseResult) {
       console.log(`🔧 手动解析成功:`, parseResult);
       const result = formatDateObject(parseResult, format);
@@ -202,8 +250,8 @@ const h5FormatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
     }
 
     // 使用 Day.js 解析日期(更强大的日期解析能力)
-    if (typeof window !== 'undefined' && window.dayjs) {
-      const dayObj = window.dayjs(date);
+    if (typeof window !== "undefined" && window.dayjs) {
+      const dayObj = window.dayjs(normalizedDate);
       console.log(`🔧 使用 Day.js 解析:`, dayObj.isValid(), dayObj.toString());
 
       if (dayObj.isValid()) {
@@ -215,22 +263,22 @@ const h5FormatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
 
     // 降级到原生 Date(如果 Day.js 未加载)
     console.log(`🔧 Day.js 未加载,使用原生 Date`);
-    const d = new Date(date);
+    const d = new Date(normalizedDate);
 
-    console.log(`🔧 解析后的日期对象:`, d, '是否有效:', !isNaN(d.getTime()));
+    console.log(`🔧 解析后的日期对象:`, d, "是否有效:", !isNaN(d.getTime()));
 
     // 检查日期是否有效
     if (isNaN(d.getTime())) {
-      console.warn('❌ 无效日期格式:', date);
-      return date; // 无效日期返回原值
+      console.warn("❌ 无效日期格式:", normalizedDate);
+      return normalizedDate; // 无效日期返回原值
     }
 
     const year = d.getFullYear();
-    const month = String(d.getMonth() + 1).padStart(2, '0');
-    const day = String(d.getDate()).padStart(2, '0');
-    const hours = String(d.getHours()).padStart(2, '0');
-    const minutes = String(d.getMinutes()).padStart(2, '0');
-    const seconds = String(d.getSeconds()).padStart(2, '0');
+    const month = String(d.getMonth() + 1).padStart(2, "0");
+    const day = String(d.getDate()).padStart(2, "0");
+    const hours = String(d.getHours()).padStart(2, "0");
+    const minutes = String(d.getMinutes()).padStart(2, "0");
+    const seconds = String(d.getSeconds()).padStart(2, "0");
 
     // 支持多种格式模式
     let result = format
@@ -246,9 +294,8 @@ const h5FormatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
 
     console.log(`日期格式化: ${date} -> ${result} (格式: ${format})`);
     return result;
-
   } catch (error) {
-    console.error('日期格式化异常:', date, format, error);
+    console.error("日期格式化异常:", date, format, error);
     return date; // 异常时返回原值
   }
 };
@@ -261,27 +308,32 @@ const h5FormatDate = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
  */
 const formatFieldValueAsync = async (fieldData, dictCache) => {
   if (!fieldData || !fieldData.field) {
-    return fieldData?.value || '';
+    return fieldData?.value || "";
   }
 
   const { field, value } = fieldData;
 
   // 空值处理
-  if (value === null || value === undefined || value === '') {
-    return '';
+  if (value === null || value === undefined || value === "") {
+    return "";
   }
 
   // 优先处理时间格式化(如果字段有fmt属性)
   if (field.fmt) {
     try {
-      console.log(`🕐 格式化时间字段 [${field.name}]:`, value, '格式:', field.fmt);
+      console.log(
+        `🕐 格式化时间字段 [${field.name}]:`,
+        value,
+        "格式:",
+        field.fmt
+      );
       console.log(`🔧 调用 h5FormatDate 前...`);
       const formattedTime = h5FormatDate(value, field.fmt);
       console.log(`🔧 调用 h5FormatDate 后,结果:`, formattedTime);
       console.log(`✅ 时间格式化结果:`, formattedTime);
       return formattedTime;
     } catch (error) {
-      console.error('❌ 时间格式化失败:', field.name, value, error);
+      console.error("❌ 时间格式化失败:", field.name, value, error);
       // 格式化失败,返回原值
       return value;
     }
@@ -303,18 +355,23 @@ const formatFieldValueAsync = async (fieldData, dictCache) => {
         {
           objectpickerparam: JSON.stringify({
             input: "false",
-            codebook: field.cbName
+            codebook: field.cbName,
           }),
           objectpickertype: 2,
-          objectpickervalue: value
+          objectpickervalue: value,
         },
         {
           loading: false,
-          formData: true
+          formData: true,
         }
       );
 
-      if (result && result.data && result.data.result && result.data.result[value]) {
+      if (
+        result &&
+        result.data &&
+        result.data.result &&
+        result.data.result[value]
+      ) {
         const translatedValue = result.data.result[value];
         // 缓存结果
         if (dictCache) {
@@ -323,7 +380,7 @@ const formatFieldValueAsync = async (fieldData, dictCache) => {
         return translatedValue;
       }
     } catch (error) {
-      console.warn('字典转换失败:', field.cbName, value, error);
+      console.warn("字典转换失败:", field.cbName, value, error);
     }
 
     // 转换失败,返回原值
@@ -336,7 +393,7 @@ const formatFieldValueAsync = async (fieldData, dictCache) => {
   }
 
   // 数字格式化
-  if (field.type === 2 && typeof value === 'number') {
+  if (field.type === 2 && typeof value === "number") {
     return value.toLocaleString();
   }
 
@@ -366,24 +423,26 @@ const getDictOptions = async (cbName, dictCache) => {
       {
         objectpickerparam: JSON.stringify({
           input: "false",
-          codebook: cbName
+          codebook: cbName,
         }),
         objectpickertype: 1,
-        objectpickersearchAll: 1
+        objectpickersearchAll: 1,
       },
       {
         loading: false,
-        formData: true
+        formData: true,
       }
     );
 
     console.log(`字典选项 [${cbName}] API返回:`, result);
 
     if (result && result.data && result.data.result) {
-      const options = Object.entries(result.data.result).map(([value, label]) => ({
-        n: label,
-        v: value
-      }));
+      const options = Object.entries(result.data.result).map(
+        ([value, label]) => ({
+          n: label,
+          v: value,
+        })
+      );
 
       // 缓存结果
       if (dictCache) {
@@ -416,12 +475,44 @@ const formatObjectList = async (objectList, dictCache) => {
 
     // 格式化 first 字段
     if (item.first) {
-      formattedItem.firstDisplay = await formatFieldValueAsync(item.first, dictCache);
+      formattedItem.firstDisplay = await formatFieldValueAsync(
+        item.first,
+        dictCache
+      );
+    } else if (
+      item.title &&
+      typeof item.title === "object" &&
+      item.title.val !== undefined
+    ) {
+      // 功能说明:兼容 title={val,fmt} 结构,标题字段为日期时间时按 fmt 格式化,避免卡片标题直接显示原始时间串 by xu 2026-03-06
+      if (item.title.fmt) {
+        formattedItem.firstDisplay = h5FormatDate(
+          item.title.val,
+          item.title.fmt
+        );
+      } else {
+        formattedItem.firstDisplay =
+          item.title.val === undefined || item.title.val === null
+            ? ""
+            : String(item.title.val);
+      }
+    } else if (typeof item.title === "string") {
+      // 兼容 title 直出字符串结构 by xu 2026-03-03
+      formattedItem.firstDisplay = item.title;
     }
 
     // 格式化 second 字段
     if (item.second) {
-      formattedItem.secondDisplay = await formatFieldValueAsync(item.second, dictCache);
+      formattedItem.secondDisplay = await formatFieldValueAsync(
+        item.second,
+        dictCache
+      );
+    } else {
+      // 兼容 abs/desc/summary 结构(移动端接口常见) by xu 2026-03-03
+      const secondFallback = item.abs ?? item.desc ?? item.summary;
+      if (secondFallback !== undefined && secondFallback !== null) {
+        formattedItem.secondDisplay = String(secondFallback);
+      }
     }
 
     // 格式化 third 字段组
@@ -432,15 +523,43 @@ const formatObjectList = async (objectList, dictCache) => {
         const formattedGroup = [];
 
         for (const fieldData of group) {
-          const displayValue = await formatFieldValueAsync(fieldData, dictCache);
+          const displayValue = await formatFieldValueAsync(
+            fieldData,
+            dictCache
+          );
           formattedGroup.push({
             ...fieldData,
-            displayValue
+            displayValue,
           });
         }
 
         formattedItem.thirdDisplay.push(formattedGroup);
       }
+    } else if (Array.isArray(item.catList)) {
+      // 功能说明:兼容 catList 结构,若有 fmt 则按日期格式化,避免列表属性时间字段直接显示 Oracle 原始串 by xu 2026-03-06
+      const formattedCatList = [];
+
+      for (const catItem of item.catList) {
+        const fieldData = {
+          field: {
+            desc: String(catItem?.desc || ""),
+            fmt: catItem?.fmt || "",
+          },
+          value: catItem?.val,
+        };
+
+        const displayValue = await formatFieldValueAsync(fieldData, dictCache);
+
+        formattedCatList.push({
+          field: {
+            desc: String(catItem?.desc || ""),
+          },
+          value: catItem?.val,
+          displayValue,
+        });
+      }
+
+      formattedItem.thirdDisplay = [formattedCatList];
     }
 
     formattedList.push(formattedItem);
@@ -456,7 +575,7 @@ window.H5FieldFormatter = {
   formatFieldValueAsync,
   formatDate: h5FormatDate,
   getDictOptions,
-  formatObjectList
+  formatObjectList,
 };
 
 // 兼容性:也导出为全局函数
@@ -466,4 +585,4 @@ window.formatFieldValueAsync = formatFieldValueAsync;
 window.getDictOptions = getDictOptions;
 window.formatObjectList = formatObjectList;
 
-console.log('✅ H5字段格式化工具加载完成');
+console.log("✅ H5字段格式化工具加载完成");

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1009 - 873
page/login.html


+ 973 - 0
page/mp_addChk.html

@@ -0,0 +1,973 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>审核</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        height: 100vh;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+        box-sizing: border-box;
+      }
+
+      .frame-wrap {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+      }
+
+      .top-iframe,
+      .bottom-iframe {
+        width: 100%;
+        border: none;
+        display: block;
+        background: #fff;
+      }
+
+      .top-iframe {
+        flex: none;
+        height: 60%;
+      }
+
+      .bottom-iframe {
+        flex: none;
+        height: 40%;
+      }
+
+      .status-wrap {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 16px;
+        box-sizing: border-box;
+      }
+
+      .status-card {
+        width: 100%;
+        max-width: 520px;
+        background: #fff;
+        border-radius: 8px;
+        padding: 16px;
+        box-sizing: border-box;
+        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
+      }
+
+      .status-title {
+        font-size: 15px;
+        font-weight: 600;
+        color: #2d3748;
+      }
+
+      .status-text {
+        margin-top: 8px;
+        line-height: 1.6;
+        color: #4a5568;
+        font-size: 13px;
+        word-break: break-all;
+      }
+
+      .status-retry {
+        margin-top: 12px;
+        height: 34px;
+        padding: 0 14px;
+        border: none;
+        border-radius: 4px;
+        background: #4a5568;
+        color: #fff;
+        font-size: 13px;
+      }
+
+      .url-log-fab {
+        position: fixed;
+        right: 12px;
+        bottom: 72px;
+        z-index: 1200;
+        border: none;
+        border-radius: 16px;
+        background: rgba(36, 40, 53, 0.88);
+        color: #fff;
+        font-size: 12px;
+        line-height: 1;
+        padding: 8px 10px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
+      }
+
+      .url-log-panel {
+        position: fixed;
+        left: 12px;
+        right: 12px;
+        bottom: 116px;
+        z-index: 1200;
+        background: rgba(36, 40, 53, 0.96);
+        color: #fff;
+        border-radius: 8px;
+        padding: 10px;
+        max-height: 42vh;
+        overflow: auto;
+        box-sizing: border-box;
+      }
+
+      .url-log-panel__head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 8px;
+        font-size: 12px;
+      }
+
+      .url-log-panel__close {
+        border: none;
+        background: transparent;
+        color: #fff;
+        font-size: 14px;
+        line-height: 1;
+      }
+
+      .url-log-panel__body {
+        margin: 0;
+        white-space: pre-wrap;
+        word-break: break-all;
+        font-size: 12px;
+        line-height: 1.5;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <div class="frame-wrap" v-if="initStatus === 'ready'">
+        <iframe
+          ref="topIframe"
+          class="top-iframe"
+          :src="topIframeSrc"
+          frameborder="0"
+          :style="topIframeHeight > 0 ? { height: `${topIframeHeight}px` } : null"
+        ></iframe>
+        <iframe
+          ref="bottomIframe"
+          class="bottom-iframe"
+          :src="bottomIframeSrc"
+          frameborder="0"
+          :style="bottomIframeHeight > 0 ? { height: `${bottomIframeHeight}px` } : null"
+        ></iframe>
+      </div>
+
+      <div class="status-wrap" v-else>
+        <div class="status-card">
+          <div class="status-title">{{ initStatusTitle }}</div>
+          <div class="status-text">{{ initStatusMessage }}</div>
+          <div class="status-text" v-if="missingParams.length > 0">
+            缺少参数:{{ missingParams.join('、') }}
+          </div>
+          <button class="status-retry" type="button" @click="callApi">
+            重新加载
+          </button>
+        </div>
+      </div>
+
+      <ss-bottom
+        :buttons="bottomButtons"
+        :show-shyj="true"
+        @button-click="handleBottomAction"
+        :disabled="submitting"
+        v-if="initStatus === 'ready' && bottomButtons.length > 0"
+      ></ss-bottom>
+
+      <button class="url-log-fab" type="button" @click="showCurrentUrls">
+        URL
+      </button>
+      <div class="url-log-panel" v-if="urlLogVisible">
+        <div class="url-log-panel__head">
+          <span>当前 URL</span>
+          <button
+            class="url-log-panel__close"
+            type="button"
+            @click="urlLogVisible = false"
+          >
+            ×
+          </button>
+        </div>
+        <pre class="url-log-panel__body">{{ urlLogText }}</pre>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              submitting: false,
+
+              jdmc: "",
+              sqid: "",
+              shid: "",
+              ssObjName: "",
+              ssObjId: "",
+
+              initStatus: "loading",
+              initStatusTitle: "页面初始化中",
+              initStatusMessage: "正在加载审核数据,请稍候...",
+              missingParams: [],
+
+              topIframeSrc: "",
+              bottomIframeSrc: "",
+
+              infoData: null,
+              agrAddData: null,
+              rejAddData: null,
+              bottomButtons: [],
+
+              layoutCalculated: false,
+              headerSectionHeight: 0,
+              availableHeight: 0,
+              topIframeHeight: 0,
+              bottomIframeHeight: 0,
+              bottomIframeMinHeight: 0,
+              isExpanded: false,
+              isDragging: false,
+              dragStartY: 0,
+              dragStartBottomHeight: 0,
+              bottomBarHeight: 0,
+              layoutObserver: null,
+              bottomResizeObserver: null,
+              dragRafId: 0,
+              pendingDeltaY: 0,
+              lastDragEndAt: 0,
+              urlLogVisible: false,
+              urlLogText: "",
+            };
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+            window.addEventListener("message", this.handleIframeMessage);
+            this.callApi();
+          },
+          beforeDestroy() {
+            window.removeEventListener("message", this.handleIframeMessage);
+            this.unbindLayoutObserver();
+            if (this.dragRafId) {
+              cancelAnimationFrame(this.dragRafId);
+              this.dragRafId = 0;
+            }
+          },
+          methods: {
+            normalizeError(error) {
+              return {
+                message: (error && error.message) || String(error),
+                stack: (error && error.stack) || "",
+                response: (error && error.response) || null,
+                data: (error && error.data) || null,
+              };
+            },
+            getUrlParams() {
+              const params = {};
+              const aliasMap = {
+                ssobjname: "ssObjName",
+                ssobjid: "ssObjId",
+                sqid: "sqid",
+                shid: "shid",
+              };
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [rawKey, rawValue] of urlSearchParams) {
+                const decodedValue = this.safeDecode(rawValue);
+                params[rawKey] = decodedValue;
+                const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
+                if (normalizedKey) {
+                  params[normalizedKey] = decodedValue;
+                }
+              }
+              return params;
+            },
+            setInitStatus(status, title, message) {
+              this.initStatus = status;
+              this.initStatusTitle = title || "";
+              this.initStatusMessage = message || "";
+            },
+            safeDecode(text) {
+              if (text === undefined || text === null || text === "") return "";
+              try {
+                return decodeURIComponent(String(text));
+              } catch (_) {
+                return String(text);
+              }
+            },
+            pickFirstValue(sources, keys) {
+              const srcArr = Array.isArray(sources) ? sources : [];
+              const keyArr = Array.isArray(keys) ? keys : [];
+              for (let i = 0; i < srcArr.length; i += 1) {
+                const src = srcArr[i];
+                if (!src || typeof src !== "object") continue;
+                for (let j = 0; j < keyArr.length; j += 1) {
+                  const key = keyArr[j];
+                  const value = src[key];
+                  if (value !== undefined && value !== null && value !== "") {
+                    return value;
+                  }
+                }
+              }
+              return "";
+            },
+            extractTokenData(apiResponse) {
+              const wantedKeys = [
+                "sqid",
+                "shid",
+                "ssObjName",
+                "ssObjId",
+                "jdmc",
+              ];
+              const candidates = [
+                apiResponse && apiResponse.data && apiResponse.data.ssData,
+                apiResponse && apiResponse.ssData,
+                apiResponse && apiResponse.data,
+                apiResponse,
+              ];
+              let best = {};
+              let bestScore = -1;
+              candidates.forEach((item) => {
+                if (!item || typeof item !== "object") return;
+                let score = 0;
+                wantedKeys.forEach((key) => {
+                  if (
+                    item[key] !== undefined &&
+                    item[key] !== null &&
+                    item[key] !== ""
+                  ) {
+                    score += 1;
+                  }
+                });
+                if (score > bestScore) {
+                  best = item;
+                  bestScore = score;
+                }
+              });
+              return best;
+            },
+            resolveBusinessContext(apiResponse) {
+              const tokenData = this.extractTokenData(apiResponse);
+              const sources = [tokenData, this.pageParams];
+              const resolvedObjId = String(
+                this.pickFirstValue(sources, ["ssObjId", "ssobjid"]) || ""
+              );
+              return {
+                sqid: String(this.pickFirstValue(sources, ["sqid"]) || ""),
+                shid: String(this.pickFirstValue(sources, ["shid"]) || ""),
+                ssObjName: String(
+                  this.pickFirstValue(sources, ["ssObjName", "ssobjname"]) || ""
+                ),
+                ssObjId: resolvedObjId,
+                jdmc: this.safeDecode(
+                  this.pickFirstValue(sources, ["jdmc"]) || ""
+                ),
+              };
+            },
+            parseInfoParm(rawParm) {
+              if (!rawParm) return {};
+              if (typeof rawParm === "object") return rawParm;
+              const text = String(rawParm).trim();
+              if (!text) return {};
+              try {
+                return JSON.parse(text);
+              } catch (_) {}
+              try {
+                const normalized = text
+                  .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                  .replace(/'/g, '"');
+                return JSON.parse(normalized);
+              } catch (_) {
+                return {};
+              }
+            },
+            resolveInfoDestPath(destName) {
+              const raw = String(destName || "").trim();
+              if (!raw) return "";
+              if (/^https?:\/\//i.test(raw)) return raw;
+              if (raw.startsWith("/")) return raw;
+              if (raw.endsWith(".html")) return `/page/${raw}`;
+              if (raw.startsWith("mp_")) return `/page/${raw}.html`;
+              return `/page/mp_${raw}.html`;
+            },
+            buildInfoIframeSrc(infoData, fallbackQuery) {
+              const conf =
+                infoData && typeof infoData === "object" ? infoData : {};
+              const serviceName = conf.service || conf.servName || "";
+              const destName = conf.dest || "";
+              const parmObj = this.parseInfoParm(conf.parm);
+              const mergedQuery = {
+                ...(fallbackQuery || {}),
+                ...(parmObj || {}),
+              };
+              if (!serviceName || !destName) {
+                return this.buildIframeSrc(
+                  "/page/mp_objInfo.html",
+                  mergedQuery
+                );
+              }
+
+              const pagePath = this.resolveInfoDestPath(destName);
+              if (!pagePath) {
+                return this.buildIframeSrc(
+                  "/page/mp_objInfo.html",
+                  mergedQuery
+                );
+              }
+
+              const paramText =
+                typeof conf.parm === "string"
+                  ? conf.parm
+                  : JSON.stringify(parmObj || {});
+              const iframeQuery = {
+                ...mergedQuery,
+                ssServ: serviceName,
+                ssDest: destName,
+                service: serviceName,
+                dest: destName,
+                param: paramText,
+              };
+              return this.buildIframeSrc(pagePath, iframeQuery);
+            },
+            buildIframeSrc(path, queryObj) {
+              const search = new URLSearchParams(queryObj);
+              return `${path}?${search.toString()}`;
+            },
+            buildCommonQuery() {
+              return {
+                sqid: this.sqid,
+                shid: this.shid,
+                ssObjName: this.ssObjName,
+                ssObjId: this.ssObjId,
+              };
+            },
+            initializeLayout() {
+              this.$nextTick(() => {
+                const bottomIframe = this.$refs.bottomIframe;
+                if (!bottomIframe || !bottomIframe.contentWindow) return;
+
+                const waitForHeaderSection = () => {
+                  try {
+                    const iframeDoc = bottomIframe.contentWindow.document;
+                    const headerSection =
+                      iframeDoc.querySelector(".header-section");
+                    if (!headerSection) {
+                      setTimeout(waitForHeaderSection, 120);
+                      return;
+                    }
+
+                    const headerHeight = headerSection.offsetHeight || 0;
+                    this.headerSectionHeight = headerHeight;
+                    this.bottomIframeMinHeight = Math.max(44, headerHeight);
+                    this.recalculateLayoutMetrics();
+
+                    if (!this.layoutCalculated) {
+                      this.bottomIframeHeight = this.bottomIframeMinHeight;
+                      this.topIframeHeight = Math.max(
+                        0,
+                        this.availableHeight - this.bottomIframeHeight
+                      );
+                    } else {
+                      const currentBottom =
+                        this.bottomIframeHeight || this.bottomIframeMinHeight;
+                      this.bottomIframeHeight = Math.max(
+                        this.bottomIframeMinHeight,
+                        Math.min(currentBottom, this.availableHeight)
+                      );
+                      this.topIframeHeight = Math.max(
+                        0,
+                        this.availableHeight - this.bottomIframeHeight
+                      );
+                    }
+
+                    this.layoutCalculated = true;
+                    this.applyIframeHeights();
+                    this.bindLayoutObserver();
+                  } catch (error) {
+                    setTimeout(waitForHeaderSection, 180);
+                  }
+                };
+
+                setTimeout(waitForHeaderSection, 80);
+              });
+            },
+            recalculateLayoutMetrics() {
+              const viewportHeight = window.innerHeight || 0;
+              const bottomBar = document.querySelector("#app .ss-bottom");
+              this.bottomBarHeight = bottomBar ? bottomBar.offsetHeight : 0;
+              this.availableHeight = Math.max(
+                0,
+                viewportHeight - this.bottomBarHeight
+              );
+            },
+            handleLayoutResize() {
+              if (!this.layoutCalculated) return;
+              this.recalculateLayoutMetrics();
+              const bottomActualHeight = Math.max(
+                this.bottomIframeMinHeight,
+                Math.min(
+                  this.bottomIframeHeight || this.bottomIframeMinHeight,
+                  this.availableHeight
+                )
+              );
+              this.bottomIframeHeight = bottomActualHeight;
+              this.topIframeHeight = Math.max(
+                0,
+                this.availableHeight - bottomActualHeight
+              );
+              this.applyIframeHeights();
+            },
+            bindLayoutObserver() {
+              if (!this.layoutObserver) {
+                this.layoutObserver = () => this.handleLayoutResize();
+                window.addEventListener("resize", this.layoutObserver);
+              }
+
+              if (
+                !this.bottomResizeObserver &&
+                typeof window.ResizeObserver === "function"
+              ) {
+                const bottomBar = document.querySelector("#app .ss-bottom");
+                if (bottomBar) {
+                  this.bottomResizeObserver = new ResizeObserver(() => {
+                    this.handleLayoutResize();
+                  });
+                  this.bottomResizeObserver.observe(bottomBar);
+                }
+              }
+            },
+            unbindLayoutObserver() {
+              if (this.layoutObserver) {
+                window.removeEventListener("resize", this.layoutObserver);
+                this.layoutObserver = null;
+              }
+              if (this.bottomResizeObserver) {
+                this.bottomResizeObserver.disconnect();
+                this.bottomResizeObserver = null;
+              }
+            },
+            applyIframeHeights() {
+              const topIframe = this.$refs.topIframe;
+              const bottomIframe = this.$refs.bottomIframe;
+              if (!topIframe || !bottomIframe) return;
+              topIframe.style.height = `${this.topIframeHeight}px`;
+              bottomIframe.style.height = `${this.bottomIframeHeight}px`;
+            },
+            applyIframeHeightsFast() {
+              const topIframe = this.$refs.topIframe;
+              const bottomIframe = this.$refs.bottomIframe;
+              if (!topIframe || !bottomIframe) return;
+              topIframe.style.height = `${this.topIframeHeight}px`;
+              bottomIframe.style.height = `${this.bottomIframeHeight}px`;
+            },
+            calculateHeights(bottomHeight) {
+              const bottomActualHeight = Math.max(
+                this.bottomIframeMinHeight,
+                Math.min(bottomHeight, this.availableHeight)
+              );
+              this.bottomIframeHeight = bottomActualHeight;
+              this.topIframeHeight = Math.max(
+                0,
+                this.availableHeight - bottomActualHeight
+              );
+              this.applyIframeHeights();
+            },
+            toggleExpand() {
+              if (!this.layoutCalculated || this.isDragging) return;
+              if (Date.now() - this.lastDragEndAt < 180) return;
+              this.isExpanded = !this.isExpanded;
+              if (this.isExpanded) {
+                this.calculateHeights(this.availableHeight);
+              } else {
+                this.calculateHeights(this.bottomIframeMinHeight);
+              }
+            },
+            handleIframeMessage(event) {
+              if (event.origin !== window.location.origin) return;
+              const payload = event.data || {};
+              const { type, data } = payload;
+              if (type === "header-section-click") {
+                this.toggleExpand();
+                return;
+              }
+              if (type === "header-section-drag-start") {
+                this.handleDragStart(data || {});
+                return;
+              }
+              if (type === "header-section-drag-move") {
+                this.handleDragMove((data && data.deltaY) || 0);
+                return;
+              }
+              if (type === "header-section-drag-end") {
+                this.handleDragEnd();
+              }
+            },
+            handleDragStart(data) {
+              if (!this.layoutCalculated) return;
+              this.isDragging = true;
+              this.dragStartY = Number(data && data.startY) || 0;
+              this.dragStartBottomHeight = this.bottomIframeHeight;
+              this.pendingDeltaY = 0;
+              if (this.dragRafId) {
+                cancelAnimationFrame(this.dragRafId);
+                this.dragRafId = 0;
+              }
+            },
+            handleDragMove(deltaY) {
+              if (!this.isDragging || !this.layoutCalculated) return;
+              this.pendingDeltaY = Number(deltaY || 0);
+              if (this.dragRafId) return;
+              this.dragRafId = requestAnimationFrame(() => {
+                this.dragRafId = 0;
+                const nextBottomHeight =
+                  this.dragStartBottomHeight - this.pendingDeltaY;
+                const bottomActualHeight = Math.max(
+                  this.bottomIframeMinHeight,
+                  Math.min(nextBottomHeight, this.availableHeight)
+                );
+                this.bottomIframeHeight = bottomActualHeight;
+                this.topIframeHeight = Math.max(
+                  0,
+                  this.availableHeight - bottomActualHeight
+                );
+                this.applyIframeHeightsFast();
+              });
+            },
+            handleDragEnd() {
+              this.isDragging = false;
+              this.dragStartY = 0;
+              this.dragStartBottomHeight = 0;
+              this.pendingDeltaY = 0;
+              this.lastDragEndAt = Date.now();
+              if (this.dragRafId) {
+                cancelAnimationFrame(this.dragRafId);
+                this.dragRafId = 0;
+              }
+              this.applyIframeHeights();
+            },
+            async callApi() {
+              this.loading = true;
+              this.infoData = null;
+              this.agrAddData = null;
+              this.rejAddData = null;
+              this.bottomButtons = [];
+              this.topIframeSrc = "";
+              this.bottomIframeSrc = "";
+              this.missingParams = [];
+              this.setInitStatus(
+                "loading",
+                "页面初始化中",
+                "正在加载审核数据,请稍候..."
+              );
+
+              let apiResponse = null;
+
+              try {
+                if (this.pageParams.ssToken) {
+                  const tokenUrl = `/service?ssToken=${this.pageParams.ssToken}`;
+                  apiResponse = await request.post(
+                    tokenUrl,
+                    {},
+                    { loading: false }
+                  );
+                }
+
+                const context = this.resolveBusinessContext(apiResponse);
+                this.sqid = context.sqid;
+                this.shid = context.shid;
+                this.ssObjName = context.ssObjName;
+                this.ssObjId = context.ssObjId;
+                this.jdmc = context.jdmc || "";
+
+                const requiredKeys = ["sqid", "shid", "ssObjId"];
+                this.missingParams = requiredKeys.filter((key) => !this[key]);
+
+                const query = this.buildCommonQuery();
+                const infoUrl = `/service?ssServ=dataTag&ssDest=data&name=info&ssObjName=${encodeURIComponent(
+                  this.ssObjName
+                )}&ssObjId=${encodeURIComponent(
+                  this.ssObjId
+                )}&sqid=${encodeURIComponent(
+                  this.sqid
+                )}&shid=${encodeURIComponent(this.shid)}`;
+                const infoRes = await request.post(
+                  infoUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.infoData =
+                  infoRes && infoRes.data ? infoRes.data.info : null;
+
+                this.topIframeSrc = this.buildInfoIframeSrc(
+                  this.infoData,
+                  query
+                );
+                this.bottomIframeSrc = this.buildIframeSrc(
+                  "/page/mp_shList.html",
+                  query
+                );
+
+                this.setInitStatus("ready", "页面已就绪", "审核数据加载完成");
+                this.$nextTick(() => {
+                  this.initializeLayout();
+                });
+
+                const agrUrl = `/service?ssServ=dataTag&ssDest=data&name=agrAdd&ssObjName=${encodeURIComponent(
+                  this.ssObjName
+                )}&ssObjId=${encodeURIComponent(
+                  this.ssObjId
+                )}&sqid=${encodeURIComponent(
+                  this.sqid
+                )}&shid=${encodeURIComponent(this.shid)}`;
+                const dataTagRes = await request.post(
+                  agrUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+
+                const rejUrl = `/service?ssServ=dataTag&ssDest=data&name=rejAdd&ssObjName=${encodeURIComponent(
+                  this.ssObjName
+                )}&ssObjId=${encodeURIComponent(
+                  this.ssObjId
+                )}&sqid=${encodeURIComponent(
+                  this.sqid
+                )}&shid=${encodeURIComponent(this.shid)}`;
+                const dataTagResRej = await request.post(
+                  rejUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+
+                this.agrAddData =
+                  dataTagRes && dataTagRes.data ? dataTagRes.data.agrAdd : null;
+                this.rejAddData =
+                  dataTagResRej && dataTagResRej.data
+                    ? dataTagResRej.data.rejAdd
+                    : null;
+
+                if (
+                  this.agrAddData &&
+                  !this.agrAddData.service &&
+                  this.agrAddData.servName
+                ) {
+                  this.agrAddData.service = this.agrAddData.servName;
+                }
+                if (
+                  this.rejAddData &&
+                  !this.rejAddData.service &&
+                  this.rejAddData.servName
+                ) {
+                  this.rejAddData.service = this.rejAddData.servName;
+                }
+
+                const btns = [];
+                if (
+                  this.rejAddData &&
+                  this.rejAddData.service &&
+                  this.rejAddData.dest
+                ) {
+                  btns.push({ text: "退回", action: "reject" });
+                }
+                if (
+                  this.agrAddData &&
+                  this.agrAddData.service &&
+                  this.agrAddData.dest
+                ) {
+                  btns.push({ text: "同意", action: "agree" });
+                }
+                this.bottomButtons = btns;
+                this.$nextTick(() => {
+                  this.initializeLayout();
+                });
+              } catch (error) {
+                const normalized = this.normalizeError(error);
+                this.setInitStatus(
+                  "error",
+                  "页面初始化失败",
+                  normalized.message || "页面初始化失败,请稍后重试"
+                );
+                console.error("[mp_addChk] 初始化失败", error);
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("页面初始化失败,请稍后重试", 2200, "error");
+                }
+              } finally {
+                this.loading = false;
+              }
+            },
+            buildSubmitPayload(actionPayload) {
+              const submitPayload = {};
+
+              if (this.shid) {
+                submitPayload.shid = this.shid;
+              }
+              if (this.sqid) {
+                submitPayload.sqid = this.sqid;
+              }
+              if (this.ssObjName) {
+                submitPayload.ssObjName = this.ssObjName;
+              }
+              if (this.ssObjId) {
+                submitPayload.ssObjId = this.ssObjId;
+              }
+
+              const payloadObj =
+                actionPayload && typeof actionPayload === "object"
+                  ? actionPayload
+                  : {};
+              const reviewValue = this.pickFirstValue(
+                [payloadObj],
+                [
+                  "shsm",
+                  "shyjValue",
+                  "shyj",
+                  "spyj",
+                  "opinion",
+                  "comment",
+                  "remark",
+                  "reason",
+                  "yj",
+                ]
+              );
+              if (
+                reviewValue !== undefined &&
+                reviewValue !== null &&
+                reviewValue !== ""
+              ) {
+                submitPayload.shsm = String(reviewValue);
+              }
+
+              return submitPayload;
+            },
+            navigateToMiniProgramHome() {
+              const homePath = "/pages/main/index";
+              const wxObj =
+                typeof wx !== "undefined" ? wx : window.wx ? window.wx : null;
+
+              const fallbackBack = () => {
+                if (
+                  window.NavigationManager &&
+                  typeof window.NavigationManager.goBack === "function"
+                ) {
+                  window.NavigationManager.goBack({ refreshParent: true });
+                } else {
+                  window.history.back();
+                }
+              };
+
+              if (!wxObj || !wxObj.miniProgram) {
+                fallbackBack();
+                return;
+              }
+
+              if (typeof wxObj.miniProgram.switchTab === "function") {
+                wxObj.miniProgram.switchTab({
+                  url: homePath,
+                  fail: () => {
+                    if (typeof wxObj.miniProgram.reLaunch === "function") {
+                      wxObj.miniProgram.reLaunch({
+                        url: homePath,
+                        fail: fallbackBack,
+                      });
+                      return;
+                    }
+                    fallbackBack();
+                  },
+                });
+                return;
+              }
+
+              if (typeof wxObj.miniProgram.reLaunch === "function") {
+                wxObj.miniProgram.reLaunch({
+                  url: homePath,
+                  fail: fallbackBack,
+                });
+                return;
+              }
+
+              fallbackBack();
+            },
+            async submitByConfig(conf, actionPayload) {
+              const serviceName = conf && (conf.service || conf.servName);
+              const destName = conf && conf.dest;
+              if (!conf || !serviceName || !destName) {
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("缺少提交配置", 2200, "error");
+                }
+                return;
+              }
+              if (this.submitting) return;
+
+              try {
+                this.submitting = true;
+                const submitUrl = `/service?ssServ=${encodeURIComponent(
+                  serviceName
+                )}&ssDest=${encodeURIComponent(destName)}`;
+                const submitPayload = this.buildSubmitPayload(actionPayload);
+
+                await request.post(submitUrl, submitPayload, {
+                  loading: true,
+                  formData: true,
+                });
+
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("提交成功", 1200, "success");
+                }
+                setTimeout(() => {
+                  this.navigateToMiniProgramHome();
+                }, 300);
+              } catch (error) {
+                console.error("[mp_addChk] 提交失败", error);
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("提交失败,请稍后重试", 2200, "error");
+                }
+              } finally {
+                this.submitting = false;
+              }
+            },
+            showCurrentUrls() {
+              const topIframe = this.$refs.topIframe;
+              const bottomIframe = this.$refs.bottomIframe;
+              const lines = [
+                `page: ${window.location.href}`,
+                `top: ${
+                  (topIframe && topIframe.src) || this.topIframeSrc || "(empty)"
+                }`,
+                `bottom: ${
+                  (bottomIframe && bottomIframe.src) ||
+                  this.bottomIframeSrc ||
+                  "(empty)"
+                }`,
+              ];
+              this.urlLogText = lines.join("\n");
+              this.urlLogVisible = true;
+            },
+            handleBottomAction(payload) {
+              if (!payload || !payload.action) return;
+              if (payload.action === "agree") {
+                this.submitByConfig(this.agrAddData, payload);
+                return;
+              }
+              if (payload.action === "reject") {
+                this.submitByConfig(this.rejAddData, payload);
+              }
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 29 - 27
page/mp_addSure.html

@@ -25,9 +25,7 @@
             max-width: 170px;
         }
 
-        .desc-editor {
-            width: 100%;
-        }
+       
     </style>
 </head>
 <body>
@@ -48,17 +46,14 @@
                     </ss-select>
                 </td>
             </tr>
-            <tr>
+            <tr class="desc-row">
                 <th>{{ descLabel }}</th>
                 <td>
-                    <!-- 功能说明:addSure 说明区改用 ss-editor(对齐PC editor.ss),提交时仍回写 sqms/sqmswj by xu 2026-02-28 -->
-                    <ss-editor
-                        class="desc-editor"
-                        name="sqmswj"
-                        :model-value="formData.sqmswj"
+                    <!-- 功能说明:addSure 说明改为普通录入字段 ms,按当前移动端需求不使用富文本 by xu 2026-03-01 -->
+                    <ss-input
+                        v-model="formData.ms"
+                        name="ms"
                         placeholder="请输入说明"
-                        :height="220"
-                        @change="handleEditorChange"
                     />
                 </td>
             </tr>
@@ -84,9 +79,7 @@
                     descLabel: '说明',
                     formData: {
                         bpmtjgwid: '',
-                        sqms: '',
-                        sqmswj: '',
-                        sqfjid: '',
+                        ms: '',
                     },
                     bottomButtons: [
                         {
@@ -103,8 +96,7 @@
                 // 功能说明:addSure 说明预填走通用字段优先级(sqms/sqmswj/ms),避免仅适配某个业务 by xu 2026-02-28
                 const presetDesc = this.pageParams.sqms || this.pageParams.sqmswj || this.pageParams.ms || ''
                 if (presetDesc) {
-                    this.formData.sqms = String(presetDesc)
-                    this.formData.sqmswj = String(presetDesc)
+                    this.formData.ms = String(presetDesc)
                 }
 
                 // 功能说明:对齐PC addSure:ssObjName=ws 时标题为“拟办意见”,否则为“说明” by xu 2026-02-28
@@ -139,17 +131,27 @@
                         window.history.back()
                     }
                 },
+                // 功能说明:addSure 提交成功后优先回退两级返回 objList,避免停留在 objInp by xu 2026-03-01
+                goBackToObjList() {
+                    try {
+                        if (window.NavigationManager && typeof window.NavigationManager.notifyParentRefresh === 'function') {
+                            window.NavigationManager.notifyParentRefresh()
+                        }
+                    } catch (_) {}
+
+                    const fromObjInp = String(this.pageParams.fromObjInp || '') === '1'
+                    if (fromObjInp && window.history.length > 2) {
+                        window.history.go(-2)
+                        return
+                    }
+
+                    this.goBack(true)
+                },
                 // 功能说明:对齐PC addSure:岗位下拉无选项时隐藏整行 by xu 2026-02-28
                 handlePostOptionsLoaded(options) {
                     const arr = Array.isArray(options) ? options : []
                     this.showPostRow = arr.length > 0
                 },
-                // 功能说明:富文本内容同步回 sqms/sqmswj,保持与后端参数兼容 by xu 2026-02-28
-                handleEditorChange(content) {
-                    const text = String(content || '')
-                    this.formData.sqms = text
-                    this.formData.sqmswj = text
-                },
                 handlePrint() {
                     try {
                         window.print()
@@ -169,17 +171,17 @@
 
                     this.submitting = true
                     try {
+                        // 功能说明:addSure 提交接口改为 ${ssObjName}_lr_tj_qr(后缀固定) by xu 2026-03-01
+                        const confirmServ = `${ssObjName}_lr_tj_qr`
                         const payload = {
                             ssObjName,
                             [ssObjIdName]: ssObjId,
                             bpmtjgwid: this.formData.bpmtjgwid || '',
-                            sqms: this.formData.sqms || '',
-                            sqmswj: this.formData.sqmswj || this.formData.sqms || '',
-                            sqfjid: this.formData.sqfjid || '',
+                            ms: this.formData.ms || '',
                         }
 
                         const response = await request.post(
-                            '/service?ssServ=sureAdd',
+                            `/service?ssServ=${encodeURIComponent(confirmServ)}`,
                             payload,
                             { loading: true, formData: true }
                         )
@@ -188,7 +190,7 @@
                         if (typeof showToastEffect === 'function') {
                             showToastEffect('确认提交成功', 1500, 'success')
                         }
-                        setTimeout(() => this.goBack(true), 600)
+                        setTimeout(() => this.goBackToObjList(), 600)
                     } catch (error) {
                         console.error('[mp_addSure] sureAdd提交失败', error)
                         if (typeof showToastEffect === 'function') {

+ 362 - 0
page/mp_bjdmKqjl_baseInfo.html

@@ -0,0 +1,362 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>基本信息</title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        padding-top: 8px;
+        box-sizing: border-box;
+      }
+
+      /* 新增班级点名考勤记录页纯转圈加载态 by xu 2026-03-06 */
+      .loading {
+        min-height: 40vh;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 32px 16px;
+      }
+
+      .loading-spinner {
+        width: 30px;
+        height: 30px;
+        border: 3px solid #e8f3ed;
+        border-top-color: #40ac6d;
+        border-radius: 50%;
+        animation: page-spin 0.9s linear infinite;
+      }
+
+      @keyframes page-spin {
+        from {
+          transform: rotate(0deg);
+        }
+        to {
+          transform: rotate(360deg);
+        }
+      }
+
+      .error {
+        text-align: center;
+        padding: 50px;
+        color: #ff4d4f;
+      }
+
+      .section-card {
+        margin: 0 8px 10px;
+        background: #ffffff;
+        border-radius: 8px;
+        overflow: hidden;
+      }
+
+      .form {
+        width: 100%;
+        border-collapse: collapse;
+      }
+
+      .form th {
+        width: 140px;
+        max-width: 170px;
+      }
+
+      .desc-text {
+        white-space: pre-wrap;
+        line-height: 1.6;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <!-- 新增班级点名考勤记录页纯转圈加载态 by xu 2026-03-06 -->
+      <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+      </div>
+      <div v-else-if="error" class="error">{{ error }}</div>
+      <!-- 新增班级点名考勤记录基本信息回显页 by xu 2026-03-06 -->
+      <div v-else class="content-div">
+        <div class="section-card">
+          <table class="form">
+            <tr>
+              <th>名称</th>
+              <td>{{ displayData.mc || formData.mc || '-' }}</td>
+            </tr>
+            <tr>
+              <th>人员</th>
+              <td>{{ displayData.rymc || formData.ryid || '-' }}</td>
+            </tr>
+            <tr>
+              <th>开始时间</th>
+              <td>{{ displayData.kssj || '-' }}</td>
+            </tr>
+            <tr>
+              <th>结束时间</th>
+              <td>{{ displayData.jssj || '-' }}</td>
+            </tr>
+            <tr>
+              <th>考勤类别</th>
+              <td>{{ displayData.kqlb || formData.kqlbm || '-' }}</td>
+            </tr>
+            <tr>
+              <th>班级</th>
+              <td>{{ displayData.bjmc || formData.bjid || '-' }}</td>
+            </tr>
+            <tr>
+              <th>描述</th>
+              <td class="desc-text">{{ displayData.mswj || formData.mswj || formData.ms || '-' }}</td>
+            </tr>
+          </table>
+        </div>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: "",
+              formData: {},
+              displayData: {
+                mc: "",
+                rymc: "",
+                kssj: "",
+                jssj: "",
+                kqlb: "",
+                bjmc: "",
+                mswj: "",
+              },
+            };
+          },
+          async mounted() {
+            this.pageParams = this.getUrlParams();
+            await this.loadData();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [key, value] of urlSearchParams) {
+                params[key] = decodeURIComponent(value);
+              }
+              return params;
+            },
+
+            parseParamObject(paramStr) {
+              if (!paramStr) return {};
+              try {
+                return JSON.parse(paramStr);
+              } catch (err) {
+                try {
+                  const fixed = paramStr.replace(
+                    /([,{]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g,
+                    '$1"$2":'
+                  );
+                  return JSON.parse(fixed);
+                } catch (err2) {
+                  console.error("解析param失败:", err2);
+                  return {};
+                }
+              }
+            },
+
+            async loadData() {
+              // 加载班级点名考勤记录详情数据并回显 by xu 2026-03-06
+              const service = this.pageParams.service;
+              if (!service) {
+                this.error = "缺少service参数";
+                return;
+              }
+
+              this.loading = true;
+              this.error = "";
+
+              try {
+                const paramObj = this.parseParamObject(this.pageParams.param);
+                const requestData = {
+                  ...paramObj,
+                };
+                const appendIfMissing = (targetKey, sourceKeys) => {
+                  if (
+                    requestData[targetKey] !== undefined &&
+                    requestData[targetKey] !== null &&
+                    requestData[targetKey] !== ""
+                  ) {
+                    return;
+                  }
+                  for (let i = 0; i < sourceKeys.length; i += 1) {
+                    const sourceKey = sourceKeys[i];
+                    const value = this.pageParams[sourceKey];
+                    if (value !== undefined && value !== null && value !== "") {
+                      requestData[targetKey] = value;
+                      return;
+                    }
+                  }
+                };
+
+                appendIfMissing("sqid", ["sqid"]);
+                appendIfMissing("shid", ["shid"]);
+                appendIfMissing("ssObjName", ["ssObjName", "ssobjname"]);
+                appendIfMissing("ssObjId", ["ssObjId", "ssobjid"]);
+                appendIfMissing("bdlbm", ["bdlbm"]);
+                appendIfMissing("dataType", ["dataType", "datatype"]);
+                appendIfMissing("encode_shid", ["encode_shid"]);
+                appendIfMissing("jdmc", ["jdmc"]);
+                appendIfMissing("ssToken", ["ssToken"]);
+
+                const response = await request.post(
+                  `/service?ssServ=${service}&ssDest=data`,
+                  requestData,
+                  { loading: false, formData: true }
+                );
+
+                const raw = this.pickKqjlData(response?.data);
+                if (!raw) {
+                  this.error = "未获取到考勤记录数据";
+                  return;
+                }
+
+                this.formData = raw;
+                await this.buildDisplayData(raw);
+              } catch (error) {
+                console.error("加载班级点名考勤记录基本信息失败:", error);
+                this.error = "加载失败:" + (error.message || "未知错误");
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            pickKqjlData(data) {
+              if (!data) return null;
+              if (data.bjdmKqjl) return data.bjdmKqjl;
+              if (data.kqjl) return data.kqjl;
+              if (data.bjdmkqjl) return data.bjdmkqjl;
+              if (Array.isArray(data.objectList) && data.objectList.length > 0) {
+                return (
+                  data.objectList[0]?.bjdmKqjl ||
+                  data.objectList[0]?.kqjl ||
+                  data.objectList[0]
+                );
+              }
+              if (Array.isArray(data.data) && data.data.length > 0) {
+                return data.data[0]?.bjdmKqjl || data.data[0]?.kqjl || data.data[0];
+              }
+              if (typeof data === "object") return data;
+              return null;
+            },
+
+            async buildDisplayData(raw) {
+              // 格式化班级点名考勤记录显示字段 by xu 2026-03-06
+              this.displayData = {
+                mc: raw.mc || raw.name || "",
+                rymc: await this.translateDict(
+                  "ry",
+                  raw.ryid,
+                  raw.rymc || raw.xm || raw.name
+                ),
+                kssj: this.formatDate(raw.kssj),
+                jssj: this.formatDate(raw.jssj),
+                kqlb: await this.translateKqlb(raw.kqlbm, raw.kqlb),
+                bjmc: await this.translateDict(
+                  "bj",
+                  raw.bjid,
+                  raw.bjmc || raw.bjname
+                ),
+                mswj: this.normalizeDesc(raw.mswj || raw.ms || raw.sqms || ""),
+              };
+            },
+
+            formatDate(value) {
+              if (!value) return "";
+              if (
+                window.H5FieldFormatter &&
+                typeof window.H5FieldFormatter.formatDate === "function"
+              ) {
+                return window.H5FieldFormatter.formatDate(
+                  value,
+                  "YYYY-MM-DD HH:mm:ss"
+                );
+              }
+              return value;
+            },
+
+            normalizeDesc(value) {
+              if (!value) return "";
+              return String(value)
+                .replace(/<br\s*\/?\>/gi, "\n")
+                .replace(/<[^>]+>/g, "")
+                .trim();
+            },
+
+            async translateDict(dictName, value, fallbackName) {
+              if (fallbackName) return fallbackName;
+              if (value === undefined || value === null || value === "") {
+                return "";
+              }
+              try {
+                if (typeof window.getDictOptions === "function") {
+                  const options = await window.getDictOptions(dictName);
+                  const target = (options || []).find(
+                    (item) => String(item.v) === String(value)
+                  );
+                  if (target && target.n) return target.n;
+                }
+              } catch (error) {
+                console.error(`${dictName} 字典翻译失败:`, error);
+              }
+              return String(value);
+            },
+
+            async translateKqlb(kqlbm, fallbackName) {
+              if (fallbackName) return fallbackName;
+              if (kqlbm === undefined || kqlbm === null || kqlbm === "") {
+                return "";
+              }
+
+              const code = String(kqlbm);
+              const localMap = {
+                81: "出勤",
+                1: "缺勤",
+                41: "病假",
+                31: "事假",
+              };
+
+              try {
+                if (typeof window.getDictOptions === "function") {
+                  const dictNames = ["kqlb", "kqlbm", "kqzt"];
+                  for (let i = 0; i < dictNames.length; i += 1) {
+                    const options = await window.getDictOptions(dictNames[i]);
+                    const target = (options || []).find(
+                      (item) => String(item.v) === code
+                    );
+                    if (target && target.n) return target.n;
+                  }
+                }
+              } catch (error) {
+                console.error("考勤类别翻译失败:", error);
+              }
+
+              return localMap[code] || code;
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 309 - 0
page/mp_bjdm_baseInfo.html

@@ -0,0 +1,309 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>基本信息</title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        padding-top: 8px;
+        box-sizing: border-box;
+      }
+
+      /* 调整班级点名页加载态为纯转圈 by xu 2026-03-06 */
+      .loading {
+        min-height: 40vh;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 32px 16px;
+      }
+
+      .loading-spinner {
+        width: 30px;
+        height: 30px;
+        border: 3px solid #e8f3ed;
+        border-top-color: #40ac6d;
+        border-radius: 50%;
+        animation: page-spin 0.9s linear infinite;
+      }
+      @keyframes page-spin {
+        from {
+          transform: rotate(0deg);
+        }
+        to {
+          transform: rotate(360deg);
+        }
+      }
+
+      .error {
+        text-align: center;
+        padding: 50px;
+        color: #ff4d4f;
+      }
+
+      .section-card {
+        margin: 0 8px 10px;
+        background: #ffffff;
+        border-radius: 8px;
+        overflow: hidden;
+      }
+
+      .form {
+        width: 100%;
+        border-collapse: collapse;
+      }
+
+      .form th {
+        width: 140px;
+        max-width: 170px;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <!-- 调整班级点名页加载态为纯转圈 by xu 2026-03-06 -->
+      <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+      </div>
+      <div v-else-if="error" class="error">{{ error }}</div>
+      <!-- 新增班级点名基本信息回显页 by xu 2026-03-06 -->
+      <div v-else class="content-div">
+        <div class="section-card">
+          <table class="form">
+            <tr>
+              <th>班级</th>
+              <td>{{ displayData.bjmc || formData.bjid || '-' }}</td>
+            </tr>
+            <tr>
+              <th>节时间</th>
+              <td>{{ displayData.jkssj || '-' }}</td>
+            </tr>
+            <tr>
+              <th>点名时间</th>
+              <td>{{ displayData.dmsj || '-' }}</td>
+            </tr>
+            <tr>
+              <th>点名人</th>
+              <td>{{ displayData.dmrymc || formData.dmryid || '-' }}</td>
+            </tr>
+            <tr>
+              <th>旷课人数</th>
+              <td>{{ displayData.kkrs ?? '-' }}</td>
+            </tr>
+            <tr>
+              <th>请假人数</th>
+              <td>{{ displayData.qjrs ?? '-' }}</td>
+            </tr>
+          </table>
+        </div>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: "",
+              formData: {},
+              displayData: {
+                bjmc: "",
+                jkssj: "",
+                dmsj: "",
+                dmrymc: "",
+                kkrs: "",
+                qjrs: "",
+              },
+            };
+          },
+          async mounted() {
+            this.pageParams = this.getUrlParams();
+            await this.loadData();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [key, value] of urlSearchParams) {
+                params[key] = decodeURIComponent(value);
+              }
+              return params;
+            },
+
+            parseParamObject(paramStr) {
+              if (!paramStr) return {};
+              try {
+                return JSON.parse(paramStr);
+              } catch (err) {
+                try {
+                  const fixed = paramStr.replace(
+                    /([,{]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g,
+                    '$1"$2":'
+                  );
+                  return JSON.parse(fixed);
+                } catch (err2) {
+                  console.error("解析param失败:", err2);
+                  return {};
+                }
+              }
+            },
+
+            async loadData() {
+              // 加载班级点名详情数据并回显 by xu 2026-03-06
+              const service = this.pageParams.service;
+              if (!service) {
+                this.error = "缺少service参数";
+                return;
+              }
+
+              this.loading = true;
+              this.error = "";
+
+              try {
+                const paramObj = this.parseParamObject(this.pageParams.param);
+                const requestData = {
+                  ...paramObj,
+                };
+                const appendIfMissing = (targetKey, sourceKeys) => {
+                  if (
+                    requestData[targetKey] !== undefined &&
+                    requestData[targetKey] !== null &&
+                    requestData[targetKey] !== ""
+                  ) {
+                    return;
+                  }
+                  for (let i = 0; i < sourceKeys.length; i += 1) {
+                    const sourceKey = sourceKeys[i];
+                    const value = this.pageParams[sourceKey];
+                    if (value !== undefined && value !== null && value !== "") {
+                      requestData[targetKey] = value;
+                      return;
+                    }
+                  }
+                };
+
+                appendIfMissing("sqid", ["sqid"]);
+                appendIfMissing("shid", ["shid"]);
+                appendIfMissing("ssObjName", ["ssObjName", "ssobjname"]);
+                appendIfMissing("ssObjId", ["ssObjId", "ssobjid"]);
+                appendIfMissing("bdlbm", ["bdlbm"]);
+                appendIfMissing("dataType", ["dataType", "datatype"]);
+                appendIfMissing("encode_shid", ["encode_shid"]);
+                appendIfMissing("jdmc", ["jdmc"]);
+                appendIfMissing("ssToken", ["ssToken"]);
+
+                const response = await request.post(
+                  `/service?ssServ=${service}&ssDest=data`,
+                  requestData,
+                  { loading: false, formData: true }
+                );
+
+                const raw = this.pickBjdmData(response?.data);
+                if (!raw) {
+                  this.error = "未获取到班级点名数据";
+                  return;
+                }
+
+                this.formData = raw;
+                await this.buildDisplayData(raw);
+              } catch (error) {
+                console.error("加载班级点名基本信息失败:", error);
+                this.error = "加载失败:" + (error.message || "未知错误");
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            pickBjdmData(data) {
+              if (!data) return null;
+              if (data.bjdm) return data.bjdm;
+              if (Array.isArray(data.objectList) && data.objectList.length > 0) {
+                return data.objectList[0]?.bjdm || data.objectList[0];
+              }
+              if (Array.isArray(data.data) && data.data.length > 0) {
+                return data.data[0]?.bjdm || data.data[0];
+              }
+              if (typeof data === "object") return data;
+              return null;
+            },
+
+            async buildDisplayData(raw) {
+              // 格式化班级点名显示字段 by xu 2026-03-06
+              const bjmc = await this.translateDict(
+                "bj",
+                raw.bjid,
+                raw.bjmc || raw.bjname || raw.bjdm_mc
+              );
+              const dmrymc = await this.translateDict(
+                "ry",
+                raw.dmryid,
+                raw.dmrymc || raw.dmryname || raw.xm
+              );
+
+              this.displayData = {
+                bjmc,
+                jkssj: this.formatDate(raw.jkssj),
+                dmsj: this.formatDate(raw.dmsj),
+                dmrymc,
+                kkrs: raw.kkrs,
+                qjrs: raw.qjrs,
+              };
+            },
+
+            formatDate(value) {
+              if (!value) return "";
+              if (
+                window.H5FieldFormatter &&
+                typeof window.H5FieldFormatter.formatDate === "function"
+              ) {
+                return window.H5FieldFormatter.formatDate(
+                  value,
+                  "YYYY-MM-DD HH:mm:ss"
+                );
+              }
+              return value;
+            },
+
+            async translateDict(dictName, value, fallbackName) {
+              if (fallbackName) return fallbackName;
+              if (value === undefined || value === null || value === "") {
+                return "";
+              }
+
+              try {
+                if (typeof window.getDictOptions === "function") {
+                  const options = await window.getDictOptions(dictName);
+                  const target = (options || []).find(
+                    (item) => String(item.v) === String(value)
+                  );
+                  if (target && target.n) return target.n;
+                }
+              } catch (error) {
+                console.error(`${dictName} 字典翻译失败:`, error);
+              }
+
+              return String(value);
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 1161 - 682
page/mp_ccChk.html

@@ -1,682 +1,1161 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
-    <title>审核</title>
-    <script src="/js/mp_base/base.js"></script>
-
-    <style>
-        /* 防止Vue模板闪烁 */
-        [v-cloak] {
-            display: none !important;
-        }
-
-        #app {
-            background: #f5f5f5;
-            height: 100vh;
-            display: flex;
-            flex-direction: column;
-            overflow: hidden; /* 防止页面级别滚动 */
-            box-sizing: border-box; /* 确保padding计算在高度内 */
-        }
-
-        /* iframe样式 */
-        .top-iframe {
-            background: #fff;
-            overflow: hidden;
-            border: none;
-            display: block;
-            box-sizing: border-box;
-        }
-
-        .bottom-iframe {
-            background: #fff;
-            overflow: hidden;
-            border: none;
-            display: block;
-            box-sizing: border-box;
-        }
-
-        .title{
-            flex-shrink: 0;
-            box-sizing: border-box;
-            font-size: 16px;
-            text-align: center;
-            /* margin: 16px auto; */
-            height: 48px;
-            line-height: 48px;
-        }
-
-        /* 录入div样式 */
-        .review-input-popup {
-            position: fixed;
-            bottom: 0;
-            left: 0;
-            right: 0;
-            height: 50px;
-            background: #e6e6e6;
-            display: flex;
-            align-items: center;
-            padding: 0 0 0 10px;
-            z-index: 1000;
-            transform: translateY(100%);
-            transition: transform 0.3s ease;
-            box-sizing: border-box;
-        }
-
-        .review-input-popup.show {
-            transform: translateY(0);
-        }
-
-        .review-input-wrapper {
-            flex: 1;
-            display: flex;
-            align-items: center;
-            gap: 4px;
-        }
-
-        .review-input {
-            flex: 1;
-            height: 32px;
-            border: none;
-            padding: 0 10px;
-            font-size: 16px;
-            background: #fff;
-            outline: none;
-            box-sizing: border-box;
-        }
-
-        .common-phrases-btn {
-            padding: 6px 12px;
-            background: #fff;
-            border: none;
-            font-size: 16px;
-            color: #333;
-            cursor: pointer;
-            white-space: nowrap;
-            margin-right: 4px;
-        }
-
-        .common-phrases-btn:hover {
-            background: #e0e0e0;
-        }
-
-        .submit-btn {
-            box-sizing: border-box;
-            height: 50px;
-            padding: 6px 16px;
-            border: none;
-            font-size: 16px;
-            color: #fff;
-            cursor: pointer;
-            white-space: nowrap;
-            transition: background-color 0.2s;
-        }
-
-        .submit-btn.agree {
-            background: #585e6e;
-        }
-
-        .submit-btn.agree:active {
-            background: #242835;
-        }
-
-        .submit-btn.reject {
-            background: #e58846;
-        }
-
-        .submit-btn.reject:active {
-            background: #eb6100;
-        }
-
-        /* 常用语popup */
-        .common-phrases-popup {
-            position: fixed;
-            bottom: 50px;
-            left: 10px;
-            right: 10px;
-            background: #fff;
-            border-radius: 4px;
-            box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
-            max-height: 200px;
-            overflow-y: auto;
-            z-index: 1001;
-            display: none;
-        }
-
-        .common-phrases-popup.show {
-            display: block;
-        }
-
-        .phrase-item {
-            padding: 12px 16px;
-            border-bottom: 1px solid #f0f0f0;
-            cursor: pointer;
-            font-size: 14px;
-        }
-
-        .phrase-item:hover {
-            background: #f8f8f8;
-        }
-
-        .phrase-item:last-child {
-            border-bottom: none;
-        }
-
-        /* 原ss-bottom隐藏 */
-        .ss-bottom.hidden {
-            display: none;
-        }
-    </style>
-</head>
-<body>
-    <div id="app" v-cloak>
-        <div class="title" @click="callApi">
-            {{jdmc}}
-        </div>
-        <!-- 上方iframe - 基本信息区域 -->
-        <iframe
-            ref="topIframe"
-            class="top-iframe"
-            :src="`/page/mp_objInfo.html?sqid=${sqid}&shid=${shid}&bdlbm=${bdlbm}&dataType=${dataType}&encode_shid=${encode_shid}&ssObjName=${ssObjName}&ssObjId=${ssObjId}`"
-            width="100%"
-            frameborder="0">
-        </iframe>
-
-        <!-- 下方iframe - 审核列表区域 -->
-        <iframe
-            ref="bottomIframe"
-            class="bottom-iframe"
-            :src="`/page/mp_shList.html?sqid=${sqid}&shid=${shid}&bdlbm=${bdlbm}&dataType=${dataType}&encode_shid=${encode_shid}&ssObjName=${ssObjName}&ssObjId=${ssObjId}`"
-            width="100%"
-            frameborder="0">
-        </iframe>
-
-        <!-- 底部按钮 -->
-        <ss-bottom
-            :show-shyj="false"
-            :buttons="bottomButtons"
-            @button-click="handleBottomAction"
-            :divider="false"
-            :disabled="submitting"
-            v-if="bottomButtons.length > 0"
-        ></ss-bottom>
-    </div>
-
-    <script>
-        // 等待SS框架加载完成
-        window.SS.ready(function () {
-            // 使用SS框架的方式创建Vue实例
-            window.SS.dom.initializeFormApp({
-                el: '#app',
-                data() {
-                    return {
-                        pageParams: {},
-                        loading: false,
-                        jdmc:'',
-                        sqid: '',
-                        shid: '',
-                        bdlbm: '',
-                        dataType:'bdplay',
-                        encode_shid:'',
-                        ssObjName:'',
-                        ssObjId:'',
-                        agrCcData: null, // 存储agrCc返回的数据
-
-
-                        // 底部按钮相关
-                        submitting: false,
-                        bottomButtons: [],
-
-                        // iframe布局相关
-                        layoutCalculated: false,
-                        headerSectionHeight: 0,
-                        availableHeight: 0,
-                        topIframeHeight: 0,
-                        bottomIframeHeight: 0,
-                        bottomIframeMinHeight: 0,
-                        isExpanded: false,
-
-                        // 拖拽相关
-                        isDragging: false,
-                        dragStartY: 0,
-                        dragStartBottomHeight: 0,
-                    }
-                },
-                mounted() {
-                    // 获取URL参数
-                    this.pageParams = this.getUrlParams()
-                    console.log('🔗 mp_ccChk页面接收到参数:', this.pageParams)
-
-                    // 打印所有参数到控制台
-                    Object.keys(this.pageParams).forEach(key => {
-                        console.log(`参数 ${key}:`, this.pageParams[key])
-                    })
-                    
-                    this.callApi()
-
-                    // 添加iframe消息监听器
-                    window.addEventListener('message', this.handleIframeMessage)
-
-                    // 初始化布局计算
-                    this.$nextTick(() => {
-                        setTimeout(() => {
-                            this.initializeLayout()
-                        }, 500) // 等待iframe加载完成
-                    })
-                },
-                beforeDestroy() {
-                    // 清理事件监听器
-                    window.removeEventListener('message', this.handleIframeMessage)
-                },
-                methods: {
-                    // 获取URL参数
-                    getUrlParams() {
-                        const params = {}
-                        const urlSearchParams = new URLSearchParams(window.location.search)
-                        for (const [key, value] of urlSearchParams) {
-                            params[key] = decodeURIComponent(value)
-                        }
-                        return params
-                    },
-
-                    // 调用API
-                    async callApi() {
-                        if (!this.pageParams.ssToken) {
-                            alert('未找到ssToken参数')
-                            return
-                        }
-
-                        this.loading = true
-                        this.apiResult = null
-
-                        try {
-                            console.log('🚀 开始调用API,ssToken:', this.pageParams.ssToken)
-                            const apiResponse = await request.post(
-                                `/service?ssToken=${this.pageParams.ssToken}`,
-                                {},
-                                { loading: false }
-                            )
-                            console.log('✅ 原有API响应:', apiResponse)
-                            this.sqid = apiResponse.data.sqid
-                            this.shid = apiResponse.data.shid
-                            this.ssObjName = apiResponse.data.ssObjName
-                            this.ssObjId = apiResponse.data.ssObjId
-                            this.bdlbm = apiResponse.data.bdlbm
-                            this.jdmc = decodeURIComponent(apiResponse.data.jdmc)
-
-
-                            console.log('📋 调用dataTag服务获取agrCc数据...')
-                            const agrCcResponse = await request.post(
-                                `/service?ssServ=dataTag&ssDest=data&name=agrCc&ssObjName=${this.ssObjName}&ssObjId=${this.ssObjId}&sqid=${this.sqid}&shid=${this.shid}&bdlbm=${this.bdlbm}&dataType=${this.dataType}&encode_shid=${this.encode_shid}`,
-                                
-                                { loading: false,formData:true }
-                            )
-
-                            // 保存agrCc数据并设置底部按钮
-                            if (agrCcResponse && agrCcResponse.data && agrCcResponse.data.agrCc) {
-                                this.agrCcData = agrCcResponse.data.agrCc
-                                console.log('📦 agrCc数据:', this.agrCcData)
-
-                                // 根据agrCc数据设置底部按钮
-                                this.bottomButtons = [{
-                                    text: '确认',
-                                    action: 'submit',
-                                    backgroundColor: '#585e6e',
-                                    color: '#fff',
-                                    clickBgColor: '#242835'
-                                }]
-                            }
-
-
-
-                        } catch (error) {
-                            console.error('❌ API调用失败:', error)
-                        } finally {
-                            this.loading = false
-                        }
-                    },
-
-                    // 处理底部按钮点击
-                    handleBottomAction(data) {
-                        if (data.action === 'submit') {
-                            this.handleConfirm()
-                        }
-                    },
-
-                    // 处理确认提交
-                    async handleConfirm() {
-                        if (!this.agrCcData) {
-                            alert('缺少必要的配置信息')
-                            return
-                        }
-
-                        if (this.submitting) return
-
-                        try {
-                            this.submitting = true
-                            console.log('📝 开始提交...')
-                            console.log('📦 提交参数:', {
-                                service: this.agrCcData.service,
-                                dest: this.agrCcData.dest,
-                                shid: this.shid
-                            })
-
-                            const response = await request.post(
-                                `/service?ssServ=${this.agrCcData.service}&ssDest=${this.agrCcData.dest}`,
-                                { shid: this.shid },
-                                { loading: true, formData: true }
-                            )
-                            console.log('✅ 提交成功:', response)
-
-                            
-                            NavigationManager.goBack({ refreshParent: true })
-                            
-
-                        } catch (error) {
-                            console.error('❌ 提交失败:', error)
-                            alert('提交失败: ' + (error.message || '未知错误'))
-                        } finally {
-                            this.submitting = false
-                        }
-                    },
-
-                    // ===== 布局相关方法 =====
-                    // 初始化布局计算
-                    initializeLayout() {
-                        console.log('🔄 开始初始化布局计算')
-
-                        try {
-                            // 获取bottom iframe
-                            const bottomIframe = document.querySelector('.bottom-iframe')
-                            const bottomWindow = bottomIframe?.contentWindow
-
-                            if (!bottomWindow) {
-                                console.error('无法获取bottom iframe的contentWindow')
-                                return
-                            }
-
-                            // 等待iframe加载完成,然后检查header-section
-                            const waitForIframeLoad = () => {
-                                if (!bottomIframe.contentWindow) {
-                                    console.log('⏳ 等待iframe contentWindow...')
-                                    setTimeout(waitForIframeLoad, 100)
-                                    return
-                                }
-
-                                const bottomWindow = bottomIframe.contentWindow
-                                console.log('✅ 获取到iframe contentWindow')
-
-                                // 等待iframe中的DOM加载完成
-                                const checkHeaderSection = () => {
-                                    try {
-                                        const headerSection = bottomWindow.document.querySelector('.header-section')
-                                        if (headerSection) {
-                                            const headerHeight = headerSection.offsetHeight
-                                            console.log('✅ 计算到header-section高度:', headerHeight)
-
-                                            // 计算布局参数
-                                            const viewportHeight = window.innerHeight
-
-                                            // 动态获取title的实际高度
-                                            const titleElement = document.querySelector('.title')
-                                            const actualTitleHeight = titleElement ? titleElement.offsetHeight : 0
-
-                                            // 等待bottom按钮渲染完成并获取实际高度
-                                            const checkBottomButton = () => {
-                                                try {
-                                                    const bottomElement = document.querySelector('ss-bottom')
-                                                    const actualBottomHeight = bottomElement ? bottomElement.offsetHeight : 50
-
-                                                    this.headerSectionHeight = headerHeight
-                                                    this.bottomIframeMinHeight = headerHeight
-                                                    this.availableHeight = viewportHeight - actualTitleHeight - actualBottomHeight
-
-                                                    // 初始状态:底部iframe刚好显示header-section高度
-                                                    this.bottomIframeHeight = this.bottomIframeMinHeight
-                                                    this.topIframeHeight = this.availableHeight - this.bottomIframeMinHeight
-                                                    this.isExpanded = false // 初始为收起状态
-                                                    this.layoutCalculated = true
-
-                                                    // 应用计算后的高度
-                                                    this.applyIframeHeights()
-
-                                                    console.log('📊 初始布局计算完成(仅显示header-section):', {
-                                                        '视口高度': viewportHeight,
-                                                        '标题实际高度': actualTitleHeight,
-                                                        '底部按钮实际高度': actualBottomHeight,
-                                                        '可用总高度': this.availableHeight,
-                                                        'header-section高度': headerHeight,
-                                                        '上iframe高度': this.topIframeHeight,
-                                                        '下iframe高度': this.bottomIframeHeight,
-                                                        'iframe总高度': this.topIframeHeight + this.bottomIframeHeight,
-                                                        '是否超限': (this.topIframeHeight + this.bottomIframeHeight) > this.availableHeight,
-                                                        '差额': (this.topIframeHeight + this.bottomIframeHeight) - this.availableHeight,
-                                                        isExpanded: this.isExpanded
-                                                    })
-                                                } catch (bottomError) {
-                                                    console.error('❌ 获取底部按钮高度时出错:', bottomError)
-                                                    // 使用默认高度50px重试
-                                                    const defaultBottomHeight = 50
-                                                    this.headerSectionHeight = headerHeight
-                                                    this.bottomIframeMinHeight = headerHeight
-                                                    this.availableHeight = viewportHeight - actualTitleHeight - defaultBottomHeight
-                                                    this.bottomIframeHeight = this.bottomIframeMinHeight
-                                                    this.topIframeHeight = this.availableHeight - this.bottomIframeMinHeight
-                                                    this.isExpanded = false
-                                                    this.layoutCalculated = true
-                                                    this.applyIframeHeights()
-                                                }
-                                            }
-
-                                            // 如果bottom按钮还没加载,稍后重试
-                                            if (!document.querySelector('ss-bottom')) {
-                                                setTimeout(checkBottomButton, 100)
-                                            } else {
-                                                checkBottomButton()
-                                            }
-                                        } else {
-                                            // 如果还没有加载完成,继续等待
-                                            console.log('⏳ 等待header-section加载...')
-                                            setTimeout(checkHeaderSection, 100)
-                                        }
-                                    } catch (error) {
-                                        console.error('❌ 访问iframe DOM时出错:', error)
-                                        console.log('⏳ 重试中...')
-                                        setTimeout(checkHeaderSection, 200)
-                                    }
-                                }
-
-                                checkHeaderSection()
-                            }
-
-                            waitForIframeLoad()
-
-                        } catch (error) {
-                            console.error('❌ 布局初始化失败:', error)
-                        }
-                    },
-
-                    // 应用iframe高度
-                    applyIframeHeights() {
-                        const topIframe = document.querySelector('.top-iframe')
-                        const bottomIframe = document.querySelector('.bottom-iframe')
-
-                        if (topIframe && bottomIframe) {
-                            console.log('📏 设置iframe高度前:', {
-                                topIframe: {
-                                    currentHeight: topIframe.style.height,
-                                    offsetHeight: topIframe.offsetHeight
-                                },
-                                bottomIframe: {
-                                    currentHeight: bottomIframe.style.height,
-                                    offsetHeight: bottomIframe.offsetHeight
-                                },
-                                newTopHeight: this.topIframeHeight,
-                                newBottomHeight: this.bottomIframeHeight
-                            })
-
-                            // 直接应用新高度到iframe
-                            topIframe.style.height = `${this.topIframeHeight}px`
-                            bottomIframe.style.height = `${this.bottomIframeHeight}px`
-
-                            console.log('🎯 应用iframe高度:', {
-                                top: this.topIframeHeight,
-                                bottom: this.bottomIframeHeight
-                            })
-
-                            // 强制重新计算iframe内容
-                            this.$nextTick(() => {
-                                try {
-                                    topIframe.contentWindow.dispatchEvent(new Event('resize'))
-                                } catch (e) {
-                                    // 忽略跨域错误
-                                }
-
-                                try {
-                                    bottomIframe.contentWindow.dispatchEvent(new Event('resize'))
-                                } catch (e) {
-                                    // 忽略跨域错误
-                                }
-
-                                console.log('✅ iframe高度更新完成')
-                            })
-                        }
-                    },
-
-                    // 快速版本的高度应用,用于拖拽时减少延迟
-                    applyIframeHeightsFast() {
-                        const topIframe = document.querySelector('.top-iframe')
-                        const bottomIframe = document.querySelector('.bottom-iframe')
-
-                        if (topIframe && bottomIframe) {
-                            // 直接设置高度,不进行日志和额外的处理
-                            topIframe.style.height = `${this.topIframeHeight}px`
-                            bottomIframe.style.height = `${this.bottomIframeHeight}px`
-                        }
-                    },
-
-                    // 计算iframe高度分配
-                    calculateHeights(bottomHeight) {
-                        // 计算实际可用的bottom iframe高度
-                        // 当bottom iframe展开时,可以覆盖top iframe,所以最大高度就是availableHeight
-                        // 最小高度不能小于header-section高度
-                        const bottomActualHeight = Math.max(
-                            this.bottomIframeMinHeight,
-                            Math.min(bottomHeight, this.availableHeight)
-                        )
-
-                        this.bottomIframeHeight = bottomActualHeight
-                        this.topIframeHeight = this.availableHeight - bottomActualHeight
-
-                        console.log('📐 高度分配计算:', {
-                            requestedBottom: bottomHeight,
-                            availableHeight: this.availableHeight,
-                            minBottomHeight: this.bottomIframeMinHeight,
-                            actualBottom: this.bottomIframeHeight,
-                            actualTop: this.topIframeHeight,
-                            isExpanded: this.isExpanded
-                        })
-
-                        this.applyIframeHeights()
-
-                        return {
-                            top: this.topIframeHeight,
-                            bottom: this.bottomIframeHeight
-                        }
-                    },
-
-                    // 切换展开/收起状态
-                    toggleExpand() {
-                        if (!this.layoutCalculated) {
-                            console.warn('布局尚未计算完成')
-                            return
-                        }
-
-                        this.isExpanded = !this.isExpanded
-
-                        if (this.isExpanded) {
-                            // 展开:bottom iframe占用所有可用高度,覆盖top iframe到title底部
-                            // 也就是bottom iframe高度 = availableHeight (title + button之间的所有空间)
-                            this.calculateHeights(this.availableHeight)
-                            console.log('📈 展开状态 - 覆盖到title底部,高度:', this.availableHeight)
-                        } else {
-                            // 收起:bottom iframe占用最小高度(header-section高度)
-                            this.calculateHeights(this.bottomIframeMinHeight)
-                            console.log('📉 收起状态 - 最小高度:', this.bottomIframeMinHeight)
-                        }
-                    },
-
-                    // 处理来自iframe的消息
-                    handleIframeMessage(event) {
-                        // 安全检查:只接受同源的消息
-                        if (event.origin !== window.location.origin) {
-                            return
-                        }
-
-                        const { type, data } = event.data
-
-                        switch (type) {
-                            case 'header-section-click':
-                                console.log('📱 收到header-section点击事件')
-                                this.toggleExpand()
-                                break
-                            case 'header-section-drag-start':
-                                console.log('🔄 开始拖拽header-section')
-                                this.handleDragStart(data)
-                                break
-                            case 'header-section-drag-move':
-                                console.log('🔄 拖拽中:', data.deltaY)
-                                this.handleDragMove(data.deltaY)
-                                break
-                            case 'header-section-drag-end':
-                                console.log('🔚 结束拖拽')
-                                this.handleDragEnd()
-                                break
-                        }
-                    },
-
-                    // 处理拖拽开始
-                    handleDragStart(data) {
-                        if (!this.layoutCalculated) return
-
-                        this.isDragging = true
-                        this.dragStartY = data.startY
-                        this.dragStartBottomHeight = this.bottomIframeHeight
-                    },
-
-                    // 处理拖拽移动
-                    handleDragMove(deltaY) {
-                        if (!this.isDragging || !this.layoutCalculated) return
-
-                        // 计算新的底部高度
-                        const newBottomHeight = this.dragStartBottomHeight - deltaY
-
-                        // 直接设置高度,避免复杂的计算导致的延迟
-                        const bottomActualHeight = Math.max(
-                            this.bottomIframeMinHeight,
-                            Math.min(newBottomHeight, this.availableHeight)
-                        )
-
-                        this.bottomIframeHeight = bottomActualHeight
-                        this.topIframeHeight = this.availableHeight - bottomActualHeight
-
-                        // 直接应用高度,减少延迟
-                        this.applyIframeHeightsFast()
-                    },
-
-                    // 处理拖拽结束
-                    handleDragEnd() {
-                        this.isDragging = false
-                        this.dragStartY = 0
-                        this.dragStartBottomHeight = 0
-
-                        // 拖拽结束后用完整的方法确保状态正确
-                        this.applyIframeHeights()
-                    },
-                }
-            })
-        })
-    </script>
-</body>
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>审核</title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+      /* 防止Vue模板闪烁 */
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        height: 100vh;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden; /* 防止页面级别滚动 */
+        box-sizing: border-box; /* 确保padding计算在高度内 */
+      }
+
+      /* iframe样式 */
+      .top-iframe {
+        background: #fff;
+        overflow: hidden;
+        border: none;
+        display: block;
+        box-sizing: border-box;
+      }
+
+      .bottom-iframe {
+        background: #fff;
+        overflow: hidden;
+        border: none;
+        display: block;
+        box-sizing: border-box;
+      }
+
+      .title {
+        flex-shrink: 0;
+        box-sizing: border-box;
+        font-size: 16px;
+        text-align: center;
+        /* margin: 16px auto; */
+        height: 48px;
+        line-height: 48px;
+      }
+
+      /* 录入div样式 */
+      .review-input-popup {
+        position: fixed;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        height: 50px;
+        background: #e6e6e6;
+        display: flex;
+        align-items: center;
+        padding: 0 0 0 10px;
+        z-index: 1000;
+        transform: translateY(100%);
+        transition: transform 0.3s ease;
+        box-sizing: border-box;
+      }
+
+      .review-input-popup.show {
+        transform: translateY(0);
+      }
+
+      .review-input-wrapper {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        gap: 4px;
+      }
+
+      .review-input {
+        flex: 1;
+        height: 32px;
+        border: none;
+        padding: 0 10px;
+        font-size: 16px;
+        background: #fff;
+        outline: none;
+        box-sizing: border-box;
+      }
+
+      .common-phrases-btn {
+        padding: 6px 12px;
+        background: #fff;
+        border: none;
+        font-size: 16px;
+        color: #333;
+        cursor: pointer;
+        white-space: nowrap;
+        margin-right: 4px;
+      }
+
+      .common-phrases-btn:hover {
+        background: #e0e0e0;
+      }
+
+      .submit-btn {
+        box-sizing: border-box;
+        height: 50px;
+        padding: 6px 16px;
+        border: none;
+        font-size: 16px;
+        color: #fff;
+        cursor: pointer;
+        white-space: nowrap;
+        transition: background-color 0.2s;
+      }
+
+      .submit-btn.agree {
+        background: #585e6e;
+      }
+
+      .submit-btn.agree:active {
+        background: #242835;
+      }
+
+      .submit-btn.reject {
+        background: #e58846;
+      }
+
+      .submit-btn.reject:active {
+        background: #eb6100;
+      }
+
+      /* 常用语popup */
+      .common-phrases-popup {
+        position: fixed;
+        bottom: 50px;
+        left: 10px;
+        right: 10px;
+        background: #fff;
+        border-radius: 4px;
+        box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
+        max-height: 200px;
+        overflow-y: auto;
+        z-index: 1001;
+        display: none;
+      }
+
+      .common-phrases-popup.show {
+        display: block;
+      }
+
+      .phrase-item {
+        padding: 12px 16px;
+        border-bottom: 1px solid #f0f0f0;
+        cursor: pointer;
+        font-size: 14px;
+      }
+
+      .phrase-item:hover {
+        background: #f8f8f8;
+      }
+
+      .phrase-item:last-child {
+        border-bottom: none;
+      }
+
+      /* 原ss-bottom隐藏 */
+      .ss-bottom.hidden {
+        display: none;
+      }
+
+      .url-log-fab {
+        position: fixed;
+        right: 12px;
+        bottom: 72px;
+        z-index: 1200;
+        border: none;
+        border-radius: 16px;
+        background: rgba(36, 40, 53, 0.88);
+        color: #fff;
+        font-size: 12px;
+        line-height: 1;
+        padding: 8px 10px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
+      }
+
+      .url-log-panel {
+        position: fixed;
+        left: 12px;
+        right: 12px;
+        bottom: 116px;
+        z-index: 1200;
+        background: rgba(36, 40, 53, 0.96);
+        color: #fff;
+        border-radius: 8px;
+        padding: 10px;
+        max-height: 42vh;
+        overflow: auto;
+        box-sizing: border-box;
+      }
+
+      .url-log-panel__head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 8px;
+        font-size: 12px;
+      }
+
+      .url-log-panel__close {
+        border: none;
+        background: transparent;
+        color: #fff;
+        font-size: 14px;
+        line-height: 1;
+      }
+
+      .url-log-panel__body {
+        margin: 0;
+        white-space: pre-wrap;
+        word-break: break-all;
+        font-size: 12px;
+        line-height: 1.5;
+      }
+
+      .review-section {
+        flex-shrink: 0;
+        background: #f5f5f5;
+      }
+
+      .review-section .ss-sub-tab__bar {
+        margin-bottom: 8px;
+      }
+
+      .review-section__card {
+        width: 95%;
+        margin: 0 auto 8px;
+        background: #fff;
+        border-radius: 8px;
+        overflow: hidden;
+        box-sizing: border-box;
+      }
+
+      .review-section__form {
+        width: 100%;
+        border-collapse: collapse;
+      }
+
+      .review-section__form th {
+        width: 140px;
+        max-width: 170px;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <!-- 新增抄送审核结果展示区 by xu 2026-03-06 -->
+      <div ref="reviewSection" class="review-section" v-if="showReviewSection">
+        <div class="ss-sub-tab__bar">
+          <div class="ss-sub-tab__item ss-sub-tab__item--active">审核情况</div>
+        </div>
+        <div class="review-section__card">
+          <table class="form review-section__form">
+            <tr>
+              <th>审核结果</th>
+              <td>{{ reviewResultText || '-' }}</td>
+            </tr>
+          </table>
+        </div>
+      </div>
+
+      <!-- 上方iframe - 基本信息区域 -->
+      <iframe
+        ref="topIframe"
+        class="top-iframe"
+        :src="topIframeSrc || 'about:blank'"
+        width="100%"
+        frameborder="0"
+      >
+      </iframe>
+
+      <!-- 下方iframe - 审核列表区域 -->
+      <iframe
+        ref="bottomIframe"
+        class="bottom-iframe"
+        :src="bottomIframeSrc || 'about:blank'"
+        width="100%"
+        frameborder="0"
+      >
+      </iframe>
+
+      <!-- 底部按钮 -->
+      <ss-bottom
+        :show-shyj="false"
+        :buttons="bottomButtons"
+        @button-click="handleBottomAction"
+        :divider="false"
+        :disabled="submitting"
+        v-if="bottomButtons.length > 0"
+      ></ss-bottom>
+
+      <button class="url-log-fab" type="button" @click="showCurrentUrls">
+        URL
+      </button>
+      <div class="url-log-panel" v-if="urlLogVisible">
+        <div class="url-log-panel__head">
+          <span>当前 URL</span>
+          <button
+            class="url-log-panel__close"
+            type="button"
+            @click="urlLogVisible = false"
+          >
+            ×
+          </button>
+        </div>
+        <pre class="url-log-panel__body">{{ urlLogText }}</pre>
+      </div>
+    </div>
+
+    <script>
+      // 等待SS框架加载完成
+      window.SS.ready(function () {
+        // 使用SS框架的方式创建Vue实例
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              jdmc: "",
+              sqid: "",
+              shid: "",
+              shyjm: "",
+              bdlbm: "",
+              dataType: "bdplay",
+              encode_shid: "",
+              ssObjName: "",
+              ssObjId: "",
+              infoData: null,
+              agrCcData: null, // 存储agrCc返回的数据
+              topIframeSrc: "",
+              bottomIframeSrc: "",
+
+              // 底部按钮相关
+              submitting: false,
+              bottomButtons: [],
+
+              // iframe布局相关
+              layoutCalculated: false,
+              headerSectionHeight: 0,
+              availableHeight: 0,
+              topIframeHeight: 0,
+              bottomIframeHeight: 0,
+              bottomIframeMinHeight: 0,
+              isExpanded: false,
+
+              // 拖拽相关
+              isDragging: false,
+              dragStartY: 0,
+              dragStartBottomHeight: 0,
+
+              urlLogVisible: false,
+              urlLogText: "",
+              reviewResultText: "",
+            };
+          },
+          computed: {
+            showReviewSection() {
+              return (
+                this.shyjm !== undefined &&
+                this.shyjm !== null &&
+                this.shyjm !== ""
+              );
+            },
+          },
+          mounted() {
+            // 获取URL参数
+            this.pageParams = this.getUrlParams();
+            this.shyjm = this.pageParams.shyjm || "";
+            console.log("🔗 mp_ccChk页面接收到参数:", this.pageParams);
+
+            // 打印所有参数到控制台
+            Object.keys(this.pageParams).forEach((key) => {
+              console.log(`参数 ${key}:`, this.pageParams[key]);
+            });
+
+            this.syncReviewResultText();
+            this.callApi();
+
+            // 添加iframe消息监听器
+            window.addEventListener("message", this.handleIframeMessage);
+
+            // 初始化布局计算
+            this.$nextTick(() => {
+              setTimeout(() => {
+                this.initializeLayout();
+              }, 500); // 等待iframe加载完成
+            });
+          },
+          beforeDestroy() {
+            // 清理事件监听器
+            window.removeEventListener("message", this.handleIframeMessage);
+          },
+          methods: {
+            // 翻译审核结果并在页面顶部展示 by xu 2026-03-06
+            async syncReviewResultText() {
+              this.reviewResultText = await this.translateReviewResult(
+                this.shyjm
+              );
+              this.$nextTick(() => {
+                if (this.layoutCalculated) {
+                  this.initializeLayout();
+                }
+              });
+            },
+
+            async translateReviewResult(shyjm) {
+              if (shyjm === undefined || shyjm === null || shyjm === "") {
+                return "";
+              }
+              try {
+                if (typeof window.getDictOptions === "function") {
+                  const options = await window.getDictOptions("shyj");
+                  const target = (options || []).find(
+                    (item) => String(item.v) === String(shyjm)
+                  );
+                  if (target && target.n) return target.n;
+                }
+              } catch (error) {
+                console.error("审核结果翻译失败:", error);
+              }
+              if (String(shyjm) === "1") return "通过";
+              return String(shyjm);
+            },
+
+            // 获取URL参数
+            getUrlParams() {
+              const params = {};
+              const aliasMap = {
+                ssobjname: "ssObjName",
+                ssobjid: "ssObjId",
+                sqid: "sqid",
+                shid: "shid",
+                shyjm: "shyjm",
+                bdlbm: "bdlbm",
+                datatype: "dataType",
+                encode_shid: "encode_shid",
+                jdmc: "jdmc",
+              };
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [rawKey, rawValue] of urlSearchParams) {
+                const decodedValue = this.safeDecode(rawValue);
+                params[rawKey] = decodedValue;
+                const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
+                if (normalizedKey) {
+                  params[normalizedKey] = decodedValue;
+                }
+              }
+              return params;
+            },
+
+            safeDecode(text) {
+              if (text === undefined || text === null || text === "") return "";
+              try {
+                return decodeURIComponent(String(text));
+              } catch (_) {
+                return String(text);
+              }
+            },
+
+            pickFirstValue(sources, keys) {
+              const srcArr = Array.isArray(sources) ? sources : [];
+              const keyArr = Array.isArray(keys) ? keys : [];
+              for (let i = 0; i < srcArr.length; i += 1) {
+                const src = srcArr[i];
+                if (!src || typeof src !== "object") continue;
+                for (let j = 0; j < keyArr.length; j += 1) {
+                  const key = keyArr[j];
+                  const value = src[key];
+                  if (value !== undefined && value !== null && value !== "") {
+                    return value;
+                  }
+                }
+              }
+              return "";
+            },
+
+            extractTokenData(apiResponse) {
+              const wantedKeys = [
+                "sqid",
+                "shid",
+                "ssObjName",
+                "ssObjId",
+                "jdmc",
+                "bdlbm",
+                "dataType",
+                "encode_shid",
+              ];
+              const candidates = [
+                apiResponse && apiResponse.data && apiResponse.data.ssData,
+                apiResponse && apiResponse.ssData,
+                apiResponse && apiResponse.data,
+                apiResponse,
+              ];
+              let best = {};
+              let bestScore = -1;
+              candidates.forEach((item) => {
+                if (!item || typeof item !== "object") return;
+                let score = 0;
+                wantedKeys.forEach((key) => {
+                  if (
+                    item[key] !== undefined &&
+                    item[key] !== null &&
+                    item[key] !== ""
+                  ) {
+                    score += 1;
+                  }
+                });
+                if (score > bestScore) {
+                  best = item;
+                  bestScore = score;
+                }
+              });
+              return best;
+            },
+
+            resolveBusinessContext(apiResponse) {
+              const tokenData = this.extractTokenData(apiResponse);
+              const sources = [tokenData, this.pageParams];
+              return {
+                sqid: String(this.pickFirstValue(sources, ["sqid"]) || ""),
+                shid: String(this.pickFirstValue(sources, ["shid"]) || ""),
+                shyjm: String(this.pickFirstValue(sources, ["shyjm"]) || ""),
+                ssObjName: String(
+                  this.pickFirstValue(sources, ["ssObjName", "ssobjname"]) || ""
+                ),
+                ssObjId: String(
+                  this.pickFirstValue(sources, ["ssObjId", "ssobjid"]) || ""
+                ),
+                bdlbm: String(this.pickFirstValue(sources, ["bdlbm"]) || ""),
+                dataType:
+                  String(
+                    this.pickFirstValue(sources, ["dataType", "datatype"]) ||
+                      this.dataType ||
+                      "bdplay"
+                  ) || "bdplay",
+                encode_shid: String(
+                  this.pickFirstValue(sources, ["encode_shid"]) || ""
+                ),
+                jdmc: this.safeDecode(
+                  this.pickFirstValue(sources, ["jdmc"]) || ""
+                ),
+              };
+            },
+
+            parseInfoParm(rawParm) {
+              if (!rawParm) return {};
+              if (typeof rawParm === "object") return rawParm;
+              const text = String(rawParm).trim();
+              if (!text) return {};
+              try {
+                return JSON.parse(text);
+              } catch (_) {}
+              try {
+                const normalized = text
+                  .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                  .replace(/'/g, '"');
+                return JSON.parse(normalized);
+              } catch (_) {
+                return {};
+              }
+            },
+
+            resolveInfoDestPath(destName) {
+              const raw = String(destName || "").trim();
+              if (!raw) return "";
+              if (/^https?:\/\//i.test(raw)) return raw;
+              if (raw.startsWith("/")) return raw;
+              if (raw.endsWith(".html")) return `/page/${raw}`;
+              if (raw.startsWith("mp_")) return `/page/${raw}.html`;
+              return `/page/mp_${raw}.html`;
+            },
+
+            buildInfoIframeSrc(infoData, fallbackQuery) {
+              const conf =
+                infoData && typeof infoData === "object" ? infoData : {};
+              const serviceName = conf.service || conf.servName || "";
+              const destName = conf.dest || "";
+              const parmObj = this.parseInfoParm(conf.parm);
+              const mergedQuery = {
+                ...(fallbackQuery || {}),
+                ...(parmObj || {}),
+              };
+              if (!serviceName || !destName) {
+                return this.buildIframeSrc(
+                  "/page/mp_objInfo.html",
+                  mergedQuery
+                );
+              }
+
+              const pagePath = this.resolveInfoDestPath(destName);
+              if (!pagePath) {
+                return this.buildIframeSrc(
+                  "/page/mp_objInfo.html",
+                  mergedQuery
+                );
+              }
+
+              const paramText =
+                typeof conf.parm === "string"
+                  ? conf.parm
+                  : JSON.stringify(parmObj || {});
+              const iframeQuery = {
+                ...mergedQuery,
+                ssServ: serviceName,
+                ssDest: destName,
+                service: serviceName,
+                dest: destName,
+                param: paramText,
+              };
+              return this.buildIframeSrc(pagePath, iframeQuery);
+            },
+
+            buildIframeSrc(path, queryObj) {
+              const search = new URLSearchParams();
+              Object.keys(queryObj || {}).forEach((key) => {
+                const value = queryObj[key];
+                if (value === undefined || value === null) return;
+                search.set(key, String(value));
+              });
+              return `${path}?${search.toString()}`;
+            },
+
+            buildCommonQuery() {
+              return {
+                sqid: this.sqid,
+                shid: this.shid,
+                shyjm: this.shyjm,
+                bdlbm: this.bdlbm,
+                dataType: this.dataType,
+                encode_shid: this.encode_shid,
+                ssObjName: this.ssObjName,
+                ssObjId: this.ssObjId,
+                jdmc: this.jdmc,
+              };
+            },
+
+            // 调用API
+            async callApi() {
+              this.loading = true;
+              this.apiResult = null;
+              this.infoData = null;
+              this.agrCcData = null;
+              this.bottomButtons = [];
+              this.topIframeSrc = "";
+              this.bottomIframeSrc = "";
+
+              try {
+                let apiResponse = null;
+                if (this.pageParams.ssToken) {
+                  console.log(
+                    "🚀 开始调用API,ssToken:",
+                    this.pageParams.ssToken
+                  );
+                  apiResponse = await request.post(
+                    `/service?ssToken=${this.pageParams.ssToken}`,
+                    {},
+                    { loading: false }
+                  );
+                  console.log("✅ 原有API响应:", apiResponse);
+                }
+
+                const context = this.resolveBusinessContext(apiResponse);
+                this.sqid = context.sqid;
+                this.shid = context.shid;
+                this.shyjm = context.shyjm;
+                await this.syncReviewResultText();
+                this.ssObjName = context.ssObjName;
+                this.ssObjId = context.ssObjId;
+                this.bdlbm = context.bdlbm;
+                this.dataType = context.dataType;
+                this.encode_shid = context.encode_shid;
+                this.jdmc = context.jdmc || "";
+
+                if (!this.sqid || !this.shid || !this.ssObjId) {
+                  const missingMessage = "缺少必要参数:sqid / shid / ssObjId";
+                  if (typeof showToastEffect === "function") {
+                    showToastEffect(missingMessage, 2200, "error");
+                  } else {
+                    this.urlLogText = missingMessage;
+                    this.urlLogVisible = true;
+                  }
+                  return;
+                }
+
+                const query = this.buildCommonQuery();
+                const infoUrl = `/service?ssServ=dataTag&ssDest=data&name=info&ssObjName=${encodeURIComponent(
+                  this.ssObjName
+                )}&ssObjId=${encodeURIComponent(
+                  this.ssObjId
+                )}&sqid=${encodeURIComponent(
+                  this.sqid
+                )}&shid=${encodeURIComponent(this.shid)}`;
+                const infoRes = await request.post(
+                  infoUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.infoData =
+                  infoRes && infoRes.data ? infoRes.data.info : null;
+                this.topIframeSrc = this.buildInfoIframeSrc(
+                  this.infoData,
+                  query
+                );
+                this.bottomIframeSrc = this.buildIframeSrc(
+                  "/page/mp_shList.html",
+                  query
+                );
+
+                console.log("📋 调用dataTag服务获取agrCc数据...");
+                const agrCcResponse = await request.post(
+                  `/service?ssServ=dataTag&ssDest=data&name=agrCc&ssObjName=${this.ssObjName}&ssObjId=${this.ssObjId}&sqid=${this.sqid}&shid=${this.shid}&bdlbm=${this.bdlbm}&dataType=${this.dataType}&encode_shid=${this.encode_shid}`,
+                  {},
+                  { loading: false, formData: true }
+                );
+
+                // 保存agrCc数据并设置底部按钮
+                if (
+                  agrCcResponse &&
+                  agrCcResponse.data &&
+                  agrCcResponse.data.agrCc
+                ) {
+                  this.agrCcData = agrCcResponse.data.agrCc;
+                  console.log("📦 agrCc数据:", this.agrCcData);
+
+                  // 根据agrCc数据设置底部按钮
+                  this.bottomButtons = [
+                    {
+                      text: "确认",
+                      action: "submit",
+                      backgroundColor: "#585e6e",
+                      color: "#fff",
+                      clickBgColor: "#242835",
+                    },
+                  ];
+                }
+              } catch (error) {
+                console.error("❌ API调用失败:", error);
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            // 处理底部按钮点击
+            handleBottomAction(data) {
+              if (data.action === "submit") {
+                this.handleConfirm();
+              }
+            },
+
+            showCurrentUrls() {
+              const topIframe = this.$refs.topIframe;
+              const bottomIframe = this.$refs.bottomIframe;
+              const lines = [
+                `page: ${window.location.href}`,
+                `top: ${(topIframe && topIframe.src) || "(empty)"}`,
+                `bottom: ${(bottomIframe && bottomIframe.src) || "(empty)"}`,
+              ];
+              this.urlLogText = lines.join("\n");
+              this.urlLogVisible = true;
+            },
+
+            // 处理确认提交
+            async handleConfirm() {
+              if (!this.agrCcData) {
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("缺少必要的配置信息", 2200, "error");
+                } else {
+                  this.urlLogText = "缺少必要的配置信息";
+                  this.urlLogVisible = true;
+                }
+                return;
+              }
+
+              if (this.submitting) return;
+
+              try {
+                this.submitting = true;
+                console.log("📝 开始提交...");
+                console.log("📦 提交参数:", {
+                  service: this.agrCcData.service,
+                  dest: this.agrCcData.dest,
+                  shid: this.shid,
+                });
+
+                const response = await request.post(
+                  `/service?ssServ=${this.agrCcData.service}&ssDest=${this.agrCcData.dest}`,
+                  { shid: this.shid },
+                  { loading: true, formData: true }
+                );
+                console.log("✅ 提交成功:", response);
+
+                NavigationManager.goBack({ refreshParent: true });
+              } catch (error) {
+                console.error("❌ 提交失败:", error);
+                const message =
+                  "提交失败: " + ((error && error.message) || "未知错误");
+                if (typeof showToastEffect === "function") {
+                  showToastEffect(message, 2200, "error");
+                } else {
+                  this.urlLogText = message;
+                  this.urlLogVisible = true;
+                }
+              } finally {
+                this.submitting = false;
+              }
+            },
+
+            // ===== 布局相关方法 =====
+            // 初始化布局计算
+            initializeLayout() {
+              console.log("🔄 开始初始化布局计算");
+
+              try {
+                // 获取bottom iframe
+                const bottomIframe = document.querySelector(".bottom-iframe");
+                const bottomWindow = bottomIframe?.contentWindow;
+
+                if (!bottomWindow) {
+                  console.error("无法获取bottom iframe的contentWindow");
+                  return;
+                }
+
+                // 等待iframe加载完成,然后检查header-section
+                const waitForIframeLoad = () => {
+                  if (!bottomIframe.contentWindow) {
+                    console.log("⏳ 等待iframe contentWindow...");
+                    setTimeout(waitForIframeLoad, 100);
+                    return;
+                  }
+
+                  const bottomWindow = bottomIframe.contentWindow;
+                  console.log("✅ 获取到iframe contentWindow");
+
+                  // 等待iframe中的DOM加载完成
+                  const checkHeaderSection = () => {
+                    try {
+                      const headerSection =
+                        bottomWindow.document.querySelector(".header-section");
+                      if (headerSection) {
+                        const headerHeight = headerSection.offsetHeight;
+                        console.log(
+                          "✅ 计算到header-section高度:",
+                          headerHeight
+                        );
+
+                        // 计算布局参数
+                        const viewportHeight = window.innerHeight;
+
+                        // 动态获取顶部审核情况区域高度 by xu 2026-03-06
+                        const reviewSectionElement = this.$refs.reviewSection;
+                        const actualReviewSectionHeight = reviewSectionElement
+                          ? reviewSectionElement.offsetHeight
+                          : 0;
+
+                        // 等待bottom按钮渲染完成并获取实际高度
+                        const checkBottomButton = () => {
+                          try {
+                            const bottomElement =
+                              document.querySelector("ss-bottom");
+                            const actualBottomHeight = bottomElement
+                              ? bottomElement.offsetHeight
+                              : 50;
+
+                            this.headerSectionHeight = headerHeight;
+                            this.bottomIframeMinHeight = headerHeight;
+                            this.availableHeight =
+                              viewportHeight -
+                              actualReviewSectionHeight -
+                              actualBottomHeight;
+
+                            // 初始状态:底部iframe刚好显示header-section高度
+                            this.bottomIframeHeight =
+                              this.bottomIframeMinHeight;
+                            this.topIframeHeight =
+                              this.availableHeight - this.bottomIframeMinHeight;
+                            this.isExpanded = false; // 初始为收起状态
+                            this.layoutCalculated = true;
+
+                            // 应用计算后的高度
+                            this.applyIframeHeights();
+
+                            console.log(
+                              "📊 初始布局计算完成(仅显示header-section):",
+                              {
+                                审核情况区域高度: actualReviewSectionHeight,
+                                底部按钮实际高度: actualBottomHeight,
+                                可用总高度: this.availableHeight,
+                                "header-section高度": headerHeight,
+                                上iframe高度: this.topIframeHeight,
+                                下iframe高度: this.bottomIframeHeight,
+                                iframe总高度:
+                                  this.topIframeHeight +
+                                  this.bottomIframeHeight,
+                                是否超限:
+                                  this.topIframeHeight +
+                                    this.bottomIframeHeight >
+                                  this.availableHeight,
+                                差额:
+                                  this.topIframeHeight +
+                                  this.bottomIframeHeight -
+                                  this.availableHeight,
+                                isExpanded: this.isExpanded,
+                              }
+                            );
+                          } catch (bottomError) {
+                            console.error(
+                              "❌ 获取底部按钮高度时出错:",
+                              bottomError
+                            );
+                            // 使用默认高度50px重试
+                            const defaultBottomHeight = 50;
+                            this.headerSectionHeight = headerHeight;
+                            this.bottomIframeMinHeight = headerHeight;
+                            this.availableHeight =
+                              viewportHeight -
+                              actualReviewSectionHeight -
+                              defaultBottomHeight;
+                            this.bottomIframeHeight =
+                              this.bottomIframeMinHeight;
+                            this.topIframeHeight =
+                              this.availableHeight - this.bottomIframeMinHeight;
+                            this.isExpanded = false;
+                            this.layoutCalculated = true;
+                            this.applyIframeHeights();
+                          }
+                        };
+
+                        // 如果bottom按钮还没加载,稍后重试
+                        if (!document.querySelector("ss-bottom")) {
+                          setTimeout(checkBottomButton, 100);
+                        } else {
+                          checkBottomButton();
+                        }
+                      } else {
+                        // 如果还没有加载完成,继续等待
+                        console.log("⏳ 等待header-section加载...");
+                        setTimeout(checkHeaderSection, 100);
+                      }
+                    } catch (error) {
+                      console.error("❌ 访问iframe DOM时出错:", error);
+                      console.log("⏳ 重试中...");
+                      setTimeout(checkHeaderSection, 200);
+                    }
+                  };
+
+                  checkHeaderSection();
+                };
+
+                waitForIframeLoad();
+              } catch (error) {
+                console.error("❌ 布局初始化失败:", error);
+              }
+            },
+
+            // 应用iframe高度
+            applyIframeHeights() {
+              const topIframe = document.querySelector(".top-iframe");
+              const bottomIframe = document.querySelector(".bottom-iframe");
+
+              if (topIframe && bottomIframe) {
+                console.log("📏 设置iframe高度前:", {
+                  topIframe: {
+                    currentHeight: topIframe.style.height,
+                    offsetHeight: topIframe.offsetHeight,
+                  },
+                  bottomIframe: {
+                    currentHeight: bottomIframe.style.height,
+                    offsetHeight: bottomIframe.offsetHeight,
+                  },
+                  newTopHeight: this.topIframeHeight,
+                  newBottomHeight: this.bottomIframeHeight,
+                });
+
+                // 直接应用新高度到iframe
+                topIframe.style.height = `${this.topIframeHeight}px`;
+                bottomIframe.style.height = `${this.bottomIframeHeight}px`;
+
+                console.log("🎯 应用iframe高度:", {
+                  top: this.topIframeHeight,
+                  bottom: this.bottomIframeHeight,
+                });
+
+                // 强制重新计算iframe内容
+                this.$nextTick(() => {
+                  try {
+                    topIframe.contentWindow.dispatchEvent(new Event("resize"));
+                  } catch (e) {
+                    // 忽略跨域错误
+                  }
+
+                  try {
+                    bottomIframe.contentWindow.dispatchEvent(
+                      new Event("resize")
+                    );
+                  } catch (e) {
+                    // 忽略跨域错误
+                  }
+
+                  console.log("✅ iframe高度更新完成");
+                });
+              }
+            },
+
+            // 快速版本的高度应用,用于拖拽时减少延迟
+            applyIframeHeightsFast() {
+              const topIframe = document.querySelector(".top-iframe");
+              const bottomIframe = document.querySelector(".bottom-iframe");
+
+              if (topIframe && bottomIframe) {
+                // 直接设置高度,不进行日志和额外的处理
+                topIframe.style.height = `${this.topIframeHeight}px`;
+                bottomIframe.style.height = `${this.bottomIframeHeight}px`;
+              }
+            },
+
+            // 计算iframe高度分配
+            calculateHeights(bottomHeight) {
+              // 计算实际可用的bottom iframe高度
+              // 当bottom iframe展开时,可以覆盖top iframe,所以最大高度就是availableHeight
+              // 最小高度不能小于header-section高度
+              const bottomActualHeight = Math.max(
+                this.bottomIframeMinHeight,
+                Math.min(bottomHeight, this.availableHeight)
+              );
+
+              this.bottomIframeHeight = bottomActualHeight;
+              this.topIframeHeight = this.availableHeight - bottomActualHeight;
+
+              console.log("📐 高度分配计算:", {
+                requestedBottom: bottomHeight,
+                availableHeight: this.availableHeight,
+                minBottomHeight: this.bottomIframeMinHeight,
+                actualBottom: this.bottomIframeHeight,
+                actualTop: this.topIframeHeight,
+                isExpanded: this.isExpanded,
+              });
+
+              this.applyIframeHeights();
+
+              return {
+                top: this.topIframeHeight,
+                bottom: this.bottomIframeHeight,
+              };
+            },
+
+            // 切换展开/收起状态
+            toggleExpand() {
+              if (!this.layoutCalculated) {
+                console.warn("布局尚未计算完成");
+                return;
+              }
+
+              this.isExpanded = !this.isExpanded;
+
+              if (this.isExpanded) {
+                // 展开:bottom iframe占用所有可用高度,覆盖top iframe到title底部
+                // 也就是bottom iframe高度 = availableHeight (title + button之间的所有空间)
+                this.calculateHeights(this.availableHeight);
+                console.log(
+                  "📈 展开状态 - 覆盖到title底部,高度:",
+                  this.availableHeight
+                );
+              } else {
+                // 收起:bottom iframe占用最小高度(header-section高度)
+                this.calculateHeights(this.bottomIframeMinHeight);
+                console.log(
+                  "📉 收起状态 - 最小高度:",
+                  this.bottomIframeMinHeight
+                );
+              }
+            },
+
+            // 处理来自iframe的消息
+            handleIframeMessage(event) {
+              // 安全检查:只接受同源的消息
+              if (event.origin !== window.location.origin) {
+                return;
+              }
+
+              const { type, data } = event.data;
+
+              switch (type) {
+                case "header-section-click":
+                  console.log("📱 收到header-section点击事件");
+                  this.toggleExpand();
+                  break;
+                case "header-section-drag-start":
+                  console.log("🔄 开始拖拽header-section");
+                  this.handleDragStart(data);
+                  break;
+                case "header-section-drag-move":
+                  console.log("🔄 拖拽中:", data.deltaY);
+                  this.handleDragMove(data.deltaY);
+                  break;
+                case "header-section-drag-end":
+                  console.log("🔚 结束拖拽");
+                  this.handleDragEnd();
+                  break;
+              }
+            },
+
+            // 处理拖拽开始
+            handleDragStart(data) {
+              if (!this.layoutCalculated) return;
+
+              this.isDragging = true;
+              this.dragStartY = data.startY;
+              this.dragStartBottomHeight = this.bottomIframeHeight;
+            },
+
+            // 处理拖拽移动
+            handleDragMove(deltaY) {
+              if (!this.isDragging || !this.layoutCalculated) return;
+
+              // 计算新的底部高度
+              const newBottomHeight = this.dragStartBottomHeight - deltaY;
+
+              // 直接设置高度,避免复杂的计算导致的延迟
+              const bottomActualHeight = Math.max(
+                this.bottomIframeMinHeight,
+                Math.min(newBottomHeight, this.availableHeight)
+              );
+
+              this.bottomIframeHeight = bottomActualHeight;
+              this.topIframeHeight = this.availableHeight - bottomActualHeight;
+
+              // 直接应用高度,减少延迟
+              this.applyIframeHeightsFast();
+            },
+
+            // 处理拖拽结束
+            handleDragEnd() {
+              this.isDragging = false;
+              this.dragStartY = 0;
+              this.dragStartBottomHeight = 0;
+
+              // 拖拽结束后用完整的方法确保状态正确
+              this.applyIframeHeights();
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 491 - 0
page/mp_chgChgchk.html

@@ -0,0 +1,491 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>变动情况 1</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+      }
+
+      /* 新增变动情况页纯转圈加载态 by xu 2026-03-06 */
+      .loading {
+        min-height: 40vh;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 32px 16px;
+      }
+
+      .loading-spinner {
+        width: 30px;
+        height: 30px;
+        border: 3px solid #e8f3ed;
+        border-top-color: #40ac6d;
+        border-radius: 50%;
+        animation: page-spin 0.9s linear infinite;
+      }
+
+      @keyframes page-spin {
+        from {
+          transform: rotate(0deg);
+        }
+        to {
+          transform: rotate(360deg);
+        }
+      }
+
+      .status-wrap {
+        padding: 16px;
+      }
+
+      .status-card {
+        background: #fff;
+        border-radius: 8px;
+        padding: 16px;
+        box-sizing: border-box;
+      }
+
+      .status-title {
+        font-size: 15px;
+        font-weight: 600;
+        color: #2d3748;
+      }
+
+      .status-text {
+        margin-top: 8px;
+        line-height: 1.6;
+        color: #4a5568;
+        font-size: 13px;
+        word-break: break-all;
+      }
+
+      .status-retry {
+        margin-top: 12px;
+        height: 34px;
+        padding: 0 14px;
+        border: none;
+        border-radius: 4px;
+        background: #4a5568;
+        color: #fff;
+        font-size: 13px;
+      }
+
+      .ss-sub-tab {
+        min-height: 100vh;
+      }
+
+      .ss-sub-tab__content {
+        background: #fff;
+      }
+
+      .ss-sub-tab__content iframe {
+        width: 100%;
+        min-height: calc(100vh - 56px);
+      }
+
+      /* 变动页签页补充 URL 调试浮层,便于打开和复制当前地址 by xu 2026-03-08 */
+      .url-log-fab {
+        position: fixed;
+        right: 12px;
+        bottom: 72px;
+        z-index: 1200;
+        border: none;
+        border-radius: 16px;
+        background: rgba(36, 40, 53, 0.88);
+        color: #fff;
+        font-size: 12px;
+        line-height: 1;
+        padding: 8px 10px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
+      }
+
+      .url-log-panel {
+        position: fixed;
+        left: 12px;
+        right: 12px;
+        bottom: 116px;
+        z-index: 1200;
+        background: rgba(36, 40, 53, 0.96);
+        color: #fff;
+        border-radius: 8px;
+        padding: 10px;
+        max-height: 42vh;
+        overflow: auto;
+        box-sizing: border-box;
+      }
+
+      .url-log-panel__head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 8px;
+        font-size: 12px;
+      }
+
+      .url-log-panel__close {
+        border: none;
+        background: transparent;
+        color: #fff;
+        font-size: 14px;
+        line-height: 1;
+      }
+
+      .url-log-panel__actions {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        margin-bottom: 8px;
+      }
+
+      .url-log-action {
+        border: none;
+        border-radius: 4px;
+        padding: 5px 10px;
+        font-size: 12px;
+        color: #fff;
+        background: rgba(255, 255, 255, 0.16);
+      }
+
+      .url-log-panel__body {
+        margin: 0;
+        white-space: pre-wrap;
+        word-break: break-all;
+        font-size: 12px;
+        line-height: 1.5;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <!-- 新增变动情况页纯转圈加载态 by xu 2026-03-06 -->
+      <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+      </div>
+
+      <div v-else-if="error" class="status-wrap">
+        <div class="status-card">
+          <div class="status-title">页面加载失败</div>
+          <div class="status-text">{{ error }}</div>
+          <button class="status-retry" type="button" @click="loadTabList">
+            重新加载
+          </button>
+        </div>
+      </div>
+
+      <div v-else-if="!tabList.length" class="status-wrap">
+        <div class="status-card">
+          <div class="status-title">暂无变动页签</div>
+          <div class="status-text">当前接口未返回可展示的页签数据。</div>
+        </div>
+      </div>
+
+      <!-- 新增变动情况子页 Tab 承接 by xu 2026-03-06 -->
+      <ss-sub-tab
+        v-else
+        :tab-list="tabList"
+        :base-params="baseParams"
+        @tab-change="handleTabChange"
+      />
+
+      <!-- 变动页签页补充 URL 调试入口,便于打开和复制当前地址 by xu 2026-03-08 -->
+      <button class="url-log-fab" type="button" @click="showCurrentUrls">
+        URL
+      </button>
+      <div class="url-log-panel" v-if="urlLogVisible">
+        <div class="url-log-panel__head">
+          <span>当前 URL</span>
+          <div>
+            <button class="url-log-action" type="button" @click="showCurrentUrls">显示当前 URL</button>
+            <button class="url-log-panel__close" type="button" @click="urlLogVisible = false">
+              ×
+            </button>
+          </div>
+        </div>
+        <div class="url-log-panel__actions">
+          <button class="url-log-action" type="button" @click="openCurrentUrl('page')">打开页面</button>
+          <button class="url-log-action" type="button" @click="copyCurrentUrl('page')">复制页面</button>
+          <button class="url-log-action" type="button" @click="openCurrentUrl('tab')">打开Tab</button>
+          <button class="url-log-action" type="button" @click="copyCurrentUrl('tab')">复制Tab</button>
+        </div>
+        <pre class="url-log-panel__body">{{ urlLogText }}</pre>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: "",
+              tabList: [],
+              baseParams: {},
+              urlLogVisible: false,
+              urlLogText: '',
+              currentPageUrl: '',
+              currentTabUrl: '',
+            };
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+            this.loadTabList();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const aliasMap = {
+                ssobjname: "ssObjName",
+                ssobjid: "ssObjId",
+                datatype: "dataType",
+              };
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [rawKey, rawValue] of urlSearchParams) {
+                const decodedValue = this.safeDecode(rawValue);
+                params[rawKey] = decodedValue;
+                const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
+                if (normalizedKey) {
+                  params[normalizedKey] = decodedValue;
+                }
+              }
+              return params;
+            },
+
+            safeDecode(text) {
+              if (text === undefined || text === null || text === "") return "";
+              try {
+                return decodeURIComponent(String(text));
+              } catch (_) {
+                return String(text);
+              }
+            },
+
+            parseParamObject(paramStr) {
+              if (!paramStr) return {};
+              if (typeof paramStr === "object") return paramStr;
+              try {
+                return JSON.parse(paramStr);
+              } catch (_) {
+                try {
+                  const fixed = String(paramStr)
+                    .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                    .replace(/'/g, '"');
+                  return JSON.parse(fixed);
+                } catch (error) {
+                  console.error("解析param失败:", error);
+                  return {};
+                }
+              }
+            },
+
+            buildBaseParams(serviceName, destName, paramText) {
+              return {
+                sqid: this.pageParams.sqid || "",
+                shid: this.pageParams.shid || "",
+                shyjm: this.pageParams.shyjm || "",
+                bdlbm: this.pageParams.bdlbm || "",
+                dataType: this.pageParams.dataType || this.pageParams.datatype || "bdplay",
+                encode_shid: this.pageParams.encode_shid || "",
+                ssObjName: this.pageParams.ssObjName || this.pageParams.ssobjname || "",
+                ssObjId: this.pageParams.ssObjId || this.pageParams.ssobjid || "",
+                jdmc: this.pageParams.jdmc || "",
+                service: serviceName,
+                dest: destName,
+                ssServ: serviceName,
+                ssDest: destName,
+                param: paramText,
+              };
+            },
+
+            pickTabList(data) {
+              if (!data) return [];
+              if (Array.isArray(data.tabList)) return data.tabList;
+              if (data.data && Array.isArray(data.data.tabList)) return data.data.tabList;
+              if (data.chgList && Array.isArray(data.chgList.tabList)) return data.chgList.tabList;
+              if (Array.isArray(data.tabs)) return data.tabs;
+              return [];
+            },
+
+            async loadTabList() {
+              // 按 chgChkTab 规则加载变动子页签数据 by xu 2026-03-06
+              this.loading = true;
+              this.error = "";
+              this.tabList = [];
+
+              try {
+                const serviceName = String(
+                  this.pageParams.service || this.pageParams.ssServ || "selChgInfo"
+                ).trim();
+                const destName = String(
+                  this.pageParams.dest || this.pageParams.ssDest || "chgList"
+                ).trim();
+                const paramObj = this.parseParamObject(this.pageParams.param);
+                const requestData = {
+                  ...paramObj,
+                };
+
+                const appendIfMissing = (targetKey, sourceKeys) => {
+                  if (
+                    requestData[targetKey] !== undefined &&
+                    requestData[targetKey] !== null &&
+                    requestData[targetKey] !== ""
+                  ) {
+                    return;
+                  }
+                  for (let i = 0; i < sourceKeys.length; i += 1) {
+                    const value = this.pageParams[sourceKeys[i]];
+                    if (value !== undefined && value !== null && value !== "") {
+                      requestData[targetKey] = value;
+                      return;
+                    }
+                  }
+                };
+
+                appendIfMissing("sqid", ["sqid"]);
+                appendIfMissing("shid", ["shid"]);
+                appendIfMissing("ssObjName", ["ssObjName", "ssobjname"]);
+                appendIfMissing("ssObjId", ["ssObjId", "ssobjid"]);
+                appendIfMissing("bdlbm", ["bdlbm"]);
+                appendIfMissing("dataType", ["dataType", "datatype"]);
+                appendIfMissing("encode_shid", ["encode_shid"]);
+                appendIfMissing("jdmc", ["jdmc"]);
+                appendIfMissing("ssToken", ["ssToken"]);
+
+                const response = await request.post(
+                  `/service?ssServ=${encodeURIComponent(
+                    serviceName
+                  )}&ssDest=${encodeURIComponent(destName)}`,
+                  requestData,
+                  { loading: false, formData: true }
+                );
+
+                const responseData = response && response.data ? response.data : response;
+                this.tabList = this.pickTabList(responseData);
+                this.baseParams = this.buildBaseParams(
+                  serviceName,
+                  destName,
+                  typeof this.pageParams.param === "string"
+                    ? this.pageParams.param
+                    : JSON.stringify(requestData || {})
+                );
+
+                // 新增变动页签单页兜底 by xu 2026-03-06
+                if (!this.tabList.length && destName) {
+                  this.tabList = [
+                    {
+                      desc: '变动详情',
+                      title: '变动详情',
+                      service: serviceName,
+                      dest: destName,
+                      param: typeof this.pageParams.param === "string"
+                        ? this.pageParams.param
+                        : JSON.stringify(requestData || {}),
+                    },
+                  ];
+                }
+
+                if (!this.tabList.length) {
+                  console.warn("[mp_chgChgchk] 未获取到tabList:", responseData);
+                }
+              } catch (error) {
+                console.error("加载变动情况页签失败:", error);
+                this.error = (error && error.message) || "加载失败,请稍后重试";
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            getCurrentTabIframeSrc() {
+              const frame = document.querySelector('.ss-sub-tab__content iframe');
+              return (frame && frame.src) || '';
+            },
+
+            getCurrentUrlMap() {
+              return {
+                page: window.location.href,
+                tab: this.getCurrentTabIframeSrc(),
+              };
+            },
+
+            // 功能说明:变动页签页补充“显示当前 URL”按钮,并直接展示 page/tab 文本 by xu 2026-03-08
+            showCurrentUrls() {
+              const currentUrls = this.getCurrentUrlMap();
+              this.currentPageUrl = currentUrls.page || '';
+              this.currentTabUrl = currentUrls.tab || '';
+              this.urlLogText = [
+                `page: ${this.currentPageUrl || '(empty)'}`,
+                `tab: ${this.currentTabUrl || '(empty)'}`,
+              ].join('\n');
+              this.urlLogVisible = true;
+            },
+
+            getUrlByType(type) {
+              const currentUrls = this.getCurrentUrlMap();
+              return String(currentUrls[type] || '').trim();
+            },
+
+            async copyText(text) {
+              const value = String(text || '').trim();
+              if (!value) throw new Error('empty');
+              if (navigator.clipboard && navigator.clipboard.writeText) {
+                await navigator.clipboard.writeText(value);
+                return;
+              }
+              const textarea = document.createElement('textarea');
+              textarea.value = value;
+              textarea.style.position = 'fixed';
+              textarea.style.opacity = '0';
+              document.body.appendChild(textarea);
+              textarea.focus();
+              textarea.select();
+              document.execCommand('copy');
+              document.body.removeChild(textarea);
+            },
+
+            async copyCurrentUrl(type) {
+              try {
+                await this.copyText(this.getUrlByType(type));
+                if (typeof showToastEffect === 'function') showToastEffect('URL 已复制', 1400, 'success');
+              } catch (_) {
+                if (typeof showToastEffect === 'function') showToastEffect('复制失败', 1800, 'error');
+              }
+            },
+
+            openCurrentUrl(type) {
+              const url = this.getUrlByType(type);
+              if (!url) {
+                if (typeof showToastEffect === 'function') showToastEffect('当前没有可打开的 URL', 1800, 'warning');
+                return;
+              }
+              const newWindow = window.open(url, '_blank');
+              if (!newWindow) {
+                window.location.href = url;
+              }
+            },
+
+            handleTabChange({ index, tab }) {
+              console.log("[mp_chgChgchk] 切换页签:", index, tab);
+              if (this.urlLogVisible) {
+                setTimeout(() => this.showCurrentUrls(), 80);
+              }
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 500 - 0
page/mp_chgChkTab.html

@@ -0,0 +1,500 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>变动情况</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+      }
+
+      .loading {
+        min-height: 40vh;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 32px 16px;
+      }
+
+      .loading-spinner {
+        width: 30px;
+        height: 30px;
+        border: 3px solid #e8f3ed;
+        border-top-color: #40ac6d;
+        border-radius: 50%;
+        animation: page-spin 0.9s linear infinite;
+      }
+
+      @keyframes page-spin {
+        from {
+          transform: rotate(0deg);
+        }
+        to {
+          transform: rotate(360deg);
+        }
+      }
+
+      .status-wrap {
+        padding: 16px;
+      }
+
+      .status-card {
+        background: #fff;
+        border-radius: 8px;
+        padding: 16px;
+        box-sizing: border-box;
+      }
+
+      .status-title {
+        font-size: 15px;
+        font-weight: 600;
+        color: #2d3748;
+      }
+
+      .status-text {
+        margin-top: 8px;
+        line-height: 1.6;
+        color: #4a5568;
+        font-size: 13px;
+        word-break: break-all;
+      }
+
+      .status-retry {
+        margin-top: 12px;
+        height: 34px;
+        padding: 0 14px;
+        border: none;
+        border-radius: 4px;
+        background: #4a5568;
+        color: #fff;
+        font-size: 13px;
+      }
+
+      .ss-sub-tab {
+        min-height: 100vh;
+      }
+
+      .ss-sub-tab__content {
+        background: #fff;
+      }
+
+      .ss-sub-tab__content iframe {
+        width: 100%;
+        min-height: calc(100vh - 56px);
+      }
+
+      /* 变动页签页补充 URL 调试浮层,便于直接查看初始化请求与返回 by xu 2026-03-08 */
+      .url-log-fab {
+        position: fixed;
+        top: 12px;
+        right: 12px;
+        z-index: 999999;
+        min-width: 56px;
+        height: 32px;
+        border: none;
+        border-radius: 16px;
+        background: #ff7a00;
+        color: #fff;
+        font-size: 12px;
+        font-weight: 600;
+        line-height: 32px;
+        padding: 0 12px;
+        box-shadow: 0 4px 12px rgba(255, 122, 0, 0.35);
+      }
+
+      .url-log-panel {
+        position: fixed;
+        left: 12px;
+        right: 12px;
+        bottom: 116px;
+        z-index: 1200;
+        background: rgba(36, 40, 53, 0.96);
+        color: #fff;
+        border-radius: 8px;
+        padding: 10px;
+        max-height: 42vh;
+        overflow: auto;
+        box-sizing: border-box;
+      }
+
+      .url-log-panel__head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 8px;
+        font-size: 12px;
+      }
+
+      .url-log-panel__close {
+        border: none;
+        background: transparent;
+        color: #fff;
+        font-size: 14px;
+        line-height: 1;
+      }
+
+      .url-log-panel__actions {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        margin-bottom: 8px;
+      }
+
+      .url-log-action {
+        border: none;
+        border-radius: 4px;
+        padding: 5px 10px;
+        font-size: 12px;
+        color: #fff;
+        background: rgba(255, 255, 255, 0.16);
+      }
+
+      .url-log-panel__body {
+        margin: 0;
+        white-space: pre-wrap;
+        word-break: break-all;
+        font-size: 12px;
+        line-height: 1.5;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+      </div>
+
+      <div v-else-if="error" class="status-wrap">
+        <div class="status-card">
+          <div class="status-title">页面加载失败</div>
+          <div class="status-text">{{ error }}</div>
+          <button class="status-retry" type="button" @click="loadTabList">
+            重新加载
+          </button>
+        </div>
+      </div>
+
+      <div v-else-if="!tabList.length" class="status-wrap">
+        <div class="status-card">
+          <div class="status-title">暂无变动页签</div>
+          <div class="status-text">当前接口未返回可展示的页签数据。</div>
+        </div>
+      </div>
+
+      <!-- 功能说明:临时改为直接走 chkChg,方便对比 child 与非 child 返回差异 by xu 2026-03-08 -->
+      <ss-sub-tab
+        v-if="!loading && !error && tabList.length > 0"
+        :tab-list="tabList"
+        :base-params="baseParams"
+        @tab-change="handleTabChange"
+      >
+      </ss-sub-tab>
+
+      <!-- 功能说明:URL 浮层固定独立渲染,避免打断 v-else 链 by xu 2026-03-08 -->
+      <button class="url-log-fab" type="button" @click="handleUrlFabClick">
+        URL
+      </button>
+      <div class="url-log-panel" v-if="urlLogVisible">
+        <div class="url-log-panel__head">
+          <span>初始化信息</span>
+          <div>
+            <button class="url-log-action" type="button" @click="showCurrentUrls">
+              刷新信息
+            </button>
+            <button class="url-log-panel__close" type="button" @click="urlLogVisible = false">
+              ×
+            </button>
+          </div>
+        </div>
+        <div class="url-log-panel__actions">
+          <button class="url-log-action" type="button" @click="openCurrentUrl('page')">打开页面</button>
+          <button class="url-log-action" type="button" @click="copyCurrentUrl('page')">复制页面</button>
+          <button class="url-log-action" type="button" @click="openCurrentUrl('tab')">打开Tab</button>
+          <button class="url-log-action" type="button" @click="copyCurrentUrl('tab')">复制Tab</button>
+        </div>
+        <pre class="url-log-panel__body">{{ urlLogText }}</pre>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: '#app',
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: '',
+              tabList: [],
+              baseParams: {},
+              urlLogVisible: false,
+              urlLogText: '',
+              currentPageUrl: '',
+              currentTabUrl: '',
+              initRequestUrl: '',
+              initRequestDataText: '',
+              initResponseDataText: '',
+              initTabListText: '',
+              initBaseParamsText: '',
+              initErrorText: '',
+            };
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+            // 功能说明:补充 chgChkTab 初始化日志,方便直接核对和 PC 的差异 by xu 2026-03-08
+            console.log('[mp_chgChkTab] mounted pageParams =', this.pageParams);
+            this.loadTabList();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const aliasMap = {
+                ssobjname: 'ssObjName',
+                ssobjid: 'ssObjId',
+                datatype: 'dataType',
+              };
+              const urlSearchParams = new URLSearchParams(window.location.search);
+              for (const [rawKey, rawValue] of urlSearchParams) {
+                const decodedValue = this.safeDecode(rawValue);
+                params[rawKey] = decodedValue;
+                const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
+                if (normalizedKey) {
+                  params[normalizedKey] = decodedValue;
+                }
+              }
+              console.log('[mp_chgChkTab] getUrlParams result =', params);
+              return params;
+            },
+
+            safeDecode(text) {
+              if (text === undefined || text === null || text === '') return '';
+              try {
+                return decodeURIComponent(String(text));
+              } catch (_) {
+                return String(text);
+              }
+            },
+
+            formatDebugValue(value) {
+              if (value === undefined) return '(undefined)';
+              if (value === null) return '(null)';
+              if (typeof value === 'string') return value || '(empty)';
+              try {
+                return JSON.stringify(value, null, 2);
+              } catch (_) {
+                return String(value);
+              }
+            },
+
+            buildBaseParams() {
+              return {
+                sqid: this.pageParams.sqid || '',
+                shid: this.pageParams.shid || '',
+                shyjm: this.pageParams.shyjm || '',
+                bdlbm: this.pageParams.bdlbm || '',
+                dataType: 'bdplay',
+                encode_shid: this.pageParams.encode_shid || '',
+                ssObjName: this.pageParams.ssObjName || this.pageParams.ssobjname || '',
+                ssObjId: this.pageParams.ssObjId || this.pageParams.ssobjid || '',
+                jdmc: this.pageParams.jdmc || '',
+                ssToken: this.pageParams.ssToken || '',
+              };
+            },
+
+            pickTabList(data) {
+              if (!data) return [];
+              if (Array.isArray(data.tabList)) return data.tabList;
+              if (data.data && Array.isArray(data.data.tabList)) return data.data.tabList;
+              if (data.ssData && Array.isArray(data.ssData.tabList)) return data.ssData.tabList;
+              return [];
+            },
+
+            async loadTabList() {
+              this.loading = true;
+              this.error = '';
+              this.tabList = [];
+              this.baseParams = this.buildBaseParams();
+              this.initErrorText = '';
+
+              try {
+                const tagQuery = new URLSearchParams({
+                  ssServ: 'tabTag',
+                  ssDest: 'data',
+                  // 功能说明:临时去掉 child,直接看 chkChg 返回结果 by xu 2026-03-08
+                  name: 'chkChg',
+                  ssObjName: this.baseParams.ssObjName || '',
+                  ssObjId: this.baseParams.ssObjId || '',
+                  sqid: this.baseParams.sqid || '',
+                  shid: this.baseParams.shid || '',
+                  shyjm: this.baseParams.shyjm || '',
+                  bdlbm: this.baseParams.bdlbm || '',
+                  dataType: 'bdplay',
+                  encode_shid: this.baseParams.encode_shid || '',
+                  jdmc: this.baseParams.jdmc || '',
+                  ssToken: this.baseParams.ssToken || '',
+                  service: this.pageParams.service || this.pageParams.ssServ || '',
+                  dest: this.pageParams.dest || this.pageParams.ssDest || '',
+                  param: this.pageParams.param || '',
+                });
+
+                const requestUrl = `/service?${tagQuery.toString()}`;
+                this.initRequestUrl = requestUrl;
+                this.initRequestDataText = this.formatDebugValue({});
+                this.initBaseParamsText = this.formatDebugValue(this.baseParams);
+                console.log('[mp_chgChkTab] requestUrl =', requestUrl);
+                console.log('[mp_chgChkTab] baseParams =', this.baseParams);
+
+                const response = await request.post(
+                  requestUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+                console.log('[mp_chgChkTab] raw response =', response);
+
+                const responseData = response && response.data ? response.data : response;
+                this.initResponseDataText = this.formatDebugValue(responseData);
+                this.tabList = this.pickTabList(responseData);
+                this.initTabListText = this.formatDebugValue(this.tabList);
+                console.log('[mp_chgChkTab] responseData =', responseData);
+                console.log('[mp_chgChkTab] picked tabList =', this.tabList);
+
+                if (!this.tabList.length) {
+                  console.warn('[mp_chgChkTab] tabTag 未返回 tabList =', responseData);
+                }
+              } catch (error) {
+                console.error('[mp_chgChkTab] loadTabList failed =', error);
+                this.error = (error && error.message) || '加载失败,请稍后重试';
+                this.initErrorText = this.formatDebugValue({
+                  message: this.error,
+                  stack: error && error.stack,
+                  raw: error,
+                });
+              } finally {
+                this.loading = false;
+                console.log('[mp_chgChkTab] loading finished, error =', this.error || '(none)');
+              }
+            },
+
+            getCurrentTabIframeSrc() {
+              const frame = document.querySelector('.ss-sub-tab__content iframe');
+              return (frame && frame.src) || '';
+            },
+
+            getCurrentUrlMap() {
+              return {
+                page: window.location.href,
+                tab: this.getCurrentTabIframeSrc(),
+              };
+            },
+
+            // 功能说明:右上角 URL 按钮点击后直接展开面板,并复制当前页面地址 by xu 2026-03-08
+            async handleUrlFabClick() {
+              this.showCurrentUrls();
+              try {
+                await this.copyText(this.getUrlByType('page'));
+                if (typeof showToastEffect === 'function') showToastEffect('当前页面 URL 已复制', 1600, 'success');
+              } catch (_) {
+                if (typeof showToastEffect === 'function') showToastEffect('当前页面 URL 获取失败', 1800, 'error');
+              }
+            },
+
+            // 功能说明:点 URL 时同时展示页面 URL 和初始化请求/返回,便于直接截图排查 by xu 2026-03-08
+            showCurrentUrls() {
+              const currentUrls = this.getCurrentUrlMap();
+              this.currentPageUrl = currentUrls.page || '';
+              this.currentTabUrl = currentUrls.tab || '';
+              this.urlLogText = [
+                `page: ${this.currentPageUrl || '(empty)'}`,
+                `tab: ${this.currentTabUrl || '(empty)'}`,
+                '',
+                '[init requestUrl]',
+                this.initRequestUrl || '(empty)',
+                '',
+                '[init requestData]',
+                this.initRequestDataText || '(empty)',
+                '',
+                '[init responseData]',
+                this.initResponseDataText || '(empty)',
+                '',
+                '[init tabList]',
+                this.initTabListText || '(empty)',
+                '',
+                '[init baseParams]',
+                this.initBaseParamsText || '(empty)',
+                '',
+                '[init error]',
+                this.initErrorText || '(none)',
+              ].join('\n');
+              this.urlLogVisible = true;
+            },
+
+            getUrlByType(type) {
+              const currentUrls = this.getCurrentUrlMap();
+              return String(currentUrls[type] || '').trim();
+            },
+
+            async copyText(text) {
+              const value = String(text || '').trim();
+              if (!value) throw new Error('empty');
+              if (navigator.clipboard && navigator.clipboard.writeText) {
+                await navigator.clipboard.writeText(value);
+                return;
+              }
+              const textarea = document.createElement('textarea');
+              textarea.value = value;
+              textarea.style.position = 'fixed';
+              textarea.style.opacity = '0';
+              document.body.appendChild(textarea);
+              textarea.focus();
+              textarea.select();
+              document.execCommand('copy');
+              document.body.removeChild(textarea);
+            },
+
+            async copyCurrentUrl(type) {
+              try {
+                await this.copyText(this.getUrlByType(type));
+                if (typeof showToastEffect === 'function') showToastEffect('URL 已复制', 1400, 'success');
+              } catch (_) {
+                if (typeof showToastEffect === 'function') showToastEffect('复制失败', 1800, 'error');
+              }
+            },
+
+            openCurrentUrl(type) {
+              const url = this.getUrlByType(type);
+              if (!url) {
+                if (typeof showToastEffect === 'function') showToastEffect('当前没有可打开的 URL', 1800, 'warning');
+                return;
+              }
+              const newWindow = window.open(url, '_blank');
+              if (!newWindow) {
+                window.location.href = url;
+              }
+            },
+
+            handleTabChange({ index, tab }) {
+              console.log('[mp_chgChkTab] 切换页签 =', {
+                index,
+                tab,
+                baseParams: this.baseParams,
+              });
+              if (this.urlLogVisible) {
+                setTimeout(() => this.showCurrentUrls(), 80);
+              }
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 551 - 0
page/mp_chgList.html

@@ -0,0 +1,551 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>变动情况3</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        padding: 8px;
+        box-sizing: border-box;
+      }
+
+      /* 新增变动详情页纯转圈加载态 by xu 2026-03-06 */
+      .loading {
+        min-height: 40vh;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+
+      .loading-spinner {
+        width: 30px;
+        height: 30px;
+        border: 3px solid #e8f3ed;
+        border-top-color: #40ac6d;
+        border-radius: 50%;
+        animation: page-spin 0.9s linear infinite;
+      }
+
+      @keyframes page-spin {
+        from { transform: rotate(0deg); }
+        to { transform: rotate(360deg); }
+      }
+
+      .error-card,
+      .summary-card,
+      .list-card,
+      .compare-card {
+        background: #fff;
+        border-radius: 8px;
+        overflow: hidden;
+        margin-bottom: 10px;
+      }
+
+      .error-card,
+      .summary-card {
+        padding: 16px;
+      }
+
+      .error-text {
+        color: #ff4d4f;
+        line-height: 1.6;
+      }
+
+      .summary-form,
+      .compare-table {
+        width: 100%;
+        border-collapse: collapse;
+      }
+
+      .summary-form th,
+      .compare-table th {
+        width: 110px;
+        color: #4b5563;
+        font-weight: 600;
+        text-align: left;
+        vertical-align: top;
+      }
+
+      .section-title {
+        padding: 14px 16px 10px;
+        font-size: 15px;
+        font-weight: 600;
+        color: #242835;
+      }
+
+      .compare-table thead th {
+        padding: 12px 10px;
+        background: #f7f8fa;
+        text-align: center;
+        width: auto;
+      }
+
+      .compare-table tbody td,
+      .compare-table tbody th,
+      .summary-form td,
+      .summary-form th {
+        padding: 12px 10px;
+        border-top: 1px solid #eef0f3;
+        line-height: 1.6;
+        word-break: break-all;
+      }
+
+      .compare-table tbody tr:first-child td,
+      .compare-table tbody tr:first-child th,
+      .summary-form tbody tr:first-child td,
+      .summary-form tbody tr:first-child th {
+        border-top: none;
+      }
+
+      .value-empty {
+        color: #9ca3af;
+      }
+
+      .compare-col {
+        width: 34%;
+      }
+
+      .label-col {
+        width: 32%;
+      }
+
+      .file-link {
+        color: #2563eb;
+        text-decoration: underline;
+      }
+
+      .cms-list {
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+      }
+
+      .cms-item {
+        padding: 8px 10px;
+        border-radius: 6px;
+        background: #f7f8fa;
+      }
+
+      .desc-text {
+        white-space: pre-wrap;
+      }
+
+      .image-mask {
+        position: fixed;
+        inset: 0;
+        background: rgba(0, 0, 0, 0.68);
+        z-index: 1200;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 12px;
+        box-sizing: border-box;
+      }
+
+      .image-dialog {
+        width: 100%;
+        max-width: 720px;
+        max-height: 88vh;
+        background: #fff;
+        border-radius: 10px;
+        overflow: auto;
+      }
+
+      .image-dialog__head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 12px 14px;
+        border-bottom: 1px solid #eef0f3;
+      }
+
+      .image-dialog__title {
+        font-size: 14px;
+        font-weight: 600;
+        color: #242835;
+      }
+
+      .image-dialog__close {
+        border: none;
+        background: transparent;
+        font-size: 20px;
+        color: #6b7280;
+      }
+
+      .image-compare {
+        display: flex;
+        gap: 10px;
+        padding: 12px;
+        box-sizing: border-box;
+      }
+
+      .image-panel {
+        flex: 1;
+        min-width: 0;
+      }
+
+      .image-label {
+        margin-bottom: 8px;
+        font-size: 13px;
+        color: #4b5563;
+        text-align: center;
+      }
+
+      .image-preview {
+        width: 100%;
+        min-height: 180px;
+        background: #f7f8fa;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        overflow: hidden;
+      }
+
+      .image-preview img {
+        width: 100%;
+        height: auto;
+        display: block;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+      </div>
+
+      <div v-else-if="error" class="error-card">
+        <div class="error-text">{{ error }}</div>
+      </div>
+
+      <!-- 新增变动详情摘要卡片 by xu 2026-03-06 -->
+      <div v-else-if="showSummaryOnly" class="summary-card">
+        <table class="summary-form">
+          <tr>
+            <th>变动类型</th>
+            <td>{{ summaryData.bdlb || pageParams.bdlbm || '-' }}</td>
+          </tr>
+          <tr>
+            <th>申报名称</th>
+            <td>{{ summaryData.mc || '-' }}</td>
+          </tr>
+          <tr>
+            <th>申报描述</th>
+            <td class="desc-text">{{ summaryData.ms || '-' }}</td>
+          </tr>
+        </table>
+      </div>
+
+      <!-- 新增变动详情对比表 by xu 2026-03-06 -->
+      <div v-else class="list-card">
+        <div class="section-title">变动属性对比</div>
+        <table class="compare-table">
+          <thead>
+            <tr>
+              <th class="label-col">字段</th>
+              <th class="compare-col">变动后</th>
+              <th class="compare-col">变动前</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(item, index) in displayList" :key="`${index}-${item.desc || ''}`">
+              <th>{{ item.desc || '-' }}</th>
+              <td>
+                <template v-if="item.renderType === 'cms'">
+                  <div class="cms-list">
+                    <div v-for="(child, childIndex) in item.newCmsList" :key="`new-${childIndex}`" class="cms-item">
+                      {{ child || '(无)' }}
+                    </div>
+                    <div v-if="!item.newCmsList.length" class="value-empty">(无)</div>
+                  </div>
+                </template>
+                <template v-else-if="item.renderType === 'image'">
+                  <span
+                    class="file-link"
+                    v-if="item.newDisplay && item.newDisplay !== '(无)'"
+                    @click="openImageCompare(item)"
+                  >{{ item.newDisplay }}</span>
+                  <span v-else class="value-empty">(无)</span>
+                </template>
+                <template v-else>
+                  <span :class="{ 'value-empty': !item.newDisplay || item.newDisplay === '(无)' }">{{ item.newDisplay || '(无)' }}</span>
+                </template>
+              </td>
+              <td>
+                <template v-if="item.renderType === 'cms'">
+                  <div class="cms-list">
+                    <div v-for="(child, childIndex) in item.oldCmsList" :key="`old-${childIndex}`" class="cms-item">
+                      {{ child || '(无)' }}
+                    </div>
+                    <div v-if="!item.oldCmsList.length" class="value-empty">(无)</div>
+                  </div>
+                </template>
+                <template v-else-if="item.renderType === 'image'">
+                  <span
+                    class="file-link"
+                    v-if="item.oldDisplay && item.oldDisplay !== '(无)'"
+                    @click="openImageCompare(item)"
+                  >{{ item.oldDisplay }}</span>
+                  <span v-else class="value-empty">(无)</span>
+                </template>
+                <template v-else>
+                  <span :class="{ 'value-empty': !item.oldDisplay || item.oldDisplay === '(无)' }">{{ item.oldDisplay || '(无)' }}</span>
+                </template>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+
+      <div v-if="imagePreview.visible" class="image-mask" @click="closeImageCompare">
+        <div class="image-dialog" @click.stop>
+          <div class="image-dialog__head">
+            <span class="image-dialog__title">图片对比</span>
+            <button class="image-dialog__close" type="button" @click="closeImageCompare">×</button>
+          </div>
+          <div class="image-compare">
+            <div class="image-panel">
+              <div class="image-label">变动后</div>
+              <div class="image-preview">
+                <img v-if="imagePreview.newUrl" :src="imagePreview.newUrl" alt="new" />
+                <span v-else class="value-empty">(无)</span>
+              </div>
+            </div>
+            <div class="image-panel">
+              <div class="image-label">变动前</div>
+              <div class="image-preview">
+                <img v-if="imagePreview.oldUrl" :src="imagePreview.oldUrl" alt="old" />
+                <span v-else class="value-empty">(无)</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: '#app',
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: '',
+              rawData: {},
+              displayList: [],
+              summaryData: {
+                bdlb: '',
+                mc: '',
+                ms: '',
+              },
+              imagePreview: {
+                visible: false,
+                newUrl: '',
+                oldUrl: '',
+              },
+            };
+          },
+          computed: {
+            showSummaryOnly() {
+              return ['51', '55'].includes(String(this.pageParams.bdlbm || this.rawData.bdlbm || ''));
+            },
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+            this.loadData();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(window.location.search);
+              for (const [key, value] of urlSearchParams) {
+                try {
+                  params[key] = decodeURIComponent(value);
+                } catch (_) {
+                  params[key] = String(value);
+                }
+              }
+              return params;
+            },
+
+            parseParamObject(paramStr) {
+              if (!paramStr) return {};
+              if (typeof paramStr === 'object') return paramStr;
+              try {
+                return JSON.parse(paramStr);
+              } catch (_) {
+                try {
+                  return JSON.parse(
+                    String(paramStr)
+                      .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                      .replace(/'/g, '"')
+                  );
+                } catch (error) {
+                  console.error('解析param失败:', error);
+                  return {};
+                }
+              }
+            },
+
+            async loadData() {
+              // 加载变动详情并转成移动端对比结构 by xu 2026-03-06
+              this.loading = true;
+              this.error = '';
+              try {
+                const service = String(this.pageParams.service || this.pageParams.ssServ || 'selChgInfo').trim();
+                const dest = String(this.pageParams.dest || this.pageParams.ssDest || 'chgList').trim();
+                const requestData = {
+                  ...this.parseParamObject(this.pageParams.param),
+                };
+                if (!requestData.sqid && this.pageParams.sqid) requestData.sqid = this.pageParams.sqid;
+                if (!requestData.ssObjId && this.pageParams.ssObjId) requestData.ssObjId = this.pageParams.ssObjId;
+                if (!requestData.ssObjName && this.pageParams.ssObjName) requestData.ssObjName = this.pageParams.ssObjName;
+                if (!requestData.bdlbm && this.pageParams.bdlbm) requestData.bdlbm = this.pageParams.bdlbm;
+
+                const response = await request.post(
+                  `/service?ssServ=${encodeURIComponent(service)}&ssDest=${encodeURIComponent(dest)}`,
+                  requestData,
+                  { loading: false, formData: true }
+                );
+                const data = response && response.data ? response.data : response;
+                this.rawData = data || {};
+                await this.buildSummaryData(data || {});
+                await this.buildDisplayList(data || {});
+              } catch (error) {
+                console.error('加载变动详情失败:', error);
+                this.error = (error && error.message) || '加载失败,请稍后重试';
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            async buildSummaryData(data) {
+              const bdlbm = String(this.pageParams.bdlbm || data.bdlbm || '');
+              this.summaryData = {
+                bdlb: await this.translateDict('bdlb', bdlbm, bdlbm),
+                mc: data.sq && data.sq.mc ? data.sq.mc : '',
+                ms: this.normalizeDesc(data.sq && (data.sq.ms || data.sq.mswj) ? (data.sq.ms || data.sq.mswj) : ''),
+              };
+            },
+
+            async buildDisplayList(data) {
+              const list = Array.isArray(data.bdList) ? data.bdList : [];
+              const result = [];
+              for (let i = 0; i < list.length; i += 1) {
+                const item = list[i] || {};
+                const renderType = this.resolveRenderType(item);
+                const nextItem = {
+                  desc: item.desc || item.field?.desc || item.name || '',
+                  renderType,
+                  newDisplay: await this.formatCompareValue(item, item.newVal),
+                  oldDisplay: await this.formatCompareValue(item, item.origVal),
+                  newPath: item.newVal || '',
+                  oldPath: item.origVal || '',
+                  newCmsList: this.extractCmsNames(item, 'newVal'),
+                  oldCmsList: this.extractCmsNames(item, 'origVal'),
+                };
+                result.push(nextItem);
+              }
+              this.displayList = result;
+            },
+
+            resolveRenderType(item) {
+              const type = String(item && item.type !== undefined ? item.type : '');
+              if (['5', '6'].includes(type)) return 'image';
+              if (type === '9') return 'cms';
+              return 'text';
+            },
+
+            extractCmsNames(item) {
+              const list = Array.isArray(item && item.cmsChgList) ? item.cmsChgList : [];
+              return list.map((child) => {
+                const newVal = child && child.newVal && child.newVal.mc ? child.newVal.mc : '';
+                const oldVal = child && child.origVal && child.origVal.mc ? child.origVal.mc : '';
+                return newVal || oldVal || '';
+              }).filter(Boolean);
+            },
+
+            async formatCompareValue(item, value) {
+              if (value === undefined || value === null || value === '') return '(无)';
+              if (item && item.cbName) {
+                return this.translateDict(item.cbName, value, String(value));
+              }
+              const type = String(item && item.type !== undefined ? item.type : '');
+              if (type === '8') return '查看对比';
+              if (type === '9') return '查看内容';
+              if (['5', '6'].includes(type)) return this.extractFileName(value);
+              if ((type === '3' || type === '11' || item.fmt) && window.H5FieldFormatter) {
+                return window.H5FieldFormatter.formatDate(value, item.fmt || 'YYYY-MM-DD HH:mm:ss') || String(value);
+              }
+              return this.normalizeDesc(String(value));
+            },
+
+            extractFileName(path) {
+              if (!path) return '(无)';
+              const parts = String(path).split('/');
+              return parts[parts.length - 1] || String(path);
+            },
+
+            normalizeDesc(value) {
+              if (!value) return '';
+              return String(value)
+                .replace(/<br\s*\/?\>/gi, '\n')
+                .replace(/<[^>]+>/g, '')
+                .trim();
+            },
+
+            async translateDict(dictName, value, fallbackName) {
+              if (fallbackName && String(fallbackName).trim() && dictName === 'bdlb' && String(fallbackName) !== String(value)) {
+                return fallbackName;
+              }
+              if (value === undefined || value === null || value === '') return '';
+              try {
+                if (typeof window.getDictOptions === 'function') {
+                  const options = await window.getDictOptions(dictName);
+                  const target = (options || []).find((item) => String(item.v) === String(value));
+                  if (target && target.n) return target.n;
+                }
+              } catch (error) {
+                console.error(`${dictName} 字典翻译失败:`, error);
+              }
+              return fallbackName || String(value);
+            },
+
+            buildImageUrl(path) {
+              if (!path) return '';
+              if (typeof window.getImageUrl === 'function') {
+                return window.getImageUrl(path);
+              }
+              return `/service?ssServ=dlByHttp&type=img&path=${encodeURIComponent(path)}`;
+            },
+
+            openImageCompare(item) {
+              this.imagePreview = {
+                visible: true,
+                newUrl: this.buildImageUrl(item.newPath),
+                oldUrl: this.buildImageUrl(item.oldPath),
+              };
+            },
+
+            closeImageCompare() {
+              this.imagePreview.visible = false;
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 785 - 0
page/mp_chgchk.html

@@ -0,0 +1,785 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>审核</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        height: 100vh;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+        box-sizing: border-box;
+      }
+
+      .content-wrap {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+        padding: 8px;
+        box-sizing: border-box;
+        overflow: hidden;
+      }
+
+      .frame-card {
+        background: #fff;
+        border-radius: 8px;
+        overflow: hidden;
+        box-sizing: border-box;
+      }
+
+      .info-frame-wrap {
+        flex: 0 0 176px;
+      }
+
+      .chg-frame-wrap,
+      .sh-frame-wrap {
+        flex: 1;
+        min-height: 0;
+      }
+
+      .info-iframe,
+      .chg-iframe,
+      .sh-iframe {
+        width: 100%;
+        height: 100%;
+        border: none;
+        display: block;
+        background: #fff;
+      }
+
+      .status-wrap {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 16px;
+        box-sizing: border-box;
+      }
+
+      .status-card {
+        width: 100%;
+        max-width: 520px;
+        background: #fff;
+        border-radius: 8px;
+        padding: 16px;
+        box-sizing: border-box;
+        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
+      }
+
+      .status-title {
+        font-size: 15px;
+        font-weight: 600;
+        color: #2d3748;
+      }
+
+      .status-text {
+        margin-top: 8px;
+        line-height: 1.6;
+        color: #4a5568;
+        font-size: 13px;
+        word-break: break-all;
+      }
+
+      .status-retry {
+        margin-top: 12px;
+        height: 34px;
+        padding: 0 14px;
+        border: none;
+        border-radius: 4px;
+        background: #4a5568;
+        color: #fff;
+        font-size: 13px;
+      }
+
+      .url-log-fab {
+        position: fixed;
+        right: 12px;
+        bottom: 72px;
+        z-index: 1200;
+        border: none;
+        border-radius: 16px;
+        background: rgba(36, 40, 53, 0.88);
+        color: #fff;
+        font-size: 12px;
+        line-height: 1;
+        padding: 8px 10px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
+      }
+
+      .url-log-panel {
+        position: fixed;
+        left: 12px;
+        right: 12px;
+        bottom: 116px;
+        z-index: 1200;
+        background: rgba(36, 40, 53, 0.96);
+        color: #fff;
+        border-radius: 8px;
+        padding: 10px;
+        max-height: 42vh;
+        overflow: auto;
+        box-sizing: border-box;
+      }
+
+      .url-log-panel__head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 8px;
+        font-size: 12px;
+      }
+
+      .url-log-panel__close {
+        border: none;
+        background: transparent;
+        color: #fff;
+        font-size: 14px;
+        line-height: 1;
+      }
+
+      .url-log-panel__body {
+        margin: 0;
+        white-space: pre-wrap;
+        word-break: break-all;
+        font-size: 12px;
+        line-height: 1.5;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <div class="content-wrap" v-if="initStatus === 'ready'">
+        <div class="frame-card info-frame-wrap">
+          <iframe
+            ref="infoIframe"
+            class="info-iframe"
+            :src="topIframeSrc || 'about:blank'"
+            frameborder="0"
+          ></iframe>
+        </div>
+        <div class="frame-card chg-frame-wrap">
+          <iframe
+            ref="chgIframe"
+            class="chg-iframe"
+            :src="chgIframeSrc || 'about:blank'"
+            frameborder="0"
+          ></iframe>
+        </div>
+        <div class="frame-card sh-frame-wrap">
+          <iframe
+            ref="shIframe"
+            class="sh-iframe"
+            :src="shIframeSrc || 'about:blank'"
+            frameborder="0"
+          ></iframe>
+        </div>
+      </div>
+
+      <div class="status-wrap" v-else>
+        <div class="status-card">
+          <div class="status-title">{{ initStatusTitle }}</div>
+          <div class="status-text">{{ initStatusMessage }}</div>
+          <div class="status-text" v-if="missingParams.length > 0">
+            缺少参数:{{ missingParams.join('、') }}
+          </div>
+          <button class="status-retry" type="button" @click="callApi">
+            重新加载
+          </button>
+        </div>
+      </div>
+
+      <ss-bottom
+        :buttons="bottomButtons"
+        :show-shyj="true"
+        @button-click="handleBottomAction"
+        :disabled="submitting"
+        v-if="initStatus === 'ready' && bottomButtons.length > 0"
+      ></ss-bottom>
+
+      <button class="url-log-fab" type="button" @click="showCurrentUrls">
+        URL
+      </button>
+      <div class="url-log-panel" v-if="urlLogVisible">
+        <div class="url-log-panel__head">
+          <span>当前 URL</span>
+          <button
+            class="url-log-panel__close"
+            type="button"
+            @click="urlLogVisible = false"
+          >
+            ×
+          </button>
+        </div>
+        <pre class="url-log-panel__body">{{ urlLogText }}</pre>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              submitting: false,
+              jdmc: "",
+              sqid: "",
+              shid: "",
+              shyjm: "",
+              bdlbm: "",
+              dataType: "bdplay",
+              encode_shid: "",
+              ssObjName: "",
+              ssObjId: "",
+              initStatus: "loading",
+              initStatusTitle: "页面初始化中",
+              initStatusMessage: "正在加载审核数据,请稍候...",
+              missingParams: [],
+              infoData: null,
+              actionAgreeData: null,
+              actionRejectData: null,
+              bottomButtons: [],
+              topIframeSrc: "",
+              chgIframeSrc: "",
+              shIframeSrc: "",
+              urlLogVisible: false,
+              urlLogText: "",
+            };
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+            this.callApi();
+          },
+          methods: {
+            // 新增变动审核页初始化状态控制 by xu 2026-03-06
+            setInitStatus(status, title, message) {
+              this.initStatus = status;
+              this.initStatusTitle = title || "";
+              this.initStatusMessage = message || "";
+            },
+
+            normalizeError(error) {
+              return {
+                message: (error && error.message) || String(error),
+              };
+            },
+
+            getUrlParams() {
+              const params = {};
+              const aliasMap = {
+                ssobjname: "ssObjName",
+                ssobjid: "ssObjId",
+                sqid: "sqid",
+                shid: "shid",
+                shyjm: "shyjm",
+                bdlbm: "bdlbm",
+                datatype: "dataType",
+                encode_shid: "encode_shid",
+                jdmc: "jdmc",
+              };
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [rawKey, rawValue] of urlSearchParams) {
+                const decodedValue = this.safeDecode(rawValue);
+                params[rawKey] = decodedValue;
+                const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
+                if (normalizedKey) {
+                  params[normalizedKey] = decodedValue;
+                }
+              }
+              return params;
+            },
+
+            safeDecode(text) {
+              if (text === undefined || text === null || text === "") return "";
+              try {
+                return decodeURIComponent(String(text));
+              } catch (_) {
+                return String(text);
+              }
+            },
+
+            pickFirstValue(sources, keys) {
+              const srcArr = Array.isArray(sources) ? sources : [];
+              const keyArr = Array.isArray(keys) ? keys : [];
+              for (let i = 0; i < srcArr.length; i += 1) {
+                const src = srcArr[i];
+                if (!src || typeof src !== "object") continue;
+                for (let j = 0; j < keyArr.length; j += 1) {
+                  const key = keyArr[j];
+                  const value = src[key];
+                  if (value !== undefined && value !== null && value !== "") {
+                    return value;
+                  }
+                }
+              }
+              return "";
+            },
+
+            extractTokenData(apiResponse) {
+              const wantedKeys = [
+                "sqid",
+                "shid",
+                "ssObjName",
+                "ssObjId",
+                "jdmc",
+                "bdlbm",
+                "dataType",
+                "encode_shid",
+              ];
+              const candidates = [
+                apiResponse && apiResponse.data && apiResponse.data.ssData,
+                apiResponse && apiResponse.ssData,
+                apiResponse && apiResponse.data,
+                apiResponse,
+              ];
+              let best = {};
+              let bestScore = -1;
+              candidates.forEach((item) => {
+                if (!item || typeof item !== "object") return;
+                let score = 0;
+                wantedKeys.forEach((key) => {
+                  if (
+                    item[key] !== undefined &&
+                    item[key] !== null &&
+                    item[key] !== ""
+                  ) {
+                    score += 1;
+                  }
+                });
+                if (score > bestScore) {
+                  best = item;
+                  bestScore = score;
+                }
+              });
+              return best;
+            },
+
+            resolveBusinessContext(apiResponse) {
+              const tokenData = this.extractTokenData(apiResponse);
+              const sources = [tokenData, this.pageParams];
+              return {
+                sqid: String(this.pickFirstValue(sources, ["sqid"]) || ""),
+                shid: String(this.pickFirstValue(sources, ["shid"]) || ""),
+                shyjm: String(this.pickFirstValue(sources, ["shyjm"]) || ""),
+                ssObjName: String(
+                  this.pickFirstValue(sources, ["ssObjName", "ssobjname"]) ||
+                    ""
+                ),
+                ssObjId: String(
+                  this.pickFirstValue(sources, ["ssObjId", "ssobjid"]) || ""
+                ),
+                bdlbm: String(this.pickFirstValue(sources, ["bdlbm"]) || ""),
+                dataType:
+                  String(
+                    this.pickFirstValue(sources, ["dataType", "datatype"]) ||
+                      this.dataType ||
+                      "bdplay"
+                  ) || "bdplay",
+                encode_shid: String(
+                  this.pickFirstValue(sources, ["encode_shid"]) || ""
+                ),
+                jdmc: this.safeDecode(
+                  this.pickFirstValue(sources, ["jdmc"]) || ""
+                ),
+              };
+            },
+
+            parseInfoParm(rawParm) {
+              if (!rawParm) return {};
+              if (typeof rawParm === "object") return rawParm;
+              const text = String(rawParm).trim();
+              if (!text) return {};
+              try {
+                return JSON.parse(text);
+              } catch (_) {}
+              try {
+                const normalized = text
+                  .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                  .replace(/'/g, '"');
+                return JSON.parse(normalized);
+              } catch (_) {
+                return {};
+              }
+            },
+
+            resolveInfoDestPath(destName) {
+              const raw = String(destName || "").trim();
+              if (!raw) return "";
+              if (/^https?:\/\//i.test(raw)) return raw;
+              if (raw.startsWith("/")) return raw;
+              if (raw.endsWith(".html")) return `/page/${raw}`;
+              if (raw.startsWith("mp_")) return `/page/${raw}.html`;
+              return `/page/mp_${raw}.html`;
+            },
+
+            buildIframeSrc(path, queryObj) {
+              const search = new URLSearchParams();
+              Object.keys(queryObj || {}).forEach((key) => {
+                const value = queryObj[key];
+                if (value === undefined || value === null) return;
+                search.set(key, String(value));
+              });
+              return `${path}?${search.toString()}`;
+            },
+
+            buildInfoIframeSrc(infoData, fallbackQuery) {
+              const conf =
+                infoData && typeof infoData === "object" ? infoData : {};
+              const serviceName = conf.service || conf.servName || "";
+              const destName = conf.dest || "";
+              const parmObj = this.parseInfoParm(conf.parm);
+              const mergedQuery = {
+                ...(fallbackQuery || {}),
+                ...(parmObj || {}),
+              };
+              if (!serviceName || !destName) {
+                return this.buildIframeSrc("/page/mp_objInfo.html", mergedQuery);
+              }
+
+              const pagePath = this.resolveInfoDestPath(destName);
+              if (!pagePath) {
+                return this.buildIframeSrc("/page/mp_objInfo.html", mergedQuery);
+              }
+
+              const paramText =
+                typeof conf.parm === "string"
+                  ? conf.parm
+                  : JSON.stringify(parmObj || {});
+              const iframeQuery = {
+                ...mergedQuery,
+                ssServ: serviceName,
+                ssDest: destName,
+                service: serviceName,
+                dest: destName,
+                param: paramText,
+              };
+              return this.buildIframeSrc(pagePath, iframeQuery);
+            },
+
+            buildCommonQuery() {
+              return {
+                sqid: this.sqid,
+                shid: this.shid,
+                shyjm: this.shyjm,
+                bdlbm: this.bdlbm,
+                dataType: this.dataType,
+                encode_shid: this.encode_shid,
+                ssObjName: this.ssObjName,
+                ssObjId: this.ssObjId,
+                jdmc: this.jdmc,
+              };
+            },
+
+            normalizeActionConfig(config) {
+              if (!config || typeof config !== "object") return null;
+              const normalized = { ...config };
+              if (!normalized.service && normalized.servName) {
+                normalized.service = normalized.servName;
+              }
+              return normalized;
+            },
+
+            async fetchActionConfigs() {
+              // 按 bdlbm 拉取变动审核同意退回动作配置 by xu 2026-03-06
+              const query = `ssObjName=${encodeURIComponent(
+                this.ssObjName
+              )}&ssObjId=${encodeURIComponent(
+                this.ssObjId
+              )}&sqid=${encodeURIComponent(
+                this.sqid
+              )}&shid=${encodeURIComponent(
+                this.shid
+              )}&bdlbm=${encodeURIComponent(
+                this.bdlbm
+              )}&dataType=${encodeURIComponent(
+                this.dataType
+              )}&encode_shid=${encodeURIComponent(this.encode_shid)}`;
+
+              let agreeTag = "";
+              let rejectTag = "";
+              if (String(this.bdlbm) === "55") {
+                agreeTag = "agrRes";
+                rejectTag = "rejRes";
+              } else if (String(this.bdlbm) === "51") {
+                agreeTag = "agrSus";
+                rejectTag = "rejSus";
+              } else if (!["1", "51", "55"].includes(String(this.bdlbm))) {
+                agreeTag = "agrChg";
+                rejectTag = "rejChg";
+              }
+
+              this.actionAgreeData = null;
+              this.actionRejectData = null;
+              const buttons = [];
+
+              if (rejectTag) {
+                const rejectRes = await request.post(
+                  `/service?ssServ=dataTag&ssDest=data&name=${rejectTag}&${query}`,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.actionRejectData = this.normalizeActionConfig(
+                  rejectRes && rejectRes.data ? rejectRes.data[rejectTag] : null
+                );
+                if (
+                  this.actionRejectData &&
+                  this.actionRejectData.service &&
+                  this.actionRejectData.dest
+                ) {
+                  buttons.push({ text: "退回", action: "reject" });
+                }
+              }
+
+              if (agreeTag) {
+                const agreeRes = await request.post(
+                  `/service?ssServ=dataTag&ssDest=data&name=${agreeTag}&${query}`,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.actionAgreeData = this.normalizeActionConfig(
+                  agreeRes && agreeRes.data ? agreeRes.data[agreeTag] : null
+                );
+                if (
+                  this.actionAgreeData &&
+                  this.actionAgreeData.service &&
+                  this.actionAgreeData.dest
+                ) {
+                  buttons.push({ text: "同意", action: "agree" });
+                }
+              }
+
+              this.bottomButtons = buttons;
+            },
+
+            async callApi() {
+              this.loading = true;
+              this.infoData = null;
+              this.actionAgreeData = null;
+              this.actionRejectData = null;
+              this.bottomButtons = [];
+              this.topIframeSrc = "";
+              this.chgIframeSrc = "";
+              this.shIframeSrc = "";
+              this.missingParams = [];
+              this.setInitStatus(
+                "loading",
+                "页面初始化中",
+                "正在加载审核数据,请稍候..."
+              );
+
+              let apiResponse = null;
+
+              try {
+                if (this.pageParams.ssToken) {
+                  apiResponse = await request.post(
+                    `/service?ssToken=${this.pageParams.ssToken}`,
+                    {},
+                    { loading: false }
+                  );
+                }
+
+                const context = this.resolveBusinessContext(apiResponse);
+                this.sqid = context.sqid;
+                this.shid = context.shid;
+                this.shyjm = context.shyjm;
+                this.ssObjName = context.ssObjName;
+                this.ssObjId = context.ssObjId;
+                this.bdlbm = context.bdlbm;
+                this.dataType = context.dataType;
+                this.encode_shid = context.encode_shid;
+                this.jdmc = context.jdmc || "";
+
+                const requiredKeys = ["sqid", "shid", "ssObjId"];
+                this.missingParams = requiredKeys.filter((key) => !this[key]);
+
+                const query = this.buildCommonQuery();
+                const infoRes = await request.post(
+                  `/service?ssServ=dataTag&ssDest=data&name=info&ssObjName=${encodeURIComponent(
+                    this.ssObjName
+                  )}&ssObjId=${encodeURIComponent(
+                    this.ssObjId
+                  )}&sqid=${encodeURIComponent(
+                    this.sqid
+                  )}&shid=${encodeURIComponent(this.shid)}`,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.infoData = infoRes && infoRes.data ? infoRes.data.info : null;
+
+                this.topIframeSrc = this.buildInfoIframeSrc(this.infoData, query);
+                // 新增变动情况子页承接 by xu 2026-03-06
+                this.chgIframeSrc = this.buildIframeSrc(
+                  // 功能说明:变动页签入口改为精确命中 mp_chgChkTab.html,对齐 chgChkTab.ss.jsp by xu 2026-03-08
+                  "/page/mp_chgChkTab.html",
+                  {
+                    ...query,
+                    service: "selChgInfo",
+                    dest: "chgList",
+                    ssServ: "selChgInfo",
+                    ssDest: "chgList",
+                    param: JSON.stringify({ sqid: this.sqid }),
+                  }
+                );
+                this.shIframeSrc = this.buildIframeSrc(
+                  "/page/mp_shList.html",
+                  query
+                );
+
+                await this.fetchActionConfigs();
+                this.setInitStatus("ready", "页面已就绪", "审核数据加载完成");
+              } catch (error) {
+                const normalized = this.normalizeError(error);
+                this.setInitStatus(
+                  "error",
+                  "页面初始化失败",
+                  normalized.message || "页面初始化失败,请稍后重试"
+                );
+                console.error("[mp_chgchk] 初始化失败", error);
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("页面初始化失败,请稍后重试", 2200, "error");
+                }
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            buildSubmitPayload(actionPayload) {
+              const submitPayload = {};
+              if (this.shid) submitPayload.shid = this.shid;
+              if (this.sqid) submitPayload.sqid = this.sqid;
+              if (this.ssObjName) submitPayload.ssObjName = this.ssObjName;
+              if (this.ssObjId) submitPayload.ssObjId = this.ssObjId;
+
+              const payloadObj =
+                actionPayload && typeof actionPayload === "object"
+                  ? actionPayload
+                  : {};
+              const reviewValue = this.pickFirstValue(
+                [payloadObj],
+                [
+                  "shsm",
+                  "shyjValue",
+                  "shyj",
+                  "spyj",
+                  "opinion",
+                  "comment",
+                  "remark",
+                  "reason",
+                  "yj",
+                ]
+              );
+              if (
+                reviewValue !== undefined &&
+                reviewValue !== null &&
+                reviewValue !== ""
+              ) {
+                submitPayload.shsm = String(reviewValue);
+              }
+              return submitPayload;
+            },
+
+            goBackWithRefresh() {
+              if (
+                window.NavigationManager &&
+                typeof window.NavigationManager.goBack === "function"
+              ) {
+                window.NavigationManager.goBack({ refreshParent: true });
+                return;
+              }
+              window.history.back();
+            },
+
+            async submitByConfig(conf, actionPayload) {
+              const serviceName = conf && (conf.service || conf.servName);
+              const destName = conf && conf.dest;
+              if (!conf || !serviceName || !destName) {
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("缺少提交配置", 2200, "error");
+                }
+                return;
+              }
+              if (this.submitting) return;
+
+              try {
+                this.submitting = true;
+                await request.post(
+                  `/service?ssServ=${encodeURIComponent(
+                    serviceName
+                  )}&ssDest=${encodeURIComponent(destName)}`,
+                  this.buildSubmitPayload(actionPayload),
+                  {
+                    loading: true,
+                    formData: true,
+                  }
+                );
+
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("提交成功", 1200, "success");
+                }
+                setTimeout(() => {
+                  this.goBackWithRefresh();
+                }, 300);
+              } catch (error) {
+                console.error("[mp_chgchk] 提交失败", error);
+                if (typeof showToastEffect === "function") {
+                  showToastEffect("提交失败,请稍后重试", 2200, "error");
+                }
+              } finally {
+                this.submitting = false;
+              }
+            },
+
+            handleBottomAction(payload) {
+              if (!payload || !payload.action) return;
+              if (payload.action === "agree") {
+                this.submitByConfig(this.actionAgreeData, payload);
+                return;
+              }
+              if (payload.action === "reject") {
+                this.submitByConfig(this.actionRejectData, payload);
+              }
+            },
+
+            showCurrentUrls() {
+              const lines = [
+                `page: ${window.location.href}`,
+                `info: ${
+                  (this.$refs.infoIframe && this.$refs.infoIframe.src) ||
+                  this.topIframeSrc ||
+                  "(empty)"
+                }`,
+                `chg: ${
+                  (this.$refs.chgIframe && this.$refs.chgIframe.src) ||
+                  this.chgIframeSrc ||
+                  "(empty)"
+                }`,
+                `sh: ${
+                  (this.$refs.shIframe && this.$refs.shIframe.src) ||
+                  this.shIframeSrc ||
+                  "(empty)"
+                }`,
+              ];
+              this.urlLogText = lines.join("\n");
+              this.urlLogVisible = true;
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 1026 - 0
page/mp_cobjList.html

@@ -0,0 +1,1026 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+    <title>二级列表</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+        #app {
+            background: #f5f5f5;
+            min-height: 100vh;
+        }
+
+        [v-cloak] {
+            display: none !important;
+        }
+
+        .search-filter-container {
+            background: #f5f5f5;
+            padding: 15px;
+            position: sticky;
+            top: 0;
+            z-index: 100;
+            display: flex;
+            flex-wrap: wrap;
+            gap: 10px;
+            align-items: center;
+            /* 搜索条件右对齐,和 mp_objList 保持一致 by xu 2026-03-06 */
+            justify-content: flex-end;
+        }
+
+        .search-filter-container .ss-select-container,
+        .search-filter-container .input,
+        .search-filter-container .ss-search-date-picker {
+            border-radius: 6px;
+            overflow: hidden;
+        }
+
+        .loading-container,
+        .error-container {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            min-height: 220px;
+            color: #666;
+            padding: 24px;
+            text-align: center;
+        }
+
+        .loading-spinner {
+            width: 40px;
+            height: 40px;
+            border: 4px solid #f3f3f3;
+            border-top: 4px solid #40ac6d;
+            border-radius: 50%;
+            animation: spin 1s linear infinite;
+            margin-bottom: 15px;
+        }
+
+        @keyframes spin {
+            0% { transform: rotate(0deg); }
+            100% { transform: rotate(360deg); }
+        }
+
+        .error-text {
+            color: #d03050;
+            line-height: 1.7;
+        }
+
+        .list-container {
+            padding: 0 15px 8px;
+        }
+
+        .empty-state {
+            text-align: center;
+            padding: 60px 20px;
+            color: #999;
+        }
+
+        .empty-icon {
+            font-size: 48px;
+            margin-bottom: 15px;
+        }
+
+        .empty-text {
+            font-size: 16px;
+        }
+
+        .card-content .card-header {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            margin-bottom: 10px;
+        }
+
+        .card-content .card-title {
+            font-size: 16px;
+            font-weight: bold;
+            color: #333;
+            line-height: 1.5;
+            word-break: break-all;
+        }
+
+        .draft-tag {
+            flex: 0 0 auto;
+            padding: 2px 8px;
+            border-radius: 999px;
+            background: rgba(64, 172, 109, 0.12);
+            color: #2a8f58;
+            font-size: 12px;
+        }
+
+        .card-content .card-description {
+            font-size: 14px;
+            color: #666;
+            margin-bottom: 8px;
+            line-height: 1.6;
+            word-break: break-all;
+        }
+
+        .card-content .attribute-group {
+            display: flex;
+            flex-wrap: wrap;
+            column-gap: 10px;
+        }
+
+        .card-content .attribute-item {
+            display: flex;
+            margin-bottom: 5px;
+            max-width: 100%;
+        }
+
+        .card-content .attr-label {
+            font-size: 13px;
+            color: #999;
+            flex: 0 0 auto;
+        }
+
+        .card-content .attr-value {
+            font-size: 13px;
+            color: #333;
+            word-break: break-all;
+        }
+
+        .load-more-container {
+            text-align: center;
+            padding: 20px;
+            color: #999;
+            font-size: 14px;
+        }
+
+        .load-more-loading {
+            color: #007aff;
+        }
+
+        .load-more-end {
+            color: #999;
+        }
+
+        .load-more-tip {
+            color: #ccc;
+        }
+
+        .back-to-top-btn {
+            width: 50px;
+            height: 50px;
+            border-radius: 50%;
+            background: rgba(87, 93, 109, 0.5);
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            position: fixed;
+            bottom: 200px;
+            right: 15px;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            z-index: 999;
+        }
+
+        .back-to-top-btn:active {
+            transform: scale(0.9);
+        }
+
+        .back-to-top-inner {
+            width: 42px;
+            height: 42px;
+            border-radius: 50%;
+            background: rgba(87, 93, 109, 0.5);
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+
+        .search-modal-mask {
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgba(0, 0, 0, 0.5);
+            z-index: 1000;
+            display: flex;
+            align-items: flex-end;
+        }
+
+        .search-modal-content {
+            width: 100%;
+            background: white;
+            animation: slideUp 0.3s ease-out;
+            position: relative;
+        }
+
+        @keyframes slideUp {
+            from { transform: translateY(100%); }
+            to { transform: translateY(0); }
+        }
+
+        .search-input-container {
+            display: flex;
+            align-items: center;
+            height: 50px;
+            background: white;
+            border-top: 1px solid #e5e5e5;
+        }
+
+        .search-input {
+            flex: 1;
+            height: 100%;
+            border: none;
+            outline: none;
+            padding: 0 15px;
+            font-size: 16px;
+            background: transparent;
+        }
+
+        .search-divider {
+            width: 1px;
+            height: 24px;
+            background: #e5e5e5;
+        }
+
+        .search-icon-btn {
+            width: 56px;
+            height: 100%;
+            border: none;
+            background: white;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+
+        .search-icon-btn:active {
+            background: #f5f5f5;
+        }
+    </style>
+</head>
+<body>
+<div id="app" v-cloak>
+    <!-- 二级列表筛选条:兼容 PC 的搜索字段、范围按钮和根功能按钮 by xu 2026-03-06 -->
+    <div class="search-filter-container">
+        <template v-for="field in searchFieldList" :key="field.name">
+            <ss-select
+                v-if="field.cbName"
+                v-model="selectedFilters[field.name]"
+                :placeholder="field.desc || '请选择'"
+                :options="filterSelectOptions[field.name] || []"
+                @change="handleFilterChange"
+            ></ss-select>
+
+            <ss-search-date-picker
+                v-else-if="isDateField(field)"
+                v-model="selectedFilters[field.name]"
+                :name="field.name"
+                :placeholder="field.desc || ''"
+                width="140px"
+                @change="applyFilters"
+            ></ss-search-date-picker>
+
+            <ss-search-input
+                v-else
+                v-model="selectedFilters[field.name]"
+                :name="field.name"
+                :placeholder="field.desc || ''"
+                width="120px"
+                @search="applyFilters"
+            ></ss-search-input>
+        </template>
+
+        <ss-search-button
+            :text="getCurrentScopeText()"
+            :options="scopeButtonOptions"
+        ></ss-search-button>
+
+        <ss-search-button
+            v-if="hasKeyWord"
+            text="关键词"
+            @click="openSearchModal"
+        ></ss-search-button>
+
+        <ss-search-button
+            v-for="(button, index) in rootFuncList"
+            :key="`${getButtonText(button)}-${index}`"
+            :text="getButtonText(button)"
+            @click="handleRootButtonClick(button)"
+        ></ss-search-button>
+    </div>
+
+    <div v-if="loading" class="loading-container">
+        <div class="loading-spinner"></div>
+        <div class="loading-text">加载中...</div>
+    </div>
+
+    <div v-else-if="errorMessage" class="error-container">
+        <div class="error-text">{{ errorMessage }}</div>
+    </div>
+
+    <div v-else class="list-container">
+        <div v-if="list.length === 0" class="empty-state">
+            <div class="empty-icon">📋</div>
+            <div class="empty-text">暂无数据</div>
+        </div>
+
+        <div v-else>
+            <!-- 二级列表卡片:兼容 draftList/objList 与 catList/title 结构 by xu 2026-03-06 -->
+            <ss-card
+                v-for="(item, index) in list"
+                :key="`${index}-${item.ssObjId || item.id || item.firstDisplay || ''}`"
+                :item="item"
+                @click="handleCardClick(item)"
+                @button-click="handleCardAction"
+            >
+                <div class="card-content">
+                    <div class="card-header" v-if="item.firstDisplay || item.isDraft">
+                        <span v-if="item.isDraft" class="draft-tag">草稿</span>
+                        <div class="card-title">{{ item.firstDisplay || '未命名记录' }}</div>
+                    </div>
+
+                    <div class="card-description" v-if="item.secondDisplay">
+                        {{ item.secondDisplay }}
+                    </div>
+
+                    <div class="card-attributes" v-if="item.thirdDisplay && item.thirdDisplay.length > 0">
+                        <div
+                            v-for="(group, groupIndex) in item.thirdDisplay"
+                            :key="groupIndex"
+                            class="attribute-group"
+                        >
+                            <div
+                                v-for="(attr, attrIndex) in group"
+                                :key="attrIndex"
+                                class="attribute-item"
+                            >
+                                <span class="attr-label">{{ getAttrLabel(attr) }}:</span>
+                                <span class="attr-value">{{ attr.displayValue }}</span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </ss-card>
+        </div>
+    </div>
+
+    <div class="load-more-container" v-if="list.length > 0">
+        <div v-if="isLoadingMore" class="load-more-loading">
+            <span>正在加载更多...</span>
+        </div>
+        <div v-else-if="!hasMore" class="load-more-end">
+            <span>没有更多数据了</span>
+        </div>
+        <div v-else class="load-more-tip">
+            <span>滚动到底部加载更多</span>
+        </div>
+    </div>
+
+    <div
+        v-if="showBackToTop"
+        class="back-to-top-btn"
+        @touchstart="handleLongPressStart"
+        @touchend="handleLongPressEnd"
+        @touchcancel="handleLongPressCancel"
+        @click.prevent="handleBackToTopClick"
+    >
+        <div class="back-to-top-inner">
+            <Icon name="icon-huidaodingbu" size="40" color="#fff"></Icon>
+        </div>
+    </div>
+
+    <div v-if="showSearchModal" class="search-modal-mask" @click="closeSearchModal">
+        <div class="search-modal-content" @click.stop>
+            <div class="search-input-container">
+                <input
+                    ref="searchInput"
+                    v-model="searchKeyword"
+                    type="search"
+                    inputmode="search"
+                    class="search-input"
+                    placeholder="请输入关键词"
+                    @keyup.enter="performSearch"
+                    autocomplete="off"
+                />
+                <div class="search-divider"></div>
+                <button class="search-icon-btn" @click="performSearch">
+                    <Icon name="icon-chazhao" size="24" color="#575d6d"></Icon>
+                </button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script>
+    window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+            el: '#app',
+            data() {
+                return {
+                    loading: true,
+                    errorMessage: '',
+                    list: [],
+                    currentPage: 1,
+                    pageSize: 12,
+                    hasMore: true,
+                    isLoadingMore: false,
+                    pageParams: {},
+                    service: '',
+                    ssSearchCobjServName: '',
+                    requestParentViewObject: '',
+                    ssObjName: '',
+                    ssObjId: '',
+                    ssPobjIdName: '',
+                    management: '1',
+                    searchFieldList: [],
+                    rootFuncList: [],
+                    selectedFilters: {},
+                    filterSelectOptions: {},
+                    dictCache: new Map(),
+                    ssPaging: null,
+                    hasKeyWord: false,
+                    scopeButtonOptions: [],
+                    showBackToTop: false,
+                    showSearchModal: false,
+                    searchKeyword: '',
+                    longPressTimer: null,
+                    isLongPress: false,
+                    requestSeq: 0,
+                };
+            },
+
+            mounted() {
+                this.setupScopeButtons();
+                this.initPage();
+                this.setupRefreshListener();
+                this.setupScrollListener();
+            },
+
+            beforeUnmount() {
+                if (this.refreshCleanup) {
+                    this.refreshCleanup();
+                }
+                if (this.scrollCleanup) {
+                    this.scrollCleanup();
+                }
+            },
+
+            methods: {
+                // 二级列表初始化:同时兼容直接传 cobj 服务和先走 init 再拿 ssSearchCobjServName 的两种入口 by xu 2026-03-06
+                async initPage() {
+                    try {
+                        this.pageParams = this.getUrlParams();
+                        this.service = String(this.pageParams.service || '').trim();
+                        this.ssSearchCobjServName = String(this.pageParams.ssSearchCobjServName || '').trim();
+                        this.requestParentViewObject = String(this.pageParams.requestParentViewObject || '').trim();
+                        this.ssObjName = String(this.pageParams.ssObjName || this.pageParams.ssobjname || '').trim();
+                        this.ssObjId = String(this.pageParams.ssObjId || this.pageParams.ssobjid || '').trim();
+                        this.ssPobjIdName = String(this.pageParams.ssPobjIdName || '').trim();
+                        this.management = String(this.pageParams.management || '1');
+                        this.searchKeyword = String(this.pageParams.ssKeyword || '').trim();
+                        this.setupScopeButtons();
+
+                        if (this.searchKeyword) {
+                            this.selectedFilters = {
+                                ...this.selectedFilters,
+                                ssKeyword: this.searchKeyword,
+                            };
+                        }
+
+                        if (this.ssSearchCobjServName) {
+                            await this.loadData(1, false);
+                            return;
+                        }
+
+                        if (!this.service) {
+                            this.errorMessage = '缺少服务名,无法加载二级列表';
+                            return;
+                        }
+
+                        await this.resolveInitService();
+                    } catch (error) {
+                        console.error('❌ 二级列表初始化失败:', error);
+                        this.errorMessage = '页面初始化失败,请稍后重试';
+                    } finally {
+                        this.loading = false;
+                    }
+                },
+
+                async resolveInitService() {
+                    const reqId = ++this.requestSeq;
+                    this.loading = true;
+                    this.errorMessage = '';
+
+                    try {
+                        const requestParams = this.buildRequestParams(1);
+                        const result = await request.post(
+                            `/service?ssServ=${this.service}&management=${encodeURIComponent(this.management)}&isReady=1`,
+                            requestParams,
+                            {
+                                loading: false,
+                                formData: true,
+                            }
+                        );
+
+                        if (reqId !== this.requestSeq) return;
+
+                        const rawData = result && result.data ? result.data : {};
+                        if (this.isBizError(rawData)) {
+                            this.errorMessage = this.formatBizError(rawData);
+                            return;
+                        }
+
+                        const payload = this.unwrapResponseData(rawData);
+                        const resolvedCobjService = String(payload.ssSearchCobjServName || '').trim();
+
+                        if (resolvedCobjService) {
+                            this.ssSearchCobjServName = resolvedCobjService;
+                            if (!this.requestParentViewObject) {
+                                this.requestParentViewObject = String(payload.requestParentViewObject || '').trim();
+                            }
+                            if (!this.ssPobjIdName) {
+                                this.ssPobjIdName = String(payload.ssPobjIdName || '').trim();
+                            }
+                            if (!this.ssObjName) {
+                                this.ssObjName = String(payload.ssObjName || payload.objName || '').trim();
+                            }
+                            if (!this.ssObjId) {
+                                this.ssObjId = String(payload.ssObjId || payload.objId || '').trim();
+                            }
+                            await this.loadData(1, false);
+                            return;
+                        }
+
+                        await this.applyApiData(payload, false);
+                    } catch (error) {
+                        console.error('❌ 初始化服务请求失败:', error);
+                        this.errorMessage = '初始化失败,请稍后重试';
+                    }
+                },
+
+                async loadData(pageNo = 1, isLoadMore = false) {
+                    const serviceName = String(this.ssSearchCobjServName || this.service || '').trim();
+                    if (!serviceName) {
+                        this.errorMessage = '缺少二级列表服务名';
+                        return;
+                    }
+
+                    if (isLoadMore && this.isLoadingMore) return;
+                    if (!isLoadMore && this.loading && this.list.length > 0) return;
+
+                    const reqId = ++this.requestSeq;
+                    this.errorMessage = '';
+
+                    if (isLoadMore) {
+                        this.isLoadingMore = true;
+                    } else {
+                        this.loading = true;
+                    }
+
+                    try {
+                        const requestParams = this.buildRequestParams(pageNo);
+                        const result = await request.post(
+                            `/service?ssServ=${serviceName}&management=${encodeURIComponent(this.management)}&isReady=1`,
+                            requestParams,
+                            {
+                                loading: false,
+                                formData: true,
+                            }
+                        );
+
+                        if (reqId !== this.requestSeq) return;
+
+                        const rawData = result && result.data ? result.data : {};
+                        if (this.isBizError(rawData)) {
+                            this.errorMessage = this.formatBizError(rawData);
+                            return;
+                        }
+
+                        const payload = this.unwrapResponseData(rawData);
+                        await this.applyApiData(payload, isLoadMore);
+                    } catch (error) {
+                        console.error('❌ 二级列表加载失败:', error);
+                        this.errorMessage = '加载失败,请稍后重试';
+                    } finally {
+                        if (reqId === this.requestSeq) {
+                            this.loading = false;
+                            this.isLoadingMore = false;
+                        }
+                    }
+                },
+
+                async applyApiData(payload, isLoadMore = false) {
+                    const data = payload && typeof payload === 'object' ? payload : {};
+
+                    if (Array.isArray(data.searchFieldList)) {
+                        this.searchFieldList = data.searchFieldList;
+                    }
+
+                    if (Array.isArray(data.rootFuncList) || Array.isArray(data.buttonList)) {
+                        this.rootFuncList = data.rootFuncList || data.buttonList || [];
+                    }
+
+                    if (Object.prototype.hasOwnProperty.call(data, 'hasKeyWord') || Object.prototype.hasOwnProperty.call(data, 'hasKeyword')) {
+                        this.hasKeyWord = !!(data.hasKeyWord || data.hasKeyword);
+                    }
+
+                    if (data.ssPaging && typeof data.ssPaging === 'object') {
+                        this.ssPaging = {
+                            pageNo: Number(data.ssPaging.pageNo || 1),
+                            rowNumPer: Number(data.ssPaging.rowNumPer || this.pageSize || 12),
+                            rowNum: Number(data.ssPaging.rowNum || 0),
+                        };
+                        if (this.ssPaging.rowNumPer > 0) {
+                            this.pageSize = this.ssPaging.rowNumPer;
+                        }
+                    }
+
+                    this.ensureSelectedFilters();
+                    await this.generateFilterOptions();
+
+                    const draftList = Array.isArray(data.draftList)
+                        ? data.draftList.map(item => ({ ...item, __isDraft: true }))
+                        : [];
+                    const objList = Array.isArray(data.objList)
+                        ? data.objList.map(item => ({ ...item, __isDraft: false }))
+                        : Array.isArray(data.objectList)
+                            ? data.objectList.map(item => ({ ...item, __isDraft: false }))
+                            : [];
+
+                    const formattedList = await this.formatCobjItems(draftList.concat(objList));
+
+                    if (isLoadMore) {
+                        this.list = this.list.concat(formattedList);
+                        this.currentPage = Number(this.currentPage || 1) + 1;
+                    } else {
+                        this.list = formattedList;
+                        this.currentPage = Number(this.ssPaging && this.ssPaging.pageNo ? this.ssPaging.pageNo : 1);
+                    }
+
+                    if (this.ssPaging && this.ssPaging.rowNum) {
+                        this.hasMore = this.list.length < Number(this.ssPaging.rowNum || 0);
+                    } else {
+                        this.hasMore = formattedList.length >= this.pageSize;
+                    }
+
+                    this.errorMessage = '';
+                },
+
+                async formatCobjItems(rawList) {
+                    const list = Array.isArray(rawList) ? rawList : [];
+                    const formattedList = await window.formatObjectList(list, this.dictCache);
+
+                    return formattedList.map((item, index) => {
+                        const rawItem = list[index] || {};
+                        const nextItem = {
+                            ...item,
+                            isDraft: !!rawItem.__isDraft,
+                            buttons: [],
+                        };
+
+                        if (Array.isArray(rawItem.catList) && rawItem.catList.length) {
+                            nextItem.thirdDisplay = [
+                                rawItem.catList
+                                    .map(catItem => {
+                                        if (!catItem || typeof catItem !== 'object') return null;
+                                        const rawValue = catItem.val;
+                                        let displayValue = rawValue === undefined || rawValue === null ? '' : String(rawValue);
+                                        if (catItem.fmt && window.H5FieldFormatter && typeof window.H5FieldFormatter.formatDate === 'function') {
+                                            displayValue = window.H5FieldFormatter.formatDate(rawValue, catItem.fmt) || displayValue;
+                                        }
+                                        return {
+                                            field: {
+                                                desc: String(catItem.desc || ''),
+                                            },
+                                            value: rawValue,
+                                            displayValue,
+                                        };
+                                    })
+                                    .filter(Boolean),
+                            ].filter(group => group.length > 0);
+                        }
+
+                        if (rawItem.chg) {
+                            nextItem.buttons = [{
+                                title: '变动',
+                                onclick: () => this.handleServiceAction(rawItem.chg),
+                            }];
+                        }
+
+                        return nextItem;
+                    });
+                },
+
+                unwrapResponseData(data) {
+                    if (!data || typeof data !== 'object') return {};
+                    return data.ssData && typeof data.ssData === 'object' ? data.ssData : data;
+                },
+
+                isBizError(data) {
+                    return !!(data && typeof data === 'object' && Object.prototype.hasOwnProperty.call(data, 'ssCode') && Object.prototype.hasOwnProperty.call(data, 'ssMsg') && !data.ssData);
+                },
+
+                formatBizError(data) {
+                    return `错误(${data.ssCode}):${data.ssMsg == null ? '' : data.ssMsg}`;
+                },
+
+                getUrlParams() {
+                    return NavigationManager.getUrlParam();
+                },
+
+                buildRequestParams(pageNo = 1) {
+                    const params = {
+                        pageNo,
+                        rowNumPer: this.pageSize,
+                        management: this.management,
+                        isReady: '1',
+                        ...this.getActiveFilterParams(this.selectedFilters),
+                    };
+
+                    if (this.ssObjId) {
+                        const objIdKey = String(this.ssPobjIdName || '').trim() || (this.ssObjName ? `${this.ssObjName}id` : 'ssObjId');
+                        params[objIdKey] = this.ssObjId;
+                    }
+
+                    if (this.requestParentViewObject) {
+                        params.requestParentViewObject = this.requestParentViewObject;
+                    }
+
+                    return params;
+                },
+
+                getActiveFilterParams(source) {
+                    const params = {};
+                    const data = source && typeof source === 'object' ? source : {};
+                    Object.entries(data).forEach(([key, value]) => {
+                        if (value === '' || value === null || value === undefined) return;
+                        params[key] = value;
+                    });
+                    return params;
+                },
+
+                ensureSelectedFilters() {
+                    const nextFilters = { ...this.selectedFilters };
+                    (this.searchFieldList || []).forEach(field => {
+                        if (!field || !field.name) return;
+                        if (nextFilters[field.name] !== undefined) return;
+                        const pageValue = this.pageParams[field.name];
+                        nextFilters[field.name] = pageValue !== undefined ? String(pageValue) : '';
+                    });
+
+                    if (this.hasKeyWord) {
+                        if (nextFilters.ssKeyword === undefined) {
+                            nextFilters.ssKeyword = this.searchKeyword || '';
+                        }
+                        this.searchKeyword = String(nextFilters.ssKeyword || '');
+                    }
+
+                    this.selectedFilters = nextFilters;
+                },
+
+                async generateFilterOptions() {
+                    for (const field of this.searchFieldList || []) {
+                        if (!field || !field.name || !field.cbName) continue;
+                        if (Array.isArray(this.filterSelectOptions[field.name]) && this.filterSelectOptions[field.name].length > 0) continue;
+
+                        try {
+                            const options = await window.getDictOptions(field.cbName, this.dictCache);
+                            this.filterSelectOptions = {
+                                ...this.filterSelectOptions,
+                                [field.name]: [{ n: `全部${field.desc || ''}`, v: '' }].concat(options || []),
+                            };
+                        } catch (error) {
+                            console.error('❌ 获取筛选选项失败:', field.cbName, error);
+                        }
+                    }
+                },
+
+                isDateField(field) {
+                    const type = Number(field && field.type);
+                    return type === 3 || type === 11;
+                },
+
+                getFieldDesc(fieldName) {
+                    const field = (this.searchFieldList || []).find(item => item && item.name === fieldName);
+                    return field ? field.desc : fieldName;
+                },
+
+                getAttrLabel(attr) {
+                    if (!attr || typeof attr !== 'object') return '';
+                    return attr.field && attr.field.desc ? attr.field.desc : '';
+                },
+
+                getButtonText(button) {
+                    if (!button || typeof button !== 'object') return '';
+                    return button.desc || button.title || button.buttonName || button.name || button.function?.desc || '';
+                },
+
+                setupScopeButtons() {
+                    const scopeList = [
+                        { id: '99', text: '所有' },
+                        { id: '2', text: '管理' },
+                        { id: '1', text: '创建' },
+                        { id: '3', text: '已办' },
+                        { id: '55', text: '停用' },
+                    ];
+                    this.scopeButtonOptions = scopeList.map(item => ({
+                        text: item.text,
+                        onclick: () => this.switchScope(item.id),
+                    }));
+                },
+
+                getCurrentScopeText() {
+                    const scopeMap = {
+                        '99': '所有',
+                        '2': '管理',
+                        '1': '创建',
+                        '3': '已办',
+                        '55': '停用',
+                    };
+                    return scopeMap[String(this.management || '1')] || '所有';
+                },
+
+                async switchScope(scopeId) {
+                    this.management = String(scopeId || '1');
+                    await this.applyFilters();
+                },
+
+                handleFilterChange() {
+                    this.applyFilters();
+                },
+
+                async applyFilters() {
+                    this.currentPage = 1;
+                    this.hasMore = true;
+                    this.list = [];
+                    await this.loadData(1, false);
+                },
+
+                handleRootButtonClick(button) {
+                    if (!NavigationManager.goToFromButton(button)) {
+                        this.handleServiceAction(button.function || button);
+                    }
+                },
+
+                handleServiceAction(serviceAction) {
+                    if (!serviceAction || typeof serviceAction !== 'object') {
+                        this.showToast('当前操作缺少配置', 'warning');
+                        return;
+                    }
+
+                    const navButton = {
+                        dest: serviceAction.dest || serviceAction.ssDest || '',
+                        servName: serviceAction.servName || serviceAction.ssServ || serviceAction.service || '',
+                        service: serviceAction.servName || serviceAction.ssServ || serviceAction.service || '',
+                        title: serviceAction.desc || serviceAction.title || '',
+                        desc: serviceAction.desc || serviceAction.title || '',
+                    };
+
+                    if (NavigationManager.goToFromButton(navButton)) {
+                        return;
+                    }
+
+                    this.showToast('暂不支持当前操作跳转', 'warning');
+                },
+
+                handleCardClick(item) {
+                    const play = (item && (item.play || (item.service && item.service.play))) || null;
+                    if (!play) {
+                        this.showToast('当前记录缺少查看配置', 'warning');
+                        return;
+                    }
+
+                    const serviceName = String(play.servName || play.ssServ || '').trim();
+                    const destName = String(play.dest || 'objPlay').trim();
+                    const paramName = String(play.param_name || '').trim();
+                    const paramValue = play.param_value;
+                    const ssToken = String(play.ssToken || '').trim();
+                    const paramText = typeof play.parm === 'string' ? play.parm : '';
+
+                    if (!serviceName) {
+                        this.showToast('缺少查看服务名', 'warning');
+                        return;
+                    }
+
+                    NavigationManager.goTo('mp_objplay', {
+                        title: play.desc || play.title || '查看',
+                        service: serviceName,
+                        dest: destName,
+                        ssDest: destName,
+                        param: paramText,
+                        playParamName: paramName,
+                        playParamValue: paramValue,
+                        ssToken,
+                        ssObjId: item.ssObjId || this.ssObjId || '',
+                        ssObjName: item.ssObjName || this.ssObjName || '',
+                    });
+                },
+
+                handleCardAction() {
+                },
+
+                async loadMore() {
+                    if (!this.hasMore || this.isLoadingMore) return;
+                    await this.loadData(Number(this.currentPage || 1) + 1, true);
+                },
+
+                setupRefreshListener() {
+                    this.refreshCleanup = NavigationManager.onRefreshNotify(() => {
+                        this.applyFilters();
+                    });
+                },
+
+                setupScrollListener() {
+                    let timer = null;
+                    const handleScroll = () => {
+                        clearTimeout(timer);
+                        timer = setTimeout(() => {
+                            const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
+                            const windowHeight = window.innerHeight;
+                            const documentHeight = document.documentElement.scrollHeight;
+
+                            this.showBackToTop = scrollTop > 300;
+
+                            if (scrollTop + windowHeight >= documentHeight - 50) {
+                                this.loadMore();
+                            }
+                        }, 200);
+                    };
+
+                    window.addEventListener('scroll', handleScroll);
+                    this.scrollCleanup = () => {
+                        window.removeEventListener('scroll', handleScroll);
+                    };
+                },
+
+                scrollToTop() {
+                    window.scrollTo({ top: 0, behavior: 'smooth' });
+                },
+
+                handleBackToTopClick() {
+                    if (!this.isLongPress) {
+                        this.scrollToTop();
+                    }
+                    this.isLongPress = false;
+                },
+
+                handleLongPressStart() {
+                    if (!this.hasKeyWord) return;
+                    this.isLongPress = false;
+                    this.longPressTimer = setTimeout(() => {
+                        this.isLongPress = true;
+                        this.openSearchModal();
+                        if (navigator.vibrate) {
+                            navigator.vibrate(50);
+                        }
+                    }, 500);
+                },
+
+                handleLongPressEnd() {
+                    if (this.longPressTimer) {
+                        clearTimeout(this.longPressTimer);
+                        this.longPressTimer = null;
+                    }
+                },
+
+                handleLongPressCancel() {
+                    if (this.longPressTimer) {
+                        clearTimeout(this.longPressTimer);
+                        this.longPressTimer = null;
+                    }
+                    this.isLongPress = false;
+                },
+
+                openSearchModal() {
+                    this.showSearchModal = true;
+                    this.$nextTick(() => {
+                        setTimeout(() => {
+                            if (this.$refs.searchInput) {
+                                this.$refs.searchInput.click();
+                                this.$refs.searchInput.focus();
+                            }
+                        }, 100);
+                    });
+                },
+
+                closeSearchModal() {
+                    this.showSearchModal = false;
+                },
+
+                async performSearch() {
+                    const keyword = String(this.searchKeyword || '').trim();
+                    this.selectedFilters = {
+                        ...this.selectedFilters,
+                        ssKeyword: keyword,
+                    };
+                    this.closeSearchModal();
+                    await this.applyFilters();
+                },
+
+                showToast(message, type = 'info') {
+                    console.log(`${type.toUpperCase()}: ${message}`);
+                    alert(message);
+                },
+            },
+        });
+    });
+</script>
+</body>
+</html>

+ 300 - 0
page/mp_kqjl_baseInfo.html

@@ -0,0 +1,300 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
+    <title>基本信息</title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        padding-top: 8px;
+        box-sizing: border-box;
+      }
+
+      /* 新增考勤记录基本信息页纯转圈加载态 by xu 2026-03-06 */
+      .loading {
+        min-height: 40vh;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 32px 16px;
+      }
+
+      .loading-spinner {
+        width: 30px;
+        height: 30px;
+        border: 3px solid #e8f3ed;
+        border-top-color: #40ac6d;
+        border-radius: 50%;
+        animation: page-spin 0.9s linear infinite;
+      }
+
+      @keyframes page-spin {
+        from { transform: rotate(0deg); }
+        to { transform: rotate(360deg); }
+      }
+
+      .error {
+        text-align: center;
+        padding: 50px;
+        color: #ff4d4f;
+      }
+
+      .section-card {
+        margin: 0 8px 10px;
+        background: #ffffff;
+        border-radius: 8px;
+        overflow: hidden;
+      }
+
+      .form {
+        width: 100%;
+        border-collapse: collapse;
+      }
+
+      .form th {
+        width: 140px;
+        max-width: 170px;
+      }
+
+      .desc-text {
+        white-space: pre-wrap;
+        line-height: 1.6;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <!-- 新增考勤记录基本信息页纯转圈加载态 by xu 2026-03-06 -->
+      <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+      </div>
+      <div v-else-if="error" class="error">{{ error }}</div>
+      <!-- 新增考勤记录基本信息回显页 by xu 2026-03-06 -->
+      <div v-else class="content-div">
+        <div class="section-card">
+          <table class="form">
+            <tr>
+              <th>名称</th>
+              <td>{{ displayData.mc || formData.mc || '-' }}</td>
+            </tr>
+            <tr>
+              <th>人员</th>
+              <td>{{ displayData.rymc || formData.ryid || '-' }}</td>
+            </tr>
+            <tr>
+              <th>开始时间</th>
+              <td>{{ displayData.kssj || '-' }}</td>
+            </tr>
+            <tr>
+              <th>结束时间</th>
+              <td>{{ displayData.jssj || '-' }}</td>
+            </tr>
+            <tr>
+              <th>考勤类别</th>
+              <td>{{ displayData.kqlb || formData.kqlbm || '-' }}</td>
+            </tr>
+            <tr>
+              <th>班级</th>
+              <td>{{ displayData.bjmc || formData.bjid || '-' }}</td>
+            </tr>
+            <tr>
+              <th>描述</th>
+              <td class="desc-text">{{ displayData.ms || formData.ms || formData.mswj || '-' }}</td>
+            </tr>
+          </table>
+        </div>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: "",
+              formData: {},
+              displayData: {
+                mc: "",
+                rymc: "",
+                kssj: "",
+                jssj: "",
+                kqlb: "",
+                bjmc: "",
+                ms: "",
+              },
+            };
+          },
+          async mounted() {
+            this.pageParams = this.getUrlParams();
+            await this.loadData();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(window.location.search);
+              for (const [key, value] of urlSearchParams) {
+                try {
+                  params[key] = decodeURIComponent(value);
+                } catch (_) {
+                  params[key] = String(value);
+                }
+              }
+              return params;
+            },
+
+            parseParamObject(paramStr) {
+              if (!paramStr) return {};
+              if (typeof paramStr === 'object') return paramStr;
+              try {
+                return JSON.parse(paramStr);
+              } catch (_) {
+                try {
+                  const fixed = String(paramStr)
+                    .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                    .replace(/'/g, '"');
+                  return JSON.parse(fixed);
+                } catch (error) {
+                  console.error('解析param失败:', error);
+                  return {};
+                }
+              }
+            },
+
+            async loadData() {
+              // 加载考勤记录详情数据并回显 by xu 2026-03-06
+              const service = String(this.pageParams.service || this.pageParams.ssServ || '').trim();
+              if (!service) {
+                this.error = '缺少service参数';
+                return;
+              }
+
+              this.loading = true;
+              this.error = '';
+
+              try {
+                const requestData = {
+                  ...this.parseParamObject(this.pageParams.param),
+                };
+                const appendIfMissing = (targetKey, sourceKeys) => {
+                  if (
+                    requestData[targetKey] !== undefined &&
+                    requestData[targetKey] !== null &&
+                    requestData[targetKey] !== ''
+                  ) {
+                    return;
+                  }
+                  for (let i = 0; i < sourceKeys.length; i += 1) {
+                    const value = this.pageParams[sourceKeys[i]];
+                    if (value !== undefined && value !== null && value !== '') {
+                      requestData[targetKey] = value;
+                      return;
+                    }
+                  }
+                };
+
+                appendIfMissing('sqid', ['sqid']);
+                appendIfMissing('shid', ['shid']);
+                appendIfMissing('ssObjName', ['ssObjName', 'ssobjname']);
+                appendIfMissing('ssObjId', ['ssObjId', 'ssobjid']);
+                appendIfMissing('bdlbm', ['bdlbm']);
+                appendIfMissing('dataType', ['dataType', 'datatype']);
+                appendIfMissing('encode_shid', ['encode_shid']);
+                appendIfMissing('jdmc', ['jdmc']);
+                appendIfMissing('ssToken', ['ssToken']);
+
+                const response = await request.post(
+                  `/service?ssServ=${service}&ssDest=data`,
+                  requestData,
+                  { loading: false, formData: true }
+                );
+
+                const raw = this.pickKqjlData(response && response.data ? response.data : response);
+                if (!raw) {
+                  this.error = '未获取到考勤记录数据';
+                  return;
+                }
+
+                this.formData = raw;
+                await this.buildDisplayData(raw);
+              } catch (error) {
+                console.error('加载考勤记录基本信息失败:', error);
+                this.error = '加载失败:' + ((error && error.message) || '未知错误');
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            pickKqjlData(data) {
+              if (!data) return null;
+              if (data.kqjl) return data.kqjl;
+              if (data.bjdmKqjl) return data.bjdmKqjl;
+              if (Array.isArray(data.objectList) && data.objectList.length > 0) {
+                return data.objectList[0].kqjl || data.objectList[0].bjdmKqjl || data.objectList[0];
+              }
+              if (Array.isArray(data.data) && data.data.length > 0) {
+                return data.data[0].kqjl || data.data[0].bjdmKqjl || data.data[0];
+              }
+              if (typeof data === 'object') return data;
+              return null;
+            },
+
+            async buildDisplayData(raw) {
+              // 格式化考勤记录显示字段 by xu 2026-03-06
+              this.displayData = {
+                mc: raw.mc || '',
+                rymc: await this.translateDict('ry', raw.ryid, raw.rymc || raw.xm),
+                kssj: this.formatDate(raw.kssj),
+                jssj: this.formatDate(raw.jssj),
+                kqlb: await this.translateDict('kqlb', raw.kqlbm, raw.kqlb),
+                bjmc: await this.translateDict('bj', raw.bjid, raw.bjmc || raw.bjname),
+                ms: this.normalizeDesc(raw.ms || raw.mswj || ''),
+              };
+            },
+
+            formatDate(value) {
+              if (!value) return '';
+              if (window.H5FieldFormatter && typeof window.H5FieldFormatter.formatDate === 'function') {
+                return window.H5FieldFormatter.formatDate(value, 'YYYY-MM-DD HH:mm:ss');
+              }
+              return value;
+            },
+
+            normalizeDesc(value) {
+              if (!value) return '';
+              return String(value)
+                .replace(/<br\s*\/?\>/gi, '\n')
+                .replace(/<[^>]+>/g, '')
+                .trim();
+            },
+
+            async translateDict(dictName, value, fallbackName) {
+              if (fallbackName) return fallbackName;
+              if (value === undefined || value === null || value === '') return '';
+              try {
+                if (typeof window.getDictOptions === 'function') {
+                  const options = await window.getDictOptions(dictName);
+                  const target = (options || []).find((item) => String(item.v) === String(value));
+                  if (target && target.n) return target.n;
+                }
+              } catch (error) {
+                console.error(`${dictName} 字典翻译失败:`, error);
+              }
+              return String(value);
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 235 - 0
page/mp_kqjl_inp.html

@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+    <title>新增考勤记录</title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+        [v-cloak] {
+            display: none !important;
+        }
+
+        #app {
+            background: #f5f5f5;
+            min-height: 100vh;
+            box-sizing: border-box;
+        }
+
+        .table th {
+            width: 140px;
+            max-width: 170px;
+        }
+
+        .table td {
+            position: relative;
+        }
+
+        .reason-input {
+            width: 100%;
+        }
+    </style>
+</head>
+<body>
+<div id="app" v-cloak>
+    <!-- 新增考勤记录发起页面 by xu 2026-03-06 -->
+    <div class="form-wrap">
+        <table class="table">
+            <tr>
+                <th>名称</th>
+                <td>
+                    <ss-input
+                        v-model="formData.mc"
+                        name="mc"
+                        class="reason-input"
+                        placeholder="请录入"
+                    />
+                </td>
+            </tr>
+            <tr>
+                <th>人员</th>
+                <td>
+                    <ss-select
+                        v-model="formData.ryid"
+                        cb="ry"
+                        :auto-select-first="true"
+                        placeholder="请选择"
+                    >
+                    </ss-select>
+                </td>
+            </tr>
+            <tr>
+                <th>开始时间</th>
+                <td>
+                    <ss-datetime-picker
+                        v-model="formData.kssj"
+                        mode="datetime"
+                        placeholder="YYYY-MM-DD HH:mm"
+                        @change="onStartTimeChange"
+                    >
+                    </ss-datetime-picker>
+                </td>
+            </tr>
+            <tr>
+                <th>结束时间</th>
+                <td>
+                    <ss-datetime-picker
+                        v-model="formData.jssj"
+                        mode="datetime"
+                        :min-date="endMinDate"
+                        placeholder="YYYY-MM-DD HH:mm"
+                    >
+                    </ss-datetime-picker>
+                </td>
+            </tr>
+            <tr>
+                <th>考勤类别</th>
+                <td>
+                    <ss-select
+                        v-model="formData.kqlbm"
+                        cb="kqlb"
+                        :auto-select-first="true"
+                        placeholder="请选择"
+                    >
+                    </ss-select>
+                </td>
+            </tr>
+            <tr>
+                <th>班级</th>
+                <td>
+                    <ss-select
+                        v-model="formData.bjid"
+                        cb="bj"
+                        :auto-select-first="true"
+                        placeholder="请选择"
+                    >
+                    </ss-select>
+                </td>
+            </tr>
+            <tr>
+                <th>描述</th>
+                <td>
+                    <ss-input
+                        v-model="formData.ms"
+                        name="ms"
+                        class="reason-input"
+                        placeholder="请录入"
+                    />
+                </td>
+            </tr>
+        </table>
+    </div>
+
+</div>
+
+<script>
+    window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+            el: '#app',
+            data() {
+                return {
+                    // 初始化考勤记录发起数据 by xu 2026-03-06
+                    pageParams: {},
+                    formData: {
+                        mc: '考勤记录',
+                        ryid: '',
+                        bjid: '',
+                        kqlbm: '',
+                        kssj: '',
+                        jssj: '',
+                        ms: ''
+                    },
+                    endMinDate: '',
+                }
+            },
+            async mounted() {
+                this.pageParams = this.getUrlParams()
+                this.initDefaultTimeRange()
+
+                // 功能说明:向 mp_objInp 暴露统一取值方法,尽量减少表单页改动 by xu 2026-03-06
+                window.__mpObjInpGetFormData = async () => {
+                    const message = this.validateForm()
+                    return {
+                        valid: !message,
+                        message: message || '',
+                        data: {
+                            mc: this.formData.mc,
+                            ryid: this.formData.ryid,
+                            bjid: this.formData.bjid,
+                            kqlbm: this.formData.kqlbm,
+                            kssj: this.formData.kssj,
+                            jssj: this.formData.jssj,
+                            ms: (this.formData.ms || '').trim(),
+                            mswj: (this.formData.ms || '').trim()
+                        }
+                    }
+                }
+            },
+
+            beforeUnmount() {
+                // 功能说明:页面卸载时清理桥接方法,避免污染其他表单页 by xu 2026-03-06
+                try {
+                    if (window.__mpObjInpGetFormData) {
+                        delete window.__mpObjInpGetFormData
+                    }
+                } catch (_) {}
+            },
+            methods: {
+                getUrlParams() {
+                    const params = {}
+                    const urlSearchParams = new URLSearchParams(window.location.search)
+                    for (const [key, value] of urlSearchParams) {
+                        params[key] = decodeURIComponent(value)
+                    }
+                    return params
+                },
+
+                initDefaultTimeRange() {
+                    // 设置默认开始/结束时间 by xu 2026-03-06
+                    const now = new Date()
+                    const start = new Date(now.getTime() + 30 * 60 * 1000)
+                    const end = new Date(start.getTime() + 2 * 60 * 60 * 1000)
+
+                    this.formData.kssj = this.formatToDateTime(start)
+                    this.formData.jssj = this.formatToDateTime(end)
+                    this.endMinDate = this.formData.kssj
+                },
+
+                formatToDateTime(date) {
+                    const year = date.getFullYear()
+                    const month = String(date.getMonth() + 1).padStart(2, '0')
+                    const day = String(date.getDate()).padStart(2, '0')
+                    const hour = String(date.getHours()).padStart(2, '0')
+                    const minute = String(date.getMinutes()).padStart(2, '0')
+                    return `${year}-${month}-${day} ${hour}:${minute}`
+                },
+
+                onStartTimeChange(val) {
+                    this.endMinDate = val || ''
+                    if (this.formData.jssj && val && new Date(this.formData.jssj).getTime() < new Date(val).getTime()) {
+                        this.formData.jssj = val
+                    }
+                },
+
+                validateForm() {
+                    if (!this.formData.mc) return '请录入名称'
+                    if (!this.formData.ryid) return '请选择人员'
+                    if (!this.formData.kssj) return '请选择开始时间'
+                    if (!this.formData.jssj) return '请选择结束时间'
+                    if (!this.formData.kqlbm) return '请选择考勤类别'
+                    if (!this.formData.bjid) return '请选择班级'
+
+                    const start = new Date(this.formData.kssj).getTime()
+                    const end = new Date(this.formData.jssj).getTime()
+                    if (!Number.isNaN(start) && !Number.isNaN(end) && end < start) {
+                        return '结束时间不能早于开始时间'
+                    }
+                    return ''
+                }
+            }
+        })
+    })
+</script>
+</body>
+</html>

+ 314 - 0
page/mp_miniInfo.html

@@ -0,0 +1,314 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>概要信息</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        padding: 8px;
+        box-sizing: border-box;
+      }
+
+      /* 新增概要信息页纯转圈加载态 by xu 2026-03-06 */
+      .loading {
+        min-height: 40vh;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+
+      .loading-spinner {
+        width: 30px;
+        height: 30px;
+        border: 3px solid #e8f3ed;
+        border-top-color: #40ac6d;
+        border-radius: 50%;
+        animation: page-spin 0.9s linear infinite;
+      }
+
+      @keyframes page-spin {
+        from { transform: rotate(0deg); }
+        to { transform: rotate(360deg); }
+      }
+
+      .error-card,
+      .empty-card,
+      .mini-card {
+        background: #fff;
+        border-radius: 10px;
+        overflow: hidden;
+      }
+
+      .error-card,
+      .empty-card {
+        padding: 16px;
+      }
+
+      .mini-card {
+        display: flex;
+        gap: 12px;
+        padding: 14px;
+        margin-bottom: 10px;
+      }
+
+      .mini-thumb {
+        width: 72px;
+        height: 72px;
+        border-radius: 10px;
+        overflow: hidden;
+        flex-shrink: 0;
+        background: #eef0f3;
+      }
+
+      .mini-thumb img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+        display: block;
+      }
+
+      .mini-body {
+        flex: 1;
+        min-width: 0;
+      }
+
+      .mini-title {
+        font-size: 16px;
+        line-height: 1.5;
+        color: #242835;
+        font-weight: 600;
+        word-break: break-all;
+      }
+
+      .mini-second {
+        margin-top: 6px;
+        font-size: 13px;
+        line-height: 1.6;
+        color: #6b7280;
+        white-space: pre-wrap;
+        word-break: break-all;
+      }
+
+      .mini-attrs {
+        margin-top: 8px;
+        display: flex;
+        flex-direction: column;
+        gap: 6px;
+      }
+
+      .mini-attr-group {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 6px 10px;
+      }
+
+      .mini-attr {
+        font-size: 12px;
+        line-height: 1.6;
+        color: #4b5563;
+      }
+
+      .mini-attr-label {
+        color: #9ca3af;
+      }
+
+      .mini-card--clickable {
+        cursor: pointer;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+      </div>
+
+      <div v-else-if="error" class="error-card">{{ error }}</div>
+
+      <div v-else-if="list.length === 0" class="empty-card">暂无概要信息</div>
+
+      <!-- 新增概要信息卡片列表 by xu 2026-03-06 -->
+      <div
+        v-else
+        v-for="(item, index) in list"
+        :key="`${index}-${item.ssObjId || item.id || item.firstDisplay || ''}`"
+        class="mini-card"
+        :class="{ 'mini-card--clickable': !!item.__play }"
+        @click="handleCardClick(item)"
+      >
+        <div class="mini-thumb" v-if="item.__thumbUrl">
+          <img :src="item.__thumbUrl" alt="thumb" />
+        </div>
+        <div class="mini-body">
+          <div class="mini-title">{{ item.firstDisplay || '未命名记录' }}</div>
+          <div class="mini-second" v-if="item.secondDisplay">{{ item.secondDisplay }}</div>
+          <div class="mini-attrs" v-if="item.thirdDisplay && item.thirdDisplay.length">
+            <div v-for="(group, groupIndex) in item.thirdDisplay" :key="groupIndex" class="mini-attr-group">
+              <div v-for="(attr, attrIndex) in group" :key="attrIndex" class="mini-attr">
+                <span class="mini-attr-label">{{ (attr.field && (attr.field.desc || attr.field.name)) || '字段' }}:</span>
+                <span>{{ attr.displayValue || '-' }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: '#app',
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: '',
+              dictCache: new Map(),
+              list: [],
+            };
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+            this.loadData();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const aliasMap = {
+                ssobjname: 'ssObjName',
+                ssobjid: 'ssObjId',
+              };
+              const urlSearchParams = new URLSearchParams(window.location.search);
+              for (const [rawKey, rawValue] of urlSearchParams) {
+                let decodedValue = '';
+                try {
+                  decodedValue = decodeURIComponent(rawValue);
+                } catch (_) {
+                  decodedValue = String(rawValue);
+                }
+                params[rawKey] = decodedValue;
+                const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
+                if (normalizedKey) params[normalizedKey] = decodedValue;
+              }
+              return params;
+            },
+
+            parseParamObject(paramStr) {
+              if (!paramStr) return {};
+              if (typeof paramStr === 'object') return paramStr;
+              try {
+                return JSON.parse(paramStr);
+              } catch (_) {
+                try {
+                  return JSON.parse(
+                    String(paramStr)
+                      .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                      .replace(/'/g, '"')
+                  );
+                } catch (error) {
+                  console.error('解析param失败:', error);
+                  return {};
+                }
+              }
+            },
+
+            async loadData() {
+              // 加载 miniInfo 概要卡片数据 by xu 2026-03-06
+              this.loading = true;
+              this.error = '';
+              try {
+                const explicitService = String(this.pageParams.service || this.pageParams.ssServ || '').trim();
+                const explicitDest = String(this.pageParams.dest || this.pageParams.ssDest || '').trim();
+                const requestData = {
+                  ...this.parseParamObject(this.pageParams.param),
+                };
+                if (!requestData.sqid && this.pageParams.sqid) requestData.sqid = this.pageParams.sqid;
+                if (!requestData.shid && this.pageParams.shid) requestData.shid = this.pageParams.shid;
+                if (!requestData.ssObjName && this.pageParams.ssObjName) requestData.ssObjName = this.pageParams.ssObjName;
+                if (!requestData.ssObjId && this.pageParams.ssObjId) requestData.ssObjId = this.pageParams.ssObjId;
+
+                let response = null;
+                if (explicitService && explicitDest) {
+                  response = await request.post(
+                    `/service?ssServ=${encodeURIComponent(explicitService)}&ssDest=${encodeURIComponent(explicitDest)}`,
+                    requestData,
+                    { loading: false, formData: true }
+                  );
+                } else {
+                  // 新增 miniInfo dataTag 兜底请求 by xu 2026-03-06
+                  response = await request.post(
+                    `/service?ssServ=dataTag&ssDest=data&name=miniInfo&ssObjName=${encodeURIComponent(this.pageParams.ssObjName || '')}&ssObjId=${encodeURIComponent(this.pageParams.ssObjId || '')}&sqid=${encodeURIComponent(this.pageParams.sqid || '')}&shid=${encodeURIComponent(this.pageParams.shid || '')}`,
+                    requestData,
+                    { loading: false, formData: true }
+                  );
+                }
+
+                const data = response && response.data ? response.data : response;
+                const rawList = Array.isArray(data && data.objList)
+                  ? data.objList
+                  : Array.isArray(data && data.objectList)
+                    ? data.objectList
+                    : Array.isArray(data && data.data)
+                      ? data.data
+                      : [];
+                const formattedList = typeof window.formatObjectList === 'function'
+                  ? await window.formatObjectList(rawList, this.dictCache)
+                  : rawList;
+                this.list = formattedList.map((item, index) => {
+                  const rawItem = rawList[index] || {};
+                  return {
+                    ...item,
+                    __thumbUrl: this.buildThumbUrl(rawItem.thn || rawItem.thumbnail || rawItem.path || ''),
+                    __play: rawItem.play || null,
+                  };
+                });
+              } catch (error) {
+                console.error('加载概要信息失败:', error);
+                this.error = (error && error.message) || '加载失败,请稍后重试';
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            buildThumbUrl(path) {
+              if (!path) return '';
+              if (typeof window.getImageUrl === 'function') {
+                return window.getImageUrl(path);
+              }
+              return `/service?ssServ=dlByHttp&type=img&path=${encodeURIComponent(path)}`;
+            },
+
+            handleCardClick(item) {
+              if (!item || !item.__play) return;
+              const play = item.__play;
+              const query = new URLSearchParams();
+              if (play.service) query.set('service', play.service);
+              if (play.dest) query.set('dest', play.dest);
+              if (play.service) query.set('ssServ', play.service);
+              if (play.dest) query.set('ssDest', play.dest);
+              if (play.parm) query.set('param', typeof play.parm === 'string' ? play.parm : JSON.stringify(play.parm));
+              if (play.title) query.set('title', play.title);
+              const targetUrl = `/page/mp_objplay.html?${query.toString()}`;
+              if (window.NavigationManager && typeof window.NavigationManager.goToUrl === 'function') {
+                window.NavigationManager.goToUrl(targetUrl);
+                return;
+              }
+              window.location.href = targetUrl;
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 143 - 0
page/mp_objChg.html

@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>变动情况4</title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        display: flex;
+        flex-direction: column;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <!-- 新增对象变动页签容器 by xu 2026-03-06 -->
+      <ss-sub-tab
+        v-if="tabList.length > 0"
+        :tab-list="tabList"
+        :base-params="baseParams"
+        @tab-change="handleTabChange"
+      />
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              tabList: [],
+              baseParams: {},
+            };
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+
+            const infoService =
+              this.pageParams.service || this.pageParams.ssServ || "";
+            const infoDest = this.pageParams.dest || this.pageParams.ssDest || "";
+            const infoParam = this.pageParams.param || "";
+
+            // 功能说明:objChg 固定透传 dataType=change,对齐 objChg.ss.jsp 的 pageContext 设置 by xu 2026-03-06
+            this.baseParams = {
+              ssObjName:
+                this.pageParams.ssObjName || this.pageParams.ssobjname || "",
+              ssObjId: this.pageParams.ssObjId || this.pageParams.ssobjid || "",
+              sqid: this.pageParams.sqid || "",
+              shid: this.pageParams.shid || "",
+              shyjm: this.pageParams.shyjm || "",
+              bdlbm: this.pageParams.bdlbm || "",
+              dataType: "change",
+              encode_shid: this.pageParams.encode_shid || "",
+              jdmc: this.pageParams.jdmc || "",
+              ssToken: this.pageParams.ssToken || "",
+              service: infoService,
+              dest: infoDest,
+              param: infoParam,
+              ssServ: infoService,
+              ssDest: infoDest,
+            };
+
+            this.loadTag();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [key, value] of urlSearchParams) {
+                try {
+                  params[key] = decodeURIComponent(value);
+                } catch (_) {
+                  params[key] = String(value);
+                }
+              }
+              return params;
+            },
+
+            async loadTag() {
+              this.loading = true;
+
+              try {
+                const { ssObjName, ssObjId, sqid, shid, service, dest, param } =
+                  this.baseParams;
+
+                // 功能说明:objChg 页签固定走 chgBaseInfo,child,和 JSP <tab.ss name="chgBaseInfo,child"/> 对齐 by xu 2026-03-06
+                const tagQuery = new URLSearchParams({
+                  ssServ: "tabTag",
+                  ssDest: "data",
+                  name: "chgBaseInfo,child",
+                  ssObjName: ssObjName || "",
+                  ssObjId: ssObjId || "",
+                  sqid: sqid || "",
+                  shid: shid || "",
+                  service: service || "",
+                  dest: dest || "",
+                  param: param || "",
+                  bdlbm: this.baseParams.bdlbm || "",
+                  dataType: "change",
+                });
+
+                const tagResponse = await request.post(
+                  `/service?${tagQuery.toString()}`,
+                  {},
+                  { loading: false, formData: true }
+                );
+
+                if (
+                  tagResponse &&
+                  tagResponse.data &&
+                  Array.isArray(tagResponse.data.tabList)
+                ) {
+                  this.tabList = tagResponse.data.tabList;
+                } else {
+                  console.warn("[mp_objChg] tabTag 未返回 tabList", tagResponse);
+                }
+              } catch (error) {
+                console.error("[mp_objChg] 加载Tab数据失败:", error);
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            handleTabChange({ index, tab }) {
+              console.log("[mp_objChg] 切换到Tab:", index, tab && (tab.desc || tab.title));
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 161 - 112
page/mp_objInfo.html

@@ -1,112 +1,161 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
-    <title></title>
-    <script src="/js/mp_base/base.js"></script>
-
-    <style>
-        #app {
-            background: #f5f5f5;
-            min-height: 100vh;
-            display: flex;
-            flex-direction: column;
-        }
-    </style>
-</head>
-<body>
-    <div id="app" v-cloak>
-        <!-- 使用 ss-sub-tab 组件 -->
-        <ss-sub-tab
-            v-if="tabList.length > 0"
-            :tab-list="tabList"
-            :base-params="baseParams"
-            @tab-change="handleTabChange"
-        />
-    </div>
-
-    <script>
-        // 等待SS框架加载完成
-        window.SS.ready(function () {
-            // 使用SS框架的方式创建Vue实例
-            window.SS.dom.initializeFormApp({
-                el: '#app',
-                data() {
-                    return {
-                        pageParams: {},
-                        loading: false,
-                        tabList: [],           // tabTag接口返回的Tab列表
-                        baseParams: {}         // 基础参数(传递给子页面)
-                    }
-                },
-                mounted() {
-                    // 获取URL参数
-                    this.pageParams = this.getUrlParams()
-                    console.log('🔗 mp_objInfo页面接收到参数:', this.pageParams)
-
-                    // 设置基础参数(会传递给所有子页面)
-                    this.baseParams = {
-                        ssObjName: this.pageParams.ssObjName || '',
-                        ssObjId: this.pageParams.ssObjId || '',
-                        sqid: this.pageParams.sqid || '',
-                        shid: this.pageParams.shid || '',
-                        bdlbm: this.pageParams.bdlbm || '',
-                        dataType: this.pageParams.dataType || '',
-                        encode_shid: this.pageParams.encode_shid || ''
-                    }
-
-                    // 只调用tabTag接口获取Tab列表
-                    if (this.pageParams.ssObjName && this.pageParams.ssObjId) {
-                        this.loadTag()
-                    }
-                },
-                methods: {
-                    // 获取URL参数
-                    getUrlParams() {
-                        const params = {}
-                        const urlSearchParams = new URLSearchParams(window.location.search)
-                        for (const [key, value] of urlSearchParams) {
-                            params[key] = decodeURIComponent(value)
-                        }
-                        return params
-                    },
-
-                    // 加载Tab列表
-                    async loadTag() {
-                        this.loading = true
-
-                        try {
-                            console.log('📋 调用tabTag服务获取Tab列表...')
-
-                            const { ssObjName, ssObjId, sqid, shid, bdlbm, dataType, encode_shid } = this.baseParams
-
-                            const tagResponse = await request.post(
-                                `/service?ssServ=tabTag&ssDest=data&name=baseInfo,child&ssObjName=${ssObjName}&ssObjId=${ssObjId}&sqid=${sqid}&shid=${shid}&bdlbm=${bdlbm}&dataType=${dataType}&encode_shid=${encode_shid}`,
-                                { loading: false, formData: true }
-                            )
-
-                            // 保存Tab列表
-                            if (tagResponse && tagResponse.data && tagResponse.data.tabList) {
-                                this.tabList = tagResponse.data.tabList
-                                console.log('✅ 获取到Tab列表:', this.tabList)
-                            } else {
-                                console.warn('⚠️ tabTag响应中没有tabList字段')
-                            }
-
-                        } catch (error) {
-                            console.error('❌ 加载Tab数据失败:', error)
-                        } finally {
-                            this.loading = false
-                        }
-                    },
-
-                    // Tab切换事件处理
-                    handleTabChange({ index, tab }) {
-                        console.log('📑 切换到Tab:', index, tab.desc || tab.title)
-                    }
-                }
-            })
-        })
-    </script>
-</body>
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title></title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        display: flex;
+        flex-direction: column;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <!-- 使用 ss-sub-tab 组件 -->
+      <ss-sub-tab
+        v-if="tabList.length > 0"
+        :tab-list="tabList"
+        :base-params="baseParams"
+        @tab-change="handleTabChange"
+      />
+    </div>
+
+    <script>
+      // 等待SS框架加载完成
+      window.SS.ready(function () {
+        // 使用SS框架的方式创建Vue实例
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              tabList: [], // tabTag接口返回的Tab列表
+              baseParams: {}, // 基础参数(传递给子页面)
+            };
+          },
+          mounted() {
+            // 获取URL参数
+            this.pageParams = this.getUrlParams();
+            console.log("🔗 mp_objInfo页面接收到参数:", this.pageParams);
+
+            const infoService =
+              this.pageParams.service || this.pageParams.ssServ || "";
+            const infoDest =
+              this.pageParams.dest || this.pageParams.ssDest || "";
+            const infoParam = this.pageParams.param || "";
+
+            // 设置基础参数(会传递给所有子页面)
+            this.baseParams = {
+              ssObjName:
+                this.pageParams.ssObjName || this.pageParams.ssobjname || "",
+              ssObjId: this.pageParams.ssObjId || this.pageParams.ssobjid || "",
+              sqid: this.pageParams.sqid || "",
+              shid: this.pageParams.shid || "",
+              shyjm: this.pageParams.shyjm || "",
+              bdlbm: this.pageParams.bdlbm || "",
+              dataType:
+                this.pageParams.dataType ||
+                this.pageParams.datatype ||
+                "bdplay",
+              encode_shid: this.pageParams.encode_shid || "",
+              jdmc: this.pageParams.jdmc || "",
+              ssToken: this.pageParams.ssToken || "",
+              service: infoService,
+              dest: infoDest,
+              param: infoParam,
+              ssServ: infoService,
+              ssDest: infoDest,
+            };
+            console.log("📦 mp_objInfo透传基础参数:", this.baseParams);
+
+            // 联调模式:不校验入参,始终尝试调用tabTag
+            this.loadTag();
+          },
+          methods: {
+            // 获取URL参数
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [key, value] of urlSearchParams) {
+                params[key] = decodeURIComponent(value);
+              }
+              return params;
+            },
+
+            // 加载Tab列表
+            async loadTag() {
+              this.loading = true;
+
+              try {
+                console.log("📋 调用tabTag服务获取Tab列表...");
+
+                const { ssObjName, ssObjId, sqid, shid, service, dest, param } =
+                  this.baseParams;
+
+                const tagQuery = new URLSearchParams({
+                  ssServ: "tabTag",
+                  ssDest: "data",
+                  name: "baseInfo,child",
+                  ssObjName: ssObjName || "",
+                  ssObjId: ssObjId || "",
+                  sqid: sqid || "",
+                  shid: shid || "",
+                  service: service || "",
+                  dest: dest || "",
+                  param: param || "",
+                });
+                const tagUrl = `/service?${tagQuery.toString()}`;
+                console.log("📋 tabTag请求URL:", tagUrl);
+
+                const tagResponse = await request.post(
+                  tagUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+                console.log("📋 tabTag响应:", tagResponse);
+
+                // 保存Tab列表
+                if (
+                  tagResponse &&
+                  tagResponse.data &&
+                  tagResponse.data.tabList
+                ) {
+                  this.tabList = tagResponse.data.tabList;
+                  console.log("✅ 获取到Tab列表:", this.tabList);
+                } else {
+                  console.warn("⚠️ tabTag响应中没有tabList字段", {
+                    service,
+                    dest,
+                    param,
+                    tagResponse,
+                  });
+                }
+              } catch (error) {
+                console.error("❌ 加载Tab数据失败:", error);
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            // Tab切换事件处理
+            handleTabChange({ index, tab }) {
+              console.log("📑 切换到Tab:", index, tab.desc || tab.title);
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 110 - 45
page/mp_objInp.html

@@ -59,6 +59,15 @@
         @button-click="handleBottomAction"
     >
     </ss-bottom>
+
+    <!-- 功能说明:inp 保存并提交后使用统一样式二次确认弹窗,不再使用浏览器原生 confirm by xu 2026-03-01 -->
+    <ss-confirm
+        v-model="showSubmitConfirm"
+        title="提示"
+        @confirm="handleConfirmSubmit"
+    >
+        <div style="text-align:center;padding:16px 0;font-size: 16px;">是否提交?</div>
+    </ss-confirm>
 </div>
 
 <script>
@@ -84,6 +93,36 @@
             iframe: null,
             submitEnabled: false,
             vm: null,
+            pendingFinalSubmit: null,
+            finalSubmitting: false,
+        };
+
+        // 功能说明:执行 inp 二次确认后的最终提交(${ssObjName}_lr_tj_qr) by xu 2026-03-01
+        const submitFinalConfirm = async () => {
+            if (state.finalSubmitting) return;
+            const pending = state.pendingFinalSubmit;
+            if (!pending || typeof pending !== 'object') {
+                if (typeof showToastEffect === 'function') showToastEffect('提交参数缺失', 1800, 'error');
+                return;
+            }
+
+            const req = ensureRequest();
+            state.finalSubmitting = true;
+            try {
+                await req.post(
+                    `/service?ssServ=${encodeURIComponent(pending.serviceName)}`,
+                    pending.payload,
+                    { loading: true, formData: true }
+                );
+                state.pendingFinalSubmit = null;
+                if (typeof showToastEffect === 'function') showToastEffect('提交成功', 1400, 'success');
+                setTimeout(() => goBack(true), 600);
+            } catch (error) {
+                console.error('[mp_objInp] 二次确认提交失败', error);
+                if (typeof showToastEffect === 'function') showToastEffect('提交失败,请稍后重试', 2200, 'error');
+            } finally {
+                state.finalSubmitting = false;
+            }
         };
 
         const getUrlParams = () => {
@@ -146,6 +185,20 @@
             return `/page/mp_${base}.html`;
         };
 
+        // 功能说明:变动页签里的 objInp 可能拿不到 include_input,此时按对象名兜底到 mp_xxx_inp.html by xu 2026-03-06
+        const resolveFallbackFormPath = () => {
+            const objName = String(state.ssObjName || state.pageParams.ssObjName || '').trim();
+            if (objName) {
+                return `/page/mp_${objName}_inp.html`;
+            }
+            const serviceName = String(state.service || state.pageParams.service || state.pageParams.ssServ || '').trim();
+            const match = serviceName.match(/^([a-zA-Z0-9_]+?)_/);
+            if (match && match[1]) {
+                return `/page/mp_${match[1]}_inp.html`;
+            }
+            return '';
+        };
+
         const goBack = (refreshParent) => {
             if (window.NavigationManager && typeof window.NavigationManager.goBack === 'function') {
                 window.NavigationManager.goBack({ refreshParent: !!refreshParent });
@@ -191,14 +244,15 @@
             setTimeout(updateContentHeight, 0);
         };
 
-        const getFrameFormData = () => {
+        const getFrameFormData = async () => {
             const frame = document.getElementById('objInpFrame');
             const frameWindow = frame ? frame.contentWindow : null;
             if (!frameWindow) return { valid: false, message: '表单页面未加载完成', data: {} };
 
             // 功能说明:优先走子页面暴露方法,尽量减少表单页工作量 by xu 2026-02-28
             if (typeof frameWindow.__mpObjInpGetFormData === 'function') {
-                const result = frameWindow.__mpObjInpGetFormData();
+                // 功能说明:支持子页异步取值(如提交前补查关联字段),确保提交参数完整 by xu 2026-03-01
+                const result = await Promise.resolve(frameWindow.__mpObjInpGetFormData());
                 if (result && typeof result === 'object') {
                     return {
                         valid: result.valid !== false,
@@ -223,26 +277,47 @@
 
         const getSubmitConfig = () => {
             const p = state.initPayload || {};
-            // 功能说明:移动端只保留“保存并提交”,按钮可见性按第一接口 saveAndCommitDest 判断 by xu 2026-02-28
-            const enabled = !!String(p.saveAndCommitDest || '').trim();
+            const saveServiceName = String(p.saveAndCommit || '').trim();
+            const saveDest = String(p.saveAndCommitDest || '').trim();
+            const routeServiceName = String(state.service || state.pageParams.service || state.pageParams.ssServ || '').trim();
+            const routeDest = String(state.pageParams.dest || state.pageParams.ssDest || '').trim();
+            const routeParamObj = parseJsonString(state.pageParams.param);
+            const routeDataType = String(state.pageParams.dataType || routeParamObj.dataType || '').trim().toLowerCase();
+            // 功能说明:变动页签若第一接口没回 saveAndCommitDest,则回退用当前 service+objInp+param 提交 by xu 2026-03-06
+            const canUseRouteSubmit = routeDataType === 'change' && routeDest.toLowerCase() === 'objinp' && !!routeServiceName;
+            const serviceName = saveServiceName || (canUseRouteSubmit ? routeServiceName : '');
+            const dest = saveDest || (canUseRouteSubmit ? routeDest : '');
+            const enabled = !!(serviceName && dest);
             return {
                 enabled,
-                serviceName: String(p.saveAndCommit || '').trim(),
-                dest: String(p.saveAndCommitDest || '').trim(),
+                serviceName,
+                dest,
                 title: String(p.saveAndCommitButtonValue || '保存并提交'),
-                paramObj: parseJsonString(p.saveAndCommitParam),
+                paramObj: pruneParams({
+                    ...(canUseRouteSubmit ? {
+                        ...routeParamObj,
+                        sqid: state.pageParams.sqid,
+                        shid: state.pageParams.shid,
+                        shyjm: state.pageParams.shyjm,
+                        bdlbm: state.pageParams.bdlbm,
+                        dataType: state.pageParams.dataType,
+                    } : {}),
+                    ...parseJsonString(p.saveAndCommitParam),
+                }),
             };
         };
 
         const renderFormFrame = () => {
             const includeInput = (state.dataTagPayload && state.dataTagPayload.include_input) || '';
-            const formPath = includeToMpFormPath(includeInput);
+            const fallbackFormPath = resolveFallbackFormPath();
+            const formPath = includeToMpFormPath(includeInput) || fallbackFormPath;
             console.log('[mp_objInp][renderFormFrame] include_input =', includeInput);
+            console.log('[mp_objInp][renderFormFrame] fallbackFormPath =', fallbackFormPath);
             console.log('[mp_objInp][renderFormFrame] formPath =', formPath);
             if (!formPath) {
                 if (state.vm) {
                     state.vm.iframeSrc = '';
-                    state.vm.placeholderText = '未获取到 include_input,无法加载表单页';
+                    state.vm.placeholderText = '未获取到 include_input,且未命中对象表单页';
                 }
                 state.submitEnabled = false;
                 console.warn('[mp_objInp][renderFormFrame] formPath为空,终止渲染iframe');
@@ -317,7 +392,7 @@
                     return;
                 }
 
-                const formResult = getFrameFormData();
+                const formResult = await getFrameFormData();
                 if (!formResult.valid) {
                     if (typeof showToastEffect === 'function') showToastEffect(formResult.message || '表单校验未通过', 2000, 'warning');
                     return;
@@ -340,44 +415,29 @@
                 console.log('[mp_objInp] saveAndCommit响应', res);
                 const submitResp = unwrapData(res ? res.data : null);
 
-                // 功能说明:对齐PC流程,保存并提交后若目标为 addSure,则进入 mp_addSure 进行二次确认 by xu 2026-02-28
+                // 功能说明:保存并提交后若目标为 addSure,则在 inp 页二次确认并直接调最终提交接口 by xu 2026-03-01
                 if (String(cfg.dest || '').trim().toLowerCase() === 'addsure') {
-                    // 功能说明:addSure 跳转参数改为白名单,避免将复杂对象/超长字段带入 URL 导致后端解析异常 by xu 2026-03-01
-                    const allowedKeys = [
-                        'msg',
-                        'ms',
-                        'print',
-                        'dataType',
-                        'ssServ',
-                        'thisViewObject',
-                        'wdclosewindowparam',
-                    ];
-
-                    const safeRespParams = {};
-                    const respObj = submitResp && typeof submitResp === 'object' ? submitResp : {};
-                    Object.keys(respObj).forEach((key) => {
-                        if (!allowedKeys.includes(key)) return;
-                        const value = respObj[key];
-                        if (value === undefined || value === null) return;
-                        const valueType = typeof value;
-                        if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') {
-                            safeRespParams[key] = value;
-                        }
-                    });
+                    const finalObjName = String((submitResp && submitResp.ssObjName) || state.ssObjName || '').trim();
+                    const finalObjIdName = String((submitResp && submitResp.ssObjIdName) || (state.initPayload && state.initPayload.ssObjIdName) || 'ssObjId').trim();
+                    const finalObjId = String((submitResp && submitResp.ssObjId) || state.ssObjId || '').trim();
+                    if (!finalObjName || !finalObjId) {
+                        if (typeof showToastEffect === 'function') showToastEffect('缺少提交参数,无法确认提交', 2200, 'error');
+                        return;
+                    }
 
-                    const nextParams = pruneParams({
-                        ssObjName: submitResp.ssObjName || state.ssObjName,
-                        ssObjId: submitResp.ssObjId || state.ssObjId,
-                        ssObjIdName: submitResp.ssObjIdName || (state.initPayload && state.initPayload.ssObjIdName) || 'ssObjId',
-                        fromObjInp: '1',
-                        ...safeRespParams,
+                    const finalServ = `${finalObjName}_lr_tj_qr`;
+                    const finalPayload = pruneParams({
+                        ssObjName: finalObjName,
+                        [finalObjIdName]: finalObjId,
                     });
-                    if (window.NavigationManager && typeof window.NavigationManager.goTo === 'function') {
-                        window.NavigationManager.goTo('mp_addSure', nextParams, { needRefresh: true });
-                    } else {
-                        const qp = new URLSearchParams();
-                        Object.keys(nextParams).forEach((k) => qp.set(k, nextParams[k]));
-                        window.location.href = `/page/mp_addSure.html?${qp.toString()}`;
+
+                    // 功能说明:先缓存最终提交参数,再弹 ss-confirm 确认 by xu 2026-03-01
+                    state.pendingFinalSubmit = {
+                        serviceName: finalServ,
+                        payload: finalPayload,
+                    };
+                    if (state.vm) {
+                        state.vm.showSubmitConfirm = true;
                     }
                     return;
                 }
@@ -423,6 +483,7 @@
                     iframeSrc: '',
                     placeholderText: '正在加载录入页面...',
                     showBottom: true,
+                    showSubmitConfirm: false,
                     bottomButtons: [
                         { text: '关闭', action: 'close' },
                         {
@@ -447,6 +508,10 @@
                     if (payload && payload.action === 'saveSubmit') {
                         submitSaveAndCommit();
                     }
+                },
+                // 功能说明:二次确认弹窗点击“确认”后执行最终提交 by xu 2026-03-01
+                handleConfirmSubmit() {
+                    submitFinalConfirm();
                 }
             },
             mounted() {

+ 86 - 28
page/mp_objList.html

@@ -856,17 +856,36 @@
                             // 处理objectList数据
                             if (combinedObjectList.length > 0) {
                                 // 使用field-formatter.js格式化列表数据
-                                const formattedList = await window.formatObjectList(combinedObjectList, this.dictCache);
-
-                                if (isLoadMore) {
-                                    // 加载更多:追加到现有列表
-                                    this.list = [...this.list, ...formattedList];
-                                    this.originalList = [...this.originalList, ...formattedList];
-                                } else {
-                                    // 首次加载或刷新:替换列表
-                                    this.originalList = formattedList;
-                                    this.list = [...this.originalList];
-                                }
+                                const formattedList = await window.formatObjectList(combinedObjectList, this.dictCache);
+                                // 功能说明:把后端 chg/chgRootFuncList 映射成卡片左滑操作按钮,标题取 desc,供 ss-card 左滑动作区使用 by xu 2026-03-06
+                                const enhancedList = formattedList.map((item, index) => {
+                                    const rawItem = combinedObjectList[index] || {};
+                                    const rawActions = Array.isArray(rawItem.chgRootFuncList) && rawItem.chgRootFuncList.length > 0
+                                        ? rawItem.chgRootFuncList
+                                        : rawItem.chg
+                                            ? [rawItem.chg]
+                                            : [];
+
+                                    return {
+                                        ...item,
+                                        ssObjId: item.ssObjId || rawItem.ssObjId || '',
+                                        ssObjName: item.ssObjName || rawItem.ssObjName || '',
+                                        swipeActions: rawActions.map(action => ({
+                                            ...action,
+                                            title: action.desc || action.title || '操作'
+                                        }))
+                                    };
+                                });
+
+                                if (isLoadMore) {
+                                    // 加载更多:追加到现有列表
+                                    this.list = [...this.list, ...enhancedList];
+                                    this.originalList = [...this.originalList, ...enhancedList];
+                                } else {
+                                    // 首次加载或刷新:替换列表
+                                    this.originalList = enhancedList;
+                                    this.list = [...this.originalList];
+                                }
 
                                 console.log('✅ 列表数据处理完成:', this.list.length, '条');
 
@@ -983,23 +1002,62 @@
                         console.log('🔽 生成筛选选项:', this.filterSelectOptions);
                     }, 
 
-                    // 卡片点击 - SsCard组件会自动传递item数据
-                    handleCardClick(item) {
-                        console.log('📄 卡片点击事件触发',item);
-                        
-                    },
-
-                    // 卡片操作 - SsCard组件的按钮点击事件
-                    handleCardAction({ button, item, index }) {
-                        console.log('⚡ 卡片操作:', button, item);
-
-                        if (button && button.onclick) {
-                            // 执行按钮的onclick回调
-                            button.onclick();
-                        }
-
-                        this.showToast(`执行操作: ${button.title}`, 'info');
-                    },
+                    // 功能说明:统一处理卡片服务跳转,页面文件名按后端 dest 自动补 mp_,但传给业务页/后端的 dest 仍保持原始值 by xu 2026-03-06
+                    openServicePage(action, item = {}) {
+                        const target = action && typeof action === 'object' ? action : null;
+                        if (!target) {
+                            this.showToast('当前记录缺少操作配置', 'warning');
+                            return;
+                        }
+
+                        const serviceName = String(target.servName || target.ssServ || '').trim();
+                        const rawDest = String(target.dest || '').trim();
+                        const normalizedDest = rawDest && rawDest.startsWith('mp_') ? rawDest : (rawDest ? `mp_${rawDest}` : '');
+                        const paramName = String(target.param_name || '').trim();
+                        const paramValue = target.param_value;
+                        const ssToken = String(target.ssToken || '').trim();
+                        const paramText = typeof target.parm === 'string' ? target.parm : '';
+
+                        if (!serviceName) {
+                            this.showToast('缺少服务名', 'warning');
+                            return;
+                        }
+
+                        if (!normalizedDest) {
+                            this.showToast('缺少目标页面', 'warning');
+                            return;
+                        }
+
+                        NavigationManager.goTo(normalizedDest, {
+                            title: target.desc || target.title || '处理',
+                            service: serviceName,
+                            dest: rawDest,
+                            ssDest: rawDest,
+                            param: paramText,
+                            playParamName: paramName,
+                            playParamValue: paramValue,
+                            ssToken: ssToken,
+                            ssObjId: item.ssObjId || '',
+                            ssObjName: item.ssObjName || '',
+                            management: this.pageParams.management || '1',
+                            [paramName || 'param_value']: paramValue
+                        });
+                    },
+
+                    // 卡片点击 - SsCard组件会自动传递item数据
+                    handleCardClick(item) {
+                        console.log('📄 卡片点击事件触发',item);
+                        // 功能说明:卡片点击统一按 play 参数跳转到通用查看页 mp_objplay(兼容 play / service.play 两种结构) by xu 2026-03-04
+                        const play = (item && (item.play || (item.service && item.service.play))) || null;
+                        this.openServicePage(play, item);
+                    },
+
+                    // 卡片操作 - SsCard组件的按钮点击事件
+                    handleCardAction({ button, item, index }) {
+                        console.log('⚡ 卡片操作:', button, item);
+                        // 功能说明:左滑操作按钮按服务配置跳转(如 chg=变动),不再弹 Toast 占位 by xu 2026-03-06
+                        this.openServicePage(button, item);
+                    },
 
                     // 加载更多数据
                     async loadMore() {

+ 213 - 0
page/mp_objPlay.html

@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+    <title>查看</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+        [v-cloak] {
+            display: none !important;
+        }
+
+        html, body {
+            margin: 0;
+            height: 100%;
+            background: #f5f5f5;
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+        }
+
+        #app {
+            height: 100%;
+            display: flex;
+            flex-direction: column;
+        }
+
+        .page-header {
+            height: 48px;
+            display: flex;
+            align-items: center;
+            background: #ffffff;
+            border-bottom: 1px solid #ececec;
+            padding: 0 12px;
+            box-sizing: border-box;
+            flex-shrink: 0;
+        }
+
+        .back-btn {
+            border: 0;
+            background: transparent;
+            color: #333;
+            font-size: 14px;
+            padding: 8px 0;
+            min-width: 44px;
+            text-align: left;
+        }
+
+        .page-title {
+            flex: 1;
+            text-align: center;
+            font-size: 16px;
+            font-weight: 600;
+            color: #222;
+            padding-right: 44px;
+            box-sizing: border-box;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }
+
+        .content {
+            flex: 1;
+            min-height: 0;
+            position: relative;
+            background: #fff;
+        }
+
+        .play-frame {
+            width: 100%;
+            height: 100%;
+            border: 0;
+            display: block;
+            background: #fff;
+        }
+
+        .state {
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: #666;
+            font-size: 14px;
+            padding: 16px;
+            box-sizing: border-box;
+            text-align: center;
+        }
+    </style>
+</head>
+<body>
+<div id="app" v-cloak>
+
+
+    <div class="content">
+        <iframe
+            v-if="iframeSrc"
+            class="play-frame"
+            :src="iframeSrc"
+            @load="handleFrameLoad"
+        ></iframe>
+        <div v-else class="state">{{ stateText }}</div>
+    </div>
+</div>
+
+<script>
+    (function () {
+        window.SS.ready(function () {
+            window.SS.dom.initializeFormApp({
+                el: '#app',
+                data() {
+                    return {
+                        pageTitle: '查看',
+                        iframeSrc: '',
+                        stateText: '加载中...'
+                    };
+                },
+                mounted() {
+                    this.initPage();
+                },
+                methods: {
+                    // 功能说明:统一解析 URL 参数,并兼容历史页面的重复 encodeURIComponent 情况 by xu 2026-03-04
+                    decodeParam(value) {
+                        let result = value || '';
+                        for (let i = 0; i < 2; i++) {
+                            try {
+                                const decoded = decodeURIComponent(result);
+                                if (decoded === result) break;
+                                result = decoded;
+                            } catch (_) {
+                                break;
+                            }
+                        }
+                        return result;
+                    },
+
+                    getUrlParams() {
+                        const params = {};
+                        const search = new URLSearchParams(window.location.search || '');
+                        for (const [key, value] of search.entries()) {
+                            params[key] = this.decodeParam(value);
+                        }
+                        return params;
+                    },
+
+                    // 功能说明:优先使用后端直接透传的 parm,缺省时按 playParamName/playParamValue 动态组装 by xu 2026-03-04
+                    buildParmText(params) {
+                        const rawParm = String(params.param || '').trim();
+                        if (rawParm) {
+                            return rawParm;
+                        }
+
+                        const parmObj = {};
+                        const paramName = String(params.playParamName || '').trim();
+                        const paramValue = params.playParamValue;
+                        const ssToken = String(params.ssToken || '').trim();
+                        const ssObjId = String(params.ssObjId || '').trim();
+                        const ssObjName = String(params.ssObjName || '').trim();
+
+                        if (paramName && paramValue !== undefined && paramValue !== null && paramValue !== '') {
+                            parmObj[paramName] = paramValue;
+                        }
+                        if (ssToken) parmObj.ssToken = ssToken;
+                        if (ssObjId) parmObj.ssObjId = ssObjId;
+                        if (ssObjName) parmObj.ssObjName = ssObjName;
+                        parmObj.wdConfirmationCaptchaService = '0';
+
+                        return JSON.stringify(parmObj);
+                    },
+
+                    initPage() {
+                        const params = this.getUrlParams();
+                        const serviceName = String(params.service || params.ssServ || '').trim();
+                        const destName = String(params.ssDest || params.dest || 'objPlay').trim();
+                        this.pageTitle = String(params.title || '查看').trim() || '查看';
+
+                        if (!serviceName) {
+                            this.stateText = '缺少服务参数,无法打开查看页';
+                            return;
+                        }
+
+                        const parmText = this.buildParmText(params);
+                        const infoParams = new URLSearchParams();
+                        // 功能说明:移动端查看统一走 mp_objInfo(tabTag + 业务 baseInfo 页),避免直接打开 /service 返回 JSON by xu 2026-03-04
+                        infoParams.set('service', serviceName);
+                        infoParams.set('dest', destName);
+                        infoParams.set('ssServ', serviceName);
+                        infoParams.set('ssDest', destName);
+                        if (parmText) infoParams.set('param', parmText);
+                        if (params.ssObjName) infoParams.set('ssObjName', String(params.ssObjName));
+                        if (params.ssObjId) infoParams.set('ssObjId', String(params.ssObjId));
+                        if (params.sqid) infoParams.set('sqid', String(params.sqid));
+                        if (params.shid) infoParams.set('shid', String(params.shid));
+                        if (params.bdlbm) infoParams.set('bdlbm', String(params.bdlbm));
+
+                        this.iframeSrc = `/page/mp_objInfo.html?${infoParams.toString()}`;
+                    },
+
+                    handleFrameLoad() {
+                        this.stateText = '';
+                    },
+
+                    goBack() {
+                        if (window.NavigationManager && typeof window.NavigationManager.goBack === 'function') {
+                            window.NavigationManager.goBack({ refreshParent: false });
+                            return;
+                        }
+                        window.history.back();
+                    }
+                }
+            });
+        });
+    })();
+</script>
+</body>
+</html>

+ 521 - 485
page/mp_shList.html

@@ -1,485 +1,521 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
-    <title>审核</title>
-    <script src="/js/mp_base/base.js"></script>
-
-    <style>
-        /* 防止Vue模板闪烁 */
-        [v-cloak] {
-            display: none !important;
-        }
-
-        #app {
-            background: #edf1f5;
-            min-height: 100vh;
-            display: flex;
-            flex-direction: column;
-        }
-
-        .header-section {
-            position: relative;
-            padding: 10px 0;
-            cursor: pointer;
-            user-select: none;
-            touch-action: none;
-            transition: background-color 0.3s ease;
-        }
-
-        .header-section::after {
-            width: 95%;
-            height: 1px;
-            content: " ";
-            position: absolute;
-            bottom: 0px;
-            left: 50%;
-            transform: translateX(-50%);
-            background: #e2e4ec;
-        }
-
-        .header-section:hover {
-            background: rgba(64, 172, 109, 0.05);
-        }
-
-        .header-section.dragging {
-            background: rgba(64, 172, 109, 0.1);
-        }
-    </style>
-</head>
-
-<body>
-    <div id="app" v-cloak>
-        <div class="sh-list-container">
-            <!-- 头部节点 -->
-            <div class="header-section" @click="handleHeaderClick" @mousedown="handleDragStart"
-                @touchstart="handleDragStart">
-                <ss-verify-node v-if="rightHeader" class="header-node" :item="rightHeader" />
-            </div>
-
-            <!-- 审批链条 -->
-            <div class="verify-section">
-                <ss-verify v-if="rightGroupList && rightGroupList.length > 0" :verify-list="rightGroupList"
-                    class="fit-height-content" @toggle-group="handleToggleGroup" />
-            </div>
-
-            <!-- 空状态 -->
-            <div v-else class="empty-state">
-                <Icon name="icon-kongzhuangtai" size="64" color="#ccc" />
-                <p>暂无审核节点数据</p>
-            </div>
-        </div>
-    </div>
-
-    <script>
-        // 等待SS框架加载完成
-        window.SS.ready(function () {
-            // 使用SS框架的方式创建Vue实例
-            window.SS.dom.initializeFormApp({
-                el: '#app',
-                data() {
-                    return {
-                        
-                        ssObjName:'',
-                        ssObjId:'',
-                        sqid: '',
-                        shid: '',
-                        bdlbm: '',
-                        dataType:'',
-                        encode_shid:'',
-                        pageParams: {},
-                        // 头部节点数据
-                        rightHeader: null,
-                        // 审批链条数据
-                        rightGroupList: [],
-                        loading: false,
-
-                        // 拖拽相关
-                        isDragging: false,
-                        dragStartY: 0,
-                        dragStartTime: 0,
-                        dragTimer: null,
-                        hasMoved: false,
-                        dragStarted: false,
-                    }
-                },
-                mounted() {
-                    // 获取URL参数
-                    this.pageParams = this.getUrlParams()
-                    console.log('🔗 mp_shList页面接收到参数:', this.pageParams)
-
-                    // 如果有sqid参数,调用selSh接口
-                    if (this.pageParams.sqid) {
-                        this.ssObjName = this.pageParams.ssObjName
-                        this.ssObjId = this.pageParams.ssObjId
-                        this.sqid = this.pageParams.sqid
-                        this.shid = this.pageParams.shid
-                        this.bdlbm = this.pageParams.bdlbm
-                        this.dataType = this.pageParams.dataType
-                        this.encode_shid = this.pageParams.encode_shid
-                        this.loadSelSh()
-                    }
-                },
-                methods: {
-                    // 获取URL参数
-                    getUrlParams() {
-                        const params = {}
-                        const urlSearchParams = new URLSearchParams(window.location.search)
-                        for (const [key, value] of urlSearchParams) {
-                            params[key] = decodeURIComponent(value)
-                        }
-                        return params
-                    },
-
-                    // 加载selSh数据
-                    async loadSelSh() {
-                        if (!this.sqid) {
-                            console.warn('⚠️ sqid为空,无法调用selSh接口')
-                            return
-                        }
-
-                        this.loading = true
-
-                        try {
-                            console.log('📋 调用selSh服务获取数据...', this.sqid)
-                            const selShResponse = await request.post(
-                                `/service?ssServ=selSh&sqid=${this.sqid}`,
-                                
-                                { loading: false, formData: true }
-                            )
-                            console.log('✅ selSh响应:', selShResponse)
-
-                            // 处理selSh数据
-                            if (selShResponse && selShResponse.data) {
-                                this.processShListData(selShResponse.data)
-                            }
-
-                        } catch (error) {
-                            console.error('❌ 加载selSh数据失败:', error)
-                        } finally {
-                            this.loading = false
-                        }
-                    },
-
-                    // 处理审核列表数据
-                    processShListData(data) {
-                        console.log('🔄 开始处理审核列表数据:', data)
-
-                        // 构建完整的审核记录列表
-                        const allRecords = []
-
-                        // 1. 添加申请人记录 (来自sq字段)
-                        if (data.sq) {
-                            allRecords.push({
-                                ...data.sq,
-                                isSqry: true // 标记为申请人
-                            })
-                        }
-
-                        // 2. 添加审核人员记录 (来自shList)
-                        if (data.shList && Array.isArray(data.shList)) {
-                            data.shList.forEach(group => {
-                                if (group.ryList && Array.isArray(group.ryList)) {
-                                    group.ryList.forEach(ry => {
-                                        allRecords.push({
-                                            ...ry,
-                                            isSqry: false // 标记为审核人
-                                        })
-                                    })
-                                }
-                            })
-                        }
-
-                        console.log('📋 所有审核记录:', allRecords)
-
-                        // 3. 设置申请人头部信息 (第一条记录)
-                        const sqry = allRecords.find(item => item.isSqry) || {}
-                        this.rightHeader = {
-                            thumb: this.getThumbUrl(sqry.zjzwj),
-                            name: sqry.xm || '',
-                            role: sqry.bmmc || '',
-                            description: sqry.sm || '发起审核',
-                            time: this.formatTime(sqry.shsj),
-                            link: false,
-                        }
-                        console.log('👤 申请人信息:', this.rightHeader)
-
-                        // 4. 按部门分组构建审核链条
-                        const shRecords = allRecords.filter(item => !item.isSqry)
-                        this.rightGroupList = this.groupByDepartment(shRecords)
-                        console.log('📊 审核链条:', this.rightGroupList)
-                    },
-
-                    // 按部门分组
-                    groupByDepartment(records) {
-                        const groups = {}
-
-                        records.forEach(item => {
-                            const groupKey = item.bmmc || '未知部门'
-
-                            if (!groups[groupKey]) {
-                                groups[groupKey] = {
-                                    groupName: groupKey,
-                                    open: true,
-                                    children: []
-                                }
-                            }
-
-                            groups[groupKey].children.push({
-                                thumb: this.getThumbUrl(item.zjzwj),
-                                name: item.xm || '',
-                                role: item.bmmc || '',
-                                description: item.sm || '同意',
-                                time: this.formatTime(item.shsj),
-                                link: false
-                            })
-                        })
-
-                        return Object.values(groups)
-                    },
-
-                    // 获取头像URL
-                    getThumbUrl(path) {
-                        // 如果没有路径,返回默认头像
-                        if (!path) {
-                            return 'skin/easy/image/default-personalPhoto.png'
-                        }
-
-                        // 使用全局的 getImageUrl 方法处理图片路径
-                        if (typeof window.getImageUrl === 'function') {
-                            return window.getImageUrl(path)
-                        }
-
-                        // 如果 getImageUrl 不存在,返回原路径
-                        console.warn('⚠️ getImageUrl 方法不存在')
-                        return path
-                    },
-
-                    // 格式化时间
-                    formatTime(timeStr) {
-                        // 检查时间字符串是否有效
-                        if (!timeStr || timeStr === '' || timeStr === null || timeStr === undefined) {
-                            console.log('⚠️ formatTime: 时间为空')
-                            return ''
-                        }
-
-                        console.log('🕐 formatTime 输入:', timeStr)
-
-                        // 检查 dayjs 是否可用
-                        if (typeof dayjs === 'undefined') {
-                            console.error('❌ dayjs 未加载')
-                            return ''
-                        }
-
-                        try {
-                            // 清理字符串:移除特殊空格字符
-                            const cleanedDateStr = String(timeStr)
-                                .replace(/[\u202F\u00A0]/g, ' ')
-                                .replace(/\s+/g, ' ')
-                                .trim()
-
-                            console.log('清理后的字符串:', cleanedDateStr)
-
-                            // 使用原生 Date 解析
-                            const jsDate = new Date(cleanedDateStr)
-                            if (isNaN(jsDate.getTime())) {
-                                console.warn('⚠️ Date 解析失败')
-                                return ''
-                            }
-
-                            // 转换为 dayjs
-                            const date = dayjs(jsDate)
-                            if (!date.isValid()) {
-                                console.warn('⚠️ dayjs 无效')
-                                return ''
-                            }
-
-                            console.log('✅ dayjs 解析成功')
-
-                            // 格式化: HH:mm   MM/DD
-                            const result = date.format('HH:mm   MM/DD')
-                            console.log('🕐 formatTime 输出:', result)
-
-                            return result
-                        } catch (error) {
-                            console.error('❌ 时间格式化失败:', error, timeStr)
-                            return ''
-                        }
-                    },
-
-                    // 处理分组展开/收起
-                    handleToggleGroup(data) {
-                        console.log('🔄 分组状态变化:', data)
-                    },
-
-                    // ===== 与父页面通信方法 =====
-                    // 发送消息到父页面
-                    sendMessageToParent(type, data = {}) {
-                        try {
-                            // 通过postMessage向父页面发送消息
-                            if (window.parent && window.parent !== window) {
-                                window.parent.postMessage({
-                                    type,
-                                    data
-                                }, '*') // 在生产环境中应该使用具体的origin
-                            }
-                        } catch (error) {
-                            console.error('发送消息到父页面失败:', error)
-                        }
-                    },
-
-                    // 处理header-section点击
-                    handleHeaderClick(event) {
-                        // 如果正在拖拽中,不处理点击
-                        if (this.isDragging) {
-                            return
-                        }
-                        console.log('🖱️ header-section被点击')
-                        this.sendMessageToParent('header-section-click')
-                    },
-
-                    // 处理拖拽开始(长按检测)
-                    handleDragStart(event) {
-                        // 阻止默认行为,防止页面滚动
-                        event.preventDefault()
-
-                        // 只处理鼠标左键或触摸事件
-                        if (event.type === 'mousedown' && event.button !== 0) {
-                            return
-                        }
-
-                        console.log('🔄 开始长按检测')
-
-                        // 记录开始时间和位置
-                        this.dragStartTime = Date.now()
-                        this.dragStartY = event.type === 'mousedown' ? event.clientY : event.touches[0].clientY
-                        this.hasMoved = false
-                        this.dragStarted = false
-
-                        // 添加长按检测
-                        this.dragTimer = setTimeout(() => {
-                            if (!this.hasMoved && !this.dragStarted) {
-                                this.dragStarted = true
-                                console.log('🔄 开始拖拽header-section')
-                                this.isDragging = true
-
-                                // 添加拖拽样式
-                                document.querySelector('.header-section').classList.add('dragging')
-
-                                // 通知父页面开始拖拽
-                                this.sendMessageToParent('header-section-drag-start', {
-                                    startY: this.dragStartY
-                                })
-
-                                // 添加全局事件监听器
-                                if (event.type === 'mousedown') {
-                                    document.addEventListener('mousemove', this.handleDragMove)
-                                    document.addEventListener('mouseup', this.handleDragEnd)
-                                } else {
-                                    document.addEventListener('touchmove', this.handleDragMove, { passive: false })
-                                    document.addEventListener('touchend', this.handleDragEnd)
-                                }
-                            }
-                        }, 500) // 500ms后开始拖拽
-
-                        // 添加临时事件监听器来检测移动
-                        const tempMouseMove = (e) => {
-                            const currentY = e.type === 'mousemove' ? e.clientY : e.touches[0].clientY
-                            const moveDistance = Math.abs(currentY - this.dragStartY)
-
-                            if (moveDistance > 10) { // 移动超过10px就认为不是长按
-                                this.hasMoved = true
-                                clearTimeout(this.dragTimer)
-
-                                // 移除临时监听器
-                                this.removeTempListeners(event.type, tempMouseMove, tempMouseUp)
-                            }
-                        }
-
-                        const tempMouseUp = (e) => {
-                            clearTimeout(this.dragTimer)
-
-                            // 如果没有开始拖拽且有移动,则触发点击事件
-                            if (!this.dragStarted && !this.hasMoved) {
-                                console.log('🖱️ 触发点击事件(mouseup)')
-                                // 延迟触发点击,避免与拖拽冲突
-                                setTimeout(() => {
-                                    this.handleHeaderClick(e)
-                                }, 50)
-                            }
-
-                            // 移除临时监听器
-                            this.removeTempListeners(event.type, tempMouseMove, tempMouseUp)
-                        }
-
-                        // 添加临时事件监听器
-                        if (event.type === 'mousedown') {
-                            document.addEventListener('mousemove', tempMouseMove)
-                            document.addEventListener('mouseup', tempMouseUp)
-                        } else {
-                            document.addEventListener('touchmove', tempMouseMove, { passive: false })
-                            document.addEventListener('touchend', tempMouseUp)
-                        }
-                    },
-
-                    // 移除临时监听器的辅助方法
-                    removeTempListeners(eventType, mouseMoveHandler, mouseUpHandler) {
-                        if (eventType === 'mousedown') {
-                            document.removeEventListener('mousemove', mouseMoveHandler)
-                            document.removeEventListener('mouseup', mouseUpHandler)
-                        } else {
-                            document.removeEventListener('touchmove', mouseMoveHandler)
-                            document.removeEventListener('touchend', mouseUpHandler)
-                        }
-                    },
-
-                    // 处理拖拽移动
-                    handleDragMove(event) {
-                        if (!this.isDragging) return
-
-                        event.preventDefault()
-
-                        const currentY = event.type === 'mousemove'
-                            ? event.clientY
-                            : event.touches[0].clientY
-
-                        const deltaY = currentY - this.dragStartY
-
-                        // 通知父页面拖拽移动
-                        this.sendMessageToParent('header-section-drag-move', {
-                            deltaY
-                        })
-                    },
-
-                    // 处理拖拽结束
-                    handleDragEnd() {
-                        if (!this.isDragging) return
-
-                        console.log('🔚 结束拖拽')
-
-                        // 移除拖拽样式
-                        const headerSection = document.querySelector('.header-section')
-                        if (headerSection) {
-                            headerSection.classList.remove('dragging')
-                        }
-
-                        // 重置状态
-                        this.isDragging = false
-                        this.dragStartY = 0
-
-                        // 移除全局事件监听器
-                        document.removeEventListener('mousemove', this.handleDragMove)
-                        document.removeEventListener('mouseup', this.handleDragEnd)
-                        document.removeEventListener('touchmove', this.handleDragMove)
-                        document.removeEventListener('touchend', this.handleDragEnd)
-
-                        // 通知父页面拖拽结束
-                        this.sendMessageToParent('header-section-drag-end')
-                    },
-                }
-            })
-        })
-    </script>
-</body>
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>审核</title>
+    <script src="/js/mp_base/base.js"></script>
+
+    <style>
+      /* 防止Vue模板闪烁 */
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #edf1f5;
+        min-height: 100vh;
+        display: flex;
+        flex-direction: column;
+      }
+
+      .header-section {
+        position: relative;
+        padding: 10px 0;
+        cursor: pointer;
+        user-select: none;
+        touch-action: none;
+        transition: background-color 0.3s ease;
+      }
+
+      .header-section::after {
+        width: 95%;
+        height: 1px;
+        content: " ";
+        position: absolute;
+        bottom: 0px;
+        left: 50%;
+        transform: translateX(-50%);
+        background: #e2e4ec;
+      }
+
+      .header-section:hover {
+        background: rgba(64, 172, 109, 0.05);
+      }
+
+      .header-section.dragging {
+        background: rgba(64, 172, 109, 0.1);
+      }
+    </style>
+  </head>
+
+  <body>
+    <div id="app" v-cloak>
+      <div class="sh-list-container">
+        <!-- 头部节点 -->
+        <div
+          class="header-section"
+          @click="handleHeaderClick"
+          @mousedown="handleDragStart"
+          @touchstart="handleDragStart"
+        >
+          <ss-verify-node
+            v-if="rightHeader"
+            class="header-node"
+            :item="rightHeader"
+          />
+        </div>
+
+        <!-- 审批链条 -->
+        <div class="verify-section">
+          <ss-verify
+            v-if="rightGroupList && rightGroupList.length > 0"
+            :verify-list="rightGroupList"
+            class="fit-height-content"
+            @toggle-group="handleToggleGroup"
+          />
+        </div>
+
+        <!-- 空状态 -->
+        <div v-else class="empty-state">
+          <Icon name="icon-kongzhuangtai" size="64" color="#ccc" />
+          <p>暂无审核节点数据</p>
+        </div>
+      </div>
+    </div>
+
+    <script>
+      // 等待SS框架加载完成
+      window.SS.ready(function () {
+        // 使用SS框架的方式创建Vue实例
+        window.SS.dom.initializeFormApp({
+          el: "#app",
+          data() {
+            return {
+              ssObjName: "",
+              ssObjId: "",
+              sqid: "",
+              shid: "",
+              pageParams: {},
+              // 头部节点数据
+              rightHeader: null,
+              // 审批链条数据
+              rightGroupList: [],
+              loading: false,
+
+              // 拖拽相关
+              isDragging: false,
+              dragStartY: 0,
+              dragStartTime: 0,
+              dragTimer: null,
+              hasMoved: false,
+              dragStarted: false,
+            };
+          },
+          mounted() {
+            // 获取URL参数
+            this.pageParams = this.getUrlParams();
+            console.log("🔗 mp_shList页面接收到参数:", this.pageParams);
+
+            // 联调模式:不校验入参,始终尝试调用selSh
+            this.ssObjName = this.pageParams.ssObjName;
+            this.ssObjId = this.pageParams.ssObjId;
+            this.sqid = this.pageParams.sqid;
+            this.shid = this.pageParams.shid;
+            this.loadSelSh();
+          },
+          methods: {
+            // 获取URL参数
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [key, value] of urlSearchParams) {
+                params[key] = decodeURIComponent(value);
+              }
+              return params;
+            },
+
+            // 加载selSh数据
+            async loadSelSh() {
+              this.loading = true;
+
+              try {
+                console.log("📋 调用selSh服务获取数据...", this.sqid);
+                const selShUrl = `/service?ssServ=selSh&sqid=${encodeURIComponent(
+                  this.sqid || ""
+                )}`;
+                console.log("📋 selSh请求URL:", selShUrl);
+                const selShResponse = await request.post(
+                  selShUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+                console.log("✅ selSh响应:", selShResponse);
+
+                // 处理selSh数据
+                if (selShResponse && selShResponse.data) {
+                  this.processShListData(selShResponse.data);
+                }
+              } catch (error) {
+                console.error("❌ 加载selSh数据失败:", error);
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            // 处理审核列表数据
+            processShListData(data) {
+              console.log("🔄 开始处理审核列表数据:", data);
+
+              // 构建完整的审核记录列表
+              const allRecords = [];
+
+              // 1. 添加申请人记录 (来自sq字段)
+              if (data.sq) {
+                allRecords.push({
+                  ...data.sq,
+                  isSqry: true, // 标记为申请人
+                });
+              }
+
+              // 2. 添加审核人员记录 (来自shList)
+              if (data.shList && Array.isArray(data.shList)) {
+                data.shList.forEach((group) => {
+                  if (group.ryList && Array.isArray(group.ryList)) {
+                    group.ryList.forEach((ry) => {
+                      allRecords.push({
+                        ...ry,
+                        isSqry: false, // 标记为审核人
+                      });
+                    });
+                  }
+                });
+              }
+
+              console.log("📋 所有审核记录:", allRecords);
+
+              // 3. 设置申请人头部信息 (第一条记录)
+              const sqry = allRecords.find((item) => item.isSqry) || {};
+              this.rightHeader = {
+                thumb: this.getThumbUrl(sqry.zjzwj),
+                name: sqry.xm || "",
+                role: sqry.bmmc || "",
+                description: sqry.sm || "发起审核",
+                time: this.formatTime(sqry.shsj),
+                link: false,
+              };
+              console.log("👤 申请人信息:", this.rightHeader);
+
+              // 4. 按部门分组构建审核链条
+              const shRecords = allRecords.filter((item) => !item.isSqry);
+              this.rightGroupList = this.groupByDepartment(shRecords);
+              console.log("📊 审核链条:", this.rightGroupList);
+            },
+
+            // 按部门分组
+            groupByDepartment(records) {
+              const groups = {};
+
+              records.forEach((item) => {
+                const groupKey = item.bmmc || "未知部门";
+
+                if (!groups[groupKey]) {
+                  groups[groupKey] = {
+                    groupName: groupKey,
+                    open: true,
+                    children: [],
+                  };
+                }
+
+                groups[groupKey].children.push({
+                  thumb: this.getThumbUrl(item.zjzwj),
+                  name: item.xm || "",
+                  role: item.bmmc || "",
+                  description: item.sm || "同意",
+                  time: this.formatTime(item.shsj),
+                  link: false,
+                });
+              });
+
+              return Object.values(groups);
+            },
+
+            // 获取头像URL
+            getThumbUrl(path) {
+              // 如果没有路径,返回默认头像
+              if (!path) {
+                return "skin/easy/image/default-personalPhoto.png";
+              }
+
+              // 使用全局的 getImageUrl 方法处理图片路径
+              if (typeof window.getImageUrl === "function") {
+                return window.getImageUrl(path);
+              }
+
+              // 如果 getImageUrl 不存在,返回原路径
+              console.warn("⚠️ getImageUrl 方法不存在");
+              return path;
+            },
+
+            // 格式化时间
+            formatTime(timeStr) {
+              // 检查时间字符串是否有效
+              if (
+                !timeStr ||
+                timeStr === "" ||
+                timeStr === null ||
+                timeStr === undefined
+              ) {
+                console.log("⚠️ formatTime: 时间为空");
+                return "";
+              }
+
+              console.log("🕐 formatTime 输入:", timeStr);
+
+              // 检查 dayjs 是否可用
+              if (typeof dayjs === "undefined") {
+                console.error("❌ dayjs 未加载");
+                return "";
+              }
+
+              try {
+                // 清理字符串:移除特殊空格字符
+                const cleanedDateStr = String(timeStr)
+                  .replace(/[\u202F\u00A0]/g, " ")
+                  .replace(/\s+/g, " ")
+                  .trim();
+
+                console.log("清理后的字符串:", cleanedDateStr);
+
+                // 使用原生 Date 解析
+                const jsDate = new Date(cleanedDateStr);
+                if (isNaN(jsDate.getTime())) {
+                  console.warn("⚠️ Date 解析失败");
+                  return "";
+                }
+
+                // 转换为 dayjs
+                const date = dayjs(jsDate);
+                if (!date.isValid()) {
+                  console.warn("⚠️ dayjs 无效");
+                  return "";
+                }
+
+                console.log("✅ dayjs 解析成功");
+
+                // 格式化: HH:mm   MM/DD
+                const result = date.format("HH:mm   MM/DD");
+                console.log("🕐 formatTime 输出:", result);
+
+                return result;
+              } catch (error) {
+                console.error("❌ 时间格式化失败:", error, timeStr);
+                return "";
+              }
+            },
+
+            // 处理分组展开/收起
+            handleToggleGroup(data) {
+              console.log("🔄 分组状态变化:", data);
+            },
+
+            // ===== 与父页面通信方法 =====
+            // 发送消息到父页面
+            sendMessageToParent(type, data = {}) {
+              try {
+                // 通过postMessage向父页面发送消息
+                if (window.parent && window.parent !== window) {
+                  window.parent.postMessage(
+                    {
+                      type,
+                      data,
+                    },
+                    "*"
+                  ); // 在生产环境中应该使用具体的origin
+                }
+              } catch (error) {
+                console.error("发送消息到父页面失败:", error);
+              }
+            },
+
+            // 处理header-section点击
+            handleHeaderClick(event) {
+              // 如果正在拖拽中,不处理点击
+              if (this.isDragging) {
+                return;
+              }
+              console.log("🖱️ header-section被点击");
+              this.sendMessageToParent("header-section-click");
+            },
+
+            // 处理拖拽开始(长按检测)
+            handleDragStart(event) {
+              // 阻止默认行为,防止页面滚动
+              event.preventDefault();
+
+              // 只处理鼠标左键或触摸事件
+              if (event.type === "mousedown" && event.button !== 0) {
+                return;
+              }
+
+              console.log("🔄 开始长按检测");
+
+              // 记录开始时间和位置
+              this.dragStartTime = Date.now();
+              this.dragStartY =
+                event.type === "mousedown"
+                  ? event.clientY
+                  : event.touches[0].clientY;
+              this.hasMoved = false;
+              this.dragStarted = false;
+
+              // 添加长按检测
+              this.dragTimer = setTimeout(() => {
+                if (!this.hasMoved && !this.dragStarted) {
+                  this.dragStarted = true;
+                  console.log("🔄 开始拖拽header-section");
+                  this.isDragging = true;
+
+                  // 添加拖拽样式
+                  document
+                    .querySelector(".header-section")
+                    .classList.add("dragging");
+
+                  // 通知父页面开始拖拽
+                  this.sendMessageToParent("header-section-drag-start", {
+                    startY: this.dragStartY,
+                  });
+
+                  // 添加全局事件监听器
+                  if (event.type === "mousedown") {
+                    document.addEventListener("mousemove", this.handleDragMove);
+                    document.addEventListener("mouseup", this.handleDragEnd);
+                  } else {
+                    document.addEventListener(
+                      "touchmove",
+                      this.handleDragMove,
+                      { passive: false }
+                    );
+                    document.addEventListener("touchend", this.handleDragEnd);
+                  }
+                }
+              }, 500); // 500ms后开始拖拽
+
+              // 添加临时事件监听器来检测移动
+              const tempMouseMove = (e) => {
+                const currentY =
+                  e.type === "mousemove" ? e.clientY : e.touches[0].clientY;
+                const moveDistance = Math.abs(currentY - this.dragStartY);
+
+                if (moveDistance > 10) {
+                  // 移动超过10px就认为不是长按
+                  this.hasMoved = true;
+                  clearTimeout(this.dragTimer);
+
+                  // 移除临时监听器
+                  this.removeTempListeners(
+                    event.type,
+                    tempMouseMove,
+                    tempMouseUp
+                  );
+                }
+              };
+
+              const tempMouseUp = (e) => {
+                clearTimeout(this.dragTimer);
+
+                // 如果没有开始拖拽且有移动,则触发点击事件
+                if (!this.dragStarted && !this.hasMoved) {
+                  console.log("🖱️ 触发点击事件(mouseup)");
+                  // 延迟触发点击,避免与拖拽冲突
+                  setTimeout(() => {
+                    this.handleHeaderClick(e);
+                  }, 50);
+                }
+
+                // 移除临时监听器
+                this.removeTempListeners(
+                  event.type,
+                  tempMouseMove,
+                  tempMouseUp
+                );
+              };
+
+              // 添加临时事件监听器
+              if (event.type === "mousedown") {
+                document.addEventListener("mousemove", tempMouseMove);
+                document.addEventListener("mouseup", tempMouseUp);
+              } else {
+                document.addEventListener("touchmove", tempMouseMove, {
+                  passive: false,
+                });
+                document.addEventListener("touchend", tempMouseUp);
+              }
+            },
+
+            // 移除临时监听器的辅助方法
+            removeTempListeners(eventType, mouseMoveHandler, mouseUpHandler) {
+              if (eventType === "mousedown") {
+                document.removeEventListener("mousemove", mouseMoveHandler);
+                document.removeEventListener("mouseup", mouseUpHandler);
+              } else {
+                document.removeEventListener("touchmove", mouseMoveHandler);
+                document.removeEventListener("touchend", mouseUpHandler);
+              }
+            },
+
+            // 处理拖拽移动
+            handleDragMove(event) {
+              if (!this.isDragging) return;
+
+              event.preventDefault();
+
+              const currentY =
+                event.type === "mousemove"
+                  ? event.clientY
+                  : event.touches[0].clientY;
+
+              const deltaY = currentY - this.dragStartY;
+
+              // 通知父页面拖拽移动
+              this.sendMessageToParent("header-section-drag-move", {
+                deltaY,
+              });
+            },
+
+            // 处理拖拽结束
+            handleDragEnd() {
+              if (!this.isDragging) return;
+
+              console.log("🔚 结束拖拽");
+
+              // 移除拖拽样式
+              const headerSection = document.querySelector(".header-section");
+              if (headerSection) {
+                headerSection.classList.remove("dragging");
+              }
+
+              // 重置状态
+              this.isDragging = false;
+              this.dragStartY = 0;
+
+              // 移除全局事件监听器
+              document.removeEventListener("mousemove", this.handleDragMove);
+              document.removeEventListener("mouseup", this.handleDragEnd);
+              document.removeEventListener("touchmove", this.handleDragMove);
+              document.removeEventListener("touchend", this.handleDragEnd);
+
+              // 通知父页面拖拽结束
+              this.sendMessageToParent("header-section-drag-end");
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 750 - 0
page/mp_stateChgChk.html

@@ -0,0 +1,750 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
+    <title>审核</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        height: 100vh;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+        box-sizing: border-box;
+      }
+
+      .content-wrap {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+        padding: 8px;
+        box-sizing: border-box;
+        overflow: hidden;
+      }
+
+      .frame-card {
+        background: #fff;
+        border-radius: 8px;
+        overflow: hidden;
+        box-sizing: border-box;
+      }
+
+      .info-frame-wrap {
+        flex: 0 0 176px;
+      }
+
+      .sh-frame-wrap {
+        flex: 1;
+        min-height: 0;
+      }
+
+      .info-iframe,
+      .sh-iframe {
+        width: 100%;
+        height: 100%;
+        border: none;
+        display: block;
+        background: #fff;
+      }
+
+      .status-wrap {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 16px;
+        box-sizing: border-box;
+      }
+
+      .status-card {
+        width: 100%;
+        max-width: 520px;
+        background: #fff;
+        border-radius: 8px;
+        padding: 16px;
+        box-sizing: border-box;
+        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
+      }
+
+      .status-title {
+        font-size: 15px;
+        font-weight: 600;
+        color: #2d3748;
+      }
+
+      .status-text {
+        margin-top: 8px;
+        line-height: 1.6;
+        color: #4a5568;
+        font-size: 13px;
+        word-break: break-all;
+      }
+
+      .status-retry {
+        margin-top: 12px;
+        height: 34px;
+        padding: 0 14px;
+        border: none;
+        border-radius: 4px;
+        background: #4a5568;
+        color: #fff;
+        font-size: 13px;
+      }
+
+      .url-log-fab {
+        position: fixed;
+        right: 12px;
+        bottom: 72px;
+        z-index: 1200;
+        border: none;
+        border-radius: 16px;
+        background: rgba(36, 40, 53, 0.88);
+        color: #fff;
+        font-size: 12px;
+        line-height: 1;
+        padding: 8px 10px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
+      }
+
+      .url-log-panel {
+        position: fixed;
+        left: 12px;
+        right: 12px;
+        bottom: 116px;
+        z-index: 1200;
+        background: rgba(36, 40, 53, 0.96);
+        color: #fff;
+        border-radius: 8px;
+        padding: 10px;
+        max-height: 42vh;
+        overflow: auto;
+        box-sizing: border-box;
+      }
+
+      .url-log-panel__head {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 8px;
+        font-size: 12px;
+      }
+
+      .url-log-panel__close {
+        border: none;
+        background: transparent;
+        color: #fff;
+        font-size: 14px;
+        line-height: 1;
+      }
+
+      .url-log-panel__body {
+        margin: 0;
+        white-space: pre-wrap;
+        word-break: break-all;
+        font-size: 12px;
+        line-height: 1.5;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <div class="content-wrap" v-if="initStatus === 'ready'">
+        <div class="frame-card info-frame-wrap">
+          <iframe
+            ref="infoIframe"
+            class="info-iframe"
+            :src="topIframeSrc || 'about:blank'"
+            frameborder="0"
+          ></iframe>
+        </div>
+        <div class="frame-card sh-frame-wrap">
+          <iframe
+            ref="shIframe"
+            class="sh-iframe"
+            :src="shIframeSrc || 'about:blank'"
+            frameborder="0"
+          ></iframe>
+        </div>
+      </div>
+
+      <div class="status-wrap" v-else>
+        <div class="status-card">
+          <div class="status-title">{{ initStatusTitle }}</div>
+          <div class="status-text">{{ initStatusMessage }}</div>
+          <div class="status-text" v-if="missingParams.length > 0">
+            缺少参数:{{ missingParams.join('、') }}
+          </div>
+          <button class="status-retry" type="button" @click="callApi">
+            重新加载
+          </button>
+        </div>
+      </div>
+
+      <ss-bottom
+        :buttons="bottomButtons"
+        :show-shyj="true"
+        @button-click="handleBottomAction"
+        :disabled="submitting"
+        v-if="initStatus === 'ready' && bottomButtons.length > 0"
+      ></ss-bottom>
+
+      <button class="url-log-fab" type="button" @click="showCurrentUrls">
+        URL
+      </button>
+      <div class="url-log-panel" v-if="urlLogVisible">
+        <div class="url-log-panel__head">
+          <span>当前 URL</span>
+          <button
+            class="url-log-panel__close"
+            type="button"
+            @click="urlLogVisible = false"
+          >
+            ×
+          </button>
+        </div>
+        <pre class="url-log-panel__body">{{ urlLogText }}</pre>
+      </div>
+    </div>
+
+    <script>
+      window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+          el: '#app',
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              submitting: false,
+              jdmc: '',
+              sqid: '',
+              shid: '',
+              shyjm: '',
+              bdlbm: '',
+              dataType: 'bdplay',
+              encode_shid: '',
+              ssObjName: '',
+              ssObjId: '',
+              initStatus: 'loading',
+              initStatusTitle: '页面初始化中',
+              initStatusMessage: '正在加载审核数据,请稍候...',
+              missingParams: [],
+              infoData: null,
+              actionAgreeData: null,
+              actionRejectData: null,
+              bottomButtons: [],
+              topIframeSrc: '',
+              shIframeSrc: '',
+              urlLogVisible: false,
+              urlLogText: '',
+            };
+          },
+          mounted() {
+            this.pageParams = this.getUrlParams();
+            this.callApi();
+          },
+          methods: {
+            // 功能说明:stateChgSure 改为复用 chgChk 审核结构,只保留概要信息和审核记录 by xu 2026-03-09
+            setInitStatus(status, title, message) {
+              this.initStatus = status;
+              this.initStatusTitle = title || '';
+              this.initStatusMessage = message || '';
+            },
+
+            normalizeError(error) {
+              return {
+                message: (error && error.message) || String(error),
+              };
+            },
+
+            getUrlParams() {
+              const params = {};
+              const aliasMap = {
+                ssobjname: 'ssObjName',
+                ssobjid: 'ssObjId',
+                sqid: 'sqid',
+                shid: 'shid',
+                shyjm: 'shyjm',
+                bdlbm: 'bdlbm',
+                datatype: 'dataType',
+                encode_shid: 'encode_shid',
+                jdmc: 'jdmc',
+              };
+              const urlSearchParams = new URLSearchParams(window.location.search);
+              for (const [rawKey, rawValue] of urlSearchParams) {
+                const decodedValue = this.safeDecode(rawValue);
+                params[rawKey] = decodedValue;
+                const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
+                if (normalizedKey) {
+                  params[normalizedKey] = decodedValue;
+                }
+              }
+              return params;
+            },
+
+            safeDecode(text) {
+              if (text === undefined || text === null || text === '') return '';
+              try {
+                return decodeURIComponent(String(text));
+              } catch (_) {
+                return String(text);
+              }
+            },
+
+            pickFirstValue(sources, keys) {
+              const srcArr = Array.isArray(sources) ? sources : [];
+              const keyArr = Array.isArray(keys) ? keys : [];
+              for (let i = 0; i < srcArr.length; i += 1) {
+                const src = srcArr[i];
+                if (!src || typeof src !== 'object') continue;
+                for (let j = 0; j < keyArr.length; j += 1) {
+                  const key = keyArr[j];
+                  const value = src[key];
+                  if (value !== undefined && value !== null && value !== '') {
+                    return value;
+                  }
+                }
+              }
+              return '';
+            },
+
+            extractTokenData(apiResponse) {
+              const wantedKeys = [
+                'sqid',
+                'shid',
+                'ssObjName',
+                'ssObjId',
+                'jdmc',
+                'bdlbm',
+                'dataType',
+                'encode_shid',
+              ];
+              const candidates = [
+                apiResponse && apiResponse.data && apiResponse.data.ssData,
+                apiResponse && apiResponse.ssData,
+                apiResponse && apiResponse.data,
+                apiResponse,
+              ];
+              let best = {};
+              let bestScore = -1;
+              candidates.forEach((item) => {
+                if (!item || typeof item !== 'object') return;
+                let score = 0;
+                wantedKeys.forEach((key) => {
+                  if (
+                    item[key] !== undefined &&
+                    item[key] !== null &&
+                    item[key] !== ''
+                  ) {
+                    score += 1;
+                  }
+                });
+                if (score > bestScore) {
+                  best = item;
+                  bestScore = score;
+                }
+              });
+              return best;
+            },
+
+            resolveBusinessContext(apiResponse) {
+              const tokenData = this.extractTokenData(apiResponse);
+              const sources = [tokenData, this.pageParams];
+              return {
+                sqid: String(this.pickFirstValue(sources, ['sqid']) || ''),
+                shid: String(this.pickFirstValue(sources, ['shid']) || ''),
+                shyjm: String(this.pickFirstValue(sources, ['shyjm']) || ''),
+                ssObjName: String(
+                  this.pickFirstValue(sources, ['ssObjName', 'ssobjname']) || ''
+                ),
+                ssObjId: String(
+                  this.pickFirstValue(sources, ['ssObjId', 'ssobjid']) || ''
+                ),
+                bdlbm: String(this.pickFirstValue(sources, ['bdlbm']) || ''),
+                dataType:
+                  String(
+                    this.pickFirstValue(sources, ['dataType', 'datatype']) ||
+                      this.dataType ||
+                      'bdplay'
+                  ) || 'bdplay',
+                encode_shid: String(
+                  this.pickFirstValue(sources, ['encode_shid']) || ''
+                ),
+                jdmc: String(this.pickFirstValue(sources, ['jdmc']) || ''),
+              };
+            },
+
+            parseInfoParm(rawParm) {
+              if (!rawParm) return {};
+              if (typeof rawParm === 'object') return rawParm;
+              const text = String(rawParm).trim();
+              if (!text) return {};
+              try {
+                return JSON.parse(text);
+              } catch (_) {}
+              try {
+                const normalized = text
+                  .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
+                  .replace(/'/g, '"');
+                return JSON.parse(normalized);
+              } catch (_) {
+                return {};
+              }
+            },
+
+            resolveInfoDestPath(destName) {
+              const raw = String(destName || '').trim();
+              if (!raw) return '';
+              if (/^https?:\/\//i.test(raw)) return raw;
+              if (raw.startsWith('/')) return raw;
+              if (raw.endsWith('.html')) return `/page/${raw}`;
+              if (raw.startsWith('mp_')) return `/page/${raw}.html`;
+              return `/page/mp_${raw}.html`;
+            },
+
+            buildInfoIframeSrc(infoData, fallbackQuery) {
+              const conf = infoData && typeof infoData === 'object' ? infoData : {};
+              const serviceName = conf.service || conf.servName || '';
+              const destName = conf.dest || '';
+              const parmObj = this.parseInfoParm(conf.parm);
+              const mergedQuery = {
+                ...(fallbackQuery || {}),
+                ...(parmObj || {}),
+              };
+              if (!serviceName || !destName) {
+                return this.buildIframeSrc('/page/mp_objInfo.html', mergedQuery);
+              }
+
+              const pagePath = this.resolveInfoDestPath(destName);
+              if (!pagePath) {
+                return this.buildIframeSrc('/page/mp_objInfo.html', mergedQuery);
+              }
+
+              const paramText =
+                typeof conf.parm === 'string'
+                  ? conf.parm
+                  : JSON.stringify(parmObj || {});
+              const iframeQuery = {
+                ...mergedQuery,
+                ssServ: serviceName,
+                ssDest: destName,
+                service: serviceName,
+                dest: destName,
+                param: paramText,
+              };
+              return this.buildIframeSrc(pagePath, iframeQuery);
+            },
+
+            buildIframeSrc(path, queryObj) {
+              const search = new URLSearchParams(queryObj);
+              return `${path}?${search.toString()}`;
+            },
+
+            buildCommonQuery() {
+              return {
+                sqid: this.sqid,
+                shid: this.shid,
+                shyjm: this.shyjm,
+                bdlbm: this.bdlbm,
+                dataType: this.dataType,
+                encode_shid: this.encode_shid,
+                ssObjName: this.ssObjName,
+                ssObjId: this.ssObjId,
+                jdmc: this.jdmc,
+              };
+            },
+
+            normalizeActionConfig(config) {
+              if (!config || typeof config !== 'object') return null;
+              const normalized = { ...config };
+              if (!normalized.service && normalized.servName) {
+                normalized.service = normalized.servName;
+              }
+              return normalized;
+            },
+
+            async fetchActionConfigs() {
+              // 功能说明:沿用 chgChk 的 bdlbm 动作规则,支持同意/退回 by xu 2026-03-09
+              const query = `ssObjName=${encodeURIComponent(
+                this.ssObjName
+              )}&ssObjId=${encodeURIComponent(
+                this.ssObjId
+              )}&sqid=${encodeURIComponent(
+                this.sqid
+              )}&shid=${encodeURIComponent(
+                this.shid
+              )}&bdlbm=${encodeURIComponent(
+                this.bdlbm
+              )}&dataType=${encodeURIComponent(
+                this.dataType
+              )}&encode_shid=${encodeURIComponent(this.encode_shid)}`;
+
+              let agreeTag = '';
+              let rejectTag = '';
+              if (String(this.bdlbm) === '55') {
+                agreeTag = 'agrRes';
+                rejectTag = 'rejRes';
+              } else if (String(this.bdlbm) === '51') {
+                agreeTag = 'agrSus';
+                rejectTag = 'rejSus';
+              } else if (!['1', '51', '55'].includes(String(this.bdlbm))) {
+                agreeTag = 'agrChg';
+                rejectTag = 'rejChg';
+              }
+
+              this.actionAgreeData = null;
+              this.actionRejectData = null;
+              const buttons = [];
+
+              if (rejectTag) {
+                const rejectRes = await request.post(
+                  `/service?ssServ=dataTag&ssDest=data&name=${rejectTag}&${query}`,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.actionRejectData = this.normalizeActionConfig(
+                  rejectRes && rejectRes.data ? rejectRes.data[rejectTag] : null
+                );
+                if (
+                  this.actionRejectData &&
+                  this.actionRejectData.service &&
+                  this.actionRejectData.dest
+                ) {
+                  buttons.push({ text: '退回', action: 'reject' });
+                }
+              }
+
+              if (agreeTag) {
+                const agreeRes = await request.post(
+                  `/service?ssServ=dataTag&ssDest=data&name=${agreeTag}&${query}`,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.actionAgreeData = this.normalizeActionConfig(
+                  agreeRes && agreeRes.data ? agreeRes.data[agreeTag] : null
+                );
+                if (
+                  this.actionAgreeData &&
+                  this.actionAgreeData.service &&
+                  this.actionAgreeData.dest
+                ) {
+                  buttons.push({ text: '同意', action: 'agree' });
+                }
+              }
+
+              this.bottomButtons = buttons;
+            },
+
+            async callApi() {
+              this.loading = true;
+              this.infoData = null;
+              this.actionAgreeData = null;
+              this.actionRejectData = null;
+              this.bottomButtons = [];
+              this.topIframeSrc = '';
+              this.shIframeSrc = '';
+              this.missingParams = [];
+              this.setInitStatus(
+                'loading',
+                '页面初始化中',
+                '正在加载审核数据,请稍候...'
+              );
+
+              let apiResponse = null;
+
+              try {
+                if (this.pageParams.ssToken) {
+                  const tokenUrl = `/service?ssToken=${this.pageParams.ssToken}`;
+                  apiResponse = await request.post(tokenUrl, {}, { loading: false });
+                }
+
+                const context = this.resolveBusinessContext(apiResponse);
+                this.sqid = context.sqid;
+                this.shid = context.shid;
+                this.shyjm = context.shyjm;
+                this.ssObjName = context.ssObjName;
+                this.ssObjId = context.ssObjId;
+                this.bdlbm = context.bdlbm;
+                this.dataType = context.dataType || 'bdplay';
+                this.encode_shid = context.encode_shid;
+                this.jdmc = context.jdmc;
+
+                ['sqid', 'shid', 'ssObjName', 'ssObjId'].forEach((key) => {
+                  if (!this[key]) this.missingParams.push(key);
+                });
+
+                if (this.missingParams.length > 0) {
+                  this.setInitStatus(
+                    'error',
+                    '页面参数不完整',
+                    '缺少关键参数,无法加载审核数据'
+                  );
+                  return;
+                }
+
+                const query = this.buildCommonQuery();
+                const infoUrl = `/service?ssServ=dataTag&ssDest=data&name=info&ssObjName=${encodeURIComponent(
+                  this.ssObjName
+                )}&ssObjId=${encodeURIComponent(
+                  this.ssObjId
+                )}&sqid=${encodeURIComponent(
+                  this.sqid
+                )}&shid=${encodeURIComponent(this.shid)}`;
+                const infoRes = await request.post(
+                  infoUrl,
+                  {},
+                  { loading: false, formData: true }
+                );
+                this.infoData = infoRes && infoRes.data ? infoRes.data.info : null;
+
+                this.topIframeSrc = this.buildInfoIframeSrc(this.infoData, query);
+                // 功能说明:按你的要求,不显示 chgChk.ss.jsp 中“变动属性对比”的中间 iframe by xu 2026-03-09
+                this.shIframeSrc = this.buildIframeSrc('/page/mp_shList.html', query);
+
+                await this.fetchActionConfigs();
+                this.setInitStatus('ready', '页面已就绪', '审核数据加载完成');
+              } catch (error) {
+                const normalized = this.normalizeError(error);
+                this.setInitStatus(
+                  'error',
+                  '页面初始化失败',
+                  normalized.message || '页面初始化失败,请稍后重试'
+                );
+                console.error('[mp_stateChgSure] 初始化失败', error);
+                if (typeof showToastEffect === 'function') {
+                  showToastEffect('页面初始化失败,请稍后重试', 2200, 'error');
+                }
+              } finally {
+                this.loading = false;
+              }
+            },
+
+            buildSubmitPayload(actionPayload) {
+              const submitPayload = {};
+              if (this.shid) submitPayload.shid = this.shid;
+              if (this.sqid) submitPayload.sqid = this.sqid;
+              if (this.ssObjName) submitPayload.ssObjName = this.ssObjName;
+              if (this.ssObjId) submitPayload.ssObjId = this.ssObjId;
+
+              const payloadObj =
+                actionPayload && typeof actionPayload === 'object'
+                  ? actionPayload
+                  : {};
+              const reviewValue = this.pickFirstValue(
+                [payloadObj],
+                [
+                  'shsm',
+                  'shyjValue',
+                  'shyj',
+                  'spyj',
+                  'opinion',
+                  'comment',
+                  'remark',
+                  'reason',
+                  'yj',
+                ]
+              );
+              if (
+                reviewValue !== undefined &&
+                reviewValue !== null &&
+                reviewValue !== ''
+              ) {
+                submitPayload.shsm = String(reviewValue);
+              }
+              return submitPayload;
+            },
+
+            goBackWithRefresh() {
+              if (
+                window.NavigationManager &&
+                typeof window.NavigationManager.goBack === 'function'
+              ) {
+                window.NavigationManager.goBack({ refreshParent: true });
+                return;
+              }
+              window.history.back();
+            },
+
+            async submitByConfig(conf, actionPayload) {
+              const serviceName = conf && (conf.service || conf.servName);
+              const destName = conf && conf.dest;
+              if (!conf || !serviceName || !destName) {
+                if (typeof showToastEffect === 'function') {
+                  showToastEffect('缺少提交配置', 2200, 'error');
+                }
+                return;
+              }
+              if (this.submitting) return;
+
+              try {
+                this.submitting = true;
+                await request.post(
+                  `/service?ssServ=${encodeURIComponent(
+                    serviceName
+                  )}&ssDest=${encodeURIComponent(destName)}`,
+                  this.buildSubmitPayload(actionPayload),
+                  {
+                    loading: true,
+                    formData: true,
+                  }
+                );
+
+                if (typeof showToastEffect === 'function') {
+                  showToastEffect('提交成功', 1200, 'success');
+                }
+                setTimeout(() => {
+                  this.goBackWithRefresh();
+                }, 300);
+              } catch (error) {
+                console.error('[mp_stateChgSure] 提交失败', error);
+                if (typeof showToastEffect === 'function') {
+                  showToastEffect('提交失败,请稍后重试', 2200, 'error');
+                }
+              } finally {
+                this.submitting = false;
+              }
+            },
+
+            handleBottomAction(payload) {
+              if (!payload || !payload.action) return;
+              if (payload.action === 'agree') {
+                this.submitByConfig(this.actionAgreeData, payload);
+                return;
+              }
+              if (payload.action === 'reject') {
+                this.submitByConfig(this.actionRejectData, payload);
+              }
+            },
+
+            showCurrentUrls() {
+              const lines = [
+                `page: ${window.location.href}`,
+                `info: ${
+                  (this.$refs.infoIframe && this.$refs.infoIframe.src) ||
+                  this.topIframeSrc ||
+                  '(empty)'
+                }`,
+                `sh: ${
+                  (this.$refs.shIframe && this.$refs.shIframe.src) ||
+                  this.shIframeSrc ||
+                  '(empty)'
+                }`,
+              ];
+              this.urlLogText = lines.join('\n');
+              this.urlLogVisible = true;
+            },
+          },
+        });
+      });
+    </script>
+  </body>
+</html>

+ 291 - 0
page/mp_stateChgSure.html

@@ -0,0 +1,291 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+    <title>变动确认</title>
+    <script src="/js/mp_base/base.js"></script>
+    <style>
+        [v-cloak] {
+            display: none !important;
+        }
+
+        #app {
+            min-height: 100vh;
+            background: #f5f5f5;
+            box-sizing: border-box;
+        }
+
+        .form-wrap {
+            background: #fff;
+        }
+
+        .table th {
+            width: 140px;
+            max-width: 170px;
+        }
+
+        .loading {
+            min-height: 40vh;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+
+        .loading-spinner {
+            width: 30px;
+            height: 30px;
+            border: 3px solid #e8f3ed;
+            border-top-color: #40ac6d;
+            border-radius: 50%;
+            animation: page-spin 0.9s linear infinite;
+        }
+
+        @keyframes page-spin {
+            from { transform: rotate(0deg); }
+            to { transform: rotate(360deg); }
+        }
+
+        .error-card {
+            margin: 8px;
+            padding: 16px;
+            background: #fff;
+            border-radius: 8px;
+            color: #ff4d4f;
+            line-height: 1.6;
+            word-break: break-all;
+        }
+    </style>
+</head>
+<body>
+<div id="app" v-cloak>
+    <div v-if="loading" class="loading">
+        <div class="loading-spinner"></div>
+    </div>
+
+    <div v-else-if="errorMessage" class="error-card">{{ errorMessage }}</div>
+
+    <form v-else id="tjform" class="form-wrap" @submit.prevent>
+        <table class="table">
+            <tr>
+                <th>标题</th>
+                <td>
+                    <!-- 状态变动确认页标题改为普通输入框 by xu 2026-03-07 -->
+                    <ss-input
+                        v-model="formData.mc"
+                        name="mc"
+                        placeholder="请输入标题"
+                    />
+                </td>
+            </tr>
+            <tr>
+                <th>说明</th>
+                <td>
+                    <!-- 状态变动确认页说明改为 ms 普通录入,不使用富文本 by xu 2026-03-07 -->
+                    <ss-input
+                        v-model="formData.ms"
+                        name="ms"
+                        placeholder="请输入说明"
+                    />
+                </td>
+            </tr>
+        </table>
+    </form>
+
+    <ss-bottom
+        v-if="!loading && !errorMessage"
+        :buttons="bottomButtons"
+        @button-click="handleBottomAction"
+    >
+    </ss-bottom>
+</div>
+
+<script>
+    window.SS.ready(function () {
+        window.SS.dom.initializeFormApp({
+            el: '#app',
+            data() {
+                return {
+                    pageParams: {},
+                    initPayload: {},
+                    loading: false,
+                    submitting: false,
+                    errorMessage: '',
+                    formData: {
+                        mc: '',
+                        ms: ''
+                    },
+                    bottomButtons: [
+                        { text: '关闭', action: 'close' },
+                        {
+                            text: '提交',
+                            action: 'submit',
+                            backgroundColor: '#575d6d',
+                            color: '#fff'
+                        }
+                    ]
+                }
+            },
+            async mounted() {
+                this.pageParams = this.getUrlParams()
+                await this.loadInitData()
+            },
+            methods: {
+                getUrlParams() {
+                    const params = {}
+                    const urlSearchParams = new URLSearchParams(window.location.search)
+                    for (const [key, value] of urlSearchParams) {
+                        params[key] = decodeURIComponent(value)
+                    }
+                    return params
+                },
+                parseJsonString(text) {
+                    if (!text || typeof text !== 'string') return {}
+                    try {
+                        const obj = JSON.parse(text)
+                        return obj && typeof obj === 'object' ? obj : {}
+                    } catch (_) {
+                        return {}
+                    }
+                },
+                unwrapData(data) {
+                    if (!data || typeof data !== 'object') return {}
+                    if (data.ssData && typeof data.ssData === 'object') return data.ssData
+                    return data
+                },
+                getMergedParams() {
+                    const routeParam = this.parseJsonString(this.pageParams.param)
+                    return {
+                        ...routeParam,
+                        ...this.pageParams
+                    }
+                },
+                goBack(refreshParent = false) {
+                    if (window.NavigationManager && typeof window.NavigationManager.goBack === 'function') {
+                        window.NavigationManager.goBack({ refreshParent })
+                    } else {
+                        window.history.back()
+                    }
+                },
+                // 功能说明:状态变动确认页先调当前 service 补齐标题、对象参数和提交服务 by xu 2026-03-07
+                async loadInitData() {
+                    const params = this.getMergedParams()
+                    const serviceName = String(params.service || params.ssServ || '').trim()
+                    const presetTitle = String(params.applicationName || params.mc || '').trim()
+                    const presetDesc = String(params.sqmswj || params.sqms || params.ms || '').trim()
+
+                    this.formData.mc = presetTitle
+                    this.formData.ms = presetDesc
+
+                    if (!serviceName) {
+                        this.initPayload = {}
+                        return
+                    }
+
+                    this.loading = true
+                    this.errorMessage = ''
+                    try {
+                        const response = await request.post(
+                            `/service?ssServ=${encodeURIComponent(serviceName)}`,
+                            params,
+                            { loading: false, formData: true }
+                        )
+                        const payload = this.unwrapData(response && response.data)
+                        this.initPayload = payload
+
+                        if (!this.formData.mc) {
+                            this.formData.mc = String(payload.applicationName || payload.mc || '')
+                        }
+                        if (!this.formData.ms) {
+                            this.formData.ms = String(payload.sqmswj || payload.sqms || payload.ms || '')
+                        }
+                    } catch (error) {
+                        console.error('[mp_stateChgSure] 初始化失败', error)
+                        this.errorMessage = '初始化失败,请稍后重试'
+                    } finally {
+                        this.loading = false
+                    }
+                },
+                validateForm() {
+                    if (!String(this.formData.mc || '').trim()) return '请输入标题'
+                    return ''
+                },
+                getSubmitConfig() {
+                    const params = this.getMergedParams()
+                    const payload = this.initPayload || {}
+                    return {
+                        serviceName: String(payload.sureChgServName || params.sureChgServName || params.service || params.ssServ || '').trim(),
+                        dest: 'info',
+                        ssObjName: String(payload.ssObjName || params.ssObjName || '').trim(),
+                        ssObjIdName: String(payload.ssObjIdName || params.ssObjIdName || 'ssObjId').trim(),
+                        ssObjId: String(payload.ssObjId || params.ssObjId || '').trim(),
+                        ssPobjName: String(payload.ssPobjName || params.ssPobjName || '').trim(),
+                    }
+                },
+                // 功能说明:状态变动确认页按 JSP tjform 提交 mc、ms、mswj 到 sureChgServName by xu 2026-03-07
+                async submitForm() {
+                    if (this.submitting) return
+
+                    const message = this.validateForm()
+                    if (message) {
+                        if (typeof showToastEffect === 'function') showToastEffect(message, 1800, 'warning')
+                        return
+                    }
+
+                    const cfg = this.getSubmitConfig()
+                    if (!cfg.serviceName) {
+                        if (typeof showToastEffect === 'function') showToastEffect('缺少提交服务,无法提交', 2200, 'error')
+                        return
+                    }
+                    if (!cfg.ssObjName || !cfg.ssObjId) {
+                        if (typeof showToastEffect === 'function') showToastEffect('缺少对象参数,无法提交', 2200, 'error')
+                        return
+                    }
+
+                    this.submitting = true
+                    try {
+                        const submitPayload = {
+                            ssObjName: cfg.ssObjName,
+                            ssPobjName: cfg.ssPobjName,
+                            mc: String(this.formData.mc || '').trim(),
+                            ms: String(this.formData.ms || '').trim(),
+                            mswj: String(this.formData.ms || '').trim(),
+                            [cfg.ssObjIdName]: cfg.ssObjId,
+                        }
+
+                        const response = await request.post(
+                            `/service?ssServ=${encodeURIComponent(cfg.serviceName)}&ssDest=${encodeURIComponent(cfg.dest)}`,
+                            submitPayload,
+                            { loading: true, formData: true }
+                        )
+                        console.log('[mp_stateChgSure] 提交响应', response)
+
+                        if (typeof showToastEffect === 'function') {
+                            showToastEffect('提交成功', 1500, 'success')
+                        }
+                        setTimeout(() => this.goBack(true), 600)
+                    } catch (error) {
+                        console.error('[mp_stateChgSure] 提交失败', error)
+                        if (typeof showToastEffect === 'function') {
+                            showToastEffect('提交失败,请稍后重试', 2200, 'error')
+                        }
+                    } finally {
+                        this.submitting = false
+                    }
+                },
+                handleBottomAction(payload) {
+                    if (!payload || !payload.action) return
+                    if (payload.action === 'close') {
+                        this.goBack(false)
+                        return
+                    }
+                    if (payload.action === 'submit') {
+                        this.submitForm()
+                    }
+                }
+            }
+        })
+    })
+</script>
+</body>
+</html>

+ 284 - 198
page/mp_xyqj_baseInfo.html

@@ -1,227 +1,313 @@
 <!DOCTYPE html>
 <html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+  <head>
+    <meta charset="UTF-8" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1.0, user-scalable=no"
+    />
     <title>基本信息</title>
     <script src="/js/mp_base/base.js"></script>
 
     <style>
-        [v-cloak] {
-            display: none !important;
-        }
-
-        #app {
-            background: #f5f5f5;
-            min-height: 100vh;
-        }
-
-        .loading {
-            text-align: center;
-            padding: 50px;
-            color: #999;
-        }
-
-        .error {
-            text-align: center;
-            padding: 50px;
-            color: #ff4d4f;
-        }
-
-        .section-title {
-            padding: 10px 12px;
-            color: #666;
-            font-size: 16px;
-            font-weight: 600;
-        }
-
-        .reason-text {
-            white-space: pre-wrap;
-            line-height: 1.6;
-        }
-
-        .attachment-text {
-            color: #999;
-            font-size: 14px;
-        }
+      [v-cloak] {
+        display: none !important;
+      }
+
+      #app {
+        background: #f5f5f5;
+        min-height: 100vh;
+        padding-top: 8px;
+        box-sizing: border-box;
+      }
+
+      .loading {
+        text-align: center;
+        padding: 50px;
+        color: #999;
+      }
+
+      .error {
+        text-align: center;
+        padding: 50px;
+        color: #ff4d4f;
+      }
+
+      .section-card {
+        margin: 0 8px 10px;
+        background: #ffffff;
+        border-radius: 8px;
+        overflow: hidden;
+      }
+
+      .form {
+        width: 100%;
+        border-collapse: collapse;
+      }
+
+      .form th {
+        width: 140px;
+        max-width: 170px;
+      }
+
+      .reason-text {
+        white-space: pre-wrap;
+        line-height: 1.6;
+      }
     </style>
-</head>
-<body>
-<div id="app" v-cloak>
-    <div v-if="loading" class="loading">加载中...</div>
-    <div v-else-if="error" class="error">{{ error }}</div>
-    <!-- 新增学员请假基本信息回显页 by xu 2026-02-28 -->
-    <div v-else class="content-div">
-        <table class="form">
+  </head>
+  <body>
+    <div id="app" v-cloak>
+      <div v-if="loading" class="loading">加载中...</div>
+      <div v-else-if="error" class="error">{{ error }}</div>
+      <!-- 新增学员请假基本信息回显页 by xu 2026-02-28 -->
+      <div v-else class="content-div">
+        <div class="section-card">
+          <table class="form">
             <tr>
-                <th>请假类别</th>
-                <td>{{ displayData.rclb || '-' }}</td>
+              <th>请假类别</th>
+              <td>{{ displayData.rclb || '-' }}</td>
             </tr>
             <tr>
-                <th>学员</th>
-                <td>{{ displayData.fzrymc || displayData.xm || '-' }}</td>
+              <th>学员</th>
+              <td>
+                {{ displayData.studentName || displayData.fzrymc ||
+                displayData.xm || displayData.fzryid || '-' }}
+              </td>
             </tr>
             <tr>
-                <th>请假开始时间</th>
-                <td>{{ displayData.kssj || '-' }}</td>
+              <th>请假开始时间</th>
+              <td>{{ displayData.kssj || '-' }}</td>
             </tr>
             <tr>
-                <th>请假结束时间</th>
-                <td>{{ displayData.jssj || '-' }}</td>
+              <th>请假结束时间</th>
+              <td>{{ displayData.jssj || '-' }}</td>
             </tr>
             <tr>
-                <th>事由</th>
-                <td class="reason-text">{{ displayData.ms || '-' }}</td>
+              <th>事由</th>
+              <td class="reason-text">{{ displayData.ms || '-' }}</td>
             </tr>
-            <tr>
-                <th>附件</th>
-                <td>
-                    <span class="attachment-text">{{ attachmentText }}</span>
-                </td>
-            </tr>
-        </table>
+          </table>
+        </div>
+      </div>
     </div>
-</div>
 
-<script>
-    window.SS.ready(function () {
+    <script>
+      window.SS.ready(function () {
         window.SS.dom.initializeFormApp({
-            el: '#app',
-            data() {
-                return {
-                    pageParams: {},
-                    loading: false,
-                    error: '',
-                    formData: {},
-                    displayData: {
-                        rclb: '',
-                        fzrymc: '',
-                        xm: '',
-                        kssj: '',
-                        jssj: '',
-                        ms: '',
-                        fjid: ''
-                    }
+          el: "#app",
+          data() {
+            return {
+              pageParams: {},
+              loading: false,
+              error: "",
+              formData: {},
+              displayData: {
+                rclb: "",
+                fzrymc: "",
+                xm: "",
+                kssj: "",
+                jssj: "",
+                ms: "",
+                fjid: "",
+              },
+            };
+          },
+          async mounted() {
+            this.pageParams = this.getUrlParams();
+            await this.loadData();
+          },
+          methods: {
+            getUrlParams() {
+              const params = {};
+              const urlSearchParams = new URLSearchParams(
+                window.location.search
+              );
+              for (const [key, value] of urlSearchParams) {
+                params[key] = decodeURIComponent(value);
+              }
+              return params;
+            },
+
+            parseParamObject(paramStr) {
+              if (!paramStr) return {};
+              try {
+                return JSON.parse(paramStr);
+              } catch (err) {
+                try {
+                  const fixed = paramStr.replace(
+                    /([,{]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g,
+                    '$1"$2":'
+                  );
+                  return JSON.parse(fixed);
+                } catch (err2) {
+                  console.error("解析param失败:", err2);
+                  return {};
                 }
+              }
             },
-            computed: {
-                attachmentText() {
-                    if (!this.displayData.fjid) return '暂无附件'
-                    return `已上传(附件ID:${this.displayData.fjid},后续接上传回显)`
+
+            async loadData() {
+              // 加载请假详情数据并回显 by xu 2026-02-28
+              const service = this.pageParams.service;
+              if (!service) {
+                this.error = "缺少service参数";
+                return;
+              }
+
+              this.loading = true;
+              this.error = "";
+
+              try {
+                const paramObj = this.parseParamObject(this.pageParams.param);
+                const requestData = {
+                  ...paramObj,
+                };
+                const appendIfMissing = (targetKey, sourceKeys) => {
+                  if (
+                    requestData[targetKey] !== undefined &&
+                    requestData[targetKey] !== null &&
+                    requestData[targetKey] !== ""
+                  ) {
+                    return;
+                  }
+                  for (let i = 0; i < sourceKeys.length; i += 1) {
+                    const sourceKey = sourceKeys[i];
+                    const value = this.pageParams[sourceKey];
+                    if (value !== undefined && value !== null && value !== "") {
+                      requestData[targetKey] = value;
+                      return;
+                    }
+                  }
+                };
+                appendIfMissing("sqid", ["sqid"]);
+                appendIfMissing("shid", ["shid"]);
+                appendIfMissing("ssObjName", ["ssObjName", "ssobjname"]);
+                appendIfMissing("ssObjId", ["ssObjId", "ssobjid"]);
+                appendIfMissing("bdlbm", ["bdlbm"]);
+                appendIfMissing("dataType", ["dataType", "datatype"]);
+                appendIfMissing("encode_shid", ["encode_shid"]);
+                appendIfMissing("jdmc", ["jdmc"]);
+                appendIfMissing("ssToken", ["ssToken"]);
+
+                const response = await request.post(
+                  `/service?ssServ=${service}&ssDest=data`,
+                  requestData,
+                  { loading: false, formData: true }
+                );
+
+                const raw = this.pickLeaveData(response?.data);
+                if (!raw) {
+                  this.error = "未获取到请假数据";
+                  return;
                 }
+
+                this.formData = raw;
+                await this.buildDisplayData(raw);
+              } catch (error) {
+                console.error("加载请假基本信息失败:", error);
+                this.error = "加载失败:" + (error.message || "未知错误");
+              } finally {
+                this.loading = false;
+              }
             },
-            async mounted() {
-                this.pageParams = this.getUrlParams()
-                await this.loadData()
+
+            pickLeaveData(data) {
+              if (!data) return null;
+              if (data.xyqj) return data.xyqj;
+              if (data.rcqj) return data.rcqj;
+              if (data.qj) return data.qj;
+              if (Array.isArray(data.objectList) && data.objectList.length > 0)
+                return data.objectList[0];
+              if (Array.isArray(data.data) && data.data.length > 0)
+                return data.data[0];
+              if (typeof data === "object") return data;
+              return null;
             },
-            methods: {
-                getUrlParams() {
-                    const params = {}
-                    const urlSearchParams = new URLSearchParams(window.location.search)
-                    for (const [key, value] of urlSearchParams) {
-                        params[key] = decodeURIComponent(value)
-                    }
-                    return params
-                },
-
-                parseParamObject(paramStr) {
-                    if (!paramStr) return {}
-                    try {
-                        return JSON.parse(paramStr)
-                    } catch (err) {
-                        try {
-                            const fixed = paramStr.replace(/([,{]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, '$1"$2":')
-                            return JSON.parse(fixed)
-                        } catch (err2) {
-                            console.error('解析param失败:', err2)
-                            return {}
-                        }
-                    }
-                },
-
-                async loadData() {
-                    // 加载请假详情数据并回显 by xu 2026-02-28
-                    const service = this.pageParams.service
-                    if (!service) {
-                        this.error = '缺少service参数'
-                        return
-                    }
 
-                    this.loading = true
-                    this.error = ''
-
-                    try {
-                        const paramObj = this.parseParamObject(this.pageParams.param)
-                        const response = await request.post(
-                            `/service?ssServ=${service}&ssDest=data`,
-                            paramObj,
-                            { loading: false, formData: true }
-                        )
-
-                        const raw = this.pickLeaveData(response?.data)
-                        if (!raw) {
-                            this.error = '未获取到请假数据'
-                            return
-                        }
-
-                        this.formData = raw
-                        await this.buildDisplayData(raw)
-                    } catch (error) {
-                        console.error('加载请假基本信息失败:', error)
-                        this.error = '加载失败:' + (error.message || '未知错误')
-                    } finally {
-                        this.loading = false
-                    }
-                },
-
-                pickLeaveData(data) {
-                    if (!data) return null
-                    if (data.xyqj) return data.xyqj
-                    if (data.rcqj) return data.rcqj
-                    if (data.qj) return data.qj
-                    if (Array.isArray(data.objectList) && data.objectList.length > 0) return data.objectList[0]
-                    if (Array.isArray(data.data) && data.data.length > 0) return data.data[0]
-                    if (typeof data === 'object') return data
-                    return null
-                },
-
-                async buildDisplayData(raw) {
-                    const cloned = { ...raw }
-                    cloned.kssj = this.formatDate(raw.kssj)
-                    cloned.jssj = this.formatDate(raw.jssj)
-                    cloned.rclb = await this.translateLeaveType(raw.rclbm, raw.rclb)
-                    this.displayData = {
-                        ...this.displayData,
-                        ...cloned
-                    }
-                },
+            async buildDisplayData(raw) {
+              const cloned = { ...raw };
+              cloned.kssj = this.formatDate(raw.kssj);
+              cloned.jssj = this.formatDate(raw.jssj);
+              cloned.rclb = await this.translateLeaveType(raw.rclbm, raw.rclb);
+              cloned.studentName = await this.resolveStudentName(raw);
+              this.displayData = {
+                ...this.displayData,
+                ...cloned,
+              };
+            },
+            async resolveStudentName(raw) {
+              const direct = raw.fzrymc || raw.xm || raw.rymc || raw.xmmc || "";
+              if (direct) return direct;
 
-                formatDate(value) {
-                    if (!value) return ''
-                    if (window.H5FieldFormatter && typeof window.H5FieldFormatter.formatDate === 'function') {
-                        return window.H5FieldFormatter.formatDate(value, 'YYYY-MM-DD HH:mm')
-                    }
-                    return value
-                },
-
-                async translateLeaveType(rclbm, fallbackName) {
-                    if (fallbackName) return fallbackName
-                    if (!rclbm) return ''
-                    try {
-                        if (typeof window.getDictOptions === 'function') {
-                            const options = await window.getDictOptions('xyqjlb')
-                            const target = (options || []).find(item => String(item.v) === String(rclbm))
-                            return target ? target.n : rclbm
-                        }
-                        return rclbm
-                    } catch (error) {
-                        console.error('请假类别翻译失败:', error)
-                        return rclbm
-                    }
+              const idValue = String(raw.fzryid || "").trim();
+              if (!idValue) return "";
+
+              try {
+                if (typeof window.getDictOptions === "function") {
+                  const options = await window.getDictOptions("ry");
+                  const target = (options || []).find(
+                    (item) => String(item.v) === String(idValue)
+                  );
+                  if (target && target.n) return target.n;
                 }
-            }
-        })
-    })
-</script>
-</body>
+              } catch (error) {
+                console.error("学员姓名翻译失败:", error);
+              }
+
+              const dictCache = [
+                this.pageParams,
+                this.formData,
+                raw,
+                this.displayData,
+              ];
+              for (let i = 0; i < dictCache.length; i += 1) {
+                const src = dictCache[i] || {};
+                const candidate =
+                  src[`${idValue}_name`] ||
+                  src[`${idValue}Name`] ||
+                  src.fzryName ||
+                  src.fzrymc;
+                if (candidate) return candidate;
+              }
+
+              return idValue;
+            },
+
+            formatDate(value) {
+              if (!value) return "";
+              if (
+                window.H5FieldFormatter &&
+                typeof window.H5FieldFormatter.formatDate === "function"
+              ) {
+                return window.H5FieldFormatter.formatDate(
+                  value,
+                  "YYYY-MM-DD HH:mm"
+                );
+              }
+              return value;
+            },
+
+            async translateLeaveType(rclbm, fallbackName) {
+              if (fallbackName) return fallbackName;
+              if (!rclbm) return "";
+              try {
+                if (typeof window.getDictOptions === "function") {
+                  const options = await window.getDictOptions("xyqjlb");
+                  const target = (options || []).find(
+                    (item) => String(item.v) === String(rclbm)
+                  );
+                  return target ? target.n : rclbm;
+                }
+                return rclbm;
+              } catch (error) {
+                console.error("请假类别翻译失败:", error);
+                return rclbm;
+              }
+            },
+          },
+        });
+      });
+    </script>
+  </body>
 </html>

+ 80 - 2
page/mp_xyqj_inp.html

@@ -65,6 +65,7 @@
                         cb="xyByJzBySess"
                         :auto-select-first="true"
                         placeholder="请选择"
+                        @change="onStudentChange"
                     >
                     </ss-select>
                 </td>
@@ -120,12 +121,27 @@
                     formData: {
                         mc: '学员请假',
                         fzryid: '',
+                        bjid: '',
                         rclbm: '',
                         kssj: '',
                         jssj: '',
                         ms: ''
                     },
-                    endMinDate: ''
+                    endMinDate: '',
+                    studentLookupSeq: 0,
+                }
+            },
+            watch: {
+                // 功能说明:学员变化时自动反查班级并缓存 bjid(兼容自动选中与手动切换) by xu 2026-03-01
+                'formData.fzryid': function (newVal, oldVal) {
+                    const next = String(newVal || '').trim()
+                    const prev = String(oldVal || '').trim()
+                    if (next === prev) return
+                    if (!next) {
+                        this.formData.bjid = ''
+                        return
+                    }
+                    this.fetchBjidByRyid(next)
                 }
             },
             async mounted() {
@@ -133,7 +149,12 @@
                 this.initDefaultTimeRange()
 
                 // 功能说明:向 mp_objInp 暴露统一取值方法,尽量减少表单页改动 by xu 2026-02-28
-                window.__mpObjInpGetFormData = () => {
+                window.__mpObjInpGetFormData = async () => {
+                    // 功能说明:提交前兜底反查 bjid,避免学员切换后接口未返回就提交导致缺参 by xu 2026-03-01
+                    if (this.formData.fzryid && !this.formData.bjid) {
+                        await this.fetchBjidByRyid(this.formData.fzryid)
+                    }
+
                     const message = this.validateForm()
                     return {
                         valid: !message,
@@ -141,6 +162,7 @@
                         data: {
                             mc: this.formData.mc,
                             fzryid: this.formData.fzryid,
+                            bjid: this.formData.bjid,
                             rclbm: this.formData.rclbm,
                             kssj: this.formData.kssj,
                             jssj: this.formData.jssj,
@@ -195,6 +217,62 @@
                     }
                 },
 
+                // 功能说明:手动触发学员变更时立即反查 bjid,避免等待 watch 异步节奏 by xu 2026-03-01
+                onStudentChange(val) {
+                    const ryid = String(val || this.formData.fzryid || '').trim()
+                    if (!ryid) {
+                        this.formData.bjid = ''
+                        return
+                    }
+                    this.fetchBjidByRyid(ryid)
+                },
+
+                // 功能说明:通过 ryid 调用 xy_selBjidByXyid 反查班级并缓存到 formData.bjid by xu 2026-03-01
+                async fetchBjidByRyid(ryid) {
+                    const value = String(ryid || '').trim()
+                    if (!value) {
+                        this.formData.bjid = ''
+                        return
+                    }
+
+                    const reqSeq = ++this.studentLookupSeq
+                    try {
+                        const req = (window && window.request) || request
+                        if (!req || typeof req.post !== 'function') {
+                            console.warn('[mp_xyqj_inp] request 不可用,跳过 bjid 反查')
+                            return
+                        }
+
+                        const response = await req.post(
+                            '/service?ssServ=xy_selBjidByXyid',
+                            { ryid: value },
+                            { loading: false, formData: true }
+                        )
+
+                        if (reqSeq !== this.studentLookupSeq) return
+
+                        const raw = response && response.data ? response.data : response
+                        const data = raw && raw.ssData !== undefined ? raw.ssData : raw
+
+                        // 功能说明:兼容 xy_selBjidByXyid 返回 ssData=数字/字符串/对象 三种结构 by xu 2026-03-01
+                        let bjid = ''
+                        if (data !== undefined && data !== null && (typeof data === 'number' || typeof data === 'string')) {
+                            bjid = String(data).trim()
+                        } else if (data && typeof data === 'object' && data.bjid !== undefined && data.bjid !== null) {
+                            bjid = String(data.bjid).trim()
+                        } else if (raw && typeof raw === 'object' && raw.bjid !== undefined && raw.bjid !== null) {
+                            bjid = String(raw.bjid).trim()
+                        }
+
+                        this.formData.bjid = bjid
+                        console.log('[mp_xyqj_inp] 学员反查班级成功', { ryid: value, bjid: this.formData.bjid })
+                    } catch (error) {
+                        if (reqSeq !== this.studentLookupSeq) return
+                        this.formData.bjid = ''
+                        console.error('[mp_xyqj_inp] 学员反查班级失败', error)
+                    }
+                },
+
                 validateForm() {
                     if (!this.formData.rclbm) return '请选择请假类别'
                     if (!this.formData.fzryid) return '请选择学员'

+ 98 - 8
skin/mp_easy/base.css

@@ -202,7 +202,7 @@ td.td-error::before {
   height: 100%;
   padding-left: 16px;
   padding-right: 16px;
-  align-items: flex-start;
+  align-items: center;
   justify-content: flex-start;
   box-sizing: border-box;
   border-bottom: 2px solid #f2f2f2;
@@ -469,11 +469,68 @@ body.modal-open {
 
 
 /* ========== SsCard 卡片组件样式 ========== */
+
+.ss-card-swipe__indicator {
+  grid-area: 1 / 1;
+  justify-self: end;
+  align-self: stretch;
+  width: 8px;
+  height: 100%;
+  z-index: 0;
+  transition: opacity 0.2s ease;
+  pointer-events: none;
+  position: relative;
+  z-index: 111;
+  border-radius: 0 4px 4px 0;
+}
+
+.ss-card-swipe__actions {
+  grid-area: 1 / 1;
+  display: flex;
+  align-items: stretch;
+  justify-content: flex-end;
+  align-self: stretch;
+  justify-self: end;
+}
+
+.ss-card-swipe__action {
+  width: 66px;
+  min-width: 66px;
+  max-width: 66px;
+  height: 100%;
+  border: none;
+  font-size: 16px;
+  padding: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  word-break: break-all;
+}
+
+.ss-card-swipe__action:last-child {
+  border-radius: 0 4px 4px 0;
+}
+
+.ss-card-swipe__action:active {
+  background: #3f4552;
+}
+
+.ss-card-swipe__content {
+  grid-area: 1 / 1;
+  position: relative;
+  z-index: 1;
+  display: block;
+  width: 100%;
+  transition: transform 0.2s ease;
+  will-change: transform;
+}
+
 .ss-card {
   background: #FFFFFF;
   border-radius: 4px;
   overflow: visible;
-  padding: 12.5px;
+  padding: 0;
   border: 1px solid #d9d9d9;
   margin-bottom: 15px;
   box-shadow: 1px 3px 3px rgba(4, 0, 0, 0.15);
@@ -483,6 +540,22 @@ body.modal-open {
   cursor: pointer;
 }
 
+.ss-card--swipeable {
+  display: grid;
+  overflow: hidden;
+  padding: 0;
+}
+
+.ss-card-main {
+  position: relative;
+  padding: 12.5px;
+  box-sizing: border-box;
+}
+
+.ss-card--swipeable .ss-card-swipe__content {
+  background: #FFFFFF;
+}
+
 /* 右上角设置按钮区域 */
 .card-setting-header {
   position: absolute;
@@ -579,8 +652,9 @@ body.modal-open {
 .options-menu {
   position: absolute;
   top: 100%;
-  left: 0;
-  width: 100%;
+  right: 0;
+  left: auto;
+  width: max-content;
   background-color: #393D51;
   z-index: 1003;
   color: #fff;
@@ -590,7 +664,9 @@ body.modal-open {
   overflow: hidden;
   max-height: 300px;
   overflow-y: auto;
-  min-width: 200px;
+  /* 功能说明:搜索按钮下拉菜单按按钮右侧对齐展开,宽度不再写死 200px,避免右侧按钮超出屏幕 by xu 2026-03-06 */
+  min-width: 100%;
+  max-width: calc(100vw - 30px);
 }
 
 .option-item {
@@ -637,6 +713,10 @@ body.modal-open {
   font-size: 14px;
   color: inherit;
   flex: 1;
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 /* ========== SsCard 卡片组件样式 ========== */
@@ -644,7 +724,7 @@ body.modal-open {
   background: #FFFFFF;
   border-radius: 4px;
   overflow: visible;
-  padding: 12.5px;
+  padding: 0px;
   border: 1px solid #d9d9d9;
   margin-bottom: 15px;
   box-shadow: 1px 3px 3px rgba(4, 0, 0, 0.15);
@@ -799,7 +879,8 @@ body.modal-open {
   display: flex;
   align-items: center;
   cursor: pointer;
-
+  box-sizing: border-box;
+  max-width: 100%;
 }
 
 .ss-select {
@@ -812,6 +893,8 @@ body.modal-open {
   justify-content: space-between;
   /* min-height: 40px; */
   box-sizing: border-box;
+  width: 100%;
+  min-width: 0;
 }
 
 .ss-select.disabled {
@@ -827,6 +910,10 @@ body.modal-open {
   flex: 1;
   color: #333;
   font-size: 16px;
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 .select-text.placeholder {
@@ -851,7 +938,9 @@ body.modal-open {
   position: absolute;
   top: 100%;
   left: 0;
+  /* 功能说明:下拉层宽度跟随组件稳定宽度,不再随 placeholder/选中项来回变化 by xu 2026-03-06 */
   width: 100%;
+  min-width: 100%;
   background-color: #393D51;
   z-index: 1000;
   color: #fff;
@@ -871,6 +960,7 @@ body.modal-open {
   padding: 10px 10px 10px 23px;
   cursor: pointer;
   position: relative;
+  white-space: nowrap;
 }
 
 .option-item::after {
@@ -1713,7 +1803,7 @@ body.modal-open {
 
 .ss-sub-tab__bar {
   width: 95%;
-  margin: 0 auto 10px;
+  margin: 10px auto 0px;
   background: #f5f5f5;
   overflow-x: auto;
   overflow-y: hidden;

+ 1 - 0
skin/mp_easy/icon/icon-obj-kqjl.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1772790252689" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2834" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M499.618909 0.139636C220.16 5.725091-4.747636 206.103273 0.093091 445.393455c2.373818 120.738909 61.905455 229.282909 156.485818 307.29309l301.754182 252.555637c29.789091 24.994909 77.637818 24.994909 107.380364 0l301.940363-252.741818C964.002909 673.000727 1024 561.431273 1024 437.899636 1023.860364 192.698182 788.014545-5.585455 499.618909 0.093091z" fill="#575D6D" p-id="2835"></path><path d="M512 791.272727c-231.424 0-418.909091-156.299636-418.909091-348.951272C93.090909 249.623273 280.576 93.090909 512 93.090909s418.909091 156.299636 418.909091 349.090909c0 192.791273-187.671273 349.090909-418.909091 349.090909z" fill="#FFFFFF" p-id="2836"></path><path d="M767.441455 443.578182h-213.41091V241.431273c0-30.254545-26.251636-55.202909-58.274909-55.202909C463.778909 186.181818 437.527273 210.990545 437.527273 241.384727v257.396364c0 30.440727 26.251636 55.249455 58.228363 55.249454h271.685819c32.023273 0 58.228364-24.808727 58.228363-55.202909 0-30.394182-26.205091-55.202909-58.181818-55.202909z" fill="#575D6D" p-id="2837"></path></svg>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů