Showing
1 changed file
with
108 additions
and
97 deletions
| 1 | <template> | 1 | <template> |
| 2 | <div | 2 | <div |
| 3 | - class="min-h-screen flex flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 py-8 px-4 sm:px-6 lg:px-8" | 3 | + class="flex min-h-screen flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 px-4 py-8 sm:px-6 lg:px-8" |
| 4 | > | 4 | > |
| 5 | - <div class="sm:mx-auto sm:w-full sm:max-w-md text-center"> | 5 | + <div class="text-center sm:mx-auto sm:w-full sm:max-w-md"> |
| 6 | - <van-icon name="https://cdn.ipadbiz.cn/mlaj/icon/behalo-logo-1.png?imageMogr2/thumbnail/200x/strip/quality/70" size="10rem" style="margin-bottom: 0.5rem;" /> | 6 | + <van-icon |
| 7 | - <h1 class="text-center text-3xl font-bold text-gray-800 mb-2">美乐爱觉教育</h1> | 7 | + name="https://cdn.ipadbiz.cn/mlaj/icon/behalo-logo-2.png?imageMogr2/thumbnail/200x/strip/quality/70" |
| 8 | + size="10rem" | ||
| 9 | + style="margin-bottom: 0.5rem" | ||
| 10 | + /> | ||
| 11 | + <h1 class="mb-2 text-center text-3xl font-bold text-gray-800">美乐爱觉教育</h1> | ||
| 8 | <!-- <h2 class="text-center text-xl font-medium text-gray-600">欢迎回来</h2> --> | 12 | <!-- <h2 class="text-center text-xl font-medium text-gray-600">欢迎回来</h2> --> |
| 9 | </div> | 13 | </div> |
| 10 | 14 | ||
| 11 | <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> | 15 | <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> |
| 12 | - <FrostedGlass class="py-8 px-6 rounded-lg"> | 16 | + <FrostedGlass class="rounded-lg px-6 py-8"> |
| 13 | <div | 17 | <div |
| 14 | v-if="error" | 18 | v-if="error" |
| 15 | - class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md" | 19 | + class="mb-4 rounded-md border border-red-400 bg-red-100 px-4 py-3 text-red-700" |
| 16 | > | 20 | > |
| 17 | {{ error }} | 21 | {{ error }} |
| 18 | </div> | 22 | </div> |
| ... | @@ -35,7 +39,7 @@ | ... | @@ -35,7 +39,7 @@ |
| 35 | @input="mobile = $event.target.value.replace(/\D/g, '')" | 39 | @input="mobile = $event.target.value.replace(/\D/g, '')" |
| 36 | @focus="handleInputFocus" | 40 | @focus="handleInputFocus" |
| 37 | @blur="validatePhone" | 41 | @blur="validatePhone" |
| 38 | - class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500" | 42 | + class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500" |
| 39 | /> | 43 | /> |
| 40 | </div> | 44 | </div> |
| 41 | 45 | ||
| ... | @@ -51,7 +55,7 @@ | ... | @@ -51,7 +55,7 @@ |
| 51 | autocomplete="current-password" | 55 | autocomplete="current-password" |
| 52 | required | 56 | required |
| 53 | placeholder="请输入6位密码" | 57 | placeholder="请输入6位密码" |
| 54 | - class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500" | 58 | + class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500" |
| 55 | /> | 59 | /> |
| 56 | </div> | 60 | </div> |
| 57 | 61 | ||
| ... | @@ -67,13 +71,13 @@ | ... | @@ -67,13 +71,13 @@ |
| 67 | required | 71 | required |
| 68 | maxlength="6" | 72 | maxlength="6" |
| 69 | placeholder="请输入验证码" | 73 | placeholder="请输入验证码" |
| 70 | - class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500" | 74 | + class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500" |
| 71 | /> | 75 | /> |
| 72 | <button | 76 | <button |
| 73 | type="button" | 77 | type="button" |
| 74 | :disabled="countdown > 0 || !isPhoneValid" | 78 | :disabled="countdown > 0 || !isPhoneValid" |
| 75 | @click="sendVerificationCode" | 79 | @click="sendVerificationCode" |
| 76 | - class="mt-1 px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap" | 80 | + class="mt-1 whitespace-nowrap rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" |
| 77 | > | 81 | > |
| 78 | {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }} | 82 | {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }} |
| 79 | </button> | 83 | </button> |
| ... | @@ -104,10 +108,10 @@ | ... | @@ -104,10 +108,10 @@ |
| 104 | <button | 108 | <button |
| 105 | type="submit" | 109 | type="submit" |
| 106 | :disabled="loading" | 110 | :disabled="loading" |
| 107 | - class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500" | 111 | + class="flex w-full justify-center rounded-md border border-transparent bg-gradient-to-r from-green-500 to-green-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2" |
| 108 | - :class="{ 'opacity-70 cursor-not-allowed': loading }" | 112 | + :class="{ 'cursor-not-allowed opacity-70': loading }" |
| 109 | > | 113 | > |
| 110 | - {{ loading ? "登录中..." : "登录/注册BEHALO宇宙账号" }} | 114 | + {{ loading ? '登录中...' : '登录/注册BEHALO宇宙账号' }} |
| 111 | </button> | 115 | </button> |
| 112 | </div> | 116 | </div> |
| 113 | </form> | 117 | </form> |
| ... | @@ -157,11 +161,11 @@ | ... | @@ -157,11 +161,11 @@ |
| 157 | </router-link> | 161 | </router-link> |
| 158 | </p> | 162 | </p> |
| 159 | </div> --> | 163 | </div> --> |
| 160 | - <div class="text-center mt-6"> | 164 | + <div class="mt-6 text-center"> |
| 161 | <p class="text-sm text-gray-600"> | 165 | <p class="text-sm text-gray-600"> |
| 162 | 登录即表示同意 | 166 | 登录即表示同意 |
| 163 | <span | 167 | <span |
| 164 | - class="font-medium text-green-600 hover:text-green-500 cursor-pointer" | 168 | + class="cursor-pointer font-medium text-green-600 hover:text-green-500" |
| 165 | @click="userAgreementRef.openAgreement()" | 169 | @click="userAgreementRef.openAgreement()" |
| 166 | > | 170 | > |
| 167 | 《美乐爱觉宇宙用户协议》 | 171 | 《美乐爱觉宇宙用户协议》 |
| ... | @@ -170,10 +174,15 @@ | ... | @@ -170,10 +174,15 @@ |
| 170 | </p> | 174 | </p> |
| 171 | </div> | 175 | </div> |
| 172 | 176 | ||
| 173 | - <div class="mt-6 flex justify-center items-center gap-4" style="flex-direction: column;"> | 177 | + <div class="mt-6 flex items-center justify-center gap-4" style="flex-direction: column"> |
| 174 | <div class="text-sm font-medium text-gray-600">其他登录方式</div> | 178 | <div class="text-sm font-medium text-gray-600">其他登录方式</div> |
| 175 | <div> | 179 | <div> |
| 176 | - <img :src="weixinLogo" alt="微信登录" style="width: 40px; height: 40px; border-radius: 8px;" @click="handleWxLoginClick"> | 180 | + <img |
| 181 | + :src="weixinLogo" | ||
| 182 | + alt="微信登录" | ||
| 183 | + style="width: 40px; height: 40px; border-radius: 8px" | ||
| 184 | + @click="handleWxLoginClick" | ||
| 185 | + /> | ||
| 177 | </div> | 186 | </div> |
| 178 | </div> | 187 | </div> |
| 179 | </FrostedGlass> | 188 | </FrostedGlass> |
| ... | @@ -182,74 +191,74 @@ | ... | @@ -182,74 +191,74 @@ |
| 182 | </template> | 191 | </template> |
| 183 | 192 | ||
| 184 | <script setup> | 193 | <script setup> |
| 185 | -import { ref } from "vue"; | 194 | +import { ref } from 'vue' |
| 186 | -import { useRoute, useRouter } from "vue-router"; | 195 | +import { useRoute, useRouter } from 'vue-router' |
| 187 | -import FrostedGlass from "@/components/effects/FrostedGlass.vue"; | 196 | +import FrostedGlass from '@/components/effects/FrostedGlass.vue' |
| 188 | -import { useAuth } from "@/contexts/auth"; | 197 | +import { useAuth } from '@/contexts/auth' |
| 189 | -import { loginAPI, getUserInfoAPI } from "@/api/users"; | 198 | +import { loginAPI, getUserInfoAPI } from '@/api/users' |
| 190 | -import { useTitle } from "@vueuse/core"; | 199 | +import { useTitle } from '@vueuse/core' |
| 191 | -import { smsAPI } from "@/api/common"; | 200 | +import { smsAPI } from '@/api/common' |
| 192 | -import { showToast } from "vant"; | 201 | +import { showToast } from 'vant' |
| 193 | -import UserAgreement from "@/components/common/UserAgreement.vue"; | 202 | +import UserAgreement from '@/components/common/UserAgreement.vue' |
| 194 | -import { setAuthHeaders } from "@/utils/axios"; | 203 | +import { setAuthHeaders } from '@/utils/axios' |
| 195 | -import { applyUserInfoAuth } from "@/utils/auth_user_info"; | 204 | +import { applyUserInfoAuth } from '@/utils/auth_user_info' |
| 196 | import weixinLogo from '@/assets/images/weixin_logo_lg.jpeg' | 205 | import weixinLogo from '@/assets/images/weixin_logo_lg.jpeg' |
| 197 | import { startWxAuth } from '@/router/guards' | 206 | import { startWxAuth } from '@/router/guards' |
| 198 | import { wxInfo } from '@/utils/tools' | 207 | import { wxInfo } from '@/utils/tools' |
| 199 | 208 | ||
| 200 | -const userAgreementRef = ref(null); | 209 | +const userAgreementRef = ref(null) |
| 201 | 210 | ||
| 202 | const handleInputFocus = () => { | 211 | const handleInputFocus = () => { |
| 203 | setTimeout(() => { | 212 | setTimeout(() => { |
| 204 | // 使用平滑滚动将页面向上移动200px(根据实际按钮区域高度调整) | 213 | // 使用平滑滚动将页面向上移动200px(根据实际按钮区域高度调整) |
| 205 | window.scrollTo({ | 214 | window.scrollTo({ |
| 206 | top: 150, | 215 | top: 150, |
| 207 | - behavior: 'smooth' | 216 | + behavior: 'smooth', |
| 208 | - }); | 217 | + }) |
| 209 | - }, 100); | 218 | + }, 100) |
| 210 | -}; | 219 | +} |
| 211 | - | 220 | + |
| 212 | -const $route = useRoute(); | 221 | +const $route = useRoute() |
| 213 | -useTitle($route.meta.title); | 222 | +useTitle($route.meta.title) |
| 214 | - | 223 | + |
| 215 | -const router = useRouter(); | 224 | +const router = useRouter() |
| 216 | -const { login } = useAuth(); | 225 | +const { login } = useAuth() |
| 217 | - | 226 | + |
| 218 | -const mobile = ref(""); | 227 | +const mobile = ref('') |
| 219 | -const password = ref(""); | 228 | +const password = ref('') |
| 220 | -const verificationCode = ref(""); | 229 | +const verificationCode = ref('') |
| 221 | -const error = ref(""); | 230 | +const error = ref('') |
| 222 | -const loading = ref(false); | 231 | +const loading = ref(false) |
| 223 | -const isVerifyCodeLogin = ref(true); | 232 | +const isVerifyCodeLogin = ref(true) |
| 224 | -const countdown = ref(0); | 233 | +const countdown = ref(0) |
| 225 | -const isPhoneValid = ref(false); | 234 | +const isPhoneValid = ref(false) |
| 226 | 235 | ||
| 227 | const validatePhone = () => { | 236 | const validatePhone = () => { |
| 228 | if (!mobile.value) { | 237 | if (!mobile.value) { |
| 229 | - error.value = '请输入手机号'; | 238 | + error.value = '请输入手机号' |
| 230 | - isPhoneValid.value = false; | 239 | + isPhoneValid.value = false |
| 231 | - return; | 240 | + return |
| 232 | } | 241 | } |
| 233 | 242 | ||
| 234 | if (!/^1[3-9]\d{9}$/.test(mobile.value)) { | 243 | if (!/^1[3-9]\d{9}$/.test(mobile.value)) { |
| 235 | - error.value = '请输入正确的手机号'; | 244 | + error.value = '请输入正确的手机号' |
| 236 | - isPhoneValid.value = false; | 245 | + isPhoneValid.value = false |
| 237 | - return; | 246 | + return |
| 238 | } | 247 | } |
| 239 | 248 | ||
| 240 | - error.value = ''; | 249 | + error.value = '' |
| 241 | - isPhoneValid.value = true; | 250 | + isPhoneValid.value = true |
| 242 | -}; | 251 | +} |
| 243 | 252 | ||
| 244 | const startCountdown = () => { | 253 | const startCountdown = () => { |
| 245 | - countdown.value = 60; | 254 | + countdown.value = 60 |
| 246 | const timer = setInterval(() => { | 255 | const timer = setInterval(() => { |
| 247 | - countdown.value--; | 256 | + countdown.value-- |
| 248 | if (countdown.value <= 0) { | 257 | if (countdown.value <= 0) { |
| 249 | - clearInterval(timer); | 258 | + clearInterval(timer) |
| 250 | } | 259 | } |
| 251 | - }, 1000); | 260 | + }, 1000) |
| 252 | -}; | 261 | +} |
| 253 | 262 | ||
| 254 | /** | 263 | /** |
| 255 | * @description 点击微信图标触发微信授权登录 | 264 | * @description 点击微信图标触发微信授权登录 |
| ... | @@ -258,46 +267,50 @@ const startCountdown = () => { | ... | @@ -258,46 +267,50 @@ const startCountdown = () => { |
| 258 | const handleWxLoginClick = async () => { | 267 | const handleWxLoginClick = async () => { |
| 259 | // 非微信环境提示并不触发授权 | 268 | // 非微信环境提示并不触发授权 |
| 260 | if (!import.meta.env.DEV && !wxInfo().isWeiXin) { | 269 | if (!import.meta.env.DEV && !wxInfo().isWeiXin) { |
| 261 | - showToast('请在微信内打开以完成微信授权登录'); | 270 | + showToast('请在微信内打开以完成微信授权登录') |
| 262 | - return; | 271 | + return |
| 263 | } | 272 | } |
| 264 | 273 | ||
| 265 | try { | 274 | try { |
| 266 | // 手动发起微信授权流程(guards.js中实现) | 275 | // 手动发起微信授权流程(guards.js中实现) |
| 267 | - await startWxAuth(); | 276 | + await startWxAuth() |
| 268 | } catch (e) { | 277 | } catch (e) { |
| 269 | - console.error('微信授权触发失败:', e); | 278 | + console.error('微信授权触发失败:', e) |
| 270 | - showToast('微信授权失败,请稍后重试'); | 279 | + showToast('微信授权失败,请稍后重试') |
| 271 | } | 280 | } |
| 272 | -}; | 281 | +} |
| 273 | 282 | ||
| 274 | const sendVerificationCode = async () => { | 283 | const sendVerificationCode = async () => { |
| 275 | if (!isPhoneValid.value) { | 284 | if (!isPhoneValid.value) { |
| 276 | - return; | 285 | + return |
| 277 | } | 286 | } |
| 278 | 287 | ||
| 279 | try { | 288 | try { |
| 280 | - const { code } = await smsAPI({ mobile: mobile.value }); | 289 | + const { code } = await smsAPI({ mobile: mobile.value }) |
| 281 | if (code === 1) { | 290 | if (code === 1) { |
| 282 | - showToast('验证码已发送'); | 291 | + showToast('验证码已发送') |
| 283 | - startCountdown(); | 292 | + startCountdown() |
| 284 | - return; | 293 | + return |
| 285 | } | 294 | } |
| 286 | } catch (err) { | 295 | } catch (err) { |
| 287 | - console.error('Send verification code error:', err); | 296 | + console.error('Send verification code error:', err) |
| 288 | - error.value = '发送验证码失败,请稍后重试'; | 297 | + error.value = '发送验证码失败,请稍后重试' |
| 289 | } | 298 | } |
| 290 | -}; | 299 | +} |
| 291 | 300 | ||
| 292 | const handleSubmit = async () => { | 301 | const handleSubmit = async () => { |
| 293 | - if (!mobile.value || (!isVerifyCodeLogin.value && !password.value) || (isVerifyCodeLogin.value && !verificationCode.value)) { | 302 | + if ( |
| 294 | - error.value = "请填写所有字段"; | 303 | + !mobile.value || |
| 295 | - return; | 304 | + (!isVerifyCodeLogin.value && !password.value) || |
| 305 | + (isVerifyCodeLogin.value && !verificationCode.value) | ||
| 306 | + ) { | ||
| 307 | + error.value = '请填写所有字段' | ||
| 308 | + return | ||
| 296 | } | 309 | } |
| 297 | 310 | ||
| 298 | try { | 311 | try { |
| 299 | - error.value = ""; | 312 | + error.value = '' |
| 300 | - loading.value = true; | 313 | + loading.value = true |
| 301 | 314 | ||
| 302 | // 调用登录接口 | 315 | // 调用登录接口 |
| 303 | const response = await loginAPI({ | 316 | const response = await loginAPI({ |
| ... | @@ -305,38 +318,36 @@ const handleSubmit = async () => { | ... | @@ -305,38 +318,36 @@ const handleSubmit = async () => { |
| 305 | ...(isVerifyCodeLogin.value | 318 | ...(isVerifyCodeLogin.value |
| 306 | ? { sms_code: verificationCode.value } | 319 | ? { sms_code: verificationCode.value } |
| 307 | : { password: password.value }), | 320 | : { password: password.value }), |
| 308 | - }); | 321 | + }) |
| 309 | 322 | ||
| 310 | if (response.code !== 1) { | 323 | if (response.code !== 1) { |
| 311 | - error.value = response.msg || "登录失败,请检查您的输入项"; | 324 | + error.value = response.msg || '登录失败,请检查您的输入项' |
| 312 | - return; | 325 | + return |
| 313 | - } else { | ||
| 314 | - applyUserInfoAuth(response, { set_auth_headers: setAuthHeaders, storage: localStorage }) | ||
| 315 | } | 326 | } |
| 327 | + applyUserInfoAuth(response, { set_auth_headers: setAuthHeaders, storage: localStorage }) | ||
| 316 | 328 | ||
| 317 | - const { code, data } = await getUserInfoAPI(); | 329 | + const { code, data } = await getUserInfoAPI() |
| 318 | if (code === 1) { | 330 | if (code === 1) { |
| 319 | // 登录成功,更新auth状态,传递完整的用户信息包括打卡信息 | 331 | // 登录成功,更新auth状态,传递完整的用户信息包括打卡信息 |
| 320 | - const success = login({ ...data.user, ...data.checkin }); | 332 | + const success = login({ ...data.user, ...data.checkin }) |
| 321 | 333 | ||
| 322 | if (success) { | 334 | if (success) { |
| 323 | // 如果有重定向参数,登录成功后跳转到对应页面 | 335 | // 如果有重定向参数,登录成功后跳转到对应页面 |
| 324 | // 说明:redirect 是经过 URL 编码的,需要先解码再跳转 | 336 | // 说明:redirect 是经过 URL 编码的,需要先解码再跳转 |
| 325 | - const redirect_raw = $route.query.redirect; | 337 | + const redirect_raw = $route.query.redirect |
| 326 | - const redirect = redirect_raw ? decodeURIComponent(redirect_raw) : "/"; | 338 | + const redirect = redirect_raw ? decodeURIComponent(redirect_raw) : '/' |
| 327 | - router.push(redirect); | 339 | + router.push(redirect) |
| 328 | } else { | 340 | } else { |
| 329 | - error.value = "登录失败,请检查您的输入项"; | 341 | + error.value = '登录失败,请检查您的输入项' |
| 330 | } | 342 | } |
| 331 | } | 343 | } |
| 332 | - | ||
| 333 | } catch (err) { | 344 | } catch (err) { |
| 334 | - console.error("Login error:", err); | 345 | + console.error('Login error:', err) |
| 335 | - error.value = "登录时发生错误"; | 346 | + error.value = '登录时发生错误' |
| 336 | } finally { | 347 | } finally { |
| 337 | - loading.value = false; | 348 | + loading.value = false |
| 338 | } | 349 | } |
| 339 | -}; | 350 | +} |
| 340 | 351 | ||
| 341 | // 说明:授权回跳的重定向处理已移动到路由层 | 352 | // 说明:授权回跳的重定向处理已移动到路由层 |
| 342 | </script> | 353 | </script> | ... | ... |
-
Please register or login to post a comment