<script lang="ts" setup>
import { ref } from 'vue' const PHONE_NUMBER_REGEX = /^1[0-9]{10}$/
const PHONE_NUMBER_MESSAGE = '手机号格式不正确'
const VERIFICATION_CODE_REGEX = /^[0-9]{6}$/
const LOGIN_ERROR_MESSAGE = '登录失败,请检查网络连接并重试'
const GENERATE_CODE_ERROR_MESSAGE = '无法生成验证码,请检查网络连接并重试' const phoneNumber = ref('')
const verificationCode = ref('')
const countdown = ref(0)
const isLoggingIn = ref(false)
const errorMessage = ref('') const handleSubmit = async () => {
// 格式验证
if (!PHONE_NUMBER_REGEX.test(phoneNumber.value)) {
errorMessage.value = PHONE_NUMBER_MESSAGE
return
} if (!VERIFICATION_CODE_REGEX.test(verificationCode.value)) {
errorMessage.value = '验证码格式不正确'
return
} isLoggingIn.value = true
errorMessage.value = '' try {
// 并行执行发送登录请求和验证验证码请求
const [loginResult, verifyResult] = await Promise.all([
fetch(`https://example.com/api/login?phone=${phoneNumber.value}&code=${verificationCode.value}`),
fetch(`https://example.com/api/verify?phone=${phoneNumber.value}&code=${verificationCode.value}`),
]) if (!loginResult.ok || !verifyResult.ok) {
errorMessage.value = LOGIN_ERROR_MESSAGE
return
} // 登录成功后重置表单
phoneNumber.value = ''
verificationCode.value = ''
}
catch (error) {
errorMessage.value = LOGIN_ERROR_MESSAGE
return
}
finally {
isLoggingIn.value = false
}
} const generateCode = async () => {
// 格式验证
if (!PHONE_NUMBER_REGEX.test(phoneNumber.value)) {
errorMessage.value = PHONE_NUMBER_MESSAGE
return
}
// 先禁用按钮
countdown.value = 60
const timer = setInterval(() => {
countdown.value -= 1
if (countdown.value === 0)
clearInterval(timer)
}, 1000) try {
const response = await fetch(`https://example.com/api/sendcode?phone=${phoneNumber.value}`)
if (!response.ok)
throw new Error(GENERATE_CODE_ERROR_MESSAGE)
}
catch (error) {
errorMessage.value = GENERATE_CODE_ERROR_MESSAGE // error.message
countdown.value = 0
clearInterval(timer)
}
}
</script> <template>
<div class="form">
<h3 class="form__title">
登录
</h3>
<div class="form__group">
<label class="form__label" for="phone">电话</label>
<input id="phone" v-model.trim="phoneNumber" class="form__input" type="tel" :class="{ 'is-invalid': errorMessage && !PHONE_NUMBER_REGEX.test(phoneNumber) }">
<!-- <div v-if="errorMessage && !PHONE_NUMBER_REGEX.test(phoneNumber)" class="form__error">
手机号格式不正确
</div> -->
</div>
<div class="form__group">
<label class="form__label" for="verification-code">验证码</label>
<div class="form__group form__group--inline">
<input
id="verification-code"
v-model.trim="verificationCode"
class="form__input form__input--inline"
type="text"
:class="{ 'is-invalid': errorMessage && !VERIFICATION_CODE_REGEX.test(verificationCode) }"
>
<button
class="form__button form__button--inline"
type="button"
:disabled="countdown > 0"
@click="generateCode"
>
{{ countdown > 0 ? `${countdown}秒后可重新获取` : '获取验证码' }}
</button>
</div>
<!-- <div v-if="errorMessage && !VERIFICATION_CODE_REGEX.test(verificationCode)" class="form__error">
验证码格式不正确
</div> -->
</div>
<div v-if="errorMessage" class="form__error">
{{ errorMessage }}
</div>
<button
class="form__button"
type="button"
:disabled="isLoggingIn"
@click="handleSubmit"
>
{{ isLoggingIn ? '登录中...' : '登录' }}
</button>
</div>
</template> <style scoped>
/* 清除默认样式 */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
} /* 设置容器最小宽度为 320px */
body {
min-width: 320px;
} /* 设置字体 */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 16px;
} /* 设置颜色 */
body {
color: #333;
background-color: #f9f9f9;
} /* 设置表单样式 */
.form {
margin: 1rem;
padding: 1rem;
background-color: #fff;
border-radius: 0.25rem;
box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.2);
} .form__title {
font-size: 1.5rem;
margin: 0 0 1rem;
} .form__group {
margin-bottom: 1rem;
} .form__label {
display: block;
margin-bottom: 0.25rem;
font-size: 1rem;
} .form__input {
display: block;
width: 100%;
padding: 0.5rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 0.25rem;
background-color: #fff;
transition: border-color 0.2s ease-in-out;
} .form__input:focus {
outline: none;
border-color: #007bff;
} .form__group--inline {
display: flex;
align-items: center;
} .form__group--2col {
display: grid;
grid-template-columns: 1fr 1fr;
grid-column-gap: 1rem;
} .form__button {
display: block;
width: 100%;
padding: 0.5rem;
font-size: 1rem;
font-weight: 600;
text-align: center;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 0.25rem;
transition: background-color 0.2s ease-in-out;
cursor: pointer;
} .form__button:focus,
.form__button:active,
.form__button:hover {
outline: none;
background-color: #0069d9;
} .form__button:disabled {
opacity: 0.5;
cursor: not-allowed;
} .form__button--inline {
margin-left: 0.5rem;
} .form__error {
margin-top: 0.5rem;
font-size: 0.875rem;
color: #dc3545;
}
</style>

