page3.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. <template>
  2. <view class="content">
  3. <!-- 顶部标题区域 -->
  4. <view class="header-section">
  5. <view class="app-logo">🪶🪶</view>
  6. <view class="app-name">两只羽毛</view>
  7. <view class="app-slogan">真诚相遇 · 携手一生</view>
  8. </view>
  9. <!-- 登录框 -->
  10. <!-- <view class="login-box"> -->
  11. <!-- 切换标签 -->
  12. <!-- <view class="login-tabs"> -->
  13. <!-- <view class="tab-item" :class="{active: loginMode === 'phone'}" @click="loginMode = 'phone'">
  14. <text class="tab-icon">📱</text>
  15. <text>验证码登录</text>
  16. </view>
  17. <view class="tab-item" :class="{active: loginMode === 'password'}" @click="loginMode = 'password'">
  18. <text class="tab-icon">🔐</text>
  19. <text>密码登录</text>
  20. </view> -->
  21. <!-- </view> -->
  22. <!-- 手机号验证码登录 -->
  23. <view v-if="loginMode === 'phone'">
  24. <!-- <view class="login-title">
  25. <text class="title-icon">💌</text>
  26. <text>快速登录</text>
  27. </view>
  28. <view class="form-group">
  29. <input class="input" type="number" maxlength="11" v-model="phoneForm.phone" placeholder="请输入手机号" />
  30. </view>
  31. <view class="form-group code-group">
  32. <input class="input code-input" type="number" maxlength="6" v-model="phoneForm.code" placeholder="请输入验证码" />
  33. <view class="code-btn" :class="{disabled: countdown > 0}" @click="sendCode">
  34. {{ countdown > 0 ? `${countdown}秒` : '发送验证码' }}
  35. </view>
  36. </view>
  37. <button class="login-btn" @click="handlePhoneLogin">登录</button> -->
  38. <!-- 微信一键登录 -->
  39. <view class="divider">
  40. <view class="line"></view>
  41. <text class="text">其他方式登录</text>
  42. <view class="line"></view>
  43. </view>
  44. <button class="wechat-login-btn" @click="login_zheshow">
  45. <text class="wechat-icon">💬</text>
  46. <text>微信一键登录</text>
  47. </button>
  48. </view>
  49. <!-- 账号密码登录 -->
  50. <view v-if="loginMode === 'password'">
  51. <!-- <view class="login-title">
  52. <text class="title-icon">💝</text>
  53. <text>账号登录</text>
  54. </view>
  55. <view class="form-group">
  56. <input class="input" v-model="passwordForm.username" placeholder="请输入账号或手机号" />
  57. </view>
  58. <view class="form-group password-group">
  59. <input class="input" :type="showPassword ? 'text' : 'password'" v-model="passwordForm.password" placeholder="请输入密码" />
  60. <view class="eye-icon" @click="showPassword = !showPassword">
  61. {{ showPassword ? '👁️' : '👁️‍🗨️' }}
  62. </view>
  63. </view>
  64. <view class="remember-row">
  65. <view class="remember-check" @click="rememberMe = !rememberMe">
  66. <image :src="rememberMe ? '/static/4checked.png' : '/static/4unchecked.png'" class="checkbox-icon"></image>
  67. <text>记住密码</text>
  68. </view>
  69. </view>
  70. <button class="login-btn" @click="handlePasswordLogin">登录</button> -->
  71. <!-- 微信一键登录 -->
  72. <!-- <view class="divider">
  73. <view class="line"></view>
  74. <text class="text">其他方式登录</text>
  75. <view class="line"></view>
  76. </view> -->
  77. <button class="wechat-login-btn" @click="login_zheshow">
  78. <text class="wechat-icon">💬</text>
  79. <text>微信一键登录</text>
  80. </button>
  81. <!-- </view> -->
  82. </view>
  83. <!-- 底部提示 -->
  84. <view class="footer-tip">
  85. <text class="tip-icon">💗</text>
  86. <text class="tip-text">遇见对的人,不再孤单</text>
  87. </view>
  88. <btnlogin :zheshow='zheshow' />
  89. </view>
  90. </template>
  91. <script>
  92. import btnlogin from '@/components/butlogin';
  93. import api from '@/utils/api.js'
  94. import userAuth from '@/utils/userAuth.js'
  95. export default {
  96. components:{
  97. btnlogin
  98. },
  99. data() {
  100. return {
  101. zheshow: false, // 控制弹窗显示隐藏
  102. loginMode: 'phone', // 登录模式:phone-验证码登录,password-密码登录
  103. redirectUrl: '', // 登录成功后跳转地址(可选)
  104. // 手机号验证码登录
  105. phoneForm: {
  106. phone: '',
  107. code: ''
  108. },
  109. countdown: 0, // 倒计时
  110. countdownTimer: null,
  111. // 账号密码登录
  112. passwordForm: {
  113. username: '',
  114. password: ''
  115. },
  116. showPassword: false, // 是否显示密码
  117. rememberMe: false // 记住密码
  118. }
  119. },
  120. onLoad(options) {
  121. // 加载记住的密码
  122. this.loadRememberedPassword();
  123. // 处理跳转参数
  124. this.redirectUrl = options && options.redirect ? decodeURIComponent(options.redirect) : ''
  125. },
  126. onUnload() {
  127. // 清除倒计时
  128. if (this.countdownTimer) {
  129. clearInterval(this.countdownTimer);
  130. }
  131. },
  132. methods: {
  133. login_zheshow(){
  134. this.zheshow = !this.zheshow
  135. },
  136. loset(Logon_Credentials){
  137. console.log(Logon_Credentials,'登录信息')
  138. // 获取微信登录code
  139. uni.login({
  140. provider: 'weixin',
  141. success: async (loginRes) => {
  142. const loginParams = {
  143. code: loginRes.code.trim(), // 必需:微信登录code
  144. nickname: Logon_Credentials.nickname?.trim() || '', // 可选:用户昵称(从Logon_Credentials获取)
  145. avatarUrl: Logon_Credentials.active?.trim() || '', // 可选:用户头像(注意:后端字段是avatarUrl,前端若存的是active,需对应)
  146. phoneCode: Logon_Credentials.code?.trim() || '' // 可选:手机号授权code(从Logon_Credentials获取)
  147. };
  148. uni.showLoading({ title: '登录中...' })
  149. try {
  150. // 1. 微信授权登录
  151. const wechatLoginResult = await api.auth.wechatLogin(loginParams)
  152. const token = wechatLoginResult?.token || wechatLoginResult?.data?.token
  153. const user = wechatLoginResult?.user || wechatLoginResult?.data?.user
  154. if (!token || !user) {
  155. throw new Error('微信登录返回数据异常')
  156. }
  157. console.log('✅ 微信登录成功,用户ID:', user.userId)
  158. uni.setStorageSync("userId", user.userId);
  159. // 2. 获取手机号
  160. const phoneCode = (Logon_Credentials && Logon_Credentials.code && String(Logon_Credentials.code).trim()) ? String(Logon_Credentials.code).trim() : 'mock_dev_code'
  161. console.log('📱 使用phoneCode:', phoneCode)
  162. try {
  163. const phoneResult = await api.auth.wechatPhone(phoneCode)
  164. const phone = phoneResult?.phone || phoneResult?.data?.phone
  165. if (phone) {
  166. console.log('✅ 成功获取手机号')
  167. user.phone = phone
  168. } else {
  169. console.warn('⚠️ 手机号为空,使用默认值(开发环境)')
  170. // 开发环境:如果后端返回了mock手机号,也可能在user对象中
  171. user.phone = user.phone || '13800138000'
  172. }
  173. } catch (phoneError) {
  174. console.error('❌ 获取手机号接口失败:', phoneError)
  175. // 开发环境容错:继续登录,使用默认手机号
  176. user.phone = user.phone || '13800138000'
  177. console.warn('⚠️ 使用默认手机号继续登录(开发环境)')
  178. }
  179. // 3. 更新用户信息(昵称和头像)
  180. user.nickname = Logon_Credentials.nickname || user.nickname
  181. user.avatar = Logon_Credentials.active || user.avatar
  182. console.log('💾 保存用户信息:', { userId: user.userId, phone: user.phone, nickname: user.nickname })
  183. // 4. 保存登录信息
  184. userAuth.saveLoginInfo(token, user)
  185. uni.hideLoading()
  186. uni.showToast({ title: '登录成功', icon: 'success' })
  187. // 5. 跳转首页
  188. setTimeout(() => {
  189. const target = this.redirectUrl || '/pages/index/index'
  190. uni.reLaunch({ url: target })
  191. }, 500)
  192. } catch (error) {
  193. console.error('❌ 微信登录失败:', error)
  194. uni.hideLoading()
  195. uni.showToast({
  196. title: error?.message || '登录失败,请重试',
  197. icon: 'none',
  198. duration: 2000
  199. })
  200. }
  201. },
  202. fail: (err) => {
  203. console.error('获取微信code失败:', err)
  204. uni.showToast({ title: '微信登录失败', icon: 'none' })
  205. }
  206. })
  207. },
  208. // 发送验证码
  209. async sendCode() {
  210. if (this.countdown > 0) return;
  211. // 验证手机号
  212. if (!this.phoneForm.phone) {
  213. uni.showToast({ title: '请输入手机号', icon: 'none' });
  214. return;
  215. }
  216. if (!/^1[3-9]\d{9}$/.test(this.phoneForm.phone)) {
  217. uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
  218. return;
  219. }
  220. try {
  221. uni.showLoading({ title: '发送中...' })
  222. await api.auth.sendCode(this.phoneForm.phone)
  223. uni.showToast({ title: '验证码已发送', icon: 'success' });
  224. // 开始倒计时
  225. this.countdown = 60;
  226. this.countdownTimer = setInterval(() => {
  227. this.countdown--;
  228. if (this.countdown <= 0) {
  229. clearInterval(this.countdownTimer);
  230. }
  231. }, 1000);
  232. } catch (err) {
  233. console.error('发送验证码失败:', err)
  234. uni.showToast({ title: err?.message || '发送失败', icon: 'none' })
  235. } finally {
  236. uni.hideLoading()
  237. }
  238. },
  239. // 手机号验证码登录
  240. async handlePhoneLogin() {
  241. if (!this.phoneForm.phone) {
  242. uni.showToast({ title: '请输入手机号', icon: 'none' });
  243. return;
  244. }
  245. if (!this.phoneForm.code) {
  246. uni.showToast({ title: '请输入验证码', icon: 'none' });
  247. return;
  248. }
  249. uni.showLoading({ title: '登录中...' });
  250. try {
  251. const result = await api.auth.smsLogin(this.phoneForm.phone, this.phoneForm.code)
  252. const token = result?.token || result?.data?.token
  253. const user = result?.user || result?.data?.user
  254. if (!token || !user) {
  255. throw new Error('登录返回数据异常')
  256. }
  257. // 保存登录信息
  258. userAuth.saveLoginInfo(token, user)
  259. uni.showToast({ title: '登录成功', icon: 'success' })
  260. // 跳转首页
  261. const target = this.redirectUrl || '/pages/index/index'
  262. uni.reLaunch({ url: target })
  263. // 清空表单
  264. this.phoneForm = { phone: '', code: '' }
  265. } catch (error) {
  266. console.error('验证码登录失败:', error)
  267. uni.showToast({ title: error?.message || '登录失败', icon: 'none' })
  268. } finally {
  269. uni.hideLoading()
  270. }
  271. },
  272. // 账号密码登录
  273. async handlePasswordLogin() {
  274. if (!this.passwordForm.username) {
  275. uni.showToast({ title: '请输入账号', icon: 'none' });
  276. return;
  277. }
  278. if (!this.passwordForm.password) {
  279. uni.showToast({ title: '请输入密码', icon: 'none' });
  280. return;
  281. }
  282. // 调用后端API进行登录
  283. uni.showLoading({ title: '登录中...' });
  284. try {
  285. const result = await api.auth.loginByPassword(this.passwordForm.username, this.passwordForm.password)
  286. // 兼容不同返回结构
  287. const token = result?.token || result?.data?.token
  288. const user = result?.user || result?.data?.user
  289. if (!token || !user) {
  290. throw new Error('登录返回数据异常')
  291. }
  292. // 使用工具类保存登录信息 - 确保userId正确处理
  293. console.log('登录成功,用户信息:', user)
  294. console.log('用户ID类型和值:', typeof user.userId, user.userId)
  295. // 确保userId是数字类型
  296. if (user.userId) {
  297. user.userId = parseInt(user.userId)
  298. console.log('处理后的用户ID:', user.userId)
  299. }
  300. userAuth.saveLoginInfo(token, user)
  301. // 是否记住密码
  302. if (this.rememberMe) {
  303. uni.setStorageSync('rememberedAccount', {
  304. username: this.passwordForm.username,
  305. password: this.passwordForm.password
  306. })
  307. } else {
  308. uni.removeStorageSync('rememberedAccount')
  309. }
  310. uni.showToast({ title: '登录成功', icon: 'success' })
  311. // 跳转页面(统一使用 reLaunch 进入首页或指定页面)
  312. const target = this.redirectUrl || '/pages/index/index'
  313. uni.reLaunch({ url: target })
  314. // 清空表单
  315. this.passwordForm = { username: '', password: '' }
  316. } catch (error) {
  317. console.error('登录失败:', error)
  318. uni.showToast({ title: error?.message || '登录失败', icon: 'none' })
  319. } finally {
  320. uni.hideLoading()
  321. }
  322. },
  323. // 加载记住的密码
  324. loadRememberedPassword() {
  325. const remembered = uni.getStorageSync('rememberedAccount');
  326. if (remembered) {
  327. this.passwordForm.username = remembered.username;
  328. this.passwordForm.password = remembered.password;
  329. this.rememberMe = true;
  330. }
  331. }
  332. }
  333. }
  334. </script>
  335. <style lang="scss" scoped>
  336. .content {
  337. padding: 40rpx;
  338. background: linear-gradient(135deg, #ffeef8 0%, #fff5f7 50%, #ffe9f0 100%);
  339. min-height: 100vh;
  340. position: relative;
  341. // 添加装饰性元素
  342. &::before {
  343. content: '❤️';
  344. position: absolute;
  345. top: 100rpx;
  346. right: 60rpx;
  347. font-size: 60rpx;
  348. opacity: 0.2;
  349. animation: heartbeat 2s infinite;
  350. }
  351. &::after {
  352. content: '💕';
  353. position: absolute;
  354. bottom: 200rpx;
  355. left: 40rpx;
  356. font-size: 50rpx;
  357. opacity: 0.2;
  358. animation: heartbeat 2s infinite 0.5s;
  359. }
  360. }
  361. @keyframes heartbeat {
  362. 0%, 100% {
  363. transform: scale(1);
  364. }
  365. 50% {
  366. transform: scale(1.1);
  367. }
  368. }
  369. .header-section {
  370. text-align: center;
  371. margin-bottom: 60rpx;
  372. .app-logo {
  373. font-size: 120rpx;
  374. margin-bottom: 20rpx;
  375. animation: heartbeat 2s infinite;
  376. }
  377. .app-name {
  378. font-size: 48rpx;
  379. font-weight: bold;
  380. background: linear-gradient(135deg, #ff6b9d 0%, #ff8fab 100%);
  381. -webkit-background-clip: text;
  382. -webkit-text-fill-color: transparent;
  383. margin-bottom: 15rpx;
  384. }
  385. .app-slogan {
  386. font-size: 26rpx;
  387. color: #ff6b9d;
  388. opacity: 0.8;
  389. }
  390. }
  391. .login-box {
  392. background: #FFFFFF;
  393. border-radius: 30rpx;
  394. padding: 50rpx 40rpx;
  395. margin-bottom: 30rpx;
  396. box-shadow: 0 8rpx 24rpx rgba(255, 107, 157, 0.15);
  397. border: 2rpx solid rgba(255, 107, 157, 0.1);
  398. .login-tabs {
  399. display: flex;
  400. background: linear-gradient(135deg, #fff0f6 0%, #ffe9f0 100%);
  401. border-radius: 50rpx;
  402. padding: 6rpx;
  403. margin-bottom: 40rpx;
  404. .tab-item {
  405. flex: 1;
  406. height: 70rpx;
  407. display: flex;
  408. align-items: center;
  409. justify-content: center;
  410. font-size: 26rpx;
  411. color: #ff6b9d;
  412. border-radius: 50rpx;
  413. transition: all 0.3s;
  414. .tab-icon {
  415. font-size: 32rpx;
  416. margin-right: 8rpx;
  417. }
  418. &.active {
  419. background: linear-gradient(135deg, #ff6b9d 0%, #ff8fab 100%);
  420. color: #FFFFFF;
  421. font-weight: bold;
  422. box-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.3);
  423. }
  424. }
  425. }
  426. .login-title {
  427. font-size: 34rpx;
  428. font-weight: bold;
  429. color: #ff6b9d;
  430. margin-bottom: 40rpx;
  431. text-align: center;
  432. display: flex;
  433. align-items: center;
  434. justify-content: center;
  435. .title-icon {
  436. font-size: 40rpx;
  437. margin-right: 10rpx;
  438. }
  439. }
  440. .form-group {
  441. margin-bottom: 25rpx;
  442. position: relative;
  443. .input {
  444. width: 100%;
  445. height: 90rpx;
  446. background: #fff5f9;
  447. border-radius: 50rpx;
  448. padding: 0 30rpx;
  449. font-size: 28rpx;
  450. border: 2rpx solid #ffe0ed;
  451. box-sizing: border-box;
  452. transition: all 0.3s;
  453. &:focus {
  454. background: #FFFFFF;
  455. border-color: #ff8fab;
  456. }
  457. }
  458. &.code-group {
  459. display: flex;
  460. align-items: center;
  461. .code-input {
  462. flex: 1;
  463. margin-right: 20rpx;
  464. }
  465. .code-btn {
  466. width: 180rpx;
  467. height: 80rpx;
  468. background: linear-gradient(135deg, #ff6b9d 0%, #ff8fab 100%);
  469. color: #FFFFFF;
  470. border-radius: 50rpx;
  471. display: flex;
  472. align-items: center;
  473. justify-content: center;
  474. font-size: 24rpx;
  475. box-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.3);
  476. &.disabled {
  477. background: #e8e8e8;
  478. color: #999999;
  479. box-shadow: none;
  480. }
  481. }
  482. }
  483. &.password-group {
  484. display: flex;
  485. align-items: center;
  486. .input {
  487. flex: 1;
  488. padding-right: 80rpx;
  489. }
  490. .eye-icon {
  491. position: absolute;
  492. right: 20rpx;
  493. font-size: 40rpx;
  494. padding: 10rpx;
  495. }
  496. }
  497. }
  498. .remember-row {
  499. margin-bottom: 25rpx;
  500. .remember-check {
  501. display: flex;
  502. align-items: center;
  503. font-size: 26rpx;
  504. color: #666666;
  505. .checkbox-icon {
  506. width: 32rpx;
  507. height: 32rpx;
  508. margin-right: 10rpx;
  509. }
  510. }
  511. }
  512. .login-btn {
  513. width: 100%;
  514. height: 90rpx;
  515. background: linear-gradient(135deg, #ff6b9d 0%, #ff8fab 100%);
  516. color: #FFFFFF;
  517. border-radius: 50rpx;
  518. font-size: 32rpx;
  519. font-weight: bold;
  520. border: none;
  521. box-shadow: 0 8rpx 20rpx rgba(255, 107, 157, 0.4);
  522. margin-bottom: 30rpx;
  523. &:active {
  524. opacity: 0.9;
  525. transform: translateY(2rpx);
  526. }
  527. }
  528. .divider {
  529. display: flex;
  530. align-items: center;
  531. margin-bottom: 30rpx;
  532. .line {
  533. flex: 1;
  534. height: 1rpx;
  535. background: linear-gradient(to right, transparent, #ffcce0, transparent);
  536. }
  537. .text {
  538. margin: 0 20rpx;
  539. font-size: 24rpx;
  540. color: #ff6b9d;
  541. }
  542. }
  543. .wechat-login-btn {
  544. width: 100%;
  545. height: 90rpx;
  546. background: linear-gradient(135deg, #ff6b9d 0%, #ff8fab 100%);
  547. color: #FFFFFF;
  548. border-radius: 50rpx;
  549. font-size: 30rpx;
  550. font-weight: bold;
  551. display: flex;
  552. align-items: center;
  553. justify-content: center;
  554. border: none;
  555. box-shadow: 0 8rpx 20rpx rgba(255, 107, 157, 0.4);
  556. .wechat-icon {
  557. font-size: 40rpx;
  558. margin-right: 10rpx;
  559. }
  560. &:active {
  561. opacity: 0.9;
  562. transform: translateY(2rpx);
  563. }
  564. }
  565. }
  566. .footer-tip {
  567. text-align: center;
  568. margin-top: 60rpx;
  569. padding: 30rpx;
  570. .tip-icon {
  571. font-size: 50rpx;
  572. display: block;
  573. margin-bottom: 15rpx;
  574. animation: heartbeat 2s infinite;
  575. }
  576. .tip-text {
  577. font-size: 26rpx;
  578. color: #ff6b9d;
  579. font-weight: 500;
  580. letter-spacing: 2rpx;
  581. }
  582. }
  583. button {
  584. &::after {
  585. border: none;
  586. }
  587. }
  588. </style>