فهرست منبع

登陆新增手机号、邮箱登陆,发送验证码验证

cxmo 1 سال پیش
والد
کامیت
789e3db238

+ 79 - 1
src/api/user.js

@@ -1,7 +1,7 @@
 import {get,post} from './index'
 
 /**
- * 登录
+ * 登录 (ETA1.4后弃用)
  * @param Username
  * @param Password
  * @param IsRemember
@@ -16,3 +16,81 @@ export function apiLogin(params){
 export function apiMenuList(params){
     return get('/system/menu/list',params)
 }
+
+export const _apiLogin = {
+    /**
+     * 获取图形验证码,返回base64格式图片
+     * @returns 
+     */
+    apiGetPicCode:(params)=>{
+        return get('/user_login/get_captcha',params)
+    },
+    /**
+     * 获取短信/邮箱验证码
+     * @param {Number} VerifyType 验证方式: 1-手机号; 2-邮箱
+     * @param {String} CaptchaId 图形验证码ID
+     * @param {String} CaptchaCode 图形验证码的值
+     * @param {String} Mobile 手机号 验证方式为1时必填
+     * @param {String} Email 邮箱 验证方式为2时必填
+     * @param {String} TelAreaCode 手机区号
+     * @param {Number} Source 来源:1-登录;3-忘记密码
+     * @returns 
+     */
+    apiGetVerifyCode:(params)=>{
+        return post('/user_login/verify_code',params)
+    },
+    /**
+     * ETA1.4后,用户登录
+     * @param {Number} LoginType 登录方式: 1-账号; 2-手机号; 3-邮箱
+     * @param {String} Username 用户名
+     * @param {String} Password 密码
+     * @param {String} Mobile 手机号
+     * @param {String} Email 邮箱
+     * @param {String} VerifyCode 手机号/邮箱的验证码
+     * @returns 
+     */
+    apiUserLogin:(params)=>{
+        return post('/user_login/login',params)
+    },
+    /**
+     * 忘记密码账号校验,获取账号信息
+     * @param {String} CaptchaId 图形验证码ID
+     * @param {String} CaptchaCode 图形验证码
+     * @param {String} UserName 账号
+     * @returns 
+    */
+    apiAccountCheck:(params)=>{
+        return post('/user_login/forget/account_get',params)
+    },
+    /**
+     * 忘记密码-手机/邮箱的验证码校验
+     * @param {Number} FindType 密码找回方式:1-手机号;2-邮箱
+     * @param {String} VerifyCode 验证码
+     * @param {String} UserName 用户名
+     * @param {String} Mobile 找回方式为手机号时必填
+     * @param {String} Email 找回方式为邮箱时必填
+     * @returns 
+     */
+    apiCheckCodeVerify:(params)=>{
+        return post('/user_login/forget/code_verify',params)
+    },
+    /**
+     * 忘记密码-重置密码
+     * @param {String} UserName 
+     * @param {String} Password 
+     * @param {String} RePassword 
+     * @returns 
+     */
+    resetPass:(params)=>{
+        return post('/user_login/forget/reset_pass',params)
+    },
+    /**
+     * 获取手机号区号
+     * @returns 
+     */
+    getPhoneAreaCode:(params)=>{
+        return get('/user_login/area_code/list',params)
+    },
+}
+
+

BIN
src/assets/imgs/login/account-icon.png


BIN
src/assets/imgs/login/email-icon.png


BIN
src/assets/imgs/login/phone-icon.png


+ 159 - 41
src/views/Login.vue

@@ -1,16 +1,21 @@
 <script setup>
 import {ref} from 'vue'
 import { showToast } from 'vant';
-import {apiLogin} from '@/api/user'
+import {apiLogin,_apiLogin} from '@/api/user'
 import md5 from 'js-md5'
 import {Base64} from 'js-base64'
 import { useRouter } from 'vue-router';
+import MobileModel from './login/MobileModel.vue';
+import OrdinaryModel from './login/OrdinaryModel.vue';
+import EmailModel from './login/EmailModel.vue';
 
 const router=useRouter()
 