vue3适配移动端的登录实现的更多相关文章

  1. React.js 入门与实战之开发适配PC端及移动端新闻头条平台课程上线了

    原文发表于我的技术博客 我在慕课网的「React.js 入门与实战之开发适配PC端及移动端新闻头条平台」课程已经上线了,文章中是目前整个课程的大纲,以后此课程还会保持持续更新,此大纲文档也会保持更新, ...

  2. 让 jQuery UI draggable 适配移动端

    背景: 在移动端,本人要实现对某个元素的拖动,想到使用 jQuery UI 的 draggable 功能.但是发现此插件的拖动只支持PC端,不支持移动端. 原因: 原始的 jQuery UI 里,都是 ...

  3. bootstrap适配移动端

    上次在pythonanywhere上挂上去的页面,是这个样子的 而在手机上看是这个样子的 总之简直不能看= = 看了一下学校几个微信公众号的页面.都是用的bootstrap,好吧我也去试试看好了. 在 ...

  4. Vue将px转化为rem适配移动端

    Vue将px转化为rem适配移动端 1.下载lib-flexible我使用的是vue-cli+webpack,所以是通过npm来安装的npm i lib-flexible --save 2.引入lib ...

  5. 无线端安全登录与鉴权一之Kerberos

    无线端登录与鉴权是安全登录以及保证用户数据安全的第一步,也是最重要的一步.之前做过一个安全登录与鉴权的方案,借这个机会,系统的思考一下,与大家交流交流 先介绍一下TX系统使用的Kerberos方案,参 ...

  6. 安卓端后台登录接口单元测试demo

    package com.js.ai.modules.pointwall.interfac; import java.io.IOException; import java.io.Unsupported ...

  7. 从0到1构建适配不同端(微信小程序、H5、React-Native 等)的taro + dva应用

    从0到1构建适配不同端(微信小程序.H5.React-Native 等)的taro + dva应用 写在前面 Taro 是一套遵循 React 语法规范的 多端开发 解决方案.现如今市面上端的形态多种 ...

  8. IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路

    1.引言 在即时通讯网经常能看到各种高大上的高并发.分布式.高性能架构设计方面的文章,平时大家参加的众多开发者大会,主题也都是各种高大上的话题——什么5G啦.AI人工智能啦.什么阿里双11分分钟多少万 ...

  9. [原创]Spring-Security-Oauth2.0浏览器端的登录项目分享

    1.简介 ​ CitySecurity项目为正式上线项目做得一个Demo,这里主要介绍浏览器端的登录.本项目使用了SpringSecurity实现表单安全登录.图形验证的校验.记住我时长控制机制.第三 ...

  10. 本周汇总 动态rem适配移动端/块状元素居中/透明度

    1.动态rem适配移动端 !function(){ var width = document.documentElement.clientWidth; var head=document.getEle ...

随机推荐

  1. JavaScript 之 高级程序设计 基础篇 (一)

    导读 此篇文章为作者拜读JavaScrpit 第四版(红宝石)的笔记内容.适用于有经验的程序员阅读:作者 java开发出身.在之前前后端不分离的时代 使用esayUI JQuery的时代 经常写 js ...

  2. 在Android源码中为APK编译系统权限

    系统权限获取 打包为APK进行系统签名 对于 部分功能的访问需要使用到系统权限,需要 添加 android:sharedUserId="android.uid.system" 权限 ...

  3. DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求

    DeepSeek 新的 LLM 推理方法 DeepSeek 通过强化学习(RL)提出了一种创新的改进大规模语言模型(LLM)推理能力的方法,这在他们最近关于 DeepSeek-R1 的论文中有详细介绍 ...

  4. SpringBoot - [02] 第一个SpringBoot程序

    jdk maven3.6.3 springboot最新版 idea 如果使用官网 Spring Initializr ,则需要jdk17.21.22,并且是Springboot3.x 可以在idea创 ...

  5. 3. Nginx 命令行参数 & nginx.conf 配置文件的详细说明(附有截图说明)

    3. Nginx 命令行参数 & nginx.conf 配置文件的详细说明(附有截图说明) @ 目录 3. Nginx 命令行参数 & nginx.conf 配置文件的详细说明(附有截 ...

  6. script 标签中 defer 和 async 的区别

    https://www.cnblogs.com/huangtq/p/18422775 在 <script> 标签中,defer 和 async 是两个用于控制 JavaScript 脚本加 ...

  7. Abaqus-Steady-State-Dynamic-Analysis的求解原理

    0. 总括 基于模态的谐响应分析,可以通过扫频的方式求解频率范围内结构的线性稳态响应情况.阻尼是和频率相关的,但模态叠加法只需要知道n个模态阻尼即可推广到其他频率范围(原因详见文内公式). 1. 谐响 ...

  8. vue学习一(指令3.v-on,v-for)

    3.1.v-on 绑定事件  可以简写为@ ,按键修饰符    v-on:keyup    =    @keyup 事件修饰符:v-on提供了事件修饰符 修饰符是由点开头的指令后缀来表示的       ...

  9. WPF MessageBox

    参考文档 MessageBox MessageBoxButton MessageBoxImage MessageBoxOptions MessageBoxResult

  10. Armbian安装防火墙软件

    在Armbian操作系统中安装防火墙是一项提升系统安全性的重要步骤.Armbian是一个针对ARM架构设备的Debian Linux发行版,它广泛应用于各类ARM开发板和单板计算机上.下面将具体展开讲 ...