mp_ccChk.html 39 KB

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