-
-let username=ref(localStorage.getItem('account')?Base64.decode(localStorage.getItem('account')):'')
-let password=ref(localStorage.getItem('checkPass')?Base64.decode(localStorage.getItem('checkPass')):'')
+//登陆模式 ordinaryModel账号密码 mobileModel手机号 emailModel邮箱
+let activeModel = ref('ordinaryModel')
+function changeModel(model){
+    activeModel.value = model
+}
 
 const onSubmit = (values) => {
     const params={
@@ -22,7 +27,7 @@ const onSubmit = (values) => {
         if(res.Ret===200){
             localStorage.setItem('token',res.Data.Authorization)
             localStorage.setItem('account',Base64.encode(values.username))
-			localStorage.setItem('checkPass',Base64.encode(values.password))
+            localStorage.setItem('checkPass',Base64.encode(values.password))
             const userInfo={
                 AdminName:res.Data.AdminName,
                 Authority:res.Data.Authority,
@@ -39,39 +44,137 @@ const onSubmit = (values) => {
         }
     })
 };
+
+
+let loginForm = ref(null)
+let mobileModel = ref(null)
+let emailModel = ref(null)
+const onSubmit2 = ()=>{
+    //根据activeModel验证值
+    const values = loginForm.value.getValues()
+    if(activeModel.value==='ordinaryModel'){
+        loginForm.value.validate(['username','password']).then(()=>{
+            userLogin({
+                LoginType:1,
+                Username:values.username,
+                Password:md5(values.password),
+            },values)
+        }).catch((error)=>{
+            console.log('error',error)
+        })
+    }
+
+    if(activeModel.value==='mobileModel'){
+        loginForm.value.validate(['mobile','mobileCode']).then(()=>{
+            userLogin({
+                LoginType:2,
+                Mobile:values.mobile,
+                VerifyCode:values.mobileCode
+            })
+        }).catch((error)=>{
+            console.log('error',error)
+        })
+    }
+    
+    if(activeModel.value==='emailModel'){
+        loginForm.value.validate(['email','emailCode']).then(()=>{
+            userLogin({
+                LoginType:3,
+                Email:values.email,
+                VerifyCode:values.emailCode
+            })
+        }).catch((error)=>{
+            console.log('error',error)
+        })
+    }
+}
+function userLogin(params,values){
+    _apiLogin.apiUserLogin(params).then(res=>{
+        //账号异常,提醒用户用其他方式登陆
+        if(res.Ret===4011){
+            showToast('您的账号异常,请使用其他方式登陆')
+        }
+        //账号或密码错误,弹窗提示就行,因为PC端有特殊提示要求所以单独设置了状态码
+        if(res.Ret===4012){
+            showToast('账号或密码错误,请检查')
+            return
+        }
+        if(res.Ret!==200){
+            activeModel.value!=='ordinaryModel'&&[activeModel].value.getCodePic()
+        }
+
+        localStorage.setItem('token',res.Data.Authorization)
+        activeModel.value==='ordinaryModel'&&localStorage.setItem('account',Base64.encode(values.username))
+        activeModel.value==='ordinaryModel'&&localStorage.setItem('checkPass',Base64.encode(values.password))
+        const userInfo={
+            AdminName:res.Data.AdminName,
+            Authority:res.Data.Authority,
+            ProductName:res.Data.ProductName,
+            RealName:res.Data.RealName,
+            RoleName:res.Data.RoleName,
+            RoleTypeCode:res.Data.RoleTypeCode,
+            SysRoleTypeCode:res.Data.SysRoleTypeCode,
+            AdminId:res.Data.AdminId,
+            DepartmentName:res.Data.DepartmentName
+        }
+        localStorage.setItem('userInfo',Base64.encode(JSON.stringify(userInfo)))
+        router.replace('/')
+    })
+}
 </script>
 
 <template>
     <div class="login-page">
-        <van-form class="form-box" @submit="onSubmit">
+        <van-form class="form-box" ref="loginForm">
             <div class="logo-wrap">
                 <img class="logo" src="@/assets/imgs/logo_icon.png" alt="">
                 <div class="title">Bind on account,</div>
                 <div class="sub-title">sign in to continue</div>
             </div>
-            <van-field
-                class="form-input-box"
-                v-model="username"
-                name="username"
-                placeholder="请输入管理后台账号"
-                :rules="[{ required: true, message: '请填写账号' }]"
-            />
-            <van-field
-                class="form-input-box"
-                v-model="password"
-                type="password"
-                name="password"
-                placeholder="请输入密码"
-                :rules="[{ required: true, message: '请填写密码' }]"
-            />
+            <div class="ordinaryModel" v-show="activeModel==='ordinaryModel'">
+                <ordinary-model :activeModel="activeModel"/>
+            </div>
+            <div class="mobileModel" v-show="activeModel==='mobileModel'">
+                <mobile-model ref="mobileModel"
+                    :activeModel="activeModel"/>
+            </div>
+            <div class="emailModel" v-show="activeModel==='emailModel'">
+                <email-model ref="emailModel"
+                    :activeModel="activeModel"/>
+            </div>
             <div class="btn-box">
                 <van-button 
                     round 
                     block 
                     type="primary" 
-                    native-type="submit"
+                    @click="onSubmit2"
                 >登录</van-button>
             </div>
+            <!-- 选择其他登陆方法 -->
+            <div class="model-icon-box">
+                <div class="model-item" 
+                    @click="changeModel('ordinaryModel')"
+                    v-show="activeModel!=='ordinaryModel'">
+                    <div class="model-item-icon">
+                        <img src="@/assets/imgs/login/account-icon.png"/>
+                    </div>
+                </div>
+                <div class="model-item" 
+                    @click="changeModel('mobileModel')"
+                    v-show="activeModel!=='mobileModel'">
+                    <div class="model-item-icon">
+                        <img src="@/assets/imgs/login/phone-icon.png"/>
+                    </div>
+                    
+                </div>
+                <div class="model-item" 
+                    @click="changeModel('emailModel')"
+                    v-show="activeModel!=='emailModel'">
+                    <div class="model-item-icon">
+                        <img src="@/assets/imgs/login/email-icon.png"/>
+                    </div>
+                </div>
+            </div>
         </van-form>
         <img class="pad-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/login_img.png" alt="">
         <div class="mobile-bot-text">Long time&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;More profit</div>
@@ -100,16 +203,24 @@ const onSubmit = (values) => {
     
 }
 
-.form-input-box{
-    padding-left: 0;
-    padding-right: 0;
-    border-bottom: 1px solid $border-color;
-    margin-bottom: 40px;
-    &::after{
-        display: none;
+.model-icon-box{
+    margin-top: 100px;
+    display: flex;
+    width: 100%;
+    justify-content: center;
+    gap:120px;
+    .model-item-icon{
+        width:80px;
+        height:80px;
+        border-radius: 50%;
+        box-shadow: 0px 2px 12px 0px #0000001A;
+        box-sizing: border-box;
+        padding:16px;
+        img{
+            width:100%;
+        }
     }
 }
-
 .mobile-bot-text{
     margin-top: 200px;
     text-align: center;
@@ -148,17 +259,6 @@ const onSubmit = (values) => {
             }
             
         }
-        .form-input-box{
-            padding-left: 0;
-            padding-right: 0;
-            border: 1px solid $border-color;
-            border-radius: 4px;
-            padding: 12px 20px;
-            margin-bottom: 40px;
-            &::after{
-                display: none;
-            }
-        }
 
         .pad-img{
             display: block;
@@ -173,5 +273,23 @@ const onSubmit = (values) => {
     .mobile-bot-text{
         display: none;
     }
+    .model-icon-box{
+        margin-top: 50px;
+        display: flex;
+        width: 100%;
+        justify-content: center;
+        gap:60px;
+        .model-item-icon{
+            width:40px;
+            height:40px;
+            border-radius: 50%;
+            box-shadow: 0px 2px 12px 0px #0000001A;
+            box-sizing: border-box;
+            padding:6px;
+            img{
+                width:100%;
+            }
+        }
+    }
 }
 </style>

+ 104 - 0
src/views/login/EmailModel.vue

@@ -0,0 +1,104 @@
+<script setup>
+import {ref,computed,watch} from 'vue'
+import {_apiLogin} from '@/api/user'
+import {useLogin} from './hooks/useLogin'
+const props = defineProps({
+    activeModel:{//当前选择的登陆模式
+        type:String,
+        default:'ordinaryModel'
+    }
+})
+const { picSrc,picId,getPicCode,
+        timer,codeStr,codeCountDown,countDown,getCode
+      } = useLogin()
+watch(()=>props.activeModel,()=>{
+    if(props.activeModel==='emailModel'){
+        if(!timer.value){
+            getPicCode()
+            codeStr.value = '获取验证码'
+        }
+    }
+})
+
+//邮箱
+let email = ref('')
+//邮箱的图形验证码
+let emailPicCode = ref('')
+//邮件验证码
+let emailCode = ref('')
+const isEmailCountDown = computed(()=>{
+    return codeCountDown.value<60&&codeCountDown.value>0
+})
+
+const emailPattern = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/
+function checkForm(){
+    if(isEmailCountDown.value) return 
+    //是否是重新获取验证码
+    if(codeStr.value==='重新获取'){
+        //清空图形验证码输入框,引导用户重新输入
+        getPicCode()
+        emailPicCode.value = ''
+    }
+    //检查邮箱和图形验证码是否正确
+    if(!emailPicCode.value.length){
+        showToast('请输入图形验证码')
+        return
+    }
+    if(!email.value.length){
+        showToast('请输入邮箱')
+        return 
+    }
+    if(!emailPattern.test(email.value)){
+        showToast('请输入正确的邮箱')
+        return 
+    }
+
+    getCode({
+        email:email.value,
+        picCode:emailPicCode.value
+    },'email',1)
+}
+</script>
+
+<template>
+    <van-field
+        class="form-input-box form-mobile-box"
+        v-model="email"
+        name="email"
+        placeholder="请输入邮箱"
+        :rules="[
+            { required: true, message: '请输入邮箱' },
+            {validator:(val)=>emailPattern.test(val),message:'请输入正确的邮箱'}
+        ]"
+    />
+    <van-field
+        class="form-input-box form-mobile-box"
+        v-model="emailPicCode"
+        name="picCode"
+        placeholder="请输入图形验证码"
+        :rules="[{ required: true, message: '请输入图形验证码' }]"
+    >
+        <template #button>
+            <div class="pic-filed" @click="getPicCode">
+                <img :src="picSrc" />
+            </div>
+        </template>
+    </van-field>
+    <van-field
+        class="form-input-box form-mobile-box"
+        v-model="emailCode"
+        name="emailCode"
+        placeholder="请输入邮件验证码"
+        :rules="[{ required: true, message: '请输入邮件验证码' }]"
+    >
+        <template #button>
+            <span class="btn-text" :class="{'disabled':isEmailCountDown}"
+                @click="checkForm"
+            >{{codeStr}}</span>
+        </template>
+    </van-field>
+</template>
+
+<style scoped lang="scss">
+@import "./style/common.scss";
+</style>

+ 12 - 0
src/views/login/ForgetPassModel.vue

@@ -0,0 +1,12 @@
+<script setup>
+</script>
+
+<template>
+    <div>
+
+    </div>
+</template>
+
+<style scoped lang="scss">
+
+</style>

+ 146 - 0
src/views/login/MobileModel.vue

@@ -0,0 +1,146 @@
+<script setup>
+import {ref,computed,watch} from 'vue'
+import { showToast } from 'vant';
+import { _apiLogin } from '@/api/user'
+import { useLogin } from './hooks/useLogin'
+import { isMobile } from 'vant/lib/utils';
+
+
+const props = defineProps({
+    activeModel:{//当前选择的登陆模式
+        type:String,
+        default:'ordinaryModel'
+    }
+})
+
+const { picSrc,picId,getPicCode,
+        timer,codeStr,codeCountDown,countDown,getCode
+    } = useLogin()
+
+watch(()=>props.activeModel,()=>{
+    if(props.activeModel==='mobileModel'){
+        if(!timer.value){
+            getPicCode()
+            codeStr.value = '获取验证码'
+        }
+    }
+})
+
+//手机号
+let mobile = ref('')
+//图形验证码
+let mobilePicCode = ref('')
+//短信验证码
+let mobileCode = ref('')
+
+//短信验证码倒计时相关
+const isMobileCountDown = computed(()=>{
+    return codeCountDown.value<60&&codeCountDown.value>0
+})
+
+//区号相关
+let mobileTelAreaCode=ref('86')
+let showMobilePicker = ref(false)
+let TelAreaCodeList = ref([])
+//获取区号
+function getTelAreaCode(){
+    _apiLogin.getPhoneAreaCode().then(res=>{
+        if(res.Ret!==200) return 
+        TelAreaCodeList.value = res.Data||[]
+    })
+}
+getTelAreaCode()
+
+//校验表单
+function checkForm(){
+    if(isMobileCountDown.value) return 
+    //是否是重新获取验证码
+    if(codeStr.value==='重新获取'){
+        //清空图形验证码输入框,引导用户重新输入
+        getPicCode()
+        mobilePicCode.value = ''
+    }
+    //检查手机号和图形验证码是否正确
+    if(!mobilePicCode.value.length){
+        showToast('请输入图形验证码')
+        return
+    }
+    if(!mobile.value.length){
+        showToast('请输入手机号')
+        return 
+    }
+    if(!isMobile(mobile.value)){
+        showToast('请输入正确的手机号')
+        return 
+    }
+    getCode({ mobile:mobile.value,
+              areaCode:mobileTelAreaCode.value,
+              picCode:mobilePicCode.value
+            },'mobile',1)
+
+}
+
+defineExpose({
+    getPicCode
+})
+</script>
+
+<template>
+    <van-field
+        class="form-input-box form-mobile-box tel"
+        v-model="mobile"
+        type="tel"
+        name="mobile"
+        placeholder="请输入手机号"
+        :rules="[
+            { required: true, message: '请输入手机号' },
+            { validator:(val)=>isMobile(val),message:'请输入正确的手机号'}
+        ]"
+    >
+        <!-- 选择区号的选择框-->
+        <template #button>
+            <div class="area-filed" @click="showMobilePicker = true" >
+                <span>+{{mobileTelAreaCode}}</span>
+                <van-icon name="arrow-down" />
+            </div>
+        </template>
+    </van-field>
+    <van-popup v-model:show="showMobilePicker" position="bottom">
+        <van-picker
+            :columns-field-names="{ text: 'Name',value: 'Value',}"
+            :columns="TelAreaCodeList"
+            @confirm="({selectedValues})=>{mobileTelAreaCode= selectedValues[0];showMobilePicker = false}"
+            @cancel="showMobilePicker = false"
+        />
+    </van-popup>
+    <van-field
+        class="form-input-box form-mobile-box"
+        v-model="mobilePicCode"
+        name="picCode"
+        placeholder="请输入图形验证码"
+        :rules="[{ required: true, message: '请输入图形验证码' }]"
+    >
+        <template #button>
+            <div class="pic-filed" @click="getPicCode">
+                <img :src="picSrc" />
+            </div>
+        </template>
+    </van-field>
+    <van-field
+        class="form-input-box form-mobile-box"
+        v-model="mobileCode"
+        name="mobileCode"
+        placeholder="请输入短信验证码"
+        :rules="[{ required: true, message: '请输入短信验证码' }]"
+    >
+        <template #button>
+            <span class="btn-text" :class="{'disabled':isMobileCountDown}"
+                @click="checkForm"
+            >{{codeStr}}</span>
+        </template>
+    </van-field>
+</template>
+
+<style scoped lang="scss">
+@import "./style/common.scss";
+</style>

+ 37 - 0
src/views/login/OrdinaryModel.vue

@@ -0,0 +1,37 @@
+<script setup>
+import {Base64} from 'js-base64'
+import {ref} from 'vue'
+const props = defineProps({
+    activeModel:{//当前选择的登陆模式
+        type:String,
+        default:'ordinaryModel'
+    }
+})
+
+//账号密码
+let username=ref(localStorage.getItem('account')?Base64.decode(localStorage.getItem('account')):'')
+let password=ref(localStorage.getItem('checkPass')?Base64.decode(localStorage.getItem('checkPass')):'')
+
+</script>
+
+<template>
+    <van-field
+        class="form-input-box"
+        v-model="username"
+        name="username"
+        placeholder="请输入管理后台账号"
+        :rules="[{ required: true, message: '请填写账号' }]"
+    />
+    <van-field
+        class="form-input-box"
+        v-model="password"
+        type="password"
+        name="password"
+        placeholder="请输入密码"
+        :rules="[{ required: true, message: '请填写密码' }]"
+    />
+</template>
+
+<style scoped lang="scss">
+@import "./style/common.scss";
+</style>

+ 60 - 0
src/views/login/hooks/useLogin.js

@@ -0,0 +1,60 @@
+import {ref} from 'vue'
+import { showToast } from 'vant';
+import {_apiLogin} from '@/api/user'
+
+export function useLogin(){
+    //图形验证码src
+    let picSrc = ref('')
+    //图形验证码id
+    let picId = ref('')
+
+    //获取图形验证码
+    async function getPicCode(){
+        const res = await _apiLogin.apiGetPicCode()
+        if(res.Ret!==200) return
+        picId.value = res.Data.Id
+        picSrc.value = res.Data.Base64Blob
+    }
+
+    async function getCode({mobile,email,picCode,areaCode},model,source){
+        const res = await _apiLogin.apiGetVerifyCode({
+            VerifyType:model==='mobile'?1:2,
+            CaptchaId:picId.value,
+            CaptchaCode:picCode,
+            Mobile:mobile||'',
+            Email:email||'',
+            TelAreaCode:areaCode+''||'',
+            Source:source
+        })
+        if(res.Ret!==200) return 
+        showToast('验证码已发送')
+        //60秒倒计时
+        countDown()
+        timer = setInterval(()=>{
+            countDown()
+        },1000)
+    }
+    let timer = ref(0)
+    let codeStr = ref('获取验证码')
+    let codeCountDown = ref(60)
+    function countDown(){
+        codeCountDown.value--
+        codeStr.value = `重新获取(${codeCountDown.value})秒`
+        if(codeCountDown.value<=0){
+            clearInterval(timer)
+            codeStr.value = '重新获取'
+        }
+    }
+
+
+
+    return {
+        picId,
+        picSrc,
+        getPicCode,
+
+        timer,codeStr,codeCountDown,
+        countDown,
+        getCode,
+    }
+}

+ 80 - 0
src/views/login/style/common.scss

@@ -0,0 +1,80 @@
+.form-input-box{
+    padding-left: 0;
+    padding-right: 0;
+    border-bottom: 1px solid $border-color;
+    margin-bottom: 40px;
+    &::after{
+        display: none;
+    }
+}
+.form-mobile-box{
+    &.tel{
+        :deep(.van-field__body){
+            flex-direction: row-reverse;
+        }
+        
+    }
+    .area-filed{
+        width:110px;
+        text-align: left;
+    }
+    .pic-filed{
+        width:180px;
+        height: 60px;
+        box-sizing: border-box;
+        padding: 0 15px;
+        background-color:#D9D9D9;
+        img{
+            width:100%;
+            height: 100%;
+        }
+    }
+    .btn-text{
+        color: #0052D9;
+        &.disabled{
+            color:#999999;
+        }
+    }
+}
+@media screen and (min-width:$media-width) {
+    .form-input-box{
+        padding-left: 0;
+        padding-right: 0;
+        border: 1px solid $border-color;
+        border-radius: 4px;
+        padding: 12px 20px;
+        margin-bottom: 40px;
+        &::after{
+            display: none;
+        }
+    }
+    .form-mobile-box{
+        &.tel{
+            :deep(.van-field__body){
+                flex-direction: row-reverse;
+            }
+            
+        }
+        .area-filed{
+            width:110px;
+            text-align: left;
+        }
+        .pic-filed{
+            width:80px;
+            height: 24px;
+            box-sizing: border-box;
+            padding: 0 15px;
+            background-color:#D9D9D9;
+            img{
+                width:100%;
+                height: 100%;
+            }
+        }
+        .btn-text{
+            color: #0052D9;
+            &.disabled{
+                color:#999999;
+            }
+        }
+    }
+}