mp_ccChk.html 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta
  6. name="viewport"
  7. content="width=device-width, initial-scale=1.0, user-scalable=no"
  8. />
  9. <title>审核</title>
  10. <script src="/js/mp_base/base.js"></script>
  11. <style>
  12. /* 防止Vue模板闪烁 */
  13. [v-cloak] {
  14. display: none !important;
  15. }
  16. #app {
  17. background: #f5f5f5;
  18. height: 100vh;
  19. display: flex;
  20. flex-direction: column;
  21. overflow: hidden; /* 防止页面级别滚动 */
  22. box-sizing: border-box; /* 确保padding计算在高度内 */
  23. }
  24. /* iframe样式 */
  25. .top-iframe {
  26. background: #fff;
  27. overflow: hidden;
  28. border: none;
  29. display: block;
  30. box-sizing: border-box;
  31. }
  32. .bottom-iframe {
  33. background: #fff;
  34. overflow: hidden;
  35. border: none;
  36. display: block;
  37. box-sizing: border-box;
  38. }
  39. .title {
  40. flex-shrink: 0;
  41. box-sizing: border-box;
  42. font-size: 16px;
  43. text-align: center;
  44. /* margin: 16px auto; */
  45. height: 48px;
  46. line-height: 48px;
  47. }
  48. /* 录入div样式 */
  49. .review-input-popup {
  50. position: fixed;
  51. bottom: 0;
  52. left: 0;
  53. right: 0;
  54. height: 50px;
  55. background: #e6e6e6;
  56. display: flex;
  57. align-items: center;
  58. padding: 0 0 0 10px;
  59. z-index: 1000;
  60. transform: translateY(100%);
  61. transition: transform 0.3s ease;
  62. box-sizing: border-box;
  63. }
  64. .review-input-popup.show {
  65. transform: translateY(0);
  66. }
  67. .review-input-wrapper {
  68. flex: 1;
  69. display: flex;
  70. align-items: center;
  71. gap: 4px;
  72. }
  73. .review-input {
  74. flex: 1;
  75. height: 32px;
  76. border: none;
  77. padding: 0 10px;
  78. font-size: 16px;
  79. background: #fff;
  80. outline: none;
  81. box-sizing: border-box;
  82. }
  83. .common-phrases-btn {
  84. padding: 6px 12px;
  85. background: #fff;
  86. border: none;
  87. font-size: 16px;
  88. color: #333;
  89. cursor: pointer;
  90. white-space: nowrap;
  91. margin-right: 4px;
  92. }
  93. .common-phrases-btn:hover {
  94. background: #e0e0e0;
  95. }
  96. .submit-btn {
  97. box-sizing: border-box;
  98. height: 50px;
  99. padding: 6px 16px;
  100. border: none;
  101. font-size: 16px;
  102. color: #fff;
  103. cursor: pointer;
  104. white-space: nowrap;
  105. transition: background-color 0.2s;
  106. }
  107. .submit-btn.agree {
  108. background: #585e6e;
  109. }
  110. .submit-btn.agree:active {
  111. background: #242835;
  112. }
  113. .submit-btn.reject {
  114. background: #e58846;
  115. }
  116. .submit-btn.reject:active {
  117. background: #eb6100;
  118. }
  119. /* 常用语popup */
  120. .common-phrases-popup {
  121. position: fixed;
  122. bottom: 50px;
  123. left: 10px;
  124. right: 10px;
  125. background: #fff;
  126. border-radius: 4px;
  127. box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
  128. max-height: 200px;
  129. overflow-y: auto;
  130. z-index: 1001;
  131. display: none;
  132. }
  133. .common-phrases-popup.show {
  134. display: block;
  135. }
  136. .phrase-item {
  137. padding: 12px 16px;
  138. border-bottom: 1px solid #f0f0f0;
  139. cursor: pointer;
  140. font-size: 14px;
  141. }
  142. .phrase-item:hover {
  143. background: #f8f8f8;
  144. }
  145. .phrase-item:last-child {
  146. border-bottom: none;
  147. }
  148. /* 原ss-bottom隐藏 */
  149. .ss-bottom.hidden {
  150. display: none;
  151. }
  152. .url-log-fab {
  153. position: fixed;
  154. right: 12px;
  155. bottom: 72px;
  156. z-index: 1200;
  157. border: none;
  158. border-radius: 16px;
  159. background: rgba(36, 40, 53, 0.88);
  160. color: #fff;
  161. font-size: 12px;
  162. line-height: 1;
  163. padding: 8px 10px;
  164. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  165. display: none;
  166. }
  167. .url-log-panel {
  168. position: fixed;
  169. left: 12px;
  170. right: 12px;
  171. bottom: 116px;
  172. z-index: 1200;
  173. background: rgba(36, 40, 53, 0.96);
  174. color: #fff;
  175. border-radius: 8px;
  176. padding: 10px;
  177. max-height: 42vh;
  178. overflow: auto;
  179. box-sizing: border-box;
  180. }
  181. .url-log-panel__head {
  182. display: flex;
  183. align-items: center;
  184. justify-content: space-between;
  185. margin-bottom: 8px;
  186. font-size: 12px;
  187. }
  188. .url-log-panel__close {
  189. border: none;
  190. background: transparent;
  191. color: #fff;
  192. font-size: 14px;
  193. line-height: 1;
  194. }
  195. .url-log-panel__body {
  196. margin: 0;
  197. white-space: pre-wrap;
  198. word-break: break-all;
  199. font-size: 12px;
  200. line-height: 1.5;
  201. }
  202. .review-section {
  203. flex-shrink: 0;
  204. background: #f5f5f5;
  205. }
  206. .review-section .ss-sub-tab__bar {
  207. margin-bottom: 8px;
  208. }
  209. .review-section__card {
  210. width: 95%;
  211. margin: 0 auto 8px;
  212. background: #fff;
  213. border-radius: 8px;
  214. overflow: hidden;
  215. box-sizing: border-box;
  216. }
  217. .review-section__form {
  218. width: 100%;
  219. border-collapse: collapse;
  220. }
  221. .review-section__form th {
  222. width: 140px;
  223. max-width: 170px;
  224. }
  225. </style>
  226. </head>
  227. <body>
  228. <div id="app" v-cloak>
  229. <!-- 新增抄送审核结果展示区 by xu 2026-03-06 -->
  230. <div ref="reviewSection" class="review-section" v-if="showReviewSection">
  231. <div class="ss-sub-tab__bar">
  232. <div class="ss-sub-tab__item ss-sub-tab__item--active">审核情况</div>
  233. </div>
  234. <div class="review-section__card">
  235. <table class="form review-section__form">
  236. <tr>
  237. <th>审核结果</th>
  238. <td>{{ reviewResultText || '-' }}</td>
  239. </tr>
  240. </table>
  241. </div>
  242. </div>
  243. <!-- 上方iframe - 基本信息区域 -->
  244. <iframe
  245. ref="topIframe"
  246. class="top-iframe"
  247. :src="topIframeSrc || 'about:blank'"
  248. width="100%"
  249. frameborder="0"
  250. >
  251. </iframe>
  252. <!-- 下方iframe - 审核列表区域 -->
  253. <iframe
  254. ref="bottomIframe"
  255. class="bottom-iframe"
  256. :src="bottomIframeSrc || 'about:blank'"
  257. width="100%"
  258. frameborder="0"
  259. >
  260. </iframe>
  261. <!-- 底部按钮 -->
  262. <ss-bottom
  263. :show-shyj="false"
  264. :buttons="bottomButtons"
  265. @button-click="handleBottomAction"
  266. :divider="false"
  267. :disabled="submitting"
  268. v-if="bottomButtons.length > 0"
  269. ></ss-bottom>
  270. <button class="url-log-fab" type="button" @click="showCurrentUrls">
  271. URL
  272. </button>
  273. <div class="url-log-panel" v-if="urlLogVisible">
  274. <div class="url-log-panel__head">
  275. <span>当前 URL</span>
  276. <button
  277. class="url-log-panel__close"
  278. type="button"
  279. @click="urlLogVisible = false"
  280. >
  281. ×
  282. </button>
  283. </div>
  284. <pre class="url-log-panel__body">{{ urlLogText }}</pre>
  285. </div>
  286. </div>
  287. <script>
  288. // 等待SS框架加载完成
  289. window.SS.ready(function () {
  290. // 使用SS框架的方式创建Vue实例
  291. window.SS.dom.initializeFormApp({
  292. el: "#app",
  293. data() {
  294. return {
  295. pageParams: {},
  296. loading: false,
  297. jdmc: "",
  298. sqid: "",
  299. shid: "",
  300. shyjm: "",
  301. bdlbm: "",
  302. dataType: "bdplay",
  303. encode_shid: "",
  304. ssObjName: "",
  305. ssObjId: "",
  306. infoData: null,
  307. agrCcData: null, // 存储agrCc返回的数据
  308. topIframeSrc: "",
  309. bottomIframeSrc: "",
  310. // 底部按钮相关
  311. submitting: false,
  312. bottomButtons: [],
  313. // iframe布局相关
  314. layoutCalculated: false,
  315. headerSectionHeight: 0,
  316. availableHeight: 0,
  317. topIframeHeight: 0,
  318. bottomIframeHeight: 0,
  319. bottomIframeMinHeight: 0,
  320. isExpanded: false,
  321. // 拖拽相关
  322. isDragging: false,
  323. dragStartY: 0,
  324. dragStartBottomHeight: 0,
  325. urlLogVisible: false,
  326. urlLogText: "",
  327. reviewResultText: "",
  328. };
  329. },
  330. computed: {
  331. showReviewSection() {
  332. return (
  333. this.shyjm !== undefined &&
  334. this.shyjm !== null &&
  335. this.shyjm !== ""
  336. );
  337. },
  338. },
  339. mounted() {
  340. // 获取URL参数
  341. this.pageParams = this.getUrlParams();
  342. this.shyjm = this.pageParams.shyjm || "";
  343. console.log("🔗 mp_ccChk页面接收到参数:", this.pageParams);
  344. // 打印所有参数到控制台
  345. Object.keys(this.pageParams).forEach((key) => {
  346. console.log(`参数 ${key}:`, this.pageParams[key]);
  347. });
  348. this.syncReviewResultText();
  349. this.callApi();
  350. // 添加iframe消息监听器
  351. window.addEventListener("message", this.handleIframeMessage);
  352. // 初始化布局计算
  353. this.$nextTick(() => {
  354. setTimeout(() => {
  355. this.initializeLayout();
  356. }, 500); // 等待iframe加载完成
  357. });
  358. },
  359. beforeDestroy() {
  360. // 清理事件监听器
  361. window.removeEventListener("message", this.handleIframeMessage);
  362. },
  363. methods: {
  364. // 翻译审核结果并在页面顶部展示 by xu 2026-03-06
  365. async syncReviewResultText() {
  366. this.reviewResultText = await this.translateReviewResult(
  367. this.shyjm
  368. );
  369. this.$nextTick(() => {
  370. if (this.layoutCalculated) {
  371. this.initializeLayout();
  372. }
  373. });
  374. },
  375. async translateReviewResult(shyjm) {
  376. if (shyjm === undefined || shyjm === null || shyjm === "") {
  377. return "";
  378. }
  379. try {
  380. if (typeof window.getDictOptions === "function") {
  381. const options = await window.getDictOptions("shyj");
  382. const target = (options || []).find(
  383. (item) => String(item.v) === String(shyjm)
  384. );
  385. if (target && target.n) return target.n;
  386. }
  387. } catch (error) {
  388. console.error("审核结果翻译失败:", error);
  389. }
  390. if (String(shyjm) === "1") return "通过";
  391. return String(shyjm);
  392. },
  393. // 获取URL参数
  394. getUrlParams() {
  395. const params = {};
  396. const aliasMap = {
  397. ssobjname: "ssObjName",
  398. ssobjid: "ssObjId",
  399. sqid: "sqid",
  400. shid: "shid",
  401. shyjm: "shyjm",
  402. bdlbm: "bdlbm",
  403. datatype: "dataType",
  404. encode_shid: "encode_shid",
  405. jdmc: "jdmc",
  406. };
  407. const urlSearchParams = new URLSearchParams(
  408. window.location.search
  409. );
  410. for (const [rawKey, rawValue] of urlSearchParams) {
  411. const decodedValue = this.safeDecode(rawValue);
  412. params[rawKey] = decodedValue;
  413. const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
  414. if (normalizedKey) {
  415. params[normalizedKey] = decodedValue;
  416. }
  417. }
  418. return params;
  419. },
  420. safeDecode(text) {
  421. if (text === undefined || text === null || text === "") return "";
  422. try {
  423. return decodeURIComponent(String(text));
  424. } catch (_) {
  425. return String(text);
  426. }
  427. },
  428. pickFirstValue(sources, keys) {
  429. const srcArr = Array.isArray(sources) ? sources : [];
  430. const keyArr = Array.isArray(keys) ? keys : [];
  431. for (let i = 0; i < srcArr.length; i += 1) {
  432. const src = srcArr[i];
  433. if (!src || typeof src !== "object") continue;
  434. for (let j = 0; j < keyArr.length; j += 1) {
  435. const key = keyArr[j];
  436. const value = src[key];
  437. if (value !== undefined && value !== null && value !== "") {
  438. return value;
  439. }
  440. }
  441. }
  442. return "";
  443. },
  444. extractTokenData(apiResponse) {
  445. const wantedKeys = [
  446. "sqid",
  447. "shid",
  448. "ssObjName",
  449. "ssObjId",
  450. "jdmc",
  451. "bdlbm",
  452. "dataType",
  453. "encode_shid",
  454. ];
  455. const candidates = [
  456. apiResponse && apiResponse.data && apiResponse.data.ssData,
  457. apiResponse && apiResponse.ssData,
  458. apiResponse && apiResponse.data,
  459. apiResponse,
  460. ];
  461. let best = {};
  462. let bestScore = -1;
  463. candidates.forEach((item) => {
  464. if (!item || typeof item !== "object") return;
  465. let score = 0;
  466. wantedKeys.forEach((key) => {
  467. if (
  468. item[key] !== undefined &&
  469. item[key] !== null &&
  470. item[key] !== ""
  471. ) {
  472. score += 1;
  473. }
  474. });
  475. if (score > bestScore) {
  476. best = item;
  477. bestScore = score;
  478. }
  479. });
  480. return best;
  481. },
  482. resolveBusinessContext(apiResponse) {
  483. const tokenData = this.extractTokenData(apiResponse);
  484. const sources = [tokenData, this.pageParams];
  485. return {
  486. sqid: String(this.pickFirstValue(sources, ["sqid"]) || ""),
  487. shid: String(this.pickFirstValue(sources, ["shid"]) || ""),
  488. shyjm: String(this.pickFirstValue(sources, ["shyjm"]) || ""),
  489. ssObjName: String(
  490. this.pickFirstValue(sources, ["ssObjName", "ssobjname"]) || ""
  491. ),
  492. ssObjId: String(
  493. this.pickFirstValue(sources, ["ssObjId", "ssobjid"]) || ""
  494. ),
  495. bdlbm: String(this.pickFirstValue(sources, ["bdlbm"]) || ""),
  496. dataType:
  497. String(
  498. this.pickFirstValue(sources, ["dataType", "datatype"]) ||
  499. this.dataType ||
  500. "bdplay"
  501. ) || "bdplay",
  502. encode_shid: String(
  503. this.pickFirstValue(sources, ["encode_shid"]) || ""
  504. ),
  505. jdmc: this.safeDecode(
  506. this.pickFirstValue(sources, ["jdmc"]) || ""
  507. ),
  508. };
  509. },
  510. parseInfoParm(rawParm) {
  511. if (!rawParm) return {};
  512. if (typeof rawParm === "object") return rawParm;
  513. const text = String(rawParm).trim();
  514. if (!text) return {};
  515. try {
  516. return JSON.parse(text);
  517. } catch (_) {}
  518. try {
  519. const normalized = text
  520. .replace(/([{,]\s*)([A-Za-z0-9_]+)\s*:/g, '$1"$2":')
  521. .replace(/'/g, '"');
  522. return JSON.parse(normalized);
  523. } catch (_) {
  524. return {};
  525. }
  526. },
  527. resolveInfoDestPath(destName) {
  528. const raw = String(destName || "").trim();
  529. if (!raw) return "";
  530. if (/^https?:\/\//i.test(raw)) return raw;
  531. if (raw.startsWith("/")) return raw;
  532. if (raw.endsWith(".html")) return `/page/${raw}`;
  533. if (raw.startsWith("mp_")) return `/page/${raw}.html`;
  534. return `/page/mp_${raw}.html`;
  535. },
  536. buildInfoIframeSrc(infoData, fallbackQuery) {
  537. const conf =
  538. infoData && typeof infoData === "object" ? infoData : {};
  539. const serviceName = conf.service || conf.servName || "";
  540. const destName = conf.dest || "";
  541. const parmObj = this.parseInfoParm(conf.parm);
  542. const mergedQuery = {
  543. ...(fallbackQuery || {}),
  544. ...(parmObj || {}),
  545. };
  546. if (!serviceName || !destName) {
  547. return this.buildIframeSrc(
  548. "/page/mp_objInfo.html",
  549. mergedQuery
  550. );
  551. }
  552. const pagePath = this.resolveInfoDestPath(destName);
  553. if (!pagePath) {
  554. return this.buildIframeSrc(
  555. "/page/mp_objInfo.html",
  556. mergedQuery
  557. );
  558. }
  559. const paramText =
  560. typeof conf.parm === "string"
  561. ? conf.parm
  562. : JSON.stringify(parmObj || {});
  563. const iframeQuery = {
  564. ...mergedQuery,
  565. ssServ: serviceName,
  566. ssDest: destName,
  567. service: serviceName,
  568. dest: destName,
  569. param: paramText,
  570. };
  571. return this.buildIframeSrc(pagePath, iframeQuery);
  572. },
  573. buildIframeSrc(path, queryObj) {
  574. const search = new URLSearchParams();
  575. Object.keys(queryObj || {}).forEach((key) => {
  576. const value = queryObj[key];
  577. if (value === undefined || value === null) return;
  578. search.set(key, String(value));
  579. });
  580. return `${path}?${search.toString()}`;
  581. },
  582. buildCommonQuery() {
  583. return {
  584. sqid: this.sqid,
  585. shid: this.shid,
  586. shyjm: this.shyjm,
  587. bdlbm: this.bdlbm,
  588. dataType: this.dataType,
  589. encode_shid: this.encode_shid,
  590. ssObjName: this.ssObjName,
  591. ssObjId: this.ssObjId,
  592. jdmc: this.jdmc,
  593. };
  594. },
  595. // 调用API
  596. async callApi() {
  597. this.loading = true;
  598. this.apiResult = null;
  599. this.infoData = null;
  600. this.agrCcData = null;
  601. this.bottomButtons = [];
  602. this.topIframeSrc = "";
  603. this.bottomIframeSrc = "";
  604. try {
  605. let apiResponse = null;
  606. if (this.pageParams.ssToken) {
  607. console.log(
  608. "🚀 开始调用API,ssToken:",
  609. this.pageParams.ssToken
  610. );
  611. apiResponse = await request.post(
  612. `/service?ssToken=${this.pageParams.ssToken}`,
  613. {},
  614. { loading: false }
  615. );
  616. console.log("✅ 原有API响应:", apiResponse);
  617. }
  618. const context = this.resolveBusinessContext(apiResponse);
  619. this.sqid = context.sqid;
  620. this.shid = context.shid;
  621. this.shyjm = context.shyjm;
  622. await this.syncReviewResultText();
  623. this.ssObjName = context.ssObjName;
  624. this.ssObjId = context.ssObjId;
  625. this.bdlbm = context.bdlbm;
  626. this.dataType = context.dataType;
  627. this.encode_shid = context.encode_shid;
  628. this.jdmc = context.jdmc || "";
  629. if (!this.sqid || !this.shid || !this.ssObjId) {
  630. const missingMessage = "缺少必要参数:sqid / shid / ssObjId";
  631. if (typeof showToastEffect === "function") {
  632. showToastEffect(missingMessage, 2200, "error");
  633. } else {
  634. this.urlLogText = missingMessage;
  635. this.urlLogVisible = true;
  636. }
  637. return;
  638. }
  639. const query = this.buildCommonQuery();
  640. const infoUrl = `/service?ssServ=dataTag&ssDest=data&name=info&ssObjName=${encodeURIComponent(
  641. this.ssObjName
  642. )}&ssObjId=${encodeURIComponent(
  643. this.ssObjId
  644. )}&sqid=${encodeURIComponent(
  645. this.sqid
  646. )}&shid=${encodeURIComponent(this.shid)}`;
  647. const infoRes = await request.post(
  648. infoUrl,
  649. {},
  650. { loading: false, formData: true }
  651. );
  652. this.infoData =
  653. infoRes && infoRes.data ? infoRes.data.info : null;
  654. this.topIframeSrc = this.buildInfoIframeSrc(
  655. this.infoData,
  656. query
  657. );
  658. this.bottomIframeSrc = this.buildIframeSrc(
  659. "/page/mp_shList.html",
  660. query
  661. );
  662. console.log("📋 调用dataTag服务获取agrCc数据...");
  663. const agrCcResponse = await request.post(
  664. `/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}`,
  665. {},
  666. { loading: false, formData: true }
  667. );
  668. // 保存agrCc数据并设置底部按钮
  669. if (
  670. agrCcResponse &&
  671. agrCcResponse.data &&
  672. agrCcResponse.data.agrCc
  673. ) {
  674. this.agrCcData = agrCcResponse.data.agrCc;
  675. console.log("📦 agrCc数据:", this.agrCcData);
  676. // 根据agrCc数据设置底部按钮
  677. this.bottomButtons = [
  678. {
  679. text: "确认",
  680. action: "submit",
  681. backgroundColor: "#585e6e",
  682. color: "#fff",
  683. clickBgColor: "#242835",
  684. },
  685. ];
  686. }
  687. } catch (error) {
  688. console.error("❌ API调用失败:", error);
  689. } finally {
  690. this.loading = false;
  691. }
  692. },
  693. // 处理底部按钮点击
  694. handleBottomAction(data) {
  695. if (data.action === "submit") {
  696. this.handleConfirm();
  697. }
  698. },
  699. showCurrentUrls() {
  700. const topIframe = this.$refs.topIframe;
  701. const bottomIframe = this.$refs.bottomIframe;
  702. const lines = [
  703. `page: ${window.location.href}`,
  704. `top: ${(topIframe && topIframe.src) || "(empty)"}`,
  705. `bottom: ${(bottomIframe && bottomIframe.src) || "(empty)"}`,
  706. ];
  707. this.urlLogText = lines.join("\n");
  708. this.urlLogVisible = true;
  709. },
  710. // 处理确认提交
  711. async handleConfirm() {
  712. if (!this.agrCcData) {
  713. if (typeof showToastEffect === "function") {
  714. showToastEffect("缺少必要的配置信息", 2200, "error");
  715. } else {
  716. this.urlLogText = "缺少必要的配置信息";
  717. this.urlLogVisible = true;
  718. }
  719. return;
  720. }
  721. if (this.submitting) return;
  722. try {
  723. this.submitting = true;
  724. console.log("📝 开始提交...");
  725. console.log("📦 提交参数:", {
  726. service: this.agrCcData.service,
  727. dest: this.agrCcData.dest,
  728. shid: this.shid,
  729. });
  730. const response = await request.post(
  731. `/service?ssServ=${this.agrCcData.service}&ssDest=${this.agrCcData.dest}`,
  732. { shid: this.shid },
  733. { loading: true, formData: true }
  734. );
  735. console.log("✅ 提交成功:", response);
  736. NavigationManager.goBack({ refreshParent: true });
  737. } catch (error) {
  738. console.error("❌ 提交失败:", error);
  739. const message =
  740. "提交失败: " + ((error && error.message) || "未知错误");
  741. if (typeof showToastEffect === "function") {
  742. showToastEffect(message, 2200, "error");
  743. } else {
  744. this.urlLogText = message;
  745. this.urlLogVisible = true;
  746. }
  747. } finally {
  748. this.submitting = false;
  749. }
  750. },
  751. // ===== 布局相关方法 =====
  752. // 初始化布局计算
  753. initializeLayout() {
  754. console.log("🔄 开始初始化布局计算");
  755. try {
  756. // 获取bottom iframe
  757. const bottomIframe = document.querySelector(".bottom-iframe");
  758. const bottomWindow = bottomIframe?.contentWindow;
  759. if (!bottomWindow) {
  760. console.error("无法获取bottom iframe的contentWindow");
  761. return;
  762. }
  763. // 等待iframe加载完成,然后检查header-section
  764. const waitForIframeLoad = () => {
  765. if (!bottomIframe.contentWindow) {
  766. console.log("⏳ 等待iframe contentWindow...");
  767. setTimeout(waitForIframeLoad, 100);
  768. return;
  769. }
  770. const bottomWindow = bottomIframe.contentWindow;
  771. console.log("✅ 获取到iframe contentWindow");
  772. // 等待iframe中的DOM加载完成
  773. const checkHeaderSection = () => {
  774. try {
  775. const headerSection =
  776. bottomWindow.document.querySelector(".header-section");
  777. if (headerSection) {
  778. const headerHeight = headerSection.offsetHeight;
  779. console.log(
  780. "✅ 计算到header-section高度:",
  781. headerHeight
  782. );
  783. // 计算布局参数
  784. const viewportHeight = window.innerHeight;
  785. // 动态获取顶部审核情况区域高度 by xu 2026-03-06
  786. const reviewSectionElement = this.$refs.reviewSection;
  787. const actualReviewSectionHeight = reviewSectionElement
  788. ? reviewSectionElement.offsetHeight
  789. : 0;
  790. // 等待bottom按钮渲染完成并获取实际高度
  791. const checkBottomButton = () => {
  792. try {
  793. const bottomElement =
  794. document.querySelector("ss-bottom");
  795. const actualBottomHeight = bottomElement
  796. ? bottomElement.offsetHeight
  797. : 50;
  798. this.headerSectionHeight = headerHeight;
  799. this.bottomIframeMinHeight = headerHeight;
  800. this.availableHeight =
  801. viewportHeight -
  802. actualReviewSectionHeight -
  803. actualBottomHeight;
  804. // 初始状态:底部iframe刚好显示header-section高度
  805. this.bottomIframeHeight =
  806. this.bottomIframeMinHeight;
  807. this.topIframeHeight =
  808. this.availableHeight - this.bottomIframeMinHeight;
  809. this.isExpanded = false; // 初始为收起状态
  810. this.layoutCalculated = true;
  811. // 应用计算后的高度
  812. this.applyIframeHeights();
  813. console.log(
  814. "📊 初始布局计算完成(仅显示header-section):",
  815. {
  816. 审核情况区域高度: actualReviewSectionHeight,
  817. 底部按钮实际高度: actualBottomHeight,
  818. 可用总高度: this.availableHeight,
  819. "header-section高度": headerHeight,
  820. 上iframe高度: this.topIframeHeight,
  821. 下iframe高度: this.bottomIframeHeight,
  822. iframe总高度:
  823. this.topIframeHeight +
  824. this.bottomIframeHeight,
  825. 是否超限:
  826. this.topIframeHeight +
  827. this.bottomIframeHeight >
  828. this.availableHeight,
  829. 差额:
  830. this.topIframeHeight +
  831. this.bottomIframeHeight -
  832. this.availableHeight,
  833. isExpanded: this.isExpanded,
  834. }
  835. );
  836. } catch (bottomError) {
  837. console.error(
  838. "❌ 获取底部按钮高度时出错:",
  839. bottomError
  840. );
  841. // 使用默认高度50px重试
  842. const defaultBottomHeight = 50;
  843. this.headerSectionHeight = headerHeight;
  844. this.bottomIframeMinHeight = headerHeight;
  845. this.availableHeight =
  846. viewportHeight -
  847. actualReviewSectionHeight -
  848. defaultBottomHeight;
  849. this.bottomIframeHeight =
  850. this.bottomIframeMinHeight;
  851. this.topIframeHeight =
  852. this.availableHeight - this.bottomIframeMinHeight;
  853. this.isExpanded = false;
  854. this.layoutCalculated = true;
  855. this.applyIframeHeights();
  856. }
  857. };
  858. // 如果bottom按钮还没加载,稍后重试
  859. if (!document.querySelector("ss-bottom")) {
  860. setTimeout(checkBottomButton, 100);
  861. } else {
  862. checkBottomButton();
  863. }
  864. } else {
  865. // 如果还没有加载完成,继续等待
  866. console.log("⏳ 等待header-section加载...");
  867. setTimeout(checkHeaderSection, 100);
  868. }
  869. } catch (error) {
  870. console.error("❌ 访问iframe DOM时出错:", error);
  871. console.log("⏳ 重试中...");
  872. setTimeout(checkHeaderSection, 200);
  873. }
  874. };
  875. checkHeaderSection();
  876. };
  877. waitForIframeLoad();
  878. } catch (error) {
  879. console.error("❌ 布局初始化失败:", error);
  880. }
  881. },
  882. // 应用iframe高度
  883. applyIframeHeights() {
  884. const topIframe = document.querySelector(".top-iframe");
  885. const bottomIframe = document.querySelector(".bottom-iframe");
  886. if (topIframe && bottomIframe) {
  887. console.log("📏 设置iframe高度前:", {
  888. topIframe: {
  889. currentHeight: topIframe.style.height,
  890. offsetHeight: topIframe.offsetHeight,
  891. },
  892. bottomIframe: {
  893. currentHeight: bottomIframe.style.height,
  894. offsetHeight: bottomIframe.offsetHeight,
  895. },
  896. newTopHeight: this.topIframeHeight,
  897. newBottomHeight: this.bottomIframeHeight,
  898. });
  899. // 直接应用新高度到iframe
  900. topIframe.style.height = `${this.topIframeHeight}px`;
  901. bottomIframe.style.height = `${this.bottomIframeHeight}px`;
  902. console.log("🎯 应用iframe高度:", {
  903. top: this.topIframeHeight,
  904. bottom: this.bottomIframeHeight,
  905. });
  906. // 强制重新计算iframe内容
  907. this.$nextTick(() => {
  908. try {
  909. topIframe.contentWindow.dispatchEvent(new Event("resize"));
  910. } catch (e) {
  911. // 忽略跨域错误
  912. }
  913. try {
  914. bottomIframe.contentWindow.dispatchEvent(
  915. new Event("resize")
  916. );
  917. } catch (e) {
  918. // 忽略跨域错误
  919. }
  920. console.log("✅ iframe高度更新完成");
  921. });
  922. }
  923. },
  924. // 快速版本的高度应用,用于拖拽时减少延迟
  925. applyIframeHeightsFast() {
  926. const topIframe = document.querySelector(".top-iframe");
  927. const bottomIframe = document.querySelector(".bottom-iframe");
  928. if (topIframe && bottomIframe) {
  929. // 直接设置高度,不进行日志和额外的处理
  930. topIframe.style.height = `${this.topIframeHeight}px`;
  931. bottomIframe.style.height = `${this.bottomIframeHeight}px`;
  932. }
  933. },
  934. // 计算iframe高度分配
  935. calculateHeights(bottomHeight) {
  936. // 计算实际可用的bottom iframe高度
  937. // 当bottom iframe展开时,可以覆盖top iframe,所以最大高度就是availableHeight
  938. // 最小高度不能小于header-section高度
  939. const bottomActualHeight = Math.max(
  940. this.bottomIframeMinHeight,
  941. Math.min(bottomHeight, this.availableHeight)
  942. );
  943. this.bottomIframeHeight = bottomActualHeight;
  944. this.topIframeHeight = this.availableHeight - bottomActualHeight;
  945. console.log("📐 高度分配计算:", {
  946. requestedBottom: bottomHeight,
  947. availableHeight: this.availableHeight,
  948. minBottomHeight: this.bottomIframeMinHeight,
  949. actualBottom: this.bottomIframeHeight,
  950. actualTop: this.topIframeHeight,
  951. isExpanded: this.isExpanded,
  952. });
  953. this.applyIframeHeights();
  954. return {
  955. top: this.topIframeHeight,
  956. bottom: this.bottomIframeHeight,
  957. };
  958. },
  959. // 切换展开/收起状态
  960. toggleExpand() {
  961. if (!this.layoutCalculated) {
  962. console.warn("布局尚未计算完成");
  963. return;
  964. }
  965. this.isExpanded = !this.isExpanded;
  966. if (this.isExpanded) {
  967. // 展开:bottom iframe占用所有可用高度,覆盖top iframe到title底部
  968. // 也就是bottom iframe高度 = availableHeight (title + button之间的所有空间)
  969. this.calculateHeights(this.availableHeight);
  970. console.log(
  971. "📈 展开状态 - 覆盖到title底部,高度:",
  972. this.availableHeight
  973. );
  974. } else {
  975. // 收起:bottom iframe占用最小高度(header-section高度)
  976. this.calculateHeights(this.bottomIframeMinHeight);
  977. console.log(
  978. "📉 收起状态 - 最小高度:",
  979. this.bottomIframeMinHeight
  980. );
  981. }
  982. },
  983. // 处理来自iframe的消息
  984. handleIframeMessage(event) {
  985. // 安全检查:只接受同源的消息
  986. if (event.origin !== window.location.origin) {
  987. return;
  988. }
  989. const { type, data } = event.data;
  990. switch (type) {
  991. case "header-section-click":
  992. console.log("📱 收到header-section点击事件");
  993. this.toggleExpand();
  994. break;
  995. case "header-section-drag-start":
  996. console.log("🔄 开始拖拽header-section");
  997. this.handleDragStart(data);
  998. break;
  999. case "header-section-drag-move":
  1000. console.log("🔄 拖拽中:", data.deltaY);
  1001. this.handleDragMove(data.deltaY);
  1002. break;
  1003. case "header-section-drag-end":
  1004. console.log("🔚 结束拖拽");
  1005. this.handleDragEnd();
  1006. break;
  1007. }
  1008. },
  1009. // 处理拖拽开始
  1010. handleDragStart(data) {
  1011. if (!this.layoutCalculated) return;
  1012. this.isDragging = true;
  1013. this.dragStartY = data.startY;
  1014. this.dragStartBottomHeight = this.bottomIframeHeight;
  1015. },
  1016. // 处理拖拽移动
  1017. handleDragMove(deltaY) {
  1018. if (!this.isDragging || !this.layoutCalculated) return;
  1019. // 计算新的底部高度
  1020. const newBottomHeight = this.dragStartBottomHeight - deltaY;
  1021. // 直接设置高度,避免复杂的计算导致的延迟
  1022. const bottomActualHeight = Math.max(
  1023. this.bottomIframeMinHeight,
  1024. Math.min(newBottomHeight, this.availableHeight)
  1025. );
  1026. this.bottomIframeHeight = bottomActualHeight;
  1027. this.topIframeHeight = this.availableHeight - bottomActualHeight;
  1028. // 直接应用高度,减少延迟
  1029. this.applyIframeHeightsFast();
  1030. },
  1031. // 处理拖拽结束
  1032. handleDragEnd() {
  1033. this.isDragging = false;
  1034. this.dragStartY = 0;
  1035. this.dragStartBottomHeight = 0;
  1036. // 拖拽结束后用完整的方法确保状态正确
  1037. this.applyIframeHeights();
  1038. },
  1039. },
  1040. });
  1041. });
  1042. </script>
  1043. </body>
  1044. </html>