login.html 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403
  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. <!-- 修改初始密码弹窗 -->
  164. <div v-if="resetPwdModal.show" class="reset-pwd-mask">
  165. <div class="reset-pwd-modal">
  166. <div class="reset-pwd-header">
  167. <div class="reset-pwd-title">请修改初始密码</div>
  168. </div>
  169. <div class="reset-pwd-body">
  170. <div class="pwd-strength-msg">
  171. {{ resetPwdModal.pwdStrengthMsg }}
  172. </div>
  173. <div class="reset-pwd-form">
  174. <div class="reset-pwd-input-item">
  175. <input
  176. :type="showNewPwd ? 'text' : 'password'"
  177. v-model="resetPwdForm.newPwd"
  178. placeholder="请输入新密码"
  179. class="reset-pwd-input"
  180. />
  181. <div class="eye-icon" @click="showNewPwd = !showNewPwd">
  182. <Icon
  183. :name="showNewPwd ? 'icon-yanjing-kai' : 'icon-yanjing-bi'"
  184. size="28"
  185. color="#999"
  186. ></Icon>
  187. </div>
  188. </div>
  189. <div class="reset-pwd-input-item">
  190. <input
  191. :type="showConfirmPwd ? 'text' : 'password'"
  192. v-model="resetPwdForm.confirmPwd"
  193. placeholder="请确认新密码"
  194. class="reset-pwd-input"
  195. />
  196. <div class="eye-icon" @click="showConfirmPwd = !showConfirmPwd">
  197. <Icon
  198. :name="showConfirmPwd ? 'icon-yanjing-kai' : 'icon-yanjing-bi'"
  199. size="28"
  200. color="#999"
  201. ></Icon>
  202. </div>
  203. </div>
  204. </div>
  205. <button
  206. class="reset-pwd-btn"
  207. :class="{ 'disabled': resetPwdLoading }"
  208. :disabled="resetPwdLoading"
  209. @click="handleResetPwd"
  210. >
  211. {{ resetPwdLoading ? '保存中...' : '保存' }}
  212. </button>
  213. </div>
  214. </div>
  215. </div>
  216. </div>
  217. <script>
  218. // 等待SS框架加载完成
  219. window.SS.ready(function () {
  220. // 使用SS框架的方式创建Vue实例
  221. window.SS.dom.initializeFormApp({
  222. el: "#app",
  223. data() {
  224. return {
  225. form: {
  226. username: "",
  227. password: "",
  228. },
  229. showPassword: false,
  230. agreed: false,
  231. isLogging: false,
  232. showLoading: false,
  233. loadingText: "",
  234. toast: {
  235. show: false,
  236. message: "",
  237. type: "info",
  238. },
  239. warnConfirm: {
  240. show: false,
  241. message: "",
  242. resolve: null,
  243. },
  244. confirmDialog: {
  245. show: false,
  246. title: "提示",
  247. message: "",
  248. showCancel: true,
  249. resolve: null,
  250. },
  251. agreementDrawer: {
  252. show: false,
  253. title: "",
  254. content: "",
  255. },
  256. // 修改初始密码弹窗状态
  257. resetPwdModal: {
  258. show: false,
  259. yhid: "",
  260. yhm: "",
  261. pwdStrengthMsg: "",
  262. },
  263. // 修改密码表单
  264. resetPwdForm: {
  265. newPwd: "",
  266. confirmPwd: "",
  267. },
  268. showNewPwd: false,
  269. showConfirmPwd: false,
  270. resetPwdLoading: false,
  271. };
  272. },
  273. mounted() {
  274. // 隐藏页面加载状态
  275. const pageLoading = document.getElementById("page-loading");
  276. if (pageLoading) {
  277. pageLoading.style.display = "none";
  278. }
  279. // 初始化页面,使用自定义结果处理
  280. if (typeof initPage === "function") {
  281. initPage("login", this.handleResult);
  282. }
  283. // 获取URL参数,用于调试
  284. const urlParams = this.getUrlParams();
  285. console.log("🔗 H5登录页面参数:", urlParams);
  286. },
  287. methods: {
  288. // 获取URL参数
  289. getUrlParams() {
  290. const params = {};
  291. const urlSearchParams = new URLSearchParams(
  292. window.location.search
  293. );
  294. for (const [key, value] of urlSearchParams) {
  295. params[key] = decodeURIComponent(value);
  296. }
  297. return params;
  298. },
  299. // 处理操作结果(来自小程序的回调)
  300. handleResult(data) {
  301. console.log("🎯 收到小程序回调:", data);
  302. // 这里可以处理来自小程序的各种回调
  303. },
  304. // 显示Loading
  305. showLoadingMask(text = "加载中...") {
  306. this.showLoading = true;
  307. this.loadingText = text;
  308. },
  309. // 隐藏Loading
  310. hideLoadingMask() {
  311. this.showLoading = false;
  312. this.loadingText = "";
  313. },
  314. // 显示Toast
  315. showToast(message, type = "info", duration = 3000) {
  316. this.toast = {
  317. show: true,
  318. message,
  319. type,
  320. };
  321. setTimeout(() => {
  322. this.toast.show = false;
  323. }, duration);
  324. },
  325. // 自定义确认对话框
  326. showConfirm(message, options = {}) {
  327. const { title = "提示", showCancel = true } = options;
  328. return new Promise((resolve) => {
  329. this.confirmDialog = {
  330. show: true,
  331. title,
  332. message,
  333. showCancel,
  334. resolve,
  335. };
  336. });
  337. },
  338. // 处理确认对话框 - 取消
  339. handleConfirmCancel() {
  340. this.confirmDialog.show = false;
  341. if (this.confirmDialog.resolve) {
  342. this.confirmDialog.resolve(false);
  343. }
  344. this.confirmDialog.resolve = null;
  345. },
  346. // 处理确认对话框 - 确认
  347. handleConfirmOk() {
  348. this.confirmDialog.show = false;
  349. if (this.confirmDialog.resolve) {
  350. this.confirmDialog.resolve(true);
  351. }
  352. this.confirmDialog.resolve = null;
  353. },
  354. // warnMsg 确认弹窗 - 确认
  355. handleWarnConfirmOk() {
  356. this.warnConfirm.show = false;
  357. if (this.warnConfirm.resolve) {
  358. this.warnConfirm.resolve(true);
  359. }
  360. this.warnConfirm.resolve = null;
  361. },
  362. // warnMsg 确认弹窗 - 取消(maskClosable=false 正常不会触发)
  363. handleWarnConfirmCancel() {
  364. this.warnConfirm.show = false;
  365. if (this.warnConfirm.resolve) {
  366. this.warnConfirm.resolve(false);
  367. }
  368. this.warnConfirm.resolve = null;
  369. },
  370. showWarnConfirm(message) {
  371. return new Promise((resolve) => {
  372. this.warnConfirm = {
  373. show: true,
  374. message,
  375. resolve,
  376. };
  377. });
  378. },
  379. // 账号密码登录
  380. async handleAccountLogin() {
  381. if (this.isLogging) {
  382. console.log("正在登录中,请勿重复点击");
  383. return;
  384. }
  385. if (!this.agreed) {
  386. // 使用自定义确认对话框替换原生confirm
  387. const userConfirmed = await this.showConfirm(
  388. "是否同意用户协议和隐私协议?"
  389. );
  390. if (userConfirmed) {
  391. this.agreed = true; // 用户点击了“确定”,视为同意
  392. // 继续执行后续逻辑
  393. } else {
  394. return; // 用户点击了“取消”,中断操作
  395. }
  396. }
  397. // 提交前清理账号中的空格
  398. const normalizedUsername = (this.form.username || "").replace(
  399. /\s+/g,
  400. ""
  401. );
  402. this.form.username = normalizedUsername;
  403. if (!this.form.username || !this.form.password) {
  404. this.showToast("请输入用户名和密码", "warning");
  405. return;
  406. }
  407. try {
  408. this.isLogging = true;
  409. this.showLoadingMask("登录中...");
  410. // 获取URL参数中的wechatCode
  411. const urlParams = this.getUrlParams();
  412. const wechatCode = urlParams.wechatCode || "";
  413. const redirectUrl = urlParams.redirect || "";
  414. const entryScene = urlParams.entryScene || "";
  415. console.log("🔗 使用wechatCode:", wechatCode);
  416. // 使用真实的API进行登录
  417. const response = await h5UserApi.accountLogin({
  418. yhm: this.form.username,
  419. mm: this.form.password,
  420. wdConfirmationCaptchaService: "0",
  421. wechatCode: wechatCode, // 传递wechatCode
  422. });
  423. // 检查是否有错误信息
  424. if (response && response.data && response.data.msg) {
  425. // 检测是否需要修改初始密码
  426. if (
  427. response.data.msg.includes("请修改初始密码") ||
  428. response.data.msg.includes("请修改密码")
  429. ) {
  430. this.hideLoadingMask();
  431. this.isLogging = false;
  432. // 显示修改密码弹窗
  433. this.openResetPwdModal(response.data);
  434. return;
  435. }
  436. // 登录失败,显示错误信息
  437. console.error("❌ 登录失败:", response.data.msg);
  438. this.showToast(response.data.msg, "error");
  439. return;
  440. }
  441. // 检查是否有告警信息(先关闭 loading,再用 ss-confirm 确认后继续)
  442. if (response && response.data && response.data.warnMsg) {
  443. this.hideLoadingMask();
  444. const confirmed = await this.showWarnConfirm(
  445. response.data.warnMsg
  446. );
  447. if (!confirmed) {
  448. return;
  449. }
  450. }
  451. console.log("response:", response);
  452. // 检查登录是否成功
  453. if (response && response.data) {
  454. if (
  455. typeof response.data === "string" &&
  456. response.data.includes("页面执行时错误")
  457. ) {
  458. throw new Error("服务器处理错误");
  459. }
  460. const userData = {
  461. devId: response.data.devId,
  462. sbmc: response.data.sbmc,
  463. sessId: response.data.sessId,
  464. xm: response.data.xm,
  465. yhsbToken: response.data.yhsbToken,
  466. onlineToken: response.data.onlineToken,
  467. syList: response.data.sylist,
  468. yhid: response.data.yhid,
  469. yhm: response.data.yhm,
  470. };
  471. // 保存用户信息到H5本地存储
  472. h5UserApi.saveUserInfo(userData);
  473. // 准备返回数据给小程序
  474. const loginResult = {
  475. success: true,
  476. userInfo: userData,
  477. redirectUrl,
  478. entryScene,
  479. };
  480. console.log("✅ 登录成功,准备返回数据:", loginResult);
  481. // this.showToast('登录成功!', 'success')
  482. // 延迟一下让用户看到成功提示
  483. setTimeout(() => {
  484. // 通过h5-bridge返回登录结果给小程序
  485. this.returnLoginResult(loginResult);
  486. }, 1000);
  487. } else {
  488. throw new Error("登录响应数据无效");
  489. }
  490. } catch (error) {
  491. console.error("❌ 登录失败:", error);
  492. this.showToast("登录失败: " + error.message, "error");
  493. } finally {
  494. this.isLogging = false;
  495. this.hideLoadingMask();
  496. }
  497. },
  498. // 返回登录结果给小程序
  499. returnLoginResult(result) {
  500. // console.log('🔄 返回登录结果给小程序:', result)
  501. // 使用h5-bridge的数据传递功能
  502. // 这里需要跳转到h5-controller中转页来传递数据
  503. callNative("loginSuccess", "登录成功", result);
  504. },
  505. // 打开协议页面
  506. openAgreement(type) {
  507. const agreementData = {
  508. user: {
  509. title: "用户协议",
  510. content: `
  511. <h3>用户协议</h3>
  512. <p>欢迎使用本平台服务。在使用本平台服务前,请您仔细阅读并充分理解本协议内容。</p>
  513. <h4>1. 服务条款的接受</h4>
  514. <p>您使用本平台服务即视为您已阅读并同意接受本协议的全部条款。</p>
  515. <h4>2. 账号注册和使用</h4>
  516. <p>用户在注册账号时,应提供真实、准确、完整的个人信息。</p>
  517. <h4>3. 用户权利和义务</h4>
  518. <p>用户有权使用本平台提供的各项服务,但应遵守相关法律法规。</p>
  519. <h4>4. 隐私保护</h4>
  520. <p>我们承诺保护用户的个人信息安全,不会泄露用户隐私。</p>
  521. <p style="margin-top: 20px; color: #999;">本协议最终解释权归本平台所有。</p>
  522. `,
  523. },
  524. privacy: {
  525. title: "隐私协议",
  526. content: `
  527. <h3>隐私协议</h3>
  528. <p>本指引是海丰县德成中英文学校小程序开发者 海丰县德成中英文学校(以下简称"开发者")为处理你的个人信息而制定。</p>
  529. <h4>开发者处理的信息</h4>
  530. <p>根据法律规定,开发者仅处理实现小程序功能所必要的信息。</p>
  531. <p>开发者收集你的设备信息,用于识别你的账号登录设备。</p>
  532. <p>开发者将在获取你的明示同意后,收集你的微信昵称、头像,用途是【更新OA系统中你的头像信息】</p>
  533. <h4>你的权益</h4>
  534. <p>关于你的个人信息,你可以通过以下方式与开发者联系,行使查阅、复制、更正、删除等法定权利。</p>
  535. <p>若你在小程序中注,不会对外公开披露你的信息,如必须公开披露时,开发者应当向你告知公开披露的目的、披露信息的类型及可能涉及的信息,并征得你的单独同意。</p>
  536. <p>你认为开发者未遵守上述约定,或有其他的投诉建议、或未成年人个人信息保护相关问题,可通过以下方式与开发者联系;或者向微信进行投诉。</p>
  537. <p><strong>邮箱:</strong> DC1612356478@163.com</p>
  538. <p style="margin-top: 20px; color: #999;">更新日期:2025-10-11</p>
  539. `,
  540. },
  541. };
  542. const agreement = agreementData[type];
  543. if (agreement) {
  544. this.agreementDrawer = {
  545. show: true,
  546. title: agreement.title,
  547. content: agreement.content,
  548. };
  549. }
  550. },
  551. // 关闭协议抽屉
  552. closeAgreementDrawer() {
  553. this.agreementDrawer.show = false;
  554. },
  555. // 打开修改初始密码弹窗
  556. openResetPwdModal(data) {
  557. this.resetPwdModal = {
  558. show: true,
  559. yhid: data.yhid || "",
  560. yhm: data.yhm || "",
  561. pwdStrengthMsg:
  562. data.pwdStrengthMsg ||
  563. "密码不能包含账号,最小6个字符,最大16个字符",
  564. };
  565. // 清空表单
  566. this.resetPwdForm = {
  567. newPwd: "",
  568. confirmPwd: "",
  569. };
  570. },
  571. // 校验密码强度(前端校验,与PC端一致)
  572. validatePassword(password, yhm) {
  573. // 获取密码强度要求
  574. const msg = this.resetPwdModal.pwdStrengthMsg || "";
  575. // 检查最小长度
  576. if (msg.includes("最小6个字符") && password.length < 6) {
  577. return { valid: false, msg: "密码长度不能小于6位" };
  578. }
  579. // 检查最大长度
  580. if (msg.includes("最大16个字符") && password.length > 16) {
  581. return { valid: false, msg: "密码长度不能大于16位" };
  582. }
  583. // 检查必须包含大小写字母和数字
  584. if (
  585. msg.includes("必须含有和字母和大小写字母和数字") ||
  586. msg.includes("必须含有大小写字母和数字")
  587. ) {
  588. if (
  589. !/[a-z]/.test(password) ||
  590. !/[A-Z]/.test(password) ||
  591. !/\d/.test(password)
  592. ) {
  593. return {
  594. valid: false,
  595. msg: "密码必须同时包含大小写字母和数字",
  596. };
  597. }
  598. }
  599. // 检查不能包含账号
  600. if (msg.includes("密码不能包含账号") && yhm) {
  601. if (password.includes(yhm)) {
  602. return { valid: false, msg: "密码不能包含账号" };
  603. }
  604. }
  605. return { valid: true, msg: "" };
  606. },
  607. // 提交修改密码
  608. async handleResetPwd() {
  609. const { newPwd, confirmPwd } = this.resetPwdForm;
  610. const { yhid, yhm } = this.resetPwdModal;
  611. // 校验输入
  612. if (!newPwd) {
  613. this.showToast("请输入新密码", "warning");
  614. return;
  615. }
  616. if (!confirmPwd) {
  617. this.showToast("请确认新密码", "warning");
  618. return;
  619. }
  620. if (newPwd !== confirmPwd) {
  621. this.showToast("新密码与确认密码不一致", "error");
  622. return;
  623. }
  624. // 前端校验密码强度
  625. const validateResult = this.validatePassword(newPwd, yhm);
  626. if (!validateResult.valid) {
  627. this.showToast(validateResult.msg, "error");
  628. return;
  629. }
  630. this.resetPwdLoading = true;
  631. try {
  632. const response = await $.ajax({
  633. url: "/service?ssServ=updPwd",
  634. data: {
  635. yhid: yhid,
  636. newPassword: newPwd,
  637. againNewPassword: confirmPwd,
  638. },
  639. type: "POST",
  640. dataType: "json",
  641. });
  642. console.log("修改密码响应:", response);
  643. // 判断是否成功(有msg且包含"成功",或没有错误信息)
  644. if (response.msg && response.msg.includes("成功")) {
  645. // 修改成功
  646. this.showToast(response.msg, "success");
  647. this.resetPwdModal.show = false;
  648. // 清空密码输入框,保留账号
  649. this.form.password = "";
  650. this.resetPwdForm.newPwd = "";
  651. this.resetPwdForm.confirmPwd = "";
  652. } else if (response.msg && response.msg.includes("失败")) {
  653. this.showToast(response.msg, "error");
  654. } else {
  655. this.showToast(
  656. response.msg || response.message || "密码修改失败",
  657. "error"
  658. );
  659. }
  660. } catch (error) {
  661. console.error("修改密码失败:", error);
  662. this.showToast("修改密码失败,请重试", "error");
  663. } finally {
  664. this.resetPwdLoading = false;
  665. }
  666. },
  667. },
  668. });
  669. console.log("✅ 登录页面初始化完成");
  670. });
  671. </script>
  672. <style>
  673. /* 防止Vue模板闪烁 */
  674. [v-cloak] {
  675. display: none !important;
  676. }
  677. /* 页面加载状态 */
  678. .page-loading {
  679. position: fixed;
  680. top: 0;
  681. left: 0;
  682. right: 0;
  683. bottom: 0;
  684. background: #f5f5f5;
  685. display: flex;
  686. align-items: center;
  687. justify-content: center;
  688. z-index: 9999;
  689. }
  690. .page-loading .loading-content {
  691. text-align: center;
  692. }
  693. .page-loading .loading-spinner {
  694. width: 40px;
  695. height: 40px;
  696. border: 4px solid #f3f3f3;
  697. border-top: 4px solid #40ac6d;
  698. border-radius: 50%;
  699. animation: page-spin 1s linear infinite;
  700. margin: 0 auto 15px;
  701. }
  702. @keyframes page-spin {
  703. 0% {
  704. transform: rotate(0deg);
  705. }
  706. 100% {
  707. transform: rotate(360deg);
  708. }
  709. }
  710. .page-loading .loading-text {
  711. color: #666;
  712. font-size: 14px;
  713. }
  714. * {
  715. margin: 0;
  716. padding: 0;
  717. box-sizing: border-box;
  718. }
  719. body {
  720. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
  721. sans-serif;
  722. background: #f5f5f5;
  723. overflow: hidden;
  724. }
  725. .login-popup {
  726. position: fixed;
  727. left: 0;
  728. right: 0;
  729. top: 0;
  730. bottom: 0;
  731. z-index: 9999;
  732. visibility: hidden;
  733. }
  734. .login-popup.show {
  735. visibility: visible;
  736. }
  737. .mask {
  738. position: absolute;
  739. left: 0;
  740. right: 0;
  741. top: 0;
  742. bottom: 0;
  743. background: rgba(0, 0, 0, 0.6);
  744. }
  745. .popup-content {
  746. width: calc(100% - 20px);
  747. position: absolute;
  748. left: 50%;
  749. top: 50%;
  750. background: #fff;
  751. border-radius: 4px;
  752. padding: 30px 20px;
  753. transform: translate(-50%, -50%);
  754. transition: transform 0.3s ease-out;
  755. border: 1px solid #d3d4d5;
  756. max-height: 80vh;
  757. overflow-y: auto;
  758. }
  759. .popup-header {
  760. display: flex;
  761. justify-content: space-between;
  762. align-items: center;
  763. margin-bottom: 20px;
  764. }
  765. .title {
  766. font-size: 18px;
  767. font-weight: bold;
  768. color: #333;
  769. }
  770. .close-btn {
  771. font-size: 24px;
  772. color: #999;
  773. padding: 10px;
  774. margin: -10px;
  775. cursor: pointer;
  776. user-select: none;
  777. }
  778. .close-btn:hover {
  779. color: #666;
  780. }
  781. .input-item {
  782. position: relative;
  783. margin-bottom: 15px;
  784. display: flex;
  785. }
  786. .input-icon {
  787. position: absolute;
  788. left: 18px;
  789. top: 50%;
  790. transform: translateY(-50%);
  791. z-index: 1;
  792. }
  793. .login-input {
  794. width: 100%;
  795. height: 44px;
  796. /* background: #f5f5f5; */
  797. border-radius: 4px;
  798. padding: 0 40px 0 50px;
  799. font-size: 16px;
  800. border: none;
  801. outline: none;
  802. border: 1px solid #d5d8dc;
  803. }
  804. .login-input:focus {
  805. /* background: #f0f0f0; */
  806. border: 1px solid #d0d2d6;
  807. }
  808. .eye-icon {
  809. position: absolute;
  810. right: 10px;
  811. top: 50%;
  812. transform: translateY(-50%);
  813. padding: 10px;
  814. display: flex;
  815. align-items: center;
  816. justify-content: center;
  817. cursor: pointer;
  818. z-index: 1000;
  819. }
  820. .account-btn {
  821. width: 100%;
  822. height: 44px;
  823. line-height: 44px;
  824. background: #575d6d;
  825. color: #fff;
  826. border-radius: 4px;
  827. font-size: 16px;
  828. border: none;
  829. margin-top: 35px;
  830. transition: all 0.3s ease;
  831. cursor: pointer;
  832. display: flex;
  833. align-items: center;
  834. justify-content: center;
  835. gap: 8px;
  836. }
  837. .btn-icon {
  838. display: inline-block;
  839. }
  840. .account-btn span {
  841. line-height: 1;
  842. }
  843. .account-btn:hover {
  844. background: #555;
  845. }
  846. .account-btn.disabled {
  847. background: #cccccc !important;
  848. color: #999999 !important;
  849. opacity: 0.6;
  850. cursor: not-allowed;
  851. }
  852. /* 用户协议 */
  853. .agreement {
  854. display: flex;
  855. align-items: center;
  856. justify-content: center;
  857. margin-bottom: 25px;
  858. }
  859. /* 自定义checkbox样式 */
  860. .agreement-checkbox {
  861. position: relative;
  862. margin-right: 8px;
  863. width: 20px;
  864. height: 20px;
  865. appearance: none;
  866. -webkit-appearance: none;
  867. border: 2px solid #d5d8dc;
  868. border-radius: 4px;
  869. outline: none;
  870. cursor: pointer;
  871. transition: all 0.2s;
  872. }
  873. .agreement-checkbox:hover {
  874. border-color: #575d6d;
  875. }
  876. .agreement-checkbox:checked {
  877. background-color: #575d6d;
  878. border-color: #575d6d;
  879. }
  880. .agreement-checkbox:checked::after {
  881. content: "";
  882. position: absolute;
  883. left: 5px;
  884. top: 0;
  885. width: 5px;
  886. height: 10px;
  887. border: solid white;
  888. border-width: 0 2px 2px 0;
  889. transform: rotate(45deg);
  890. }
  891. .agreement-text {
  892. font-size: 0.372rem;
  893. font-weight: 500;
  894. color: #999;
  895. cursor: pointer;
  896. }
  897. .link {
  898. /* color: #40ac6d; */
  899. cursor: pointer;
  900. }
  901. .link:hover {
  902. text-decoration: underline;
  903. }
  904. /* Loading 遮罩 */
  905. .loading-mask {
  906. position: fixed;
  907. top: 0;
  908. left: 0;
  909. right: 0;
  910. bottom: 0;
  911. background: rgba(0, 0, 0, 0.5);
  912. display: flex;
  913. align-items: center;
  914. justify-content: center;
  915. z-index: 10000;
  916. }
  917. .loading-content {
  918. background: white;
  919. padding: 20px;
  920. border-radius: 4px;
  921. text-align: center;
  922. min-width: 120px;
  923. }
  924. .loading-spinner {
  925. width: 30px;
  926. height: 30px;
  927. border: 3px solid #f3f3f3;
  928. border-top: 3px solid #40ac6d;
  929. border-radius: 50%;
  930. animation: spin 1s linear infinite;
  931. margin: 0 auto 10px;
  932. }
  933. @keyframes spin {
  934. 0% {
  935. transform: rotate(0deg);
  936. }
  937. 100% {
  938. transform: rotate(360deg);
  939. }
  940. }
  941. .loading-text {
  942. color: #333;
  943. font-size: 14px;
  944. }
  945. /* Toast 提示 */
  946. .toast {
  947. position: fixed;
  948. top: 50%;
  949. left: 50%;
  950. transform: translate(-50%, -50%);
  951. background: rgba(0, 0, 0, 0.8);
  952. color: white;
  953. padding: 12px 20px;
  954. border-radius: 4px;
  955. font-size: 14px;
  956. z-index: 10010;
  957. max-width: 80%;
  958. text-align: center;
  959. }
  960. .toast.success {
  961. background: rgba(40, 167, 69, 0.9);
  962. }
  963. .toast.warning {
  964. background: rgba(255, 193, 7, 0.9);
  965. color: #212529;
  966. }
  967. .toast.error {
  968. background: rgba(220, 53, 69, 0.9);
  969. }
  970. /* ss-confirm 内容样式(标题沿用组件默认,文本做适配) */
  971. .warn-confirm-text {
  972. text-align: center;
  973. padding: 12px 4px;
  974. font-size: 14px;
  975. line-height: 1.6;
  976. color: #333;
  977. }
  978. /* warnMsg 的 ss-confirm 仅保留确认按钮 */
  979. .ss-confirm .confirm-btn-cancel {
  980. display: none;
  981. }
  982. .ss-confirm .confirm-btn-confirm {
  983. width: 100%;
  984. }
  985. /* 自定义确认对话框 - 优化样式 */
  986. .confirm-dialog-mask {
  987. position: fixed;
  988. top: 0;
  989. left: 0;
  990. right: 0;
  991. bottom: 0;
  992. background: rgba(0, 0, 0, 0.6);
  993. display: flex;
  994. align-items: center;
  995. justify-content: center;
  996. z-index: 10002;
  997. }
  998. .confirm-dialog {
  999. background: white;
  1000. border-radius: 4px;
  1001. min-width: 280px;
  1002. max-width: 90%;
  1003. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
  1004. overflow: hidden;
  1005. animation: confirmFadeIn 0.3s ease-out;
  1006. }
  1007. @keyframes confirmFadeIn {
  1008. from {
  1009. opacity: 0;
  1010. transform: scale(0.95) translateY(-20px);
  1011. }
  1012. to {
  1013. opacity: 1;
  1014. transform: scale(1) translateY(0);
  1015. }
  1016. }
  1017. .confirm-message {
  1018. font-size: 16px;
  1019. color: #333;
  1020. text-align: center;
  1021. margin-bottom: 24px;
  1022. line-height: 1.6;
  1023. }
  1024. .confirm-buttons {
  1025. display: flex;
  1026. gap: 10px;
  1027. }
  1028. .confirm-btn {
  1029. flex: 1;
  1030. height: 40px;
  1031. border: none;
  1032. border-radius: 4px;
  1033. font-size: 15px;
  1034. cursor: pointer;
  1035. transition: all 0.2s ease;
  1036. font-weight: 500;
  1037. }
  1038. .cancel-btn {
  1039. background: #f5f5f5;
  1040. color: #666;
  1041. border: 1px solid #e5e5e5;
  1042. }
  1043. .cancel-btn:hover {
  1044. background: #eeeeee;
  1045. border-color: #d5d5d5;
  1046. }
  1047. .confirm-btn-primary {
  1048. background: #575d6d;
  1049. color: white;
  1050. }
  1051. .confirm-btn-primary:hover {
  1052. background: #494e5b;
  1053. }
  1054. .confirm-btn:active {
  1055. transform: scale(0.98);
  1056. }
  1057. /* 底部抽屉样式 */
  1058. .drawer-mask {
  1059. position: fixed;
  1060. top: 0;
  1061. left: 0;
  1062. right: 0;
  1063. bottom: 0;
  1064. background: rgba(0, 0, 0, 0.6);
  1065. z-index: 10003;
  1066. animation: drawerMaskFadeIn 0.3s ease-out;
  1067. }
  1068. @keyframes drawerMaskFadeIn {
  1069. from {
  1070. opacity: 0;
  1071. }
  1072. to {
  1073. opacity: 1;
  1074. }
  1075. }
  1076. .drawer-content {
  1077. position: absolute;
  1078. left: 0;
  1079. right: 0;
  1080. bottom: 0;
  1081. max-height: 80vh;
  1082. background: white;
  1083. border-radius: 4px 4px 0 0;
  1084. animation: drawerSlideUp 0.3s ease-out;
  1085. display: flex;
  1086. flex-direction: column;
  1087. }
  1088. @keyframes drawerSlideUp {
  1089. from {
  1090. transform: translateY(100%);
  1091. }
  1092. to {
  1093. transform: translateY(0);
  1094. }
  1095. }
  1096. .drawer-header {
  1097. display: flex;
  1098. align-items: center;
  1099. justify-content: space-between;
  1100. padding: 16px 20px;
  1101. border-bottom: 1px solid #f0f0f0;
  1102. flex-shrink: 0;
  1103. }
  1104. .drawer-title {
  1105. font-size: 18px;
  1106. font-weight: 600;
  1107. color: #333;
  1108. }
  1109. .drawer-close {
  1110. width: 32px;
  1111. height: 32px;
  1112. display: flex;
  1113. align-items: center;
  1114. justify-content: center;
  1115. cursor: pointer;
  1116. border-radius: 4px;
  1117. transition: background 0.2s;
  1118. }
  1119. .drawer-close:hover {
  1120. background: #f5f5f5;
  1121. }
  1122. .drawer-body {
  1123. flex: 1;
  1124. overflow-y: auto;
  1125. padding: 20px;
  1126. -webkit-overflow-scrolling: touch;
  1127. }
  1128. .drawer-body h3 {
  1129. font-size: 18px;
  1130. color: #333;
  1131. margin-bottom: 16px;
  1132. }
  1133. .drawer-body h4 {
  1134. font-size: 16px;
  1135. color: #555;
  1136. margin-top: 20px;
  1137. margin-bottom: 12px;
  1138. }
  1139. .drawer-body p {
  1140. font-size: 14px;
  1141. color: #666;
  1142. line-height: 1.8;
  1143. margin-bottom: 12px;
  1144. }
  1145. /* 修改初始密码弹窗样式 */
  1146. .reset-pwd-mask {
  1147. position: fixed;
  1148. top: 0;
  1149. left: 0;
  1150. right: 0;
  1151. bottom: 0;
  1152. background: rgba(0, 0, 0, 0.7);
  1153. display: flex;
  1154. align-items: center;
  1155. justify-content: center;
  1156. z-index: 10005;
  1157. }
  1158. .reset-pwd-modal {
  1159. width: calc(100% - 20px);
  1160. position: absolute;
  1161. left: 50%;
  1162. top: 50%;
  1163. background: #fff;
  1164. border-radius: 4px;
  1165. transform: translate(-50%, -50%);
  1166. border: 1px solid #d3d4d5;
  1167. overflow: hidden;
  1168. }
  1169. .reset-pwd-header {
  1170. padding: 20px;
  1171. text-align: left;
  1172. border-bottom: 1px solid #f0f0f0;
  1173. }
  1174. .reset-pwd-title {
  1175. font-size: 18px;
  1176. font-weight: 600;
  1177. color: #333;
  1178. }
  1179. .reset-pwd-body {
  1180. padding: 20px;
  1181. }
  1182. .pwd-strength-msg {
  1183. font-size: 13px;
  1184. color: #ff6b6b;
  1185. margin-bottom: 15px;
  1186. padding: 10px;
  1187. background: #fff5f5;
  1188. border-radius: 4px;
  1189. line-height: 1.5;
  1190. }
  1191. .reset-pwd-form {
  1192. margin-bottom: 20px;
  1193. }
  1194. .reset-pwd-input-item {
  1195. position: relative;
  1196. margin-bottom: 12px;
  1197. display: flex;
  1198. }
  1199. .reset-pwd-input {
  1200. width: 100%;
  1201. height: 44px;
  1202. padding: 0 40px 0 15px;
  1203. border: 1px solid #d5d8dc;
  1204. border-radius: 4px;
  1205. font-size: 15px;
  1206. outline: none;
  1207. transition: border-color 0.2s;
  1208. }
  1209. .reset-pwd-input:focus {
  1210. border-color: #575d6d;
  1211. }
  1212. .reset-pwd-input-item .eye-icon {
  1213. position: absolute;
  1214. right: 10px;
  1215. top: 50%;
  1216. transform: translateY(-50%);
  1217. padding: 10px;
  1218. display: flex;
  1219. align-items: center;
  1220. justify-content: center;
  1221. cursor: pointer;
  1222. z-index: 10;
  1223. }
  1224. .reset-pwd-btn {
  1225. width: 100%;
  1226. height: 44px;
  1227. background: #575d6d;
  1228. color: white;
  1229. border: none;
  1230. border-radius: 4px;
  1231. font-size: 16px;
  1232. cursor: pointer;
  1233. transition: background 0.2s;
  1234. }
  1235. .reset-pwd-btn:hover {
  1236. background: #494e5b;
  1237. }
  1238. .reset-pwd-btn.disabled {
  1239. background: #cccccc;
  1240. cursor: not-allowed;
  1241. }
  1242. /* 响应式设计 */
  1243. /* @media (max-width: 480px) {
  1244. .popup-content {
  1245. padding: 15px 10px;
  1246. }
  1247. .login-input {
  1248. height: 40px;
  1249. font-size: 13px;
  1250. }
  1251. .account-btn {
  1252. height: 40px;
  1253. line-height: 40px;
  1254. font-size: 14px;
  1255. }
  1256. } */
  1257. </style>
  1258. </body>
  1259. </html>