---
# System prepended metadata

title: 2-1.Vue外送平台App-登入介面
tags: [Vue, async await, Login]

---

# 2-1.Vue外送平台App-登入介面
## 登入介面相關效果

Login.vue
### 1. 切換登入方式
![](https://i.imgur.com/kDYy5oj.png)
* 當點擊短信登入或密碼登入能切換效果
* 此切換效果搭配樣式 
class="on" style="on{color:green font-weight:700 border-bottom:2px solid green}"

* loginWay 切換功能使用boolean的ture ,false來定義進行切換功能
```htmlmixed=
<template>
<div class="loginContainer">
    <div class="loginInner">
        <div class="login_header">
            <h2 class="login_logo">Vue外送平台</h2>
            <div class="login_header_title">
                    //加入切換點擊監聽事件
                <a href="javascript:;" :class="{on : loginWay}" @click="loginWay=true">短訊登入</a>
                <a href="javascript:;" :class="{on : !loginWay}" @click="loginWay=false">密碼登入</a>
            </div>
        </div>
        <div class="login_content">
            <form>
            //判斷如果現在是ture會@click="loginWay=true"事件一起轉動到這邊表格樣式
                <div :class="{on : loginWay}">
                    <section class="login_message">
                        <input type="tel" maxlength="11" placeholder="手機號碼">
                        <button disabled="disabled" class="get_verification">獲得驗證碼</button>
                    </section>
                    <section class="login_verification">
                        <input type="tel" maxlength="8" placeholder="驗證碼">
                    </section>
                    <section class="login_hint">
                        溫馨提醒：未註冊Vue外送平台帳號的手機，登入時將自動註冊，且代表已同意
                        <a href="javascript:;">《用戶服務協議》</a>
                    </section>
                </div>
                //判斷如果現在是false會連著@click="loginWay=false事件一起轉動到這邊表格樣式
                <div :class="{on : !loginWay}">
                    <section>
                        <section class="login_message">
                            <input type="tel" maxlength="11" placeholder="手機/郵箱/用户名">
                        </section>
                        <section class="login_verification">
                            <input type="tel" maxlength="8" placeholder="密码">
                            <div class="switch_button off">
                                <div class="switch_circle"></div>
                                <span class="switch_text">...</span>
                            </div>
                        </section>
                        <section class="login_message">
                            <input type="text" maxlength="11" placeholder="驗證碼">
                            <img class="get_verification" src="../Login/images/captcha.svg" alt="captcha">
                        </section>
                    </section>
                </div>
                <button class="login_submit">登入</button>
            </form>
            <a href="javascript:;" class="about_us">關於我们</a>
        </div>
        <!--跳轉道上一頁-->
        <span href="javascript:" class="go_back" @click="$router.back()">
            <i class="iconfont icon-jiantou2"></i>
        </span>
    </div>
</div>
</template>
<script>
export default {
    data() {
        return {
        //需先初始化
            loginWay: true, //false:短訊登入 true:密碼登入
        }
    },
}
</script>
```
### 2. 手機合法檢查
* 運用正則表達式，手機格式是否正確
* 要驗證短訊登入->手機號碼
 ### 3. 倒數計時效果
* 當手機號碼格式與驗證相符時，點取獲得驗證碼會切換成倒數計器
```htmlmixed=
<!--<from>表單中-->
 <div :class="{on : loginWay}">
    <section class="login_message">
                                <!--在v-model綁定與api接口一樣的值-->
        <input type="text" maxlength="10" placeholder="手機號碼" v-model="phone">
        <!--:disabled="!rightPhone"如果正則表達式在input中格式正確 那麼就變化成倒數計時器  -->
        <!--:class="{right_phone :rightPhone} 綁定class屬性 在樣式中加入right_phone{color:black} 
                rightPhone格式正確，啟動right_phone樣式
        -->
        <button :disabled="!rightPhone" class="get_verification" :class="{right_phone :rightPhone}"             @click.prevent="getCode">
        <!-- computeTime 如果大於零 執行`已發送(${computeTime}s)` 不然就執行 '獲得驗證碼' -->
            {{ computeTime > 0 ? `已發送(${computeTime}s)`:'獲得驗證碼'}}</button>
    </section>
    <section class="login_verification">
        <input type="text" maxlength="8" placeholder="驗證碼">
    </section>
    <section class="login_hint">
        溫馨提醒：未註冊Vue外送平台帳號的手機，登入時將自動註冊，且代表已同意
        <a href="javascript:;">《用戶服務協議》</a>
    </section>
</div>
<script>
    export default {
        data() {
            return {
                loginWay: true, //false:短訊登入 true:密碼登入
            }
        },
        computed: {
            // 運用計算屬性來運算正則表達式
            rightPhone() {
                return /^[0-9]{10}$/g.test(this.phone)
            }
    }
</script>
```

### 4. 切換顯示或隱藏密碼
* 密碼登入->密碼->製作隱藏切換按鈕後能顯示密碼
* 不顯示和顯示密碼按鈕切換樣式
* ![](https://i.imgur.com/7iVumY6.png)

```htmlmixed=
<section>
    <section class="login_message">
        <input type="tel" maxlength="11" placeholder="手機/郵箱/用户名" v-model="name">
    </section>
    <section class="login_verification">
        <!--再增加一個input -> v-model="pwd"(要跟api的值相同) 運用v-if v-else來定義ture or false-->
        <input type="text" maxlength="8" placeholder="密码" v-model="pwd" v-if="pwdShow">
        <input type="password" maxlength="8" placeholder="密码" v-model="pwd" v-else="!pwdShow">
        <!-- 綁定class  pwdShow 是false 顯示on=pwdShow 不然就顯示 off=!pwdShow -->
        <div class="switch_button" :class="pwdShow ? 'on' : 'off'" @click="pwdShow = !pwdShow">
            <!--增加style="switch_circle_right{transform: translateX(30px);  : pwdShow 
            (兩個條件都符合才可以成立此功能)}"-->
            <div class="switch_circle" :class="{switch_circle_right : pwdShow}"></div>
            <!--pwdShow 如果城裡那麼顯示abc 不然就...--->
            <span class="switch_text">{{pwdShow? 'abc':'...'}}</span>
        </div>
    </section>
    <section class="login_message">
        <input type="text" maxlength="11" placeholder="驗證碼">
        <img class="get_verification" src="../Login/images/captcha.svg" alt="captcha">
    </section>
</section>
<script>
export default {
    data() {
        return {
            pwdShow: false, //不顯示密碼
        }
    }
</script>
```

### 5. 前後台正規表達驗證與提示(alert)
* 收集form 所有input中的數據 v-model=""(裡面值必須對應api接口) 
* <button class="login_submit">登入</button>點擊後默認直接提交表單，在form 中禁止submit冒泡事件(prevent)
* 收集完數據並在data中初始化數據 
* 運用methods方法->判斷現在是短信登入還是密碼登入並且驗證格式是否與定義的相符
* 新創建AlertTip Component 
 1. Login.vue接收AlertTip組件和屬性
 2. 完成錯誤訊息視窗靜態組件
 3. 判斷什麼時候會出現此視窗
 4. 根據格式判斷後alert中的文字變化
```htmlmixed=
<template>
<div class="loginContainer">
    <div class="loginInner">
        <div class="login_header">
            <h2 class="login_logo">Vue外送平台</h2>
            <div class="login_header_title">
                <a href="javascript:;" :class="{on : loginWay}" @click="loginWay=true">短訊登入</a>
                <a href="javascript:;" :class="{on : !loginWay}" @click="loginWay=false">密碼登入</a>
            </div>
        </div>
        <div class="login_content">
            <!--阻止冒泡事件 收集完input所有數據後,透過login 方法來->判斷現在是訊息登入曬密碼登入->判斷數據是否與自定義                    的格式相同 -->ㄨ            <form @submit.prevent="login">
                <div :class="{on : loginWay}">
                    <section class="login_message">
                        <!--收集數據:在v-model綁定與api接口一樣的值-->
                        <input type="text" maxlength="10" placeholder="手機號碼" v-model="phone">
                        <button :disabled="!rightPhone" class="get_verification" :class="{right_phone :rightPhone}" @click.prevent="getCode">
                            {{ computeTime > 0 ? `已發送(${computeTime}s)`:'獲得驗證碼'}}</button>
                    </section>
                    <section class="login_verification">
                    <!--收集數據:在v-model綁定與api接口一樣的值-->
                        <input type="text" maxlength="6" placeholder="驗證碼" v-model="code">
                    </section>
                    <section class="login_hint">
                        溫馨提醒：未註冊Vue外送平台帳號的手機，登入時將自動註冊，且代表已同意
                        <a href="javascript:;">《用戶服務協議》</a>
                    </section>
                </div>
                <div :class="{on : !loginWay}">
                    <section>
                        <section class="login_message">
                        <!--收集數據:在v-model綁定與api接口一樣的值-->
                            <input type="tel" maxlength="11" placeholder="手機/郵箱/用户名" v-model="name">
                        </section>
                        <section class="login_verification">
                        <!--收集數據:在v-model綁定與api接口一樣的值-->
                            <input type="text" maxlength="8" placeholder="密码" v-model="pwd" v-if="pwdShow">       
                            <input type="password" maxlength="8" placeholder="密码" v-model="pwd" v-else="!pwdShow">
                            <div class="switch_button" :class="pwdShow ? 'on' : 'off'" @click="pwdShow = !pwdShow">
                                <div class="switch_circle" :class="{switch_circle_right : pwdShow}"></div>
                                <span class="switch_text">{{pwdShow? 'abc':'...'}}</span>
                            </div>
                        </section>
                        <section class="login_message">
                        <!--收集數據:在v-model綁定與api接口一樣的值-->
                            <input type="text" maxlength="11" placeholder="驗證碼" v-model="captcha">
                            <img class="get_verification" src="../Login/images/captcha.svg" alt="captcha">
                        </section>
                    </section>
                </div>
                <button class="login_submit">登入</button>
            </form>
            <a href="javascript:;" class="about_us">關於我們</a>
        </div>
        <!--跳轉道上一頁-->
        <span href="javascript:" class="go_back" @click="$router.back()">
            <i class="iconfont icon-jiantou2"></i>
        </span>
    </div>
    <!--1.完成錯誤訊息視窗靜態組件，
        2.定義什麼時候會出現此視窗
        3.裡面的訊息文字變化
    -->
    <AlertTip :alertText="AlertText" v-show="showAlert" @closeTip="closeTip" />
</div>
</template>
<script>
import AlertTip from '../../components/AlertTip/AlertTip';
export default {
    data() {
        return {
            loginWay: true, //true:短訊登入 false:密碼登入
            computeTime: 0, //計時的時間
            pwdShow: false, //是否顯示密碼
            phone: '', //手機號碼
            code: '', //短信驗證碼
            name: '', //用戶名
            pwd: '', //密码
            captcha: '', //圖形驗證碼
            showAlert: false, //是否顯示錯誤訊息視窗 
            AlertText: '' //錯誤訊息裡面文字
        }
    },
    computed: {
        rightPhone() {
            return /^[0-9]{10}$/g.test(this.phone)
        }
    },
    methods: {
        // 異步獲取短信驗證碼
        getCode() {
            // 开始倒计时
            if (this.computeTime === 0) {
                this.computeTime = 60
                // // 启动循环定时器, 每隔 1s 减少 1
                let interval = setInterval(() => {
                    this.computeTime--
                    //什麼時候要停止??
                    if (this.computeTime === 0) {
                        //interval一旦執行會產生回調函數(要定義const or let)
                        clearInterval(this.interval)
                    }
                }, 1000)
            }
        },
        //前台表單驗證
        login() {
            //假如loginWay = true:短訊登入
            if (this.loginWay) {
                const {
                    rightPhone,
                    phone,
                    code
                } = this
                if (!this.rightPhone) {
                    //手機號碼格式不正確
                    this.showAlert = true
                    this.AlertText = '手機號碼格式不正確'
                } else if (!(/^\d{6}$/gi.test(this.code))) {
                    //短信驗證碼格式不正確
                    this.showAlert = true
                    this.AlertText = '短信驗證碼格式不正確'
                }
                //不然密碼登入
            } else {
                const {
                    name,
                    pwd,
                    captcha
                } = this
                if (!this.name) {
                    //用户名格式不正確
                    this.showAlert = true
                    this.AlertText = '用户名格式不正確'
                } else if (!this.pwd) {
                    //密碼格式不正確
                    this.showAlert = true
                    this.AlertText = '密碼格式不正確'
                } else if (!this.captcha) {
                    //圖形驗證不正確
                    this.showAlert = true
                    this.AlertText = '圖形驗證不正確'
                }
            }
        },
        //關閉提示框
        closeTip() {
            this.showAlert = false
            this.AlertText = ''
        }
    },
    components: {
        AlertTip
    }
}
</script>

<!-- src > components > AlertTip > AlertTip.vue-->
<template>
<div class="alert_container">
    <section class="tip_text_container">
        <div class="tip_icon">
            <span></span>
            <span></span>
        </div>
        <p class="tip_text">{{alertText}}</p>
        <div class="confrim" @click="closeTip">確定</div>
    </section>
</div>
</template>

<script>
export default {
    props: {
        alertText: String
    },
    methods: {
        closeTip() {
            this.$emit('closeTip')
        }
    },
}
</script>

<style lang="scss">
@import '../../assets/style/common/_mixins.scss';

@keyframes tipMove {
    100% {
        transform: scale(1)
    }

    35% {
        transform: scale(.8)
    }

    70% {
        transform: scale(1.1)
    }

    100% {
        transform: scale(1)
    }
}

.alert_container {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 200;
    background: rgba(0, 0, 0, .5);

    .tip_text_container {
        position: absolute;
        top: 50%;
        left: 50%;
        //transform: translate(-50%, -50%);
        margin-top: -110px;
        margin-left: -110px;
        width: 60%;
        animation: tipMove .5s;
        background-color: rgba(255, 255, 255, 1);
        border: 1px;
        padding-top: 20px;
        display: flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
        border-radius: 5px;

        .tip_icon {
            width: 55px;
            height: 55px;
            border: 2px solid #f8cb86;
            border-radius: 50%;
            font-size: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;

            span:nth-of-type(1) {
                width: 2px;
                height: 30px;
                background-color: #f8cb86;
            }

            span:nth-of-type(2) {
                width: 2px;
                height: 2px;
                border: 1px;
                border-radius: 50%;
                margin-top: 2px background-color #f8cb86;
            }
        }

        .tip_text {
            font-size: 14px;
            color: #333;
            line-height: 20px;
            text-align: center;
            margin-top: 10px;
            padding: 0 5px;

        }

        .confrim {
            font-size: 18px;
            font-weight: bold;
            margin-top: 10px;
            background-color: #4cd964;
            width: 100%;
            text-align: center;
            line-height: 35px;
            border: 1px;
            color: #fff;
            border-bottom-left-radius: 5px;
            border-bottom-right-radius: 5px;
        }

    }
}
</style>

```


---


## 前後台交互功能
### 1. 動態一次性圖型驗證碼
* 不需與透過ajax交互
* 如何讓介面點即時切換下一個圖形驗證碼?
* 運用$refs
 1. 想要從 父元件 取得 子元件 的資料時
 2. 想要取得 DOM Element 的資訊時（例如寬度）
 3. $refs 這個 Instance Property 的來解決上述問題；只要在透過 ref 這個 Attribute 就可以讓 Vue 定位取得你想要的 Instance。
 4. 做法很簡單，只要在你想要的 Element 上給定一個 ref value，在元件中只要搭配 this.$refs.<your_ref_value> 就可以取得這個 Element or 元件的 Instance 了。
```htmlmixed=
<section class="login_message">
    <input type="text" maxlength="11" placeholder="驗證碼" v-model="captcha">
    <!--scr放入api url 給定ref value -->
    <img class="get_verification" src="http://localhost:4000/captcha" alt="captcha"     @click="getCaptcha" ref="captcha">
<script>
</section>
    export default {
            methods:{

            //圖形介面切換
            getCaptcha() {
                    //在元件中搭配this.$refs.value
                this.$refs.captcha.src = 'http://localhost:4000/captcha?time=' + Date.now()
            },
        }
    }
</script>
```
### 2. 動態一次性短信驗證碼
### 3. 短信登入
### 4. 密碼登入
### 5. 登出

---
### async await

| async | await |
| -------- | -------- | 
| 宣非告被定義成非同步函式，而且只能回傳一個promise    | 等待一個Promise的回傳(只能用在async function裡)     | 

###### tags: `Vue` `Login` `async await`