h5-controller.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. <template>
  2. <view class="controller-page">
  3. <!-- 简单的loading,用户几乎看不到 -->
  4. <view class="loading-container">
  5. <view class="loading-spinner"></view>
  6. <text class="loading-text">{{ loadingText }}</text>
  7. </view>
  8. </view>
  9. </template>
  10. <script setup>
  11. import { ref } from "vue";
  12. import { onLoad } from "@dcloudio/uni-app";
  13. import { goToMainTab } from "@/utils/navigation";
  14. // 响应式数据
  15. const loadingText = ref("正在处理...");
  16. // 页面参数
  17. let pageParams = {};
  18. /**
  19. * 页面加载
  20. */
  21. onLoad((options) => {
  22. console.log("🎛️ H5控制器页面加载:", options);
  23. pageParams = options || {};
  24. // 打印详细的参数信息
  25. console.log("📋 接收到的参数:");
  26. Object.keys(pageParams).forEach((key) => {
  27. console.log(` ${key}:`, pageParams[key]);
  28. });
  29. // 立即执行,减少延迟
  30. setTimeout(() => {
  31. executeCommand();
  32. }, 100);
  33. });
  34. /**
  35. * 执行H5指令
  36. */
  37. const executeCommand = async () => {
  38. try {
  39. const { action, message, data, source } = pageParams;
  40. if (!action) {
  41. showError("缺少action参数");
  42. return;
  43. }
  44. console.log("🎯 执行H5指令:", { action, message, data, source });
  45. switch (action) {
  46. case "goBack":
  47. await handleGoBack();
  48. break;
  49. case "scanCode":
  50. await handleScanCode();
  51. break;
  52. case "chooseImage":
  53. await handleChooseImage();
  54. break;
  55. case "chooseFile":
  56. await handleChooseFile();
  57. break;
  58. case "makePhoneCall":
  59. await handleMakePhoneCall(data);
  60. break;
  61. case "getStorage":
  62. await handleGetStorage(data);
  63. break;
  64. case "setStorage":
  65. await handleSetStorage(data);
  66. break;
  67. case "loginSuccess":
  68. await handleLoginSuccess(data);
  69. break;
  70. case "noServiceAuth":
  71. await handleNoServiceAuth(data);
  72. break;
  73. default:
  74. showError(`未知指令: ${action}`);
  75. }
  76. } catch (error) {
  77. console.error("❌ 执行指令失败:", error);
  78. showError(`执行失败: ${error.message || "未知错误"}`);
  79. }
  80. };
  81. /**
  82. * 智能返回(根据来源决定返回位置)
  83. */
  84. const handleGoBack = async () => {
  85. loadingText.value = "正在返回...";
  86. // 获取当前页面栈
  87. const pages = getCurrentPages();
  88. console.log(
  89. "📚 当前页面栈:",
  90. pages.map((p) => p.route)
  91. );
  92. // 查找webview页面的前一个页面
  93. let targetTab = "my"; // 默认返回我的
  94. if (pages.length >= 2) {
  95. const prevPage = pages[pages.length - 2]; // webview的前一个页面
  96. if (prevPage && prevPage.route) {
  97. // 简单映射:根据来源路由推断tab
  98. if (prevPage.route.includes("todo")) targetTab = "todo";
  99. else if (prevPage.route.includes("my")) targetTab = "my";
  100. else if (prevPage.route.includes("statistics")) targetTab = "statistics";
  101. console.log("🎯 返回到来源tab:", targetTab);
  102. }
  103. }
  104. // 统一回主容器指定tab
  105. goToMainTab(targetTab);
  106. };
  107. /**
  108. * 扫码
  109. */
  110. const handleScanCode = async () => {
  111. loadingText.value = "打开扫码...";
  112. uni.scanCode({
  113. success: (res) => {
  114. console.log("✅ 扫码成功:", res);
  115. // 扫码成功,带结果回到H5页面
  116. const resultData = {
  117. action: "scanCode",
  118. success: true,
  119. result: res.result,
  120. scanType: res.scanType,
  121. timestamp: Date.now(),
  122. };
  123. returnToH5WithData(resultData);
  124. },
  125. fail: (err) => {
  126. console.error("❌ 扫码失败:", err);
  127. // 扫码失败,也要回到H5页面
  128. const resultData = {
  129. action: "scanCode",
  130. success: false,
  131. error: "扫码失败",
  132. timestamp: Date.now(),
  133. };
  134. returnToH5WithData(resultData);
  135. },
  136. });
  137. };
  138. /**
  139. * 选择图片
  140. */
  141. const handleChooseImage = async () => {
  142. loadingText.value = "选择图片...";
  143. uni.chooseImage({
  144. count: 1,
  145. success: (res) => {
  146. console.log("✅ 选择图片成功:", res);
  147. // 选择图片成功,带结果回到H5页面
  148. const resultData = {
  149. action: "chooseImage",
  150. success: true,
  151. count: res.tempFilePaths.length,
  152. paths: res.tempFilePaths,
  153. timestamp: Date.now(),
  154. };
  155. returnToH5WithData(resultData);
  156. },
  157. fail: (err) => {
  158. console.error("❌ 选择图片失败:", err);
  159. // 选择图片失败,也要回到H5页面
  160. const resultData = {
  161. action: "chooseImage",
  162. success: false,
  163. error: "选择图片失败",
  164. timestamp: Date.now(),
  165. };
  166. returnToH5WithData(resultData);
  167. },
  168. });
  169. };
  170. /**
  171. * 选择文件
  172. */
  173. const handleChooseFile = async () => {
  174. loadingText.value = "选择文件...";
  175. // 使用微信小程序的chooseMessageFile API
  176. uni.chooseMessageFile({
  177. count: 5, // 最多选择5个文件
  178. type: "all", // 所有文件类型:image、video、file
  179. success: (res) => {
  180. console.log("✅ 选择文件成功:", res);
  181. // 选择文件成功,带结果回到H5页面
  182. const resultData = {
  183. action: "chooseFile",
  184. success: true,
  185. count: res.tempFiles.length,
  186. paths: res.tempFiles.map((file) => file.path),
  187. files: res.tempFiles.map((file) => ({
  188. name: file.name,
  189. size: file.size,
  190. type: file.type,
  191. path: file.path,
  192. })),
  193. timestamp: Date.now(),
  194. };
  195. returnToH5WithData(resultData);
  196. },
  197. fail: (err) => {
  198. console.error("❌ 选择文件失败:", err);
  199. // 选择文件失败,也要回到H5页面
  200. const resultData = {
  201. action: "chooseFile",
  202. success: false,
  203. error: "选择文件失败",
  204. timestamp: Date.now(),
  205. };
  206. returnToH5WithData(resultData);
  207. },
  208. });
  209. };
  210. /**
  211. * 拨打电话
  212. */
  213. const handleMakePhoneCall = async (data) => {
  214. loadingText.value = "拨打电话...";
  215. try {
  216. const phoneData = data ? JSON.parse(decodeURIComponent(data)) : {};
  217. if (phoneData.phoneNumber) {
  218. uni.makePhoneCall({
  219. phoneNumber: phoneData.phoneNumber,
  220. success: () => {
  221. const resultData = {
  222. action: "makePhoneCall",
  223. success: true,
  224. phoneNumber: phoneData.phoneNumber,
  225. timestamp: Date.now(),
  226. };
  227. returnToH5WithData(resultData);
  228. },
  229. fail: (err) => {
  230. const resultData = {
  231. action: "makePhoneCall",
  232. success: false,
  233. error: "拨打电话失败",
  234. timestamp: Date.now(),
  235. };
  236. returnToH5WithData(resultData);
  237. },
  238. });
  239. } else {
  240. showError("电话号码参数错误");
  241. }
  242. } catch (error) {
  243. console.error("解析电话数据失败:", error);
  244. showError("电话参数解析失败");
  245. }
  246. };
  247. /**
  248. * 获取存储
  249. */
  250. const handleGetStorage = async (data) => {
  251. loadingText.value = "获取数据...";
  252. try {
  253. const storageData = data ? JSON.parse(decodeURIComponent(data)) : {};
  254. if (storageData.key) {
  255. const value = uni.getStorageSync(storageData.key);
  256. const resultData = {
  257. action: "getStorage",
  258. success: true,
  259. key: storageData.key,
  260. value: value,
  261. found: !!value,
  262. timestamp: Date.now(),
  263. };
  264. returnToH5WithData(resultData);
  265. } else {
  266. showError("获取参数错误");
  267. }
  268. } catch (error) {
  269. console.error("解析存储数据失败:", error);
  270. showError("存储参数解析失败");
  271. }
  272. };
  273. /**
  274. * 存储数据
  275. */
  276. const handleSetStorage = async (data) => {
  277. loadingText.value = "存储数据...";
  278. try {
  279. const storageData = data ? JSON.parse(decodeURIComponent(data)) : {};
  280. if (storageData.key && storageData.value !== undefined) {
  281. uni.setStorageSync(storageData.key, storageData.value);
  282. const resultData = {
  283. action: "setStorage",
  284. success: true,
  285. key: storageData.key,
  286. value: storageData.value,
  287. timestamp: Date.now(),
  288. };
  289. returnToH5WithData(resultData);
  290. } else {
  291. showError("存储参数错误");
  292. }
  293. } catch (error) {
  294. console.error("解析存储数据失败:", error);
  295. showError("存储参数解析失败");
  296. }
  297. };
  298. /**
  299. * 处理登录成功
  300. */
  301. const handleLoginSuccess = async (data) => {
  302. loadingText.value = "处理登录结果...";
  303. try {
  304. const loginData = data ? JSON.parse(decodeURIComponent(data)) : {};
  305. if (loginData.success && loginData.userInfo) {
  306. // 保存用户信息到小程序存储
  307. if (loginData.userInfo) {
  308. uni.setStorageSync("userInfo", loginData.userInfo);
  309. }
  310. // 保存JSESSIONID
  311. if (loginData.userInfo.sessId) {
  312. uni.setStorageSync("JSESSIONID", loginData.userInfo.sessId);
  313. }
  314. // 触发登录事件,通知其他页面
  315. uni.$emit("login", loginData.userInfo);
  316. console.log("📢 已触发登录事件");
  317. // 根据登录类型显示不同提示
  318. const isAutoLogin = loginData.isAutoLogin;
  319. const toastTitle = isAutoLogin ? "自动登录成功" : "登录成功";
  320. const delay = isAutoLogin ? 1000 : 2000; // 自动登录延迟更短
  321. console.log(
  322. "🔐 处理登录成功数据:",
  323. loginData,
  324. "是自动登录吗",
  325. isAutoLogin
  326. );
  327. uni.showToast({
  328. title: toastTitle,
  329. icon: "success",
  330. duration: delay,
  331. });
  332. // 延迟返回
  333. setTimeout(() => {
  334. if (isAutoLogin) {
  335. // 自动登录成功,直接返回到原页面(跳过webview和h5-controller)
  336. console.log("🎯 自动登录成功,返回原页面");
  337. uni.navigateBack({
  338. delta: 2, // 返回2层:h5-controller -> webview -> 原页面
  339. success: () => {
  340. console.log("✅ 已返回原页面");
  341. },
  342. fail: (err) => {
  343. console.log("⚠️ 返回原页面失败:", err);
  344. // 返回失败,重启到主容器首页(默认第一个页签)
  345. uni.reLaunch({
  346. url: "/pages/main/index",
  347. });
  348. },
  349. });
  350. } else {
  351. // 普通登录成功,重启到主容器首页(默认第一个页签)
  352. console.log("🎯 登录成功,返回主容器首页");
  353. uni.reLaunch({
  354. url: "/pages/main/index",
  355. });
  356. }
  357. }, delay);
  358. } else {
  359. console.error("❌ 登录数据无效:", loginData);
  360. showError("登录数据无效");
  361. }
  362. } catch (error) {
  363. console.error("❌ 处理登录数据失败:", error);
  364. showError("登录数据处理失败: " + error.message);
  365. }
  366. };
  367. /**
  368. * 处理没有服务授权
  369. */
  370. const handleNoServiceAuth = async (data) => {
  371. loadingText.value = "处理授权错误...";
  372. try {
  373. const authData = data ? JSON.parse(decodeURIComponent(data)) : {};
  374. console.log("🚫 处理没有服务授权:", authData);
  375. // 显示错误提示
  376. uni.showToast({
  377. title: "没有服务授权",
  378. icon: "error",
  379. duration: 2000,
  380. });
  381. // 延迟一下让用户看到提示,然后返回
  382. setTimeout(() => {
  383. handleGoBack();
  384. }, 2000);
  385. } catch (error) {
  386. console.error("❌ 处理授权错误失败:", error);
  387. // 出错也要返回
  388. handleGoBack();
  389. }
  390. };
  391. /**
  392. * 显示错误并返回
  393. */
  394. const showError = (message) => {
  395. console.error("❌ 控制器错误:", message);
  396. // 错误也要返回H5页面
  397. const resultData = {
  398. success: false,
  399. error: message,
  400. timestamp: Date.now(),
  401. };
  402. returnToH5WithData(resultData);
  403. };
  404. /**
  405. * 带数据返回H5页面
  406. */
  407. const returnToH5WithData = (data) => {
  408. try {
  409. // 构建带数据的H5页面URL
  410. const { source } = pageParams;
  411. console.log("🔄 带数据返回H5:", data);
  412. // 手动构建URL参数,避免URLSearchParams
  413. const encodedData = encodeURIComponent(JSON.stringify(data));
  414. const webviewUrl = `/pages/common/webview?dest=${
  415. source || "home"
  416. }&result=${encodedData}`;
  417. console.log("🔗 跳转URL:", webviewUrl);
  418. // 重新加载webview页面,传递新的URL
  419. uni.redirectTo({
  420. url: webviewUrl,
  421. success: () => {
  422. console.log("✅ 返回H5成功");
  423. },
  424. fail: (err) => {
  425. console.error("❌ 返回H5失败:", err);
  426. // 如果失败,直接返回
  427. uni.navigateBack();
  428. },
  429. });
  430. } catch (error) {
  431. console.error("❌ 返回H5数据处理失败:", error);
  432. // 任何错误都要回到H5页面
  433. uni.navigateBack();
  434. }
  435. };
  436. </script>
  437. <style scoped lang="scss">
  438. .controller-page {
  439. min-height: 100vh;
  440. background: #f5f5f5;
  441. display: flex;
  442. align-items: center;
  443. justify-content: center;
  444. padding: 40rpx;
  445. }
  446. .loading-container {
  447. text-align: center;
  448. color: white;
  449. }
  450. .loading-spinner {
  451. width: 80rpx;
  452. height: 80rpx;
  453. border: 6rpx solid rgba(255, 255, 255, 0.3);
  454. border-top: 6rpx solid white;
  455. border-radius: 50%;
  456. animation: spin 1s linear infinite;
  457. margin: 0 auto 32rpx;
  458. }
  459. @keyframes spin {
  460. 0% {
  461. transform: rotate(0deg);
  462. }
  463. 100% {
  464. transform: rotate(360deg);
  465. }
  466. }
  467. .loading-text {
  468. font-size: 32rpx;
  469. color: white;
  470. }
  471. .result-container {
  472. background: white;
  473. border-radius: 24rpx;
  474. padding: 48rpx;
  475. max-width: 600rpx;
  476. width: 100%;
  477. box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
  478. }
  479. .result-header {
  480. text-align: center;
  481. margin-bottom: 32rpx;
  482. }
  483. .result-title {
  484. display: block;
  485. font-size: 36rpx;
  486. font-weight: bold;
  487. color: #333;
  488. margin-bottom: 16rpx;
  489. }
  490. .result-subtitle {
  491. display: block;
  492. font-size: 28rpx;
  493. color: #666;
  494. }
  495. .result-data {
  496. background: #f8f9fa;
  497. border-radius: 12rpx;
  498. padding: 24rpx;
  499. margin-bottom: 32rpx;
  500. }
  501. .data-title {
  502. display: block;
  503. font-size: 28rpx;
  504. font-weight: 500;
  505. color: #333;
  506. margin-bottom: 16rpx;
  507. }
  508. .data-content {
  509. display: block;
  510. font-size: 24rpx;
  511. color: #666;
  512. white-space: pre-wrap;
  513. font-family: monospace;
  514. }
  515. .action-buttons {
  516. display: flex;
  517. gap: 24rpx;
  518. }
  519. .btn {
  520. flex: 1;
  521. height: 88rpx;
  522. border-radius: 12rpx;
  523. font-size: 28rpx;
  524. border: none;
  525. color: white;
  526. }
  527. .btn.primary {
  528. background: #007aff;
  529. }
  530. .btn.secondary {
  531. background: #6c757d;
  532. }
  533. </style>