login.html 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089
  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. <!-- 引入基础依赖 -->
  11. <script src="/js/mp_base/base.js"></script>
  12. </head>
  13. <body>
  14. <!-- 页面加载状态 -->
  15. <div id="page-loading" class="page-loading">
  16. <div class="loading-content">
  17. <div class="loading-spinner"></div>
  18. <div class="loading-text">加载中...</div>
  19. </div>
  20. </div>
  21. <div id="app" v-cloak>
  22. <div class="login-popup show">
  23. <div class="mask"></div>
  24. <div class="popup-content">
  25. <!-- <div class="popup-header">
  26. <span class="title">登录</span>
  27. </div> -->
  28. <!-- 用户协议 -->
  29. <div class="agreement">
  30. <input
  31. type="checkbox"
  32. v-model="agreed"
  33. id="agreement-checkbox"
  34. class="agreement-checkbox"
  35. />
  36. <label for="agreement-checkbox" class="agreement-text">
  37. 我已阅读并同意
  38. <span class="link" @click="openAgreement('user')"
  39. >《用户协议》</span
  40. >与<span class="link" @click="openAgreement('privacy')"
  41. >《隐私协议》</span
  42. >
  43. </label>
  44. </div>
  45. <!-- 账号密码登录 -->
  46. <div class="account-login">
  47. <div class="input-item">
  48. <Icon
  49. name="icon-renyuan"
  50. size="32"
  51. color="#575d6d"
  52. class="input-icon"
  53. ></Icon>
  54. <input
  55. type="text"
  56. v-model="form.username"
  57. placeholder="请输入您的账号"
  58. class="login-input"
  59. />
  60. </div>
  61. <div class="input-item">
  62. <Icon
  63. name="icon-kaisuo"
  64. size="32"
  65. color="#575d6d"
  66. class="input-icon"
  67. ></Icon>
  68. <input
  69. :type="showPassword ? 'text' : 'password'"
  70. v-model="form.password"
  71. placeholder="请输入您的密码"
  72. class="login-input"
  73. />
  74. <div class="eye-icon" @click="showPassword = !showPassword">
  75. <Icon
  76. :name="showPassword ? 'icon-yanjing-kai' : 'icon-yanjing-bi'"
  77. size="32"
  78. color="#575d6d"
  79. ></Icon>
  80. </div>
  81. </div>
  82. <button
  83. class="account-btn"
  84. :class="{ 'disabled': isLogging }"
  85. :disabled="isLogging"
  86. @click="handleAccountLogin"
  87. >
  88. <Icon
  89. name="icon-tuichu"
  90. size="32"
  91. color="#fff"
  92. class="btn-icon"
  93. ></Icon>
  94. <span>{{ isLogging ? '登录中...' : '登录' }}</span>
  95. </button>
  96. </div>
  97. </div>
  98. </div>
  99. <!-- Loading 遮罩 -->
  100. <div v-if="showLoading" class="loading-mask">
  101. <div class="loading-content">
  102. <div class="loading-spinner"></div>
  103. <div class="loading-text">{{ loadingText }}</div>
  104. </div>
  105. </div>
  106. <!-- Toast 提示 -->
  107. <div v-if="toast.show" class="toast" :class="toast.type">
  108. {{ toast.message }}
  109. </div>
  110. <!-- warnMsg 使用 ss-confirm -->
  111. <ss-confirm
  112. v-model="warnConfirm.show"
  113. title="提示"
  114. :mask-closable="false"
  115. @confirm="handleWarnConfirmOk"
  116. @cancel="handleWarnConfirmCancel"
  117. >
  118. <div class="warn-confirm-text">{{ warnConfirm.message }}</div>
  119. </ss-confirm>
  120. <!-- 自定义确认弹窗 -->
  121. <div v-if="confirmDialog.show" class="confirm-dialog-mask">
  122. <div class="confirm-dialog">
  123. <div class="confirm-content">
  124. <div class="confirm-title">{{ confirmDialog.title }}</div>
  125. <div class="confirm-message">{{ confirmDialog.message }}</div>
  126. <div class="confirm-buttons">
  127. <button
  128. v-if="confirmDialog.showCancel"
  129. class="confirm-btn cancel-btn"
  130. @click="handleConfirmCancel"
  131. >
  132. 取消
  133. </button>
  134. <button
  135. class="confirm-btn confirm-btn-primary"
  136. :class="{ 'full-width': !confirmDialog.showCancel }"
  137. @click="handleConfirmOk"
  138. >
  139. 确认
  140. </button>
  141. </div>
  142. </div>
  143. </div>
  144. </div>
  145. <!-- 底部抽屉:显示协议内容 -->
  146. <div
  147. v-if="agreementDrawer.show"
  148. class="drawer-mask"
  149. @click="closeAgreementDrawer"
  150. >
  151. <div class="drawer-content" @click.stop>
  152. <div class="drawer-header">
  153. <div class="drawer-title">{{ agreementDrawer.title }}</div>
  154. <div class="drawer-close" @click="closeAgreementDrawer">
  155. <Icon name="icon-X-xi" size="32" color="#666"></Icon>
  156. </div>
  157. </div>
  158. <div class="drawer-body">
  159. <div v-html="agreementDrawer.content"></div>
  160. </div>
  161. </div>
  162. </div>
  163. </div>
  164. <script>
  165. // 等待SS框架加载完成
  166. window.SS.ready(function () {
  167. // 使用SS框架的方式创建Vue实例
  168. window.SS.dom.initializeFormApp({
  169. el: "#app",
  170. data() {
  171. return {
  172. form: {
  173. username: "",
  174. password: "",
  175. },
  176. showPassword: false,
  177. agreed: false,
  178. isLogging: false,
  179. showLoading: false,
  180. loadingText: "",
  181. toast: {
  182. show: false,
  183. message: "",
  184. type: "info",
  185. },
  186. warnConfirm: {
  187. show: false,
  188. message: "",
  189. resolve: null,
  190. },
  191. confirmDialog: {
  192. show: false,
  193. title: "提示",
  194. message: "",
  195. showCancel: true,
  196. resolve: null,
  197. },
  198. agreementDrawer: {
  199. show: false,
  200. title: "",
  201. content: "",
  202. },
  203. };
  204. },
  205. mounted() {
  206. // 隐藏页面加载状态
  207. const pageLoading = document.getElementById("page-loading");
  208. if (pageLoading) {
  209. pageLoading.style.display = "none";
  210. }
  211. // 初始化页面,使用自定义结果处理
  212. if (typeof initPage === "function") {
  213. initPage("login", this.handleResult);
  214. }
  215. // 获取URL参数,用于调试
  216. const urlParams = this.getUrlParams();
  217. console.log("🔗 H5登录页面参数:", urlParams);
  218. },
  219. methods: {
  220. // 获取URL参数
  221. getUrlParams() {
  222. const params = {};
  223. const urlSearchParams = new URLSearchParams(
  224. window.location.search
  225. );
  226. for (const [key, value] of urlSearchParams) {
  227. params[key] = decodeURIComponent(value);
  228. }
  229. return params;
  230. },
  231. // 处理操作结果(来自小程序的回调)
  232. handleResult(data) {
  233. console.log("🎯 收到小程序回调:", data);
  234. // 这里可以处理来自小程序的各种回调
  235. },
  236. // 显示Loading
  237. showLoadingMask(text = "加载中...") {
  238. this.showLoading = true;
  239. this.loadingText = text;
  240. },
  241. // 隐藏Loading
  242. hideLoadingMask() {
  243. this.showLoading = false;
  244. this.loadingText = "";
  245. },
  246. // 显示Toast
  247. showToast(message, type = "info", duration = 3000) {
  248. this.toast = {
  249. show: true,
  250. message,
  251. type,
  252. };
  253. setTimeout(() => {
  254. this.toast.show = false;
  255. }, duration);
  256. },
  257. // 自定义确认对话框
  258. showConfirm(message, options = {}) {
  259. const { title = "提示", showCancel = true } = options;
  260. return new Promise((resolve) => {
  261. this.confirmDialog = {
  262. show: true,
  263. title,
  264. message,
  265. showCancel,
  266. resolve,
  267. };
  268. });
  269. },
  270. // 处理确认对话框 - 取消
  271. handleConfirmCancel() {
  272. this.confirmDialog.show = false;
  273. if (this.confirmDialog.resolve) {
  274. this.confirmDialog.resolve(false);
  275. }
  276. this.confirmDialog.resolve = null;
  277. },
  278. // 处理确认对话框 - 确认
  279. handleConfirmOk() {
  280. this.confirmDialog.show = false;
  281. if (this.confirmDialog.resolve) {
  282. this.confirmDialog.resolve(true);
  283. }
  284. this.confirmDialog.resolve = null;
  285. },
  286. // warnMsg 确认弹窗 - 确认
  287. handleWarnConfirmOk() {
  288. this.warnConfirm.show = false;
  289. if (this.warnConfirm.resolve) {
  290. this.warnConfirm.resolve(true);
  291. }
  292. this.warnConfirm.resolve = null;
  293. },
  294. // warnMsg 确认弹窗 - 取消(maskClosable=false 正常不会触发)
  295. handleWarnConfirmCancel() {
  296. this.warnConfirm.show = false;
  297. if (this.warnConfirm.resolve) {
  298. this.warnConfirm.resolve(false);
  299. }
  300. this.warnConfirm.resolve = null;
  301. },
  302. showWarnConfirm(message) {
  303. return new Promise((resolve) => {
  304. this.warnConfirm = {
  305. show: true,
  306. message,
  307. resolve,
  308. };
  309. });
  310. },
  311. // 账号密码登录
  312. async handleAccountLogin() {
  313. if (this.isLogging) {
  314. console.log("正在登录中,请勿重复点击");
  315. return;
  316. }
  317. if (!this.agreed) {
  318. // 使用自定义确认对话框替换原生confirm
  319. const userConfirmed = await this.showConfirm(
  320. "是否同意用户协议和隐私协议?"
  321. );
  322. if (userConfirmed) {
  323. this.agreed = true; // 用户点击了“确定”,视为同意
  324. // 继续执行后续逻辑
  325. } else {
  326. return; // 用户点击了“取消”,中断操作
  327. }
  328. }
  329. // 提交前清理账号中的空格
  330. const normalizedUsername = (this.form.username || "").replace(
  331. /\s+/g,
  332. ""
  333. );
  334. this.form.username = normalizedUsername;
  335. if (!this.form.username || !this.form.password) {
  336. this.showToast("请输入用户名和密码", "warning");
  337. return;
  338. }
  339. try {
  340. this.isLogging = true;
  341. this.showLoadingMask("登录中...");
  342. // 获取URL参数中的wechatCode
  343. const urlParams = this.getUrlParams();
  344. const wechatCode = urlParams.wechatCode || "";
  345. const redirectUrl = urlParams.redirect || "";
  346. const entryScene = urlParams.entryScene || "";
  347. console.log("🔗 使用wechatCode:", wechatCode);
  348. // 使用真实的API进行登录
  349. const response = await h5UserApi.accountLogin({
  350. yhm: this.form.username,
  351. mm: this.form.password,
  352. wdConfirmationCaptchaService: "0",
  353. wechatCode: wechatCode, // 传递wechatCode
  354. });
  355. // 检查是否有错误信息
  356. if (response && response.data && response.data.msg) {
  357. // 登录失败,显示错误信息
  358. console.error("❌ 登录失败:", response.data.msg);
  359. this.showToast(response.data.msg, "error");
  360. return;
  361. }
  362. // 检查是否有告警信息(先关闭 loading,再用 ss-confirm 确认后继续)
  363. if (response && response.data && response.data.warnMsg) {
  364. this.hideLoadingMask();
  365. const confirmed = await this.showWarnConfirm(
  366. response.data.warnMsg
  367. );
  368. if (!confirmed) {
  369. return;
  370. }
  371. }
  372. console.log("response:", response);
  373. // 检查登录是否成功
  374. if (response && response.data) {
  375. if (
  376. typeof response.data === "string" &&
  377. response.data.includes("页面执行时错误")
  378. ) {
  379. throw new Error("服务器处理错误");
  380. }
  381. const userData = {
  382. devId: response.data.devId,
  383. sbmc: response.data.sbmc,
  384. sessId: response.data.sessId,
  385. xm: response.data.xm,
  386. yhsbToken: response.data.yhsbToken,
  387. onlineToken: response.data.onlineToken,
  388. syList: response.data.sylist,
  389. yhid: response.data.yhid,
  390. yhm: response.data.yhm,
  391. };
  392. // 保存用户信息到H5本地存储
  393. h5UserApi.saveUserInfo(userData);
  394. // 准备返回数据给小程序
  395. const loginResult = {
  396. success: true,
  397. userInfo: userData,
  398. redirectUrl,
  399. entryScene,
  400. };
  401. console.log("✅ 登录成功,准备返回数据:", loginResult);
  402. // this.showToast('登录成功!', 'success')
  403. // 延迟一下让用户看到成功提示
  404. setTimeout(() => {
  405. // 通过h5-bridge返回登录结果给小程序
  406. this.returnLoginResult(loginResult);
  407. }, 1000);
  408. } else {
  409. throw new Error("登录响应数据无效");
  410. }
  411. } catch (error) {
  412. console.error("❌ 登录失败:", error);
  413. this.showToast("登录失败: " + error.message, "error");
  414. } finally {
  415. this.isLogging = false;
  416. this.hideLoadingMask();
  417. }
  418. },
  419. // 返回登录结果给小程序
  420. returnLoginResult(result) {
  421. // console.log('🔄 返回登录结果给小程序:', result)
  422. // 使用h5-bridge的数据传递功能
  423. // 这里需要跳转到h5-controller中转页来传递数据
  424. callNative("loginSuccess", "登录成功", result);
  425. },
  426. // 打开协议页面
  427. openAgreement(type) {
  428. const agreementData = {
  429. user: {
  430. title: "用户协议",
  431. content: `
  432. <h3>用户协议</h3>
  433. <p>欢迎使用本平台服务。在使用本平台服务前,请您仔细阅读并充分理解本协议内容。</p>
  434. <h4>1. 服务条款的接受</h4>
  435. <p>您使用本平台服务即视为您已阅读并同意接受本协议的全部条款。</p>
  436. <h4>2. 账号注册和使用</h4>
  437. <p>用户在注册账号时,应提供真实、准确、完整的个人信息。</p>
  438. <h4>3. 用户权利和义务</h4>
  439. <p>用户有权使用本平台提供的各项服务,但应遵守相关法律法规。</p>
  440. <h4>4. 隐私保护</h4>
  441. <p>我们承诺保护用户的个人信息安全,不会泄露用户隐私。</p>
  442. <p style="margin-top: 20px; color: #999;">本协议最终解释权归本平台所有。</p>
  443. `,
  444. },
  445. privacy: {
  446. title: "隐私协议",
  447. content: `
  448. <h3>隐私协议</h3>
  449. <p>本指引是海丰县德成中英文学校小程序开发者 海丰县德成中英文学校(以下简称"开发者")为处理你的个人信息而制定。</p>
  450. <h4>开发者处理的信息</h4>
  451. <p>根据法律规定,开发者仅处理实现小程序功能所必要的信息。</p>
  452. <p>开发者收集你的设备信息,用于识别你的账号登录设备。</p>
  453. <p>开发者将在获取你的明示同意后,收集你的微信昵称、头像,用途是【更新OA系统中你的头像信息】</p>
  454. <h4>你的权益</h4>
  455. <p>关于你的个人信息,你可以通过以下方式与开发者联系,行使查阅、复制、更正、删除等法定权利。</p>
  456. <p>若你在小程序中注,不会对外公开披露你的信息,如必须公开披露时,开发者应当向你告知公开披露的目的、披露信息的类型及可能涉及的信息,并征得你的单独同意。</p>
  457. <p>你认为开发者未遵守上述约定,或有其他的投诉建议、或未成年人个人信息保护相关问题,可通过以下方式与开发者联系;或者向微信进行投诉。</p>
  458. <p><strong>邮箱:</strong> DC1612356478@163.com</p>
  459. <p style="margin-top: 20px; color: #999;">更新日期:2025-10-11</p>
  460. `,
  461. },
  462. };
  463. const agreement = agreementData[type];
  464. if (agreement) {
  465. this.agreementDrawer = {
  466. show: true,
  467. title: agreement.title,
  468. content: agreement.content,
  469. };
  470. }
  471. },
  472. // 关闭协议抽屉
  473. closeAgreementDrawer() {
  474. this.agreementDrawer.show = false;
  475. },
  476. },
  477. });
  478. console.log("✅ 登录页面初始化完成");
  479. });
  480. </script>
  481. <style>
  482. /* 防止Vue模板闪烁 */
  483. [v-cloak] {
  484. display: none !important;
  485. }
  486. /* 页面加载状态 */
  487. .page-loading {
  488. position: fixed;
  489. top: 0;
  490. left: 0;
  491. right: 0;
  492. bottom: 0;
  493. background: #f5f5f5;
  494. display: flex;
  495. align-items: center;
  496. justify-content: center;
  497. z-index: 9999;
  498. }
  499. .page-loading .loading-content {
  500. text-align: center;
  501. }
  502. .page-loading .loading-spinner {
  503. width: 40px;
  504. height: 40px;
  505. border: 4px solid #f3f3f3;
  506. border-top: 4px solid #40ac6d;
  507. border-radius: 50%;
  508. animation: page-spin 1s linear infinite;
  509. margin: 0 auto 15px;
  510. }
  511. @keyframes page-spin {
  512. 0% {
  513. transform: rotate(0deg);
  514. }
  515. 100% {
  516. transform: rotate(360deg);
  517. }
  518. }
  519. .page-loading .loading-text {
  520. color: #666;
  521. font-size: 14px;
  522. }
  523. * {
  524. margin: 0;
  525. padding: 0;
  526. box-sizing: border-box;
  527. }
  528. body {
  529. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
  530. sans-serif;
  531. background: #f5f5f5;
  532. overflow: hidden;
  533. }
  534. .login-popup {
  535. position: fixed;
  536. left: 0;
  537. right: 0;
  538. top: 0;
  539. bottom: 0;
  540. z-index: 9999;
  541. visibility: hidden;
  542. }
  543. .login-popup.show {
  544. visibility: visible;
  545. }
  546. .mask {
  547. position: absolute;
  548. left: 0;
  549. right: 0;
  550. top: 0;
  551. bottom: 0;
  552. background: rgba(0, 0, 0, 0.6);
  553. }
  554. .popup-content {
  555. width: calc(100% - 20px);
  556. position: absolute;
  557. left: 50%;
  558. top: 50%;
  559. background: #fff;
  560. border-radius: 4px;
  561. padding: 30px 20px;
  562. transform: translate(-50%, -50%);
  563. transition: transform 0.3s ease-out;
  564. border: 1px solid #d3d4d5;
  565. max-height: 80vh;
  566. overflow-y: auto;
  567. }
  568. .popup-header {
  569. display: flex;
  570. justify-content: space-between;
  571. align-items: center;
  572. margin-bottom: 20px;
  573. }
  574. .title {
  575. font-size: 18px;
  576. font-weight: bold;
  577. color: #333;
  578. }
  579. .close-btn {
  580. font-size: 24px;
  581. color: #999;
  582. padding: 10px;
  583. margin: -10px;
  584. cursor: pointer;
  585. user-select: none;
  586. }
  587. .close-btn:hover {
  588. color: #666;
  589. }
  590. .input-item {
  591. position: relative;
  592. margin-bottom: 15px;
  593. display: flex;
  594. }
  595. .input-icon {
  596. position: absolute;
  597. left: 18px;
  598. top: 50%;
  599. transform: translateY(-50%);
  600. z-index: 1;
  601. }
  602. .login-input {
  603. width: 100%;
  604. height: 44px;
  605. /* background: #f5f5f5; */
  606. border-radius: 4px;
  607. padding: 0 40px 0 50px;
  608. font-size: 16px;
  609. border: none;
  610. outline: none;
  611. border: 1px solid #d5d8dc;
  612. }
  613. .login-input:focus {
  614. /* background: #f0f0f0; */
  615. border: 1px solid #d0d2d6;
  616. }
  617. .eye-icon {
  618. position: absolute;
  619. right: 10px;
  620. top: 50%;
  621. transform: translateY(-50%);
  622. padding: 10px;
  623. display: flex;
  624. align-items: center;
  625. justify-content: center;
  626. cursor: pointer;
  627. z-index: 1000;
  628. }
  629. .account-btn {
  630. width: 100%;
  631. height: 44px;
  632. line-height: 44px;
  633. background: #575d6d;
  634. color: #fff;
  635. border-radius: 4px;
  636. font-size: 16px;
  637. border: none;
  638. margin-top: 35px;
  639. transition: all 0.3s ease;
  640. cursor: pointer;
  641. display: flex;
  642. align-items: center;
  643. justify-content: center;
  644. gap: 8px;
  645. }
  646. .btn-icon {
  647. display: inline-block;
  648. }
  649. .account-btn span {
  650. line-height: 1;
  651. }
  652. .account-btn:hover {
  653. background: #555;
  654. }
  655. .account-btn.disabled {
  656. background: #cccccc !important;
  657. color: #999999 !important;
  658. opacity: 0.6;
  659. cursor: not-allowed;
  660. }
  661. /* 用户协议 */
  662. .agreement {
  663. display: flex;
  664. align-items: center;
  665. justify-content: center;
  666. margin-bottom: 25px;
  667. }
  668. /* 自定义checkbox样式 */
  669. .agreement-checkbox {
  670. position: relative;
  671. margin-right: 8px;
  672. width: 20px;
  673. height: 20px;
  674. appearance: none;
  675. -webkit-appearance: none;
  676. border: 2px solid #d5d8dc;
  677. border-radius: 4px;
  678. outline: none;
  679. cursor: pointer;
  680. transition: all 0.2s;
  681. }
  682. .agreement-checkbox:hover {
  683. border-color: #575d6d;
  684. }
  685. .agreement-checkbox:checked {
  686. background-color: #575d6d;
  687. border-color: #575d6d;
  688. }
  689. .agreement-checkbox:checked::after {
  690. content: "";
  691. position: absolute;
  692. left: 5px;
  693. top: 0;
  694. width: 5px;
  695. height: 10px;
  696. border: solid white;
  697. border-width: 0 2px 2px 0;
  698. transform: rotate(45deg);
  699. }
  700. .agreement-text {
  701. font-size: 0.372rem;
  702. font-weight: 500;
  703. color: #999;
  704. cursor: pointer;
  705. }
  706. .link {
  707. /* color: #40ac6d; */
  708. cursor: pointer;
  709. }
  710. .link:hover {
  711. text-decoration: underline;
  712. }
  713. /* Loading 遮罩 */
  714. .loading-mask {
  715. position: fixed;
  716. top: 0;
  717. left: 0;
  718. right: 0;
  719. bottom: 0;
  720. background: rgba(0, 0, 0, 0.5);
  721. display: flex;
  722. align-items: center;
  723. justify-content: center;
  724. z-index: 10000;
  725. }
  726. .loading-content {
  727. background: white;
  728. padding: 20px;
  729. border-radius: 4px;
  730. text-align: center;
  731. min-width: 120px;
  732. }
  733. .loading-spinner {
  734. width: 30px;
  735. height: 30px;
  736. border: 3px solid #f3f3f3;
  737. border-top: 3px solid #40ac6d;
  738. border-radius: 50%;
  739. animation: spin 1s linear infinite;
  740. margin: 0 auto 10px;
  741. }
  742. @keyframes spin {
  743. 0% {
  744. transform: rotate(0deg);
  745. }
  746. 100% {
  747. transform: rotate(360deg);
  748. }
  749. }
  750. .loading-text {
  751. color: #333;
  752. font-size: 14px;
  753. }
  754. /* Toast 提示 */
  755. .toast {
  756. position: fixed;
  757. top: 50%;
  758. left: 50%;
  759. transform: translate(-50%, -50%);
  760. background: rgba(0, 0, 0, 0.8);
  761. color: white;
  762. padding: 12px 20px;
  763. border-radius: 4px;
  764. font-size: 14px;
  765. z-index: 10001;
  766. max-width: 80%;
  767. text-align: center;
  768. }
  769. .toast.success {
  770. background: rgba(40, 167, 69, 0.9);
  771. }
  772. .toast.warning {
  773. background: rgba(255, 193, 7, 0.9);
  774. color: #212529;
  775. }
  776. .toast.error {
  777. background: rgba(220, 53, 69, 0.9);
  778. }
  779. /* ss-confirm 内容样式(标题沿用组件默认,文本做适配) */
  780. .warn-confirm-text {
  781. text-align: center;
  782. padding: 12px 4px;
  783. font-size: 14px;
  784. line-height: 1.6;
  785. color: #333;
  786. }
  787. /* warnMsg 的 ss-confirm 仅保留确认按钮 */
  788. .ss-confirm .confirm-btn-cancel {
  789. display: none;
  790. }
  791. .ss-confirm .confirm-btn-confirm {
  792. width: 100%;
  793. }
  794. /* 自定义确认对话框 - 优化样式 */
  795. .confirm-dialog-mask {
  796. position: fixed;
  797. top: 0;
  798. left: 0;
  799. right: 0;
  800. bottom: 0;
  801. background: rgba(0, 0, 0, 0.6);
  802. display: flex;
  803. align-items: center;
  804. justify-content: center;
  805. z-index: 10002;
  806. }
  807. .confirm-dialog {
  808. background: white;
  809. border-radius: 4px;
  810. min-width: 280px;
  811. max-width: 90%;
  812. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  813. overflow: hidden;
  814. animation: confirmFadeIn 0.3s ease-out;
  815. }
  816. @keyframes confirmFadeIn {
  817. from {
  818. opacity: 0;
  819. transform: scale(0.95) translateY(-20px);
  820. }
  821. to {
  822. opacity: 1;
  823. transform: scale(1) translateY(0);
  824. }
  825. }
  826. .confirm-message {
  827. font-size: 16px;
  828. color: #333;
  829. text-align: center;
  830. margin-bottom: 24px;
  831. line-height: 1.6;
  832. }
  833. .confirm-buttons {
  834. display: flex;
  835. gap: 10px;
  836. }
  837. .confirm-btn {
  838. flex: 1;
  839. height: 40px;
  840. border: none;
  841. border-radius: 4px;
  842. font-size: 15px;
  843. cursor: pointer;
  844. transition: all 0.2s ease;
  845. font-weight: 500;
  846. }
  847. .cancel-btn {
  848. background: #f5f5f5;
  849. color: #666;
  850. border: 1px solid #e5e5e5;
  851. }
  852. .cancel-btn:hover {
  853. background: #eeeeee;
  854. border-color: #d5d5d5;
  855. }
  856. .confirm-btn-primary {
  857. background: #575d6d;
  858. color: white;
  859. }
  860. .confirm-btn-primary:hover {
  861. background: #494e5b;
  862. }
  863. .confirm-btn:active {
  864. transform: scale(0.98);
  865. }
  866. /* 底部抽屉样式 */
  867. .drawer-mask {
  868. position: fixed;
  869. top: 0;
  870. left: 0;
  871. right: 0;
  872. bottom: 0;
  873. background: rgba(0, 0, 0, 0.6);
  874. z-index: 10003;
  875. animation: drawerMaskFadeIn 0.3s ease-out;
  876. }
  877. @keyframes drawerMaskFadeIn {
  878. from {
  879. opacity: 0;
  880. }
  881. to {
  882. opacity: 1;
  883. }
  884. }
  885. .drawer-content {
  886. position: absolute;
  887. left: 0;
  888. right: 0;
  889. bottom: 0;
  890. max-height: 80vh;
  891. background: white;
  892. border-radius: 4px 4px 0 0;
  893. animation: drawerSlideUp 0.3s ease-out;
  894. display: flex;
  895. flex-direction: column;
  896. }
  897. @keyframes drawerSlideUp {
  898. from {
  899. transform: translateY(100%);
  900. }
  901. to {
  902. transform: translateY(0);
  903. }
  904. }
  905. .drawer-header {
  906. display: flex;
  907. align-items: center;
  908. justify-content: space-between;
  909. padding: 16px 20px;
  910. border-bottom: 1px solid #f0f0f0;
  911. flex-shrink: 0;
  912. }
  913. .drawer-title {
  914. font-size: 18px;
  915. font-weight: 600;
  916. color: #333;
  917. }
  918. .drawer-close {
  919. width: 32px;
  920. height: 32px;
  921. display: flex;
  922. align-items: center;
  923. justify-content: center;
  924. cursor: pointer;
  925. border-radius: 4px;
  926. transition: background 0.2s;
  927. }
  928. .drawer-close:hover {
  929. background: #f5f5f5;
  930. }
  931. .drawer-body {
  932. flex: 1;
  933. overflow-y: auto;
  934. padding: 20px;
  935. -webkit-overflow-scrolling: touch;
  936. }
  937. .drawer-body h3 {
  938. font-size: 18px;
  939. color: #333;
  940. margin-bottom: 16px;
  941. }
  942. .drawer-body h4 {
  943. font-size: 16px;
  944. color: #555;
  945. margin-top: 20px;
  946. margin-bottom: 12px;
  947. }
  948. .drawer-body p {
  949. font-size: 14px;
  950. color: #666;
  951. line-height: 1.8;
  952. margin-bottom: 12px;
  953. }
  954. /* 响应式设计 */
  955. /* @media (max-width: 480px) {
  956. .popup-content {
  957. padding: 15px 10px;
  958. }
  959. .login-input {
  960. height: 40px;
  961. font-size: 13px;
  962. }
  963. .account-btn {
  964. height: 40px;
  965. line-height: 40px;
  966. font-size: 14px;
  967. }
  968. } */
  969. </style>
  970. </body>
  971. </html>