index.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>微信扫码登录 - 官方实现</title>
  7. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  8. <style>
  9. * {
  10. margin: 0;
  11. padding: 0;
  12. box-sizing: border-box;
  13. font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
  14. }
  15. body {
  16. background-color: #f5f7fa;
  17. color: #333;
  18. line-height: 1.6;
  19. min-height: 100vh;
  20. display: flex;
  21. flex-direction: column;
  22. align-items: center;
  23. justify-content: center;
  24. padding: 20px;
  25. }
  26. .container {
  27. width: 100%;
  28. max-width: 900px;
  29. display: flex;
  30. flex-direction: column;
  31. align-items: center;
  32. }
  33. header {
  34. text-align: center;
  35. margin-bottom: 40px;
  36. width: 100%;
  37. }
  38. h1 {
  39. color: #2c3e50;
  40. margin-bottom: 10px;
  41. font-size: 2.5rem;
  42. display: flex;
  43. align-items: center;
  44. justify-content: center;
  45. gap: 15px;
  46. }
  47. .subtitle {
  48. color: #7f8c8d;
  49. font-size: 1.1rem;
  50. max-width: 600px;
  51. margin: 0 auto;
  52. }
  53. .content-wrapper {
  54. display: flex;
  55. flex-direction: row;
  56. width: 100%;
  57. gap: 30px;
  58. margin-bottom: 40px;
  59. }
  60. .login-section {
  61. flex: 1;
  62. background-color: white;
  63. border-radius: 16px;
  64. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
  65. padding: 40px;
  66. display: flex;
  67. flex-direction: column;
  68. }
  69. .info-section {
  70. flex: 1;
  71. background-color: white;
  72. border-radius: 16px;
  73. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
  74. padding: 40px;
  75. display: flex;
  76. flex-direction: column;
  77. }
  78. .section-title {
  79. font-size: 1.5rem;
  80. color: #2c3e50;
  81. margin-bottom: 25px;
  82. padding-bottom: 15px;
  83. border-bottom: 2px solid #f0f2f5;
  84. display: flex;
  85. align-items: center;
  86. gap: 10px;
  87. }
  88. #login_container {
  89. width: 100%;
  90. min-height: 320px;
  91. display: flex;
  92. justify-content: center;
  93. align-items: center;
  94. border-radius: 12px;
  95. background-color: #f8fafc;
  96. margin-bottom: 25px;
  97. border: 1px solid #eaeaea;
  98. }
  99. .qrcode-loading {
  100. text-align: center;
  101. color: #999;
  102. font-size: 1.1rem;
  103. }
  104. .qrcode-loading i {
  105. font-size: 3rem;
  106. margin-bottom: 15px;
  107. color: #07c160;
  108. display: block;
  109. }
  110. .status-indicator {
  111. margin-top: 20px;
  112. padding: 15px;
  113. border-radius: 10px;
  114. background-color: #f8fafc;
  115. border-left: 4px solid #07c160;
  116. }
  117. .status-title {
  118. font-weight: 600;
  119. color: #2c3e50;
  120. margin-bottom: 8px;
  121. display: flex;
  122. align-items: center;
  123. gap: 8px;
  124. }
  125. .status-desc {
  126. color: #666;
  127. font-size: 0.95rem;
  128. }
  129. .steps-container {
  130. margin-top: 10px;
  131. }
  132. .step {
  133. display: flex;
  134. margin-bottom: 20px;
  135. padding-bottom: 20px;
  136. border-bottom: 1px solid #f0f2f5;
  137. }
  138. .step:last-child {
  139. margin-bottom: 0;
  140. padding-bottom: 0;
  141. border-bottom: none;
  142. }
  143. .step-number {
  144. width: 32px;
  145. height: 32px;
  146. border-radius: 50%;
  147. background-color: #07c160;
  148. color: white;
  149. display: flex;
  150. align-items: center;
  151. justify-content: center;
  152. font-weight: bold;
  153. margin-right: 15px;
  154. flex-shrink: 0;
  155. }
  156. .step-content h4 {
  157. color: #2c3e50;
  158. margin-bottom: 5px;
  159. }
  160. .step-content p {
  161. color: #666;
  162. font-size: 0.95rem;
  163. }
  164. .config-section {
  165. margin-top: 30px;
  166. background-color: #f8fafc;
  167. padding: 20px;
  168. border-radius: 10px;
  169. }
  170. .config-title {
  171. font-size: 1.1rem;
  172. color: #2c3e50;
  173. margin-bottom: 15px;
  174. display: flex;
  175. align-items: center;
  176. gap: 10px;
  177. }
  178. .config-item {
  179. margin-bottom: 15px;
  180. display: flex;
  181. flex-direction: column;
  182. }
  183. .config-item label {
  184. font-weight: 600;
  185. color: #555;
  186. margin-bottom: 5px;
  187. font-size: 0.95rem;
  188. }
  189. .config-item input {
  190. padding: 10px 12px;
  191. border-radius: 6px;
  192. border: 1px solid #ddd;
  193. font-size: 0.95rem;
  194. transition: border-color 0.3s;
  195. }
  196. .config-item input:focus {
  197. outline: none;
  198. border-color: #07c160;
  199. box-shadow: 0 0 0 2px rgba(7, 193, 96, 0.1);
  200. }
  201. .config-hint {
  202. color: #888;
  203. font-size: 0.85rem;
  204. margin-top: 5px;
  205. }
  206. .buttons-container {
  207. display: flex;
  208. gap: 15px;
  209. margin-top: 25px;
  210. flex-wrap: wrap;
  211. }
  212. .btn {
  213. padding: 12px 24px;
  214. border-radius: 8px;
  215. border: none;
  216. font-size: 1rem;
  217. font-weight: 600;
  218. cursor: pointer;
  219. transition: all 0.3s ease;
  220. display: flex;
  221. align-items: center;
  222. justify-content: center;
  223. gap: 8px;
  224. }
  225. .btn-primary {
  226. background-color: #07c160;
  227. color: white;
  228. }
  229. .btn-primary:hover {
  230. background-color: #06ad56;
  231. transform: translateY(-2px);
  232. box-shadow: 0 5px 15px rgba(7, 193, 96, 0.2);
  233. }
  234. .btn-secondary {
  235. background-color: #f0f2f5;
  236. color: #606266;
  237. }
  238. .btn-secondary:hover {
  239. background-color: #e4e6e9;
  240. transform: translateY(-2px);
  241. }
  242. .btn-refresh {
  243. background-color: #1677ff;
  244. color: white;
  245. }
  246. .btn-refresh:hover {
  247. background-color: #0d66d9;
  248. transform: translateY(-2px);
  249. box-shadow: 0 5px 15px rgba(22, 119, 255, 0.2);
  250. }
  251. .btn:disabled {
  252. opacity: 0.6;
  253. cursor: not-allowed;
  254. transform: none !important;
  255. box-shadow: none !important;
  256. }
  257. .note-box {
  258. margin-top: 25px;
  259. padding: 15px;
  260. border-radius: 10px;
  261. background-color: #fff9e6;
  262. border-left: 4px solid #ffc107;
  263. }
  264. .note-title {
  265. font-weight: 600;
  266. color: #e6a700;
  267. margin-bottom: 8px;
  268. display: flex;
  269. align-items: center;
  270. gap: 8px;
  271. }
  272. .note-content {
  273. color: #8a6d3b;
  274. font-size: 0.9rem;
  275. line-height: 1.5;
  276. }
  277. .footer {
  278. margin-top: 40px;
  279. text-align: center;
  280. color: #95a5a6;
  281. font-size: 0.9rem;
  282. width: 100%;
  283. max-width: 900px;
  284. padding-top: 20px;
  285. border-top: 1px solid #eee;
  286. }
  287. .wechat-qrcode {
  288. text-align: center;
  289. margin-top: 20px;
  290. }
  291. .wechat-qrcode img {
  292. max-width: 200px;
  293. border-radius: 8px;
  294. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
  295. }
  296. @media (max-width: 768px) {
  297. .content-wrapper {
  298. flex-direction: column;
  299. }
  300. h1 {
  301. font-size: 2rem;
  302. flex-direction: column;
  303. gap: 10px;
  304. }
  305. .login-section, .info-section {
  306. padding: 30px 20px;
  307. }
  308. .buttons-container {
  309. flex-direction: column;
  310. }
  311. .btn {
  312. width: 100%;
  313. }
  314. }
  315. @media (max-width: 480px) {
  316. .section-title {
  317. font-size: 1.3rem;
  318. }
  319. #login_container {
  320. min-height: 280px;
  321. }
  322. }
  323. </style>
  324. </head>
  325. <body>
  326. <div class="container">
  327. <header>
  328. <h1>
  329. <i class="fab fa-weixin"></i>
  330. 微信官方扫码登录
  331. </h1>
  332. <p class="subtitle">
  333. 本页面使用微信官方提供的JS SDK实现扫码登录功能。请按照下方步骤配置参数并体验微信扫码登录流程。
  334. </p>
  335. </header>
  336. <div class="content-wrapper">
  337. <div class="login-section">
  338. <h2 class="section-title">
  339. <i class="fas fa-qrcode"></i>
  340. 微信扫码登录
  341. </h2>
  342. <!-- 微信登录二维码容器 -->
  343. <div id="login_container">
  344. <div class="qrcode-loading">
  345. <i class="fas fa-spinner fa-spin"></i>
  346. <p>正在加载微信登录二维码...</p>
  347. </div>
  348. </div>
  349. <div class="status-indicator">
  350. <div class="status-title">
  351. <i class="fas fa-info-circle"></i>
  352. 状态监控
  353. </div>
  354. <p class="status-desc" id="statusText">等待二维码加载完成</p>
  355. </div>
  356. <div class="buttons-container">
  357. <button class="btn btn-refresh" id="refreshBtn">
  358. <i class="fas fa-sync-alt"></i>
  359. 刷新二维码
  360. </button>
  361. <button class="btn btn-secondary" id="testBtn">
  362. <i class="fas fa-mobile-alt"></i>
  363. 模拟扫码测试
  364. </button>
  365. </div>
  366. <div class="note-box">
  367. <div class="note-title">
  368. <i class="fas fa-exclamation-triangle"></i>
  369. 重要提示
  370. </div>
  371. <div class="note-content">
  372. 此演示使用了测试参数,在实际生产环境中需要:
  373. 1. 在微信开放平台注册应用获取真实appid
  374. 2. 配置有效的redirect_uri(授权回调地址)
  375. 3. 部署在备案的域名下(微信要求HTTPS)
  376. </div>
  377. </div>
  378. </div>
  379. <div class="info-section">
  380. <h2 class="section-title">
  381. <i class="fas fa-cogs"></i>
  382. 配置说明
  383. </h2>
  384. <div class="steps-container">
  385. <div class="step">
  386. <div class="step-number">1</div>
  387. <div class="step-content">
  388. <h4>获取AppID</h4>
  389. <p>访问微信开放平台,注册并创建网站应用,获取AppID和AppSecret</p>
  390. </div>
  391. </div>
  392. <div class="step">
  393. <div class="step-number">2</div>
  394. <div class="step-content">
  395. <h4>配置回调地址</h4>
  396. <p>在微信开放平台配置授权回调域名,必须与redirect_uri域名一致</p>
  397. </div>
  398. </div>
  399. <div class="step">
  400. <div class="step-number">3</div>
  401. <div class="step-content">
  402. <h4>部署代码</h4>
  403. <p>将配置好参数的代码部署到已备案的域名(必须支持HTTPS)</p>
  404. </div>
  405. </div>
  406. </div>
  407. <div class="config-section">
  408. <h3 class="config-title">
  409. <i class="fas fa-sliders-h"></i>
  410. 参数配置
  411. </h3>
  412. <div class="config-item">
  413. <label for="appid">AppID</label>
  414. <input type="text" id="appid" placeholder="请输入微信开放平台AppID" value="wxbdc5610cc59c1631">
  415. <div class="config-hint">此处为测试AppID,实际使用时请替换为自己的AppID</div>
  416. </div>
  417. <div class="config-item">
  418. <label for="redirect_uri">Redirect URI</label>
  419. <input type="text" id="redirect_uri" placeholder="请输入授权回调地址" value="https://open.weixin.qq.com/connect/qrconnect">
  420. <div class="config-hint">用户授权后微信会跳转到此地址,需与开放平台配置一致</div>
  421. </div>
  422. <div class="config-item">
  423. <label for="scope">Scope</label>
  424. <input type="text" id="scope" placeholder="请输入授权作用域" value="snsapi_login">
  425. <div class="config-hint">应用授权作用域,snsapi_login为微信登录专用</div>
  426. </div>
  427. <div class="buttons-container">
  428. <button class="btn btn-primary" id="applyConfigBtn">
  429. <i class="fas fa-check-circle"></i>
  430. 应用配置
  431. </button>
  432. <button class="btn btn-secondary" id="resetConfigBtn">
  433. <i class="fas fa-undo"></i>
  434. 重置配置
  435. </button>
  436. </div>
  437. </div>
  438. <div class="wechat-qrcode">
  439. <p>使用微信扫描体验</p>
  440. <img src="https://res.wx.qq.com/a/wx_fed/assets/res/OTE0YTAw.png" alt="微信扫码">
  441. </div>
  442. </div>
  443. </div>
  444. <footer class="footer">
  445. <p>本页面使用微信官方JS SDK实现扫码登录功能 | 请遵循微信开放平台相关规范</p>
  446. <p>© 2023 微信扫码登录演示 | 仅用于学习和演示目的</p>
  447. </footer>
  448. </div>
  449. <!-- 微信官方JS SDK -->
  450. <script src="https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js"></script>
  451. <script>
  452. // 微信登录对象
  453. let wxLoginObj = null;
  454. // 默认配置参数
  455. const defaultConfig = {
  456. self_redirect: true,
  457. id: "login_container",
  458. appid: "wxbdc5610cc59c1631", // 微信提供的测试AppID:wxbdc5610cc59c1631
  459. scope: "snsapi_login",
  460. redirect_uri: encodeURIComponent("https://passport.yhd.com/wechat/callback.do"),
  461. state: "STATE", // 可以自定义的参数,用于防止CSRF攻击
  462. style: "black", // 二维码样式:black黑色,white白色
  463. href: "" // 自定义样式链接,可以为空
  464. };
  465. // DOM元素
  466. const loginContainer = document.getElementById('login_container');
  467. const statusText = document.getElementById('statusText');
  468. const refreshBtn = document.getElementById('refreshBtn');
  469. const testBtn = document.getElementById('testBtn');
  470. const applyConfigBtn = document.getElementById('applyConfigBtn');
  471. const resetConfigBtn = document.getElementById('resetConfigBtn');
  472. const appidInput = document.getElementById('appid');
  473. const redirectUriInput = document.getElementById('redirect_uri');
  474. const scopeInput = document.getElementById('scope');
  475. // 初始化函数
  476. function initWeChatLogin(config) {
  477. // 清除现有二维码
  478. loginContainer.innerHTML = '<div class="qrcode-loading"><i class="fas fa-spinner fa-spin"></i><p>正在加载微信登录二维码...</p></div>';
  479. statusText.textContent = "正在初始化微信登录...";
  480. // 销毁之前的实例
  481. if (wxLoginObj) {
  482. // 微信官方SDK没有提供销毁方法,我们只能替换容器内容
  483. loginContainer.innerHTML = '';
  484. }
  485. // 创建微信登录实例
  486. try {
  487. wxLoginObj = new WxLogin({
  488. self_redirect: config.self_redirect,
  489. id: config.id,
  490. appid: config.appid,
  491. scope: config.scope,
  492. redirect_uri: config.redirect_uri,
  493. state: config.state,
  494. style: config.style,
  495. href: config.href,
  496. onReady: function(isReady) {
  497. console.log("微信登录二维码加载状态:", isReady);
  498. if (isReady) {
  499. statusText.textContent = "二维码已就绪,请使用微信扫描";
  500. refreshBtn.disabled = false;
  501. } else {
  502. statusText.textContent = "二维码加载失败,请检查配置";
  503. }
  504. }
  505. });
  506. // 监听二维码状态变化(模拟)
  507. setTimeout(() => {
  508. statusText.textContent = "二维码已加载完成,请使用微信扫描";
  509. }, 1000);
  510. } catch (error) {
  511. console.error("微信登录初始化失败:", error);
  512. statusText.textContent = "微信登录初始化失败: " + error.message;
  513. loginContainer.innerHTML = `<div class="qrcode-loading" style="color:#f44336;">
  514. <i class="fas fa-exclamation-triangle"></i>
  515. <p>微信登录初始化失败</p>
  516. <p style="font-size:0.8rem; margin-top:10px;">${error.message}</p>
  517. </div>`;
  518. }
  519. }
  520. // 刷新二维码
  521. function refreshQRCode() {
  522. // 获取当前配置
  523. const currentConfig = {
  524. ...defaultConfig,
  525. appid: appidInput.value.trim() || defaultConfig.appid,
  526. scope: scopeInput.value.trim() || defaultConfig.scope,
  527. redirect_uri: encodeURIComponent(redirectUriInput.value.trim() || defaultConfig.redirect_uri)
  528. };
  529. // 重新初始化
  530. initWeChatLogin(currentConfig);
  531. // 禁用按钮避免重复点击
  532. refreshBtn.disabled = true;
  533. refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 刷新中...';
  534. // 3秒后启用按钮
  535. setTimeout(() => {
  536. refreshBtn.disabled = false;
  537. refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 刷新二维码';
  538. }, 30000);
  539. }
  540. // 模拟扫码测试
  541. function simulateScanTest() {
  542. statusText.textContent = "已模拟扫描,请在手机上确认登录";
  543. testBtn.disabled = true;
  544. testBtn.innerHTML = '<i class="fas fa-check-circle"></i> 已模拟扫码';
  545. // 5秒后重置
  546. setTimeout(() => {
  547. statusText.textContent = "二维码已就绪,请使用微信扫描";
  548. testBtn.disabled = false;
  549. testBtn.innerHTML = '<i class="fas fa-mobile-alt"></i> 模拟扫码测试';
  550. }, 5000);
  551. }
  552. // 应用配置
  553. function applyConfig() {
  554. // 验证配置
  555. const appid = appidInput.value.trim();
  556. const redirectUri = redirectUriInput.value.trim();
  557. if (!appid) {
  558. alert("请填写AppID");
  559. appidInput.focus();
  560. return;
  561. }
  562. if (!redirectUri) {
  563. alert("请填写Redirect URI");
  564. redirectUriInput.focus();
  565. return;
  566. }
  567. // 应用配置并刷新二维码
  568. applyConfigBtn.disabled = true;
  569. applyConfigBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 应用中...';
  570. setTimeout(() => {
  571. refreshQRCode();
  572. applyConfigBtn.disabled = false;
  573. applyConfigBtn.innerHTML = '<i class="fas fa-check-circle"></i> 应用配置';
  574. }, 1000);
  575. }
  576. // 重置配置
  577. function resetConfig() {
  578. appidInput.value = defaultConfig.appid;
  579. redirectUriInput.value = decodeURIComponent(defaultConfig.redirect_uri);
  580. scopeInput.value = defaultConfig.scope;
  581. // 应用重置的配置
  582. applyConfig();
  583. }
  584. // 页面加载完成后初始化
  585. document.addEventListener('DOMContentLoaded', function() {
  586. // 初始化微信登录
  587. initWeChatLogin(defaultConfig);
  588. // 设置输入框默认值
  589. appidInput.value = defaultConfig.appid;
  590. redirectUriInput.value = decodeURIComponent(defaultConfig.redirect_uri);
  591. scopeInput.value = defaultConfig.scope;
  592. // 绑定按钮事件
  593. refreshBtn.addEventListener('click', refreshQRCode);
  594. testBtn.addEventListener('click', simulateScanTest);
  595. applyConfigBtn.addEventListener('click', applyConfig);
  596. resetConfigBtn.addEventListener('click', resetConfig);
  597. // 监听输入框变化
  598. appidInput.addEventListener('input', function() {
  599. applyConfigBtn.disabled = false;
  600. });
  601. redirectUriInput.addEventListener('input', function() {
  602. applyConfigBtn.disabled = false;
  603. });
  604. scopeInput.addEventListener('input', function() {
  605. applyConfigBtn.disabled = false;
  606. });
  607. // 添加键盘快捷键
  608. document.addEventListener('keydown', function(e) {
  609. // Ctrl+R 刷新二维码
  610. if (e.ctrlKey && e.key === 'r') {
  611. e.preventDefault();
  612. if (!refreshBtn.disabled) refreshQRCode();
  613. }
  614. // Enter 在输入框时应用配置
  615. if (e.key === 'Enter' && document.activeElement.tagName === 'INPUT') {
  616. applyConfig();
  617. }
  618. });
  619. });
  620. // 页面可见性变化时刷新二维码
  621. document.addEventListener('visibilitychange', function() {
  622. if (!document.hidden && wxLoginObj) {
  623. // 页面从后台切换回来时刷新二维码
  624. setTimeout(refreshQRCode, 500);
  625. }
  626. });
  627. </script>
  628. </body>
  629. </html>