login.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. // 登录/注册页面逻辑
  2. (function () {
  3. // DOM元素
  4. const loginOverlay = document.getElementById('loginOverlay');
  5. const loginContainer = document.getElementById('loginContainer');
  6. const closeBtn = document.getElementById('closeBtn');
  7. const tabBtns = document.querySelectorAll('.tab-btn');
  8. const loginForm = document.getElementById('loginForm');
  9. const registerForm = document.getElementById('registerForm');
  10. const typeBtns = document.querySelectorAll('.type-btn');
  11. const accountLogin = document.getElementById('accountLogin');
  12. const phoneLogin = document.getElementById('phoneLogin');
  13. // 登录表单元素
  14. const loginAccount = document.getElementById('loginAccount');
  15. const loginPassword = document.getElementById('loginPassword');
  16. const loginPhone = document.getElementById('loginPhone');
  17. const loginCode = document.getElementById('loginCode');
  18. const getLoginCodeBtn = document.getElementById('getLoginCode');
  19. const loginSubmit = document.getElementById('loginSubmit');
  20. // 注册表单元素
  21. const registerAvatar = document.getElementById('registerAvatar');
  22. const avatarPreview = document.getElementById('avatarPreview');
  23. const avatarImage = document.getElementById('avatarImage');
  24. const registerUsername = document.getElementById('registerUsername');
  25. const registerPhone = document.getElementById('registerPhone');
  26. const registerCode = document.getElementById('registerCode');
  27. const getRegisterCodeBtn = document.getElementById('getRegisterCode');
  28. const registerPassword = document.getElementById('registerPassword');
  29. const registerPasswordConfirm = document.getElementById('registerPasswordConfirm');
  30. const registerSubmit = document.getElementById('registerSubmit');
  31. // 验证码倒计时
  32. let loginCodeCountdown = 0;
  33. let registerCodeCountdown = 0;
  34. // 默认头像URL(注册时使用)
  35. let defaultAvatarUrl = null;
  36. // 初始化
  37. function init() {
  38. // 标签切换
  39. tabBtns.forEach(btn => {
  40. btn.addEventListener('click', () => {
  41. const tab = btn.getAttribute('data-tab');
  42. switchTab(tab);
  43. });
  44. });
  45. // 登录方式切换
  46. typeBtns.forEach(btn => {
  47. btn.addEventListener('click', () => {
  48. const type = btn.getAttribute('data-type');
  49. switchLoginType(type);
  50. });
  51. });
  52. // 关闭按钮
  53. closeBtn.addEventListener('click', closeLogin);
  54. loginOverlay.addEventListener('click', closeLogin);
  55. // 阻止容器内点击关闭
  56. loginContainer.addEventListener('click', (e) => {
  57. e.stopPropagation();
  58. });
  59. // 头像上传
  60. avatarPreview.addEventListener('click', () => {
  61. registerAvatar.click();
  62. });
  63. registerAvatar.addEventListener('change', handleAvatarUpload);
  64. // 获取验证码按钮
  65. getLoginCodeBtn.addEventListener('click', () => {
  66. getVerificationCode('login');
  67. });
  68. getRegisterCodeBtn.addEventListener('click', () => {
  69. getVerificationCode('register');
  70. });
  71. // 提交按钮
  72. loginSubmit.addEventListener('click', handleLogin);
  73. registerSubmit.addEventListener('click', handleRegister);
  74. // 密码验证提示
  75. registerPassword.addEventListener('input', validatePassword);
  76. registerPasswordConfirm.addEventListener('input', validatePasswordConfirm);
  77. // 手机号实时验证
  78. loginPhone.addEventListener('input', validatePhone);
  79. loginPhone.addEventListener('blur', validatePhone);
  80. registerPhone.addEventListener('input', validatePhone);
  81. registerPhone.addEventListener('blur', validatePhone);
  82. // 监听来自父窗口的消息
  83. window.addEventListener('message', handleMessage);
  84. }
  85. // 切换标签
  86. function switchTab(tab) {
  87. tabBtns.forEach(btn => {
  88. if (btn.getAttribute('data-tab') === tab) {
  89. btn.classList.add('active');
  90. } else {
  91. btn.classList.remove('active');
  92. }
  93. });
  94. if (tab === 'login') {
  95. loginForm.style.display = 'block';
  96. registerForm.style.display = 'none';
  97. } else {
  98. loginForm.style.display = 'none';
  99. registerForm.style.display = 'block';
  100. // 切换到注册标签时,加载默认头像
  101. loadDefaultAvatar();
  102. }
  103. }
  104. // 加载默认头像
  105. async function loadDefaultAvatar() {
  106. try {
  107. const response = await fetch('http://localhost:3000/api/avatars/default');
  108. if (!response.ok) {
  109. throw new Error('获取默认头像失败');
  110. }
  111. const data = await response.json();
  112. if (data.success && data.avatars && data.avatars.length > 0) {
  113. // 随机选择一个默认头像
  114. const randomIndex = Math.floor(Math.random() * data.avatars.length);
  115. const avatarFilename = data.avatars[randomIndex];
  116. defaultAvatarUrl = `http://localhost:3000/avatar/${encodeURIComponent(avatarFilename)}`;
  117. // 显示默认头像
  118. if (avatarImage && !registerAvatar.files[0]) {
  119. avatarImage.src = defaultAvatarUrl;
  120. avatarImage.style.display = 'block';
  121. avatarPreview.querySelector('.avatar-placeholder').style.display = 'none';
  122. }
  123. }
  124. } catch (error) {
  125. console.error('[Login] 加载默认头像失败:', error);
  126. }
  127. }
  128. // 切换登录方式
  129. function switchLoginType(type) {
  130. typeBtns.forEach(btn => {
  131. if (btn.getAttribute('data-type') === type) {
  132. btn.classList.add('active');
  133. } else {
  134. btn.classList.remove('active');
  135. }
  136. });
  137. if (type === 'account') {
  138. accountLogin.style.display = 'block';
  139. phoneLogin.style.display = 'none';
  140. } else {
  141. accountLogin.style.display = 'none';
  142. phoneLogin.style.display = 'block';
  143. }
  144. }
  145. // 处理头像上传
  146. function handleAvatarUpload(e) {
  147. const file = e.target.files[0];
  148. if (!file) return;
  149. // 验证文件类型
  150. if (!file.type.startsWith('image/')) {
  151. showError('请上传图片文件');
  152. return;
  153. }
  154. // 验证文件大小(最大5MB)
  155. if (file.size > 5 * 1024 * 1024) {
  156. showError('图片大小不能超过5MB');
  157. return;
  158. }
  159. // 预览图片
  160. const reader = new FileReader();
  161. reader.onload = (e) => {
  162. avatarImage.src = e.target.result;
  163. avatarImage.style.display = 'block';
  164. avatarPreview.querySelector('.avatar-placeholder').style.display = 'none';
  165. };
  166. reader.readAsDataURL(file);
  167. }
  168. // 获取验证码
  169. function getVerificationCode(type) {
  170. const phoneInput = type === 'login' ? loginPhone : registerPhone;
  171. const phone = phoneInput.value.trim();
  172. // 验证手机号
  173. if (!phone) {
  174. showError('请输入手机号');
  175. return;
  176. }
  177. // 验证手机号格式:1开头,第二位3-9,总共11位
  178. const phoneRegex = /^1[3-9]\d{9}$/;
  179. if (!phoneRegex.test(phone)) {
  180. if (phone.length !== 11) {
  181. showError('手机号必须是11位数字');
  182. } else if (!phone.startsWith('1')) {
  183. showError('手机号必须以1开头');
  184. } else if (!/^1[3-9]/.test(phone)) {
  185. showError('手机号第二位必须是3-9');
  186. } else {
  187. showError('请输入正确的手机号格式');
  188. }
  189. return;
  190. }
  191. // 先检查手机号是否存在(登录时检查是否存在,注册时检查是否已存在)
  192. httpRequest('http://localhost:3000/api/check-phone', {
  193. method: 'POST',
  194. headers: {
  195. 'Content-Type': 'application/json'
  196. },
  197. body: JSON.stringify({
  198. phone: phone,
  199. type: type
  200. })
  201. }, (error, response, result) => {
  202. if (error) {
  203. showError('网络错误,请稍后重试');
  204. return;
  205. }
  206. if (!response) {
  207. showError('检查失败,请稍后重试');
  208. return;
  209. }
  210. // 如果检查失败,显示错误信息
  211. if (result.error) {
  212. if (response.status === 404 && type === 'login') {
  213. showError('手机未注册');
  214. } else if (response.status === 409 && type === 'register') {
  215. showError('手机号已被注册');
  216. } else {
  217. showError(result.message || '检查失败');
  218. }
  219. return;
  220. }
  221. // 如果检查失败(手机号不符合要求)
  222. if (!result.success) {
  223. showError(result.message || '检查失败');
  224. return;
  225. }
  226. // 检查通过,开始倒计时并发送验证码
  227. const codeBtn = type === 'login' ? getLoginCodeBtn : getRegisterCodeBtn;
  228. let countdown = 50;
  229. if (type === 'login') {
  230. loginCodeCountdown = countdown;
  231. } else {
  232. registerCodeCountdown = countdown;
  233. }
  234. codeBtn.disabled = true;
  235. codeBtn.classList.add('countdown');
  236. codeBtn.textContent = `${countdown}秒后重试`;
  237. const timer = setInterval(() => {
  238. countdown--;
  239. codeBtn.textContent = `${countdown}秒后重试`;
  240. if (countdown <= 0) {
  241. clearInterval(timer);
  242. codeBtn.disabled = false;
  243. codeBtn.classList.remove('countdown');
  244. codeBtn.textContent = '获取验证码';
  245. if (type === 'login') {
  246. loginCodeCountdown = 0;
  247. } else {
  248. registerCodeCountdown = 0;
  249. }
  250. } else {
  251. if (type === 'login') {
  252. loginCodeCountdown = countdown;
  253. } else {
  254. registerCodeCountdown = countdown;
  255. }
  256. }
  257. }, 1000);
  258. // 验证码功能(固定验证码 9527,仅开发者可见)
  259. // TODO: 后续实现真实的验证码发送
  260. console.log('[验证码] 9527 (仅开发者可见)');
  261. });
  262. }
  263. // 验证密码格式
  264. function validatePassword() {
  265. const password = registerPassword.value;
  266. const hint = registerPassword.parentElement.querySelector('.hint-text');
  267. if (!password) {
  268. hint.style.color = '#6b7280';
  269. return false;
  270. }
  271. // 密码要求:8-20位,包含大小写字母和数字
  272. const hasLength = password.length >= 8 && password.length <= 20;
  273. const hasUpper = /[A-Z]/.test(password);
  274. const hasLower = /[a-z]/.test(password);
  275. const hasNumber = /\d/.test(password);
  276. if (hasLength && hasUpper && hasLower && hasNumber) {
  277. hint.style.color = '#10b981';
  278. hint.textContent = '✓ 密码格式正确';
  279. return true;
  280. } else {
  281. hint.style.color = '#ef4444';
  282. const issues = [];
  283. if (!hasLength) issues.push('长度8-20位');
  284. if (!hasUpper) issues.push('大写字母');
  285. if (!hasLower) issues.push('小写字母');
  286. if (!hasNumber) issues.push('数字');
  287. hint.textContent = `缺少:${issues.join('、')}`;
  288. return false;
  289. }
  290. }
  291. // 验证确认密码
  292. function validatePasswordConfirm() {
  293. const password = registerPassword.value;
  294. const passwordConfirm = registerPasswordConfirm.value;
  295. const input = registerPasswordConfirm;
  296. if (!passwordConfirm) {
  297. input.style.borderColor = '#e5e7eb';
  298. return false;
  299. }
  300. if (password === passwordConfirm) {
  301. input.style.borderColor = '#10b981';
  302. return true;
  303. } else {
  304. input.style.borderColor = '#ef4444';
  305. return false;
  306. }
  307. }
  308. // 处理登录
  309. function handleLogin() {
  310. const isAccountLogin = accountLogin.style.display !== 'none';
  311. if (isAccountLogin) {
  312. // 账号密码登录
  313. const account = loginAccount.value.trim();
  314. const password = loginPassword.value;
  315. if (!account) {
  316. showError('请输入账号');
  317. return;
  318. }
  319. if (!password) {
  320. showError('请输入密码');
  321. return;
  322. }
  323. // 发送登录请求到服务器
  324. const formData = new FormData();
  325. formData.append('loginType', 'account');
  326. formData.append('account', account);
  327. formData.append('password', password);
  328. httpRequest('http://localhost:3000/api/login', {
  329. method: 'POST',
  330. body: formData
  331. }, (error, response, result) => {
  332. if (error) {
  333. // 网络错误通过 alert-view 显示
  334. showError('网络错误,请稍后重试');
  335. return;
  336. }
  337. if (!response) {
  338. showError('登录失败,请稍后重试');
  339. return;
  340. }
  341. // 如果解析JSON失败,根据状态码显示错误
  342. if (result.error) {
  343. if (response.status === 401) {
  344. showError('账号或密码错误');
  345. } else if (response.status === 400) {
  346. showError('请求参数错误');
  347. } else if (response.status >= 500) {
  348. showError('服务器错误,请稍后重试');
  349. } else {
  350. showError('登录失败,请稍后重试');
  351. }
  352. return;
  353. }
  354. if (response.ok && result.success) {
  355. // 保存登录信息到 localStorage(有效期2小时)
  356. if (result.user) {
  357. const loginData = {
  358. user: result.user,
  359. expireTime: Date.now() + 2 * 60 * 60 * 1000 // 2小时后过期
  360. };
  361. try {
  362. localStorage.setItem('loginData', JSON.stringify(loginData));
  363. console.log('[Login] 登录信息已保存到 localStorage,有效期2小时');
  364. } catch (error) {
  365. console.warn('[Login] 保存登录信息失败:', error);
  366. }
  367. }
  368. // 显示成功提示(在登录 iframe 中)
  369. if (window.HintView) {
  370. window.HintView.success('登录成功', 2000);
  371. } else {
  372. showSuccess('登录成功');
  373. }
  374. // 通知父窗口登录成功,更新导航栏
  375. if (window.parent !== window && result.user) {
  376. window.parent.postMessage({
  377. type: 'login-success',
  378. user: result.user
  379. }, '*');
  380. }
  381. // 延迟关闭
  382. setTimeout(() => {
  383. closeLogin();
  384. }, 2000);
  385. } else {
  386. // 处理各种错误状态,使用 alert-view 显示错误
  387. // 优先使用服务器返回的错误消息(区分账号不存在和密码错误)
  388. const errorMessage = result.message ||
  389. (response.status === 401 ? '账号或密码错误' :
  390. response.status === 400 ? '请求参数错误' :
  391. response.status >= 500 ? '服务器错误,请稍后重试' :
  392. '登录失败');
  393. // 使用 alert-view 显示错误提示
  394. showError(errorMessage);
  395. }
  396. });
  397. } else {
  398. // 手机验证码登录
  399. const phone = loginPhone.value.trim();
  400. const code = loginCode.value.trim();
  401. if (!phone) {
  402. showError('请输入手机号');
  403. return;
  404. }
  405. // 验证手机号格式:1开头,第二位3-9,总共11位
  406. const phoneRegex = /^1[3-9]\d{9}$/;
  407. if (!phoneRegex.test(phone)) {
  408. if (phone.length !== 11) {
  409. showError('手机号必须是11位数字');
  410. } else if (!phone.startsWith('1')) {
  411. showError('手机号必须以1开头');
  412. } else if (!/^1[3-9]/.test(phone)) {
  413. showError('手机号第二位必须是3-9');
  414. } else {
  415. showError('请输入正确的手机号格式');
  416. }
  417. return;
  418. }
  419. if (!code) {
  420. showError('请输入验证码');
  421. return;
  422. }
  423. // 验证码固定为 9527
  424. if (code !== '9527') {
  425. showError('验证码错误');
  426. return;
  427. }
  428. // 发送登录请求到服务器
  429. const formData = new FormData();
  430. formData.append('loginType', 'phone');
  431. formData.append('phone', phone);
  432. formData.append('code', code);
  433. httpRequest('http://localhost:3000/api/login', {
  434. method: 'POST',
  435. body: formData
  436. }, (error, response, result) => {
  437. if (error) {
  438. // 网络错误通过 alert-view 显示
  439. showError('网络错误,请稍后重试');
  440. return;
  441. }
  442. if (!response) {
  443. showError('登录失败,请稍后重试');
  444. return;
  445. }
  446. // 如果解析JSON失败,根据状态码显示错误
  447. if (result.error) {
  448. if (response.status === 401) {
  449. showError('手机未注册');
  450. } else if (response.status === 400) {
  451. showError('请求参数错误');
  452. } else if (response.status >= 500) {
  453. showError('服务器错误,请稍后重试');
  454. } else {
  455. showError('登录失败,请稍后重试');
  456. }
  457. return;
  458. }
  459. if (response.ok && result.success) {
  460. // 保存登录信息到 localStorage(有效期2小时)
  461. if (result.user) {
  462. const loginData = {
  463. user: result.user,
  464. expireTime: Date.now() + 2 * 60 * 60 * 1000 // 2小时后过期
  465. };
  466. try {
  467. localStorage.setItem('loginData', JSON.stringify(loginData));
  468. console.log('[Login] 登录信息已保存到 localStorage,有效期2小时');
  469. } catch (error) {
  470. console.warn('[Login] 保存登录信息失败:', error);
  471. }
  472. }
  473. // 显示成功提示(在登录 iframe 中)
  474. if (window.HintView) {
  475. window.HintView.success('登录成功', 2000);
  476. } else {
  477. showSuccess('登录成功');
  478. }
  479. // 通知父窗口登录成功,更新导航栏
  480. if (window.parent !== window && result.user) {
  481. window.parent.postMessage({
  482. type: 'login-success',
  483. user: result.user
  484. }, '*');
  485. }
  486. // 延迟关闭
  487. setTimeout(() => {
  488. closeLogin();
  489. }, 2000);
  490. } else {
  491. // 处理各种错误状态
  492. if (response.status === 401) {
  493. // 手机验证码登录时,401 表示手机未注册
  494. showError(result.message || '手机未注册');
  495. } else if (response.status === 400) {
  496. // 400 可能是验证码错误或其他参数错误
  497. showError(result.message || '请求参数错误');
  498. } else if (response.status >= 500) {
  499. showError(result.message || '服务器错误,请稍后重试');
  500. } else {
  501. showError(result.message || '登录失败');
  502. }
  503. }
  504. });
  505. }
  506. }
  507. // 处理注册
  508. function handleRegister() {
  509. const username = registerUsername.value.trim();
  510. const phone = registerPhone.value.trim();
  511. const code = registerCode.value.trim();
  512. const password = registerPassword.value;
  513. const passwordConfirm = registerPasswordConfirm.value;
  514. const avatarFile = registerAvatar.files[0];
  515. // 注意:头像不再是必填,如果没有上传则使用默认头像
  516. // 验证用户名(现代网站标准)
  517. if (!username) {
  518. showError('请输入用户名');
  519. return;
  520. }
  521. // 长度:4-20个字符
  522. if (username.length < 4 || username.length > 20) {
  523. showError('用户名长度为4-20个字符');
  524. return;
  525. }
  526. // 只能包含字母、数字、下划线、连字符,且必须以字母开头
  527. if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(username)) {
  528. showError('用户名只能包含字母、数字、下划线和连字符,且必须以字母开头');
  529. return;
  530. }
  531. // 不能全部是数字
  532. if (/^\d+$/.test(username)) {
  533. showError('用户名不能全部是数字');
  534. return;
  535. }
  536. // 验证手机号
  537. if (!phone) {
  538. showError('请输入手机号');
  539. return;
  540. }
  541. // 验证手机号格式:1开头,第二位3-9,总共11位
  542. const phoneRegex = /^1[3-9]\d{9}$/;
  543. if (!phoneRegex.test(phone)) {
  544. if (phone.length !== 11) {
  545. showError('手机号必须是11位数字');
  546. } else if (!phone.startsWith('1')) {
  547. showError('手机号必须以1开头');
  548. } else if (!/^1[3-9]/.test(phone)) {
  549. showError('手机号第二位必须是3-9');
  550. } else {
  551. showError('请输入正确的手机号格式');
  552. }
  553. return;
  554. }
  555. // 验证验证码(固定验证码 9527)
  556. if (!code) {
  557. showError('请输入验证码');
  558. return;
  559. }
  560. if (code !== '9527') {
  561. showError('验证码错误');
  562. return;
  563. }
  564. // 验证密码
  565. if (!validatePassword()) {
  566. showError('密码格式不正确');
  567. return;
  568. }
  569. if (!validatePasswordConfirm()) {
  570. showError('两次输入的密码不一致');
  571. return;
  572. }
  573. // 发送注册请求到服务器
  574. const formData = new FormData();
  575. formData.append('username', username);
  576. formData.append('phone', phone);
  577. formData.append('code', code);
  578. formData.append('password', password);
  579. formData.append('passwordConfirm', passwordConfirm);
  580. // 添加头像文件(必填,已在前面验证)
  581. formData.append('avatar', avatarFile);
  582. httpRequest('http://localhost:3000/api/register', {
  583. method: 'POST',
  584. body: formData
  585. }, (error, response, result) => {
  586. if (error) {
  587. // 网络错误通过 alert-view 显示
  588. showError('网络错误,请稍后重试');
  589. return;
  590. }
  591. if (!response) {
  592. showError('注册失败,请稍后重试');
  593. return;
  594. }
  595. // 如果解析JSON失败
  596. if (result.error) {
  597. showError('注册失败,请稍后重试');
  598. return;
  599. }
  600. if (result.success) {
  601. // 显示成功提示
  602. if (window.HintView) {
  603. window.HintView.success('注册成功', 2000);
  604. } else {
  605. showSuccess('注册成功');
  606. }
  607. // 延迟关闭并切换到登录
  608. setTimeout(() => {
  609. switchTab('login');
  610. clearRegisterForm();
  611. }, 2000);
  612. } else {
  613. showError(result.message || '注册失败');
  614. }
  615. });
  616. }
  617. // 清空注册表单
  618. function clearRegisterForm() {
  619. registerUsername.value = '';
  620. registerPhone.value = '';
  621. registerCode.value = '';
  622. registerPassword.value = '';
  623. registerPasswordConfirm.value = '';
  624. registerAvatar.value = '';
  625. avatarImage.src = '';
  626. avatarImage.style.display = 'none';
  627. avatarPreview.querySelector('.avatar-placeholder').style.display = 'block';
  628. registerPasswordConfirm.style.borderColor = '#e5e7eb';
  629. const hint = registerPassword.parentElement.querySelector('.hint-text');
  630. hint.style.color = '#6b7280';
  631. hint.textContent = '密码要求:8-20位,包含大小写字母和数字';
  632. // 重新加载默认头像
  633. loadDefaultAvatar();
  634. }
  635. // 验证手机号格式
  636. function validatePhone(event) {
  637. const phoneInput = event.target;
  638. const phone = phoneInput.value.trim();
  639. // 清除之前的错误样式
  640. phoneInput.style.borderColor = '';
  641. // 如果为空,不验证
  642. if (!phone) {
  643. phoneInput.removeAttribute('title');
  644. return;
  645. }
  646. // 验证手机号格式:1开头,第二位3-9,总共11位
  647. const phoneRegex = /^1[3-9]\d{9}$/;
  648. if (!phoneRegex.test(phone)) {
  649. // 设置错误样式
  650. phoneInput.style.borderColor = '#ef4444';
  651. // 根据错误类型显示提示
  652. if (phone.length !== 11) {
  653. phoneInput.setAttribute('title', '手机号必须是11位数字');
  654. } else if (!phone.startsWith('1')) {
  655. phoneInput.setAttribute('title', '手机号必须以1开头');
  656. } else if (!/^1[3-9]/.test(phone)) {
  657. phoneInput.setAttribute('title', '手机号第二位必须是3-9');
  658. } else {
  659. phoneInput.setAttribute('title', '请输入正确的手机号格式');
  660. }
  661. } else {
  662. // 验证通过,清除提示
  663. phoneInput.removeAttribute('title');
  664. phoneInput.style.borderColor = '#10b981';
  665. }
  666. }
  667. // HTTP 请求封装(使用回调函数)
  668. function httpRequest(url, options, callback) {
  669. fetch(url, options)
  670. .then(response => {
  671. response.text()
  672. .then(text => {
  673. let result = {};
  674. if (text) {
  675. try {
  676. result = JSON.parse(text);
  677. } catch (e) {
  678. // JSON 解析失败,返回原始文本
  679. result = { error: text };
  680. }
  681. }
  682. callback(null, response, result);
  683. })
  684. .catch(error => {
  685. callback(error, response, null);
  686. });
  687. })
  688. .catch(error => {
  689. callback(error, null, null);
  690. });
  691. }
  692. // 显示错误信息(直接在 login.html 的 UI 中显示)
  693. function showError(message) {
  694. if (!message) {
  695. message = '操作失败,请稍后重试';
  696. }
  697. // 根据当前显示的表单,选择对应的错误提示元素
  698. const isLoginForm = loginForm.style.display !== 'none';
  699. const errorElement = isLoginForm ? document.getElementById('loginError') : document.getElementById('registerError');
  700. if (errorElement) {
  701. errorElement.textContent = message;
  702. errorElement.classList.add('show');
  703. // 3秒后自动隐藏
  704. setTimeout(() => {
  705. errorElement.classList.remove('show');
  706. }, 3000);
  707. } else {
  708. // 降级处理
  709. alert(message);
  710. }
  711. }
  712. // 显示成功信息(通过 postMessage 发送消息给 alert-view.js)
  713. function showSuccess(message) {
  714. if (!message) {
  715. message = '操作成功';
  716. }
  717. try {
  718. // 通过 postMessage 发送消息给父窗口的 alert-view
  719. const targetWindow = window.parent && window.parent !== window ? window.parent : window;
  720. targetWindow.postMessage({
  721. type: 'alert-view',
  722. message: message,
  723. messageType: 'success',
  724. duration: 2000
  725. }, '*');
  726. } catch (error) {
  727. // 静默处理错误
  728. }
  729. }
  730. // 关闭登录窗口
  731. function closeLogin() {
  732. if (loginOverlay) {
  733. loginOverlay.style.display = 'none';
  734. }
  735. if (loginContainer) {
  736. loginContainer.style.display = 'none';
  737. }
  738. if (window.parent !== window) {
  739. window.parent.postMessage({
  740. type: 'close-login-view'
  741. }, '*');
  742. }
  743. }
  744. // 处理来自父窗口的消息
  745. function handleMessage(event) {
  746. if (event.origin !== window.location.origin) {
  747. return;
  748. }
  749. const { data } = event;
  750. if (data && data.type === 'open-login-view') {
  751. // console.log('[5-Login] 收到open-login-view消息');
  752. const mode = data.mode || 'login';
  753. switchTab(mode);
  754. if (loginOverlay) {
  755. loginOverlay.style.display = 'block';
  756. }
  757. if (loginContainer) {
  758. loginContainer.style.display = 'block';
  759. }
  760. // console.log('[6-Login] overlay和container已显示');
  761. }
  762. }
  763. // 页面加载完成后初始化
  764. window.addEventListener('DOMContentLoaded', function() {
  765. init();
  766. });
  767. })();