mp_chgChkTab.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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. [v-cloak] {
  13. display: none !important;
  14. }
  15. #app {
  16. background: #f5f5f5;
  17. min-height: 100vh;
  18. }
  19. .loading {
  20. min-height: 40vh;
  21. display: flex;
  22. align-items: center;
  23. justify-content: center;
  24. padding: 32px 16px;
  25. }
  26. .loading-spinner {
  27. width: 30px;
  28. height: 30px;
  29. border: 3px solid #e8f3ed;
  30. border-top-color: #40ac6d;
  31. border-radius: 50%;
  32. animation: page-spin 0.9s linear infinite;
  33. }
  34. @keyframes page-spin {
  35. from {
  36. transform: rotate(0deg);
  37. }
  38. to {
  39. transform: rotate(360deg);
  40. }
  41. }
  42. .status-wrap {
  43. padding: 16px;
  44. }
  45. .status-card {
  46. background: #fff;
  47. border-radius: 8px;
  48. padding: 16px;
  49. box-sizing: border-box;
  50. }
  51. .status-title {
  52. font-size: 15px;
  53. font-weight: 600;
  54. color: #2d3748;
  55. }
  56. .status-text {
  57. margin-top: 8px;
  58. line-height: 1.6;
  59. color: #4a5568;
  60. font-size: 13px;
  61. word-break: break-all;
  62. }
  63. .status-retry {
  64. margin-top: 12px;
  65. height: 34px;
  66. padding: 0 14px;
  67. border: none;
  68. border-radius: 4px;
  69. background: #4a5568;
  70. color: #fff;
  71. font-size: 13px;
  72. }
  73. .ss-sub-tab {
  74. min-height: 100vh;
  75. }
  76. .ss-sub-tab__content {
  77. background: #fff;
  78. }
  79. .ss-sub-tab__content iframe {
  80. width: 100%;
  81. min-height: calc(100vh - 56px);
  82. }
  83. /* 变动页签页补充 URL 调试浮层,便于直接查看初始化请求与返回 by xu 2026-03-08 */
  84. .url-log-fab {
  85. position: fixed;
  86. top: 12px;
  87. right: 12px;
  88. z-index: 999999;
  89. min-width: 56px;
  90. height: 32px;
  91. border: none;
  92. border-radius: 16px;
  93. background: #ff7a00;
  94. color: #fff;
  95. font-size: 12px;
  96. font-weight: 600;
  97. line-height: 32px;
  98. padding: 0 12px;
  99. display: none;
  100. box-shadow: 0 4px 12px rgba(255, 122, 0, 0.35);
  101. }
  102. .url-log-panel {
  103. position: fixed;
  104. left: 12px;
  105. right: 12px;
  106. bottom: 116px;
  107. z-index: 1200;
  108. background: rgba(36, 40, 53, 0.96);
  109. color: #fff;
  110. border-radius: 8px;
  111. padding: 10px;
  112. max-height: 42vh;
  113. overflow: auto;
  114. box-sizing: border-box;
  115. }
  116. .url-log-panel__head {
  117. display: flex;
  118. align-items: center;
  119. justify-content: space-between;
  120. margin-bottom: 8px;
  121. font-size: 12px;
  122. }
  123. .url-log-panel__close {
  124. border: none;
  125. background: transparent;
  126. color: #fff;
  127. font-size: 14px;
  128. line-height: 1;
  129. }
  130. .url-log-panel__actions {
  131. display: flex;
  132. flex-wrap: wrap;
  133. gap: 8px;
  134. margin-bottom: 8px;
  135. }
  136. .url-log-action {
  137. border: none;
  138. border-radius: 4px;
  139. padding: 5px 10px;
  140. font-size: 12px;
  141. color: #fff;
  142. background: rgba(255, 255, 255, 0.16);
  143. }
  144. .url-log-panel__body {
  145. margin: 0;
  146. white-space: pre-wrap;
  147. word-break: break-all;
  148. font-size: 12px;
  149. line-height: 1.5;
  150. }
  151. </style>
  152. </head>
  153. <body>
  154. <div id="app" v-cloak>
  155. <div v-if="loading" class="loading">
  156. <div class="loading-spinner"></div>
  157. </div>
  158. <div v-else-if="error" class="status-wrap">
  159. <div class="status-card">
  160. <div class="status-title">页面加载失败</div>
  161. <div class="status-text">{{ error }}</div>
  162. <button class="status-retry" type="button" @click="loadTabList">
  163. 重新加载
  164. </button>
  165. </div>
  166. </div>
  167. <div v-else-if="!tabList.length" class="status-wrap">
  168. <div class="status-card">
  169. <div class="status-title">暂无变动页签</div>
  170. <div class="status-text">当前接口未返回可展示的页签数据。</div>
  171. </div>
  172. </div>
  173. <!-- 功能说明:临时改为直接走 chkChg,方便对比 child 与非 child 返回差异 by xu 2026-03-08 -->
  174. <ss-sub-tab
  175. v-if="!loading && !error && tabList.length > 0"
  176. :tab-list="tabList"
  177. :base-params="baseParams"
  178. @tab-change="handleTabChange"
  179. >
  180. </ss-sub-tab>
  181. <!-- 功能说明:URL 浮层固定独立渲染,避免打断 v-else 链 by xu 2026-03-08 -->
  182. <button class="url-log-fab" type="button" @click="handleUrlFabClick">
  183. URL
  184. </button>
  185. <div class="url-log-panel" v-if="urlLogVisible">
  186. <div class="url-log-panel__head">
  187. <span>初始化信息</span>
  188. <div>
  189. <button class="url-log-action" type="button" @click="showCurrentUrls">
  190. 刷新信息
  191. </button>
  192. <button class="url-log-panel__close" type="button" @click="urlLogVisible = false">
  193. ×
  194. </button>
  195. </div>
  196. </div>
  197. <div class="url-log-panel__actions">
  198. <button class="url-log-action" type="button" @click="openCurrentUrl('page')">打开页面</button>
  199. <button class="url-log-action" type="button" @click="copyCurrentUrl('page')">复制页面</button>
  200. <button class="url-log-action" type="button" @click="openCurrentUrl('tab')">打开Tab</button>
  201. <button class="url-log-action" type="button" @click="copyCurrentUrl('tab')">复制Tab</button>
  202. </div>
  203. <pre class="url-log-panel__body">{{ urlLogText }}</pre>
  204. </div>
  205. </div>
  206. <script>
  207. window.SS.ready(function () {
  208. window.SS.dom.initializeFormApp({
  209. el: '#app',
  210. data() {
  211. return {
  212. pageParams: {},
  213. loading: false,
  214. error: '',
  215. tabList: [],
  216. baseParams: {},
  217. urlLogVisible: false,
  218. urlLogText: '',
  219. currentPageUrl: '',
  220. currentTabUrl: '',
  221. initRequestUrl: '',
  222. initRequestDataText: '',
  223. initResponseDataText: '',
  224. initTabListText: '',
  225. initBaseParamsText: '',
  226. initErrorText: '',
  227. };
  228. },
  229. mounted() {
  230. this.pageParams = this.getUrlParams();
  231. // 功能说明:补充 chgChkTab 初始化日志,方便直接核对和 PC 的差异 by xu 2026-03-08
  232. console.log('[mp_chgChkTab] mounted pageParams =', this.pageParams);
  233. this.loadTabList();
  234. },
  235. methods: {
  236. getUrlParams() {
  237. const params = {};
  238. const aliasMap = {
  239. ssobjname: 'ssObjName',
  240. ssobjid: 'ssObjId',
  241. datatype: 'dataType',
  242. };
  243. const urlSearchParams = new URLSearchParams(window.location.search);
  244. for (const [rawKey, rawValue] of urlSearchParams) {
  245. const decodedValue = this.safeDecode(rawValue);
  246. params[rawKey] = decodedValue;
  247. const normalizedKey = aliasMap[String(rawKey).toLowerCase()];
  248. if (normalizedKey) {
  249. params[normalizedKey] = decodedValue;
  250. }
  251. }
  252. console.log('[mp_chgChkTab] getUrlParams result =', params);
  253. return params;
  254. },
  255. safeDecode(text) {
  256. if (text === undefined || text === null || text === '') return '';
  257. try {
  258. return decodeURIComponent(String(text));
  259. } catch (_) {
  260. return String(text);
  261. }
  262. },
  263. formatDebugValue(value) {
  264. if (value === undefined) return '(undefined)';
  265. if (value === null) return '(null)';
  266. if (typeof value === 'string') return value || '(empty)';
  267. try {
  268. return JSON.stringify(value, null, 2);
  269. } catch (_) {
  270. return String(value);
  271. }
  272. },
  273. buildBaseParams() {
  274. return {
  275. sqid: this.pageParams.sqid || '',
  276. shid: this.pageParams.shid || '',
  277. shyjm: this.pageParams.shyjm || '',
  278. bdlbm: this.pageParams.bdlbm || '',
  279. dataType: 'bdplay',
  280. encode_shid: this.pageParams.encode_shid || '',
  281. ssObjName: this.pageParams.ssObjName || this.pageParams.ssobjname || '',
  282. ssObjId: this.pageParams.ssObjId || this.pageParams.ssobjid || '',
  283. jdmc: this.pageParams.jdmc || '',
  284. ssToken: this.pageParams.ssToken || '',
  285. };
  286. },
  287. pickTabList(data) {
  288. if (!data) return [];
  289. if (Array.isArray(data.tabList)) return data.tabList;
  290. if (data.data && Array.isArray(data.data.tabList)) return data.data.tabList;
  291. if (data.ssData && Array.isArray(data.ssData.tabList)) return data.ssData.tabList;
  292. return [];
  293. },
  294. async loadTabList() {
  295. this.loading = true;
  296. this.error = '';
  297. this.tabList = [];
  298. this.baseParams = this.buildBaseParams();
  299. this.initErrorText = '';
  300. try {
  301. const tagQuery = new URLSearchParams({
  302. ssServ: 'tabTag',
  303. ssDest: 'data',
  304. // 功能说明:临时去掉 child,直接看 chkChg 返回结果 by xu 2026-03-08
  305. name: 'chkChg',
  306. ssObjName: this.baseParams.ssObjName || '',
  307. ssObjId: this.baseParams.ssObjId || '',
  308. sqid: this.baseParams.sqid || '',
  309. shid: this.baseParams.shid || '',
  310. shyjm: this.baseParams.shyjm || '',
  311. bdlbm: this.baseParams.bdlbm || '',
  312. dataType: 'bdplay',
  313. encode_shid: this.baseParams.encode_shid || '',
  314. jdmc: this.baseParams.jdmc || '',
  315. ssToken: this.baseParams.ssToken || '',
  316. service: this.pageParams.service || this.pageParams.ssServ || '',
  317. dest: this.pageParams.dest || this.pageParams.ssDest || '',
  318. param: this.pageParams.param || '',
  319. });
  320. const requestUrl = `/service?${tagQuery.toString()}`;
  321. this.initRequestUrl = requestUrl;
  322. this.initRequestDataText = this.formatDebugValue({});
  323. this.initBaseParamsText = this.formatDebugValue(this.baseParams);
  324. console.log('[mp_chgChkTab] requestUrl =', requestUrl);
  325. console.log('[mp_chgChkTab] baseParams =', this.baseParams);
  326. const response = await request.post(
  327. requestUrl,
  328. {},
  329. { loading: false, formData: true }
  330. );
  331. console.log('[mp_chgChkTab] raw response =', response);
  332. const responseData = response && response.data ? response.data : response;
  333. this.initResponseDataText = this.formatDebugValue(responseData);
  334. this.tabList = this.pickTabList(responseData);
  335. this.initTabListText = this.formatDebugValue(this.tabList);
  336. console.log('[mp_chgChkTab] responseData =', responseData);
  337. console.log('[mp_chgChkTab] picked tabList =', this.tabList);
  338. if (!this.tabList.length) {
  339. console.warn('[mp_chgChkTab] tabTag 未返回 tabList =', responseData);
  340. }
  341. } catch (error) {
  342. console.error('[mp_chgChkTab] loadTabList failed =', error);
  343. this.error = (error && error.message) || '加载失败,请稍后重试';
  344. this.initErrorText = this.formatDebugValue({
  345. message: this.error,
  346. stack: error && error.stack,
  347. raw: error,
  348. });
  349. } finally {
  350. this.loading = false;
  351. console.log('[mp_chgChkTab] loading finished, error =', this.error || '(none)');
  352. }
  353. },
  354. getCurrentTabIframeSrc() {
  355. const frame = document.querySelector('.ss-sub-tab__content iframe');
  356. return (frame && frame.src) || '';
  357. },
  358. getCurrentUrlMap() {
  359. return {
  360. page: window.location.href,
  361. tab: this.getCurrentTabIframeSrc(),
  362. };
  363. },
  364. // 功能说明:右上角 URL 按钮点击后直接展开面板,并复制当前页面地址 by xu 2026-03-08
  365. async handleUrlFabClick() {
  366. this.showCurrentUrls();
  367. try {
  368. await this.copyText(this.getUrlByType('page'));
  369. if (typeof showToastEffect === 'function') showToastEffect('当前页面 URL 已复制', 1600, 'success');
  370. } catch (_) {
  371. if (typeof showToastEffect === 'function') showToastEffect('当前页面 URL 获取失败', 1800, 'error');
  372. }
  373. },
  374. // 功能说明:点 URL 时同时展示页面 URL 和初始化请求/返回,便于直接截图排查 by xu 2026-03-08
  375. showCurrentUrls() {
  376. const currentUrls = this.getCurrentUrlMap();
  377. this.currentPageUrl = currentUrls.page || '';
  378. this.currentTabUrl = currentUrls.tab || '';
  379. this.urlLogText = [
  380. `page: ${this.currentPageUrl || '(empty)'}`,
  381. `tab: ${this.currentTabUrl || '(empty)'}`,
  382. '',
  383. '[init requestUrl]',
  384. this.initRequestUrl || '(empty)',
  385. '',
  386. '[init requestData]',
  387. this.initRequestDataText || '(empty)',
  388. '',
  389. '[init responseData]',
  390. this.initResponseDataText || '(empty)',
  391. '',
  392. '[init tabList]',
  393. this.initTabListText || '(empty)',
  394. '',
  395. '[init baseParams]',
  396. this.initBaseParamsText || '(empty)',
  397. '',
  398. '[init error]',
  399. this.initErrorText || '(none)',
  400. ].join('\n');
  401. this.urlLogVisible = true;
  402. },
  403. getUrlByType(type) {
  404. const currentUrls = this.getCurrentUrlMap();
  405. return String(currentUrls[type] || '').trim();
  406. },
  407. async copyText(text) {
  408. const value = String(text || '').trim();
  409. if (!value) throw new Error('empty');
  410. if (navigator.clipboard && navigator.clipboard.writeText) {
  411. await navigator.clipboard.writeText(value);
  412. return;
  413. }
  414. const textarea = document.createElement('textarea');
  415. textarea.value = value;
  416. textarea.style.position = 'fixed';
  417. textarea.style.opacity = '0';
  418. document.body.appendChild(textarea);
  419. textarea.focus();
  420. textarea.select();
  421. document.execCommand('copy');
  422. document.body.removeChild(textarea);
  423. },
  424. async copyCurrentUrl(type) {
  425. try {
  426. await this.copyText(this.getUrlByType(type));
  427. if (typeof showToastEffect === 'function') showToastEffect('URL 已复制', 1400, 'success');
  428. } catch (_) {
  429. if (typeof showToastEffect === 'function') showToastEffect('复制失败', 1800, 'error');
  430. }
  431. },
  432. openCurrentUrl(type) {
  433. const url = this.getUrlByType(type);
  434. if (!url) {
  435. if (typeof showToastEffect === 'function') showToastEffect('当前没有可打开的 URL', 1800, 'warning');
  436. return;
  437. }
  438. const newWindow = window.open(url, '_blank');
  439. if (!newWindow) {
  440. window.location.href = url;
  441. }
  442. },
  443. handleTabChange({ index, tab }) {
  444. console.log('[mp_chgChkTab] 切换页签 =', {
  445. index,
  446. tab,
  447. baseParams: this.baseParams,
  448. });
  449. if (this.urlLogVisible) {
  450. setTimeout(() => this.showCurrentUrls(), 80);
  451. }
  452. },
  453. },
  454. });
  455. });
  456. </script>
  457. </body>
  458. </html>