login.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <!-- pages/mine/login.vue -->
  2. <template>
  3. <view class="login-page">
  4. <!-- 标题图片占位区 -->
  5. <view class="logo-placeholder">
  6. <image src="/static/img/logo.png"></image>
  7. </view>
  8. <!-- 账号密码登录区域(根据模式显示) -->
  9. <template v-if="loginMode === 'password'">
  10. <!-- 手机号输入框 -->
  11. <view class="input-wrapper">
  12. <input v-model="phone" type="number" maxlength="11" class="input-field" placeholder="请输入帐号"
  13. placeholder-style="color: #999;" confirm-type="done" />
  14. </view>
  15. <!-- 密码输入框 -->
  16. <view class="input-wrapper">
  17. <input v-model="password" :password="true" maxlength="20" class="input-field" placeholder="请输入密码"
  18. placeholder-style="color: #999;" confirm-type="done" />
  19. </view>
  20. </template>
  21. <!-- 协议同意行 -->
  22. <view class="agreement-row">
  23. <checkbox-group @change="onAgreeChange">
  24. <checkbox :value="'agree'" :checked="agree" class="agree-checkbox" color="#007AFF">
  25. 我已阅读并同意
  26. </checkbox>
  27. </checkbox-group>
  28. <text class="agree-text"></text>
  29. <text class="link-text" @click.stop="toUserAgreement">《用户协议》</text>
  30. <text class="link-text" @click.stop="toPrivacyPolicy">《隐私政策》</text>
  31. </view>
  32. <!-- 密码登录按钮(密码模式显示) -->
  33. <button v-if="loginMode === 'password'" class="btn login-btn" @click="handlePhoneLogin">登录</button>
  34. <!-- 微信快捷登录按钮(快捷模式显示) -->
  35. <button v-if="loginMode === 'quick'" class="btn wechat-btn" open-type="getPhoneNumber"
  36. @getphonenumber="handleWechatLogin" :loading="wechatLoading" :disabled="wechatLoading">快捷登录</button>
  37. <!-- 登录方式切换链接 - 放在按钮正下方 -->
  38. <view class="toggle-mode" @click="toggleMode">
  39. {{ loginMode === 'quick' ? '账号密码登录' : '快捷登录' }}
  40. </view>
  41. </view>
  42. </template>
  43. <script setup>
  44. import { ref } from 'vue'
  45. import { quickLogin, telEncrypt } from '@/utils/util.js'
  46. import { getLoginByPwdApi } from '@/api/user.js'
  47. import { useAppStore } from '@/stores/app'
  48. const appStore = useAppStore()
  49. const phone = ref('') // 手机号
  50. const password = ref('') // 密码
  51. const agree = ref(false) // 协议勾选状态
  52. const wechatLoading = ref(false) // 微信登录loading
  53. const loginMode = ref('quick') // 登录模式:quick-快捷登录,password-密码登录
  54. // 复选框组变化处理
  55. const onAgreeChange = (e) => {
  56. agree.value = e.detail.value.includes('agree')
  57. }
  58. // 切换登录模式
  59. const toggleMode = () => {
  60. loginMode.value = loginMode.value === 'quick' ? 'password' : 'quick'
  61. }
  62. // 手机号密码登录
  63. const handlePhoneLogin = async () => {
  64. if (!phone.value) {
  65. uni.showToast({ title: '请输入帐号', icon: 'none' })
  66. return
  67. }
  68. if (!password.value) {
  69. uni.showToast({ title: '请输入密码', icon: 'none' })
  70. return
  71. }
  72. if (!agree.value) {
  73. uni.showToast({ title: '请先同意用户协议和隐私政策', icon: 'none' })
  74. return
  75. }
  76. const params = {
  77. username: phone.value,
  78. password: password.value
  79. }
  80. try {
  81. const res = await getLoginByPwdApi(params)
  82. if (res.code == 200) {
  83. uni.showToast({ title: '登录成功~', icon: 'none' })
  84. appStore.UPDATE_TOKEN(res.data.access_token || res.data);
  85. setTimeout(() => {
  86. uni.navigateBack()
  87. }, 200)
  88. } else {
  89. uni.showToast({ title: res.msg, icon: 'none' })
  90. }
  91. } catch (err) {
  92. console.error('登录接口异常', err)
  93. uni.showToast({ title: err, icon: 'none' })
  94. }
  95. }
  96. // 微信快捷登录
  97. const handleWechatLogin = async (e) => {
  98. // 检查协议同意
  99. if (!agree.value) {
  100. uni.showToast({ title: '请先同意用户协议和隐私政策', icon: 'none' })
  101. return
  102. }
  103. if (wechatLoading.value) return
  104. wechatLoading.value = true
  105. uni.showLoading({ title: '登录中...', mask: true })
  106. try {
  107. await quickLogin(e, {
  108. onSuccess: (result) => {
  109. console.log('登录成功', result)
  110. if (result.data) {
  111. appStore.UPDATE_USERINFO(result.data)
  112. }
  113. uni.hideLoading()
  114. uni.showToast({ title: '登录成功', icon: 'success' })
  115. setTimeout(() => {
  116. uni.navigateBack()
  117. }, 1500)
  118. },
  119. onFail: (error) => {
  120. console.error('登录失败', error)
  121. uni.hideLoading()
  122. let msg = error.message || '登录失败'
  123. if (error.type === 'auth_denied') {
  124. msg = '您拒绝了授权,无法登录'
  125. uni.showModal({
  126. title: '提示',
  127. content: '需要手机号授权才能正常使用,请点击右上角菜单,选择「重新进入小程序」后重新授权',
  128. showCancel: false
  129. })
  130. } else {
  131. uni.showToast({ title: msg, icon: 'none' })
  132. }
  133. }
  134. })
  135. } catch (err) {
  136. console.error('quickLogin 异常', err)
  137. uni.hideLoading()
  138. uni.showToast({ title: '登录异常', icon: 'none' })
  139. } finally {
  140. wechatLoading.value = false
  141. }
  142. }
  143. // 跳转用户协议
  144. const toUserAgreement = () => {
  145. uni.navigateTo({
  146. url: '/pages/webView/webView?title=用户协议&url=' + encodeURIComponent('https://rjsd.mychery.com/user_agreement.html')
  147. });
  148. }
  149. // 跳转隐私政策
  150. const toPrivacyPolicy = () => {
  151. uni.navigateTo({
  152. url: '/pages/webView/webView?title=隐私政策&url=' + encodeURIComponent('https://rjsd.mychery.com/privacy_policy.html')
  153. });
  154. }
  155. </script>
  156. <style scoped lang="less">
  157. /* 页面容器 */
  158. .login-page {
  159. width: 100%;
  160. min-height: 100vh;
  161. background: linear-gradient(135deg, #CFE9FF 0%, #F5F7FA 50.86%);
  162. display: flex;
  163. flex-direction: column;
  164. align-items: center;
  165. padding: 40rpx 80rpx;
  166. box-sizing: border-box;
  167. }
  168. .logo-placeholder {
  169. width: 100%;
  170. margin: 120rpx 0rpx 90rpx 0rpx;
  171. display: flex;
  172. justify-content: center;
  173. image {
  174. width: 425rpx;
  175. height: 100rpx;
  176. }
  177. }
  178. /* 通用输入框包装 */
  179. .input-wrapper {
  180. width: 100%;
  181. padding: 24rpx 32rpx;
  182. height: 88rpx;
  183. background: #FFFFFF;
  184. border-radius: 44rpx;
  185. box-sizing: border-box;
  186. display: flex;
  187. align-items: center;
  188. transition: border-color 0.2s;
  189. margin-bottom: 32rpx;
  190. &:focus-within {
  191. border-color: #007AFF;
  192. }
  193. }
  194. .input-field {
  195. flex: 1;
  196. font-size: 28rpx;
  197. color: #333;
  198. background-color: transparent;
  199. height: 88rpx;
  200. line-height: 88rpx;
  201. padding: 0;
  202. }
  203. /* 协议行 */
  204. .agreement-row {
  205. width: 100%;
  206. display: flex;
  207. align-items: center;
  208. flex-wrap: wrap;
  209. margin-bottom: 20rpx;
  210. font-size: 28rpx;
  211. color: #4a5568;
  212. checkbox-group {
  213. display: flex;
  214. align-items: center;
  215. }
  216. }
  217. .agree-checkbox {
  218. transform: scale(0.9);
  219. margin-right: 8rpx;
  220. }
  221. /* 覆盖 checkbox 样式 */
  222. ::v-deep .agree-checkbox .wx-checkbox-input,
  223. ::v-deep .agree-checkbox .uni-checkbox-input {
  224. border-radius: 50%;
  225. width: 36rpx;
  226. height: 36rpx;
  227. background-color: #ffffff;
  228. border: 2rpx solid #ccc;
  229. }
  230. ::v-deep .agree-checkbox .wx-checkbox-input.wx-checkbox-input-checked,
  231. ::v-deep .agree-checkbox .uni-checkbox-input.uni-checkbox-input-checked {
  232. background-color: #007AFF !important;
  233. border-color: #007AFF !important;
  234. }
  235. ::v-deep .agree-checkbox .wx-checkbox-input.wx-checkbox-input-checked::before,
  236. ::v-deep .agree-checkbox .uni-checkbox-input.uni-checkbox-input-checked::before {
  237. color: #ffffff !important;
  238. }
  239. .link-text {
  240. color: #007AFF;
  241. display: inline-block;
  242. }
  243. /* 登录方式切换链接 - 按钮正下方 */
  244. .toggle-mode {
  245. width: 100%;
  246. text-align: right;
  247. color: #007AFF;
  248. font-size: 28rpx;
  249. margin-top: 30rpx; /* 与按钮保持适当间距 */
  250. margin-bottom: 0;
  251. padding: 10rpx 0;
  252. }
  253. /* 按钮基础样式 */
  254. .btn {
  255. width: 100%;
  256. height: 88rpx;
  257. border-radius: 44rpx;
  258. font-size: 32rpx;
  259. font-weight: bold;
  260. display: flex;
  261. align-items: center;
  262. justify-content: center;
  263. border: none;
  264. color: #ffffff;
  265. margin-bottom: 32rpx;
  266. box-shadow: 0 8rpx 20rpx rgba(0, 122, 255, 0.25);
  267. transition: opacity 0.2s;
  268. &.login-btn {
  269. background-color: #007AFF;
  270. }
  271. &.wechat-btn {
  272. background-color: #2DAB6C;
  273. }
  274. }
  275. /* 重置按钮默认样式 */
  276. button {
  277. padding: 0;
  278. margin: 0;
  279. line-height: 1.5;
  280. background: none;
  281. box-shadow: none;
  282. }
  283. button::after {
  284. border: none;
  285. }
  286. </style>