製作登入介面 step1. 我們在components資料夾中新增一個pages資料夾,並將我們要新增的元件檔都塞在這裡。 接著,在裡面新增一個login.vue元件檔。
step2. 在router資料夾中的index.js中,引入login元件檔,並為這個login元件新增一些屬性和路徑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ---index.js檔案--- import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import Login from '@/components/pages/login' // 引入login元件檔 Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { path:/login, name: 'Login', component: Login } ] })
step3. 接下來,到 Bootstrap 提供的登入頁面的公版,官方登入公版 可以找到提供的公版。 打開這個連結之後,你可以直接對該頁面按右鍵查看原始碼。 接著,把login的html版型內容,直接貼到login.vue元件檔中。如此,應該可以看到一些登入內容呈現在畫面上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ---Login.vue 元件檔--- <template> <div> <form class="form-signin"> <h1 class="h3 mb-3 fw-normal">請先登入</h1> <label for="inputEmail" class="visually-hidden">Email address</label> <input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus> <label for="inputPassword" class="visually-hidden">Password</label> <input type="password" id="inputPassword" class="form-control" placeholder="Password" required> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2021</p> </form> </div> </template>
step4. 但是,剛剛引入的畫面樣式是不正確的,所以,在剛剛的 Bootstrap 的公版的 HTML 原始碼內容,可以看到在 App 以下引入該login內容的專用css 接著,把它的超連結打開,然後,把這個頁面中的css樣式,貼到login.vue元件中。 像下面這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <style scoped> html, body { height: 100%; } body { display: flex; align-items: center; padding-top: 40px; padding-bottom: 40px; background-color: #f5f5f5; } .form-signin { width: 100%; max-width: 330px; padding: 15px; margin: auto; } .form-signin .checkbox { font-weight: 400; } .form-signin .form-control { position: relative; box-sizing: border-box; height: auto; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; } </style>
step5. 到六角提供的登入api中六角提供的登入api 接著,會看到相關 api的 連結 和 參數
step6. 在login.vue元件檔為它的data
新增回傳的內容,並在input
欄位分別用v-model
綁定連動user.username和user.password
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ---login.vue元件檔--- <template> <div> <form class="form-signin"> <h1 class="h3 mb-3 fw-normal">請先登入</h1> <label for="inputEmail" class="visually-hidden">Email address</label> <input type="email" id="inputEmail" class="form-control" placeholder="Email address" v-model="user.username" required autofocus> //綁定user.username <label for="inputPassword" class="visually-hidden">Password</label> <input type="password" id="inputPassword" v-model="user.password" class="form-control" placeholder="Password" required> //綁定user.password <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2021</p> </form> </div> </template> <script> export default { name: 'login', data () { return { user:{ username:'', password:'' } } } } </script>
setp7. 在login.vue元件中的form
為它加入一個連動submit
的事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ---login.vue元件檔--- <template> <div> <form class="form-signin" @submit.prevent="signin"> // 加入 submit 事件 <h1 class="h3 mb-3 fw-normal">請先登入</h1> <label for="inputEmail" class="visually-hidden">Email address</label> <input type="email" id="inputEmail" class="form-control" placeholder="Email address" v-model="user.username" required autofocus> <label for="inputPassword" class="visually-hidden">Password</label> <input type="password" id="inputPassword" v-model="user.password" class="form-control" placeholder="Password" required> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2021</p> </form> </div> </template> <script> export default { name: 'login', data () { return { user:{ username:'', password:'' } } }, methods:{ signin:function(){ const api = `${process.env.APIPATH}/signin`; const vm = this; this.$http.post(api, vm.user).then((response) => { if(response.data.success){ vm.$router.push('/'); } }) } } } </script>
a. 你可以看到,我們在form
標籤中加入了submit
事件,並連動到signin方法。 要注意的是,我們在submit
後面還有加入prevent
的修飾符,用來去除原本的預設事件。 b. 然後,在signin的方法中,有一個api,那這個api其實就跟App.vue中的差不多,只不過是將後面的內容替換掉,只留下signin的路徑。 c. 在串接登入的api中,我們還有將username和password透過vm.user物件的方式,丟到伺服器端來驗證。 d. 然後,你要注意,這邊的axios的方法是用post
不是用get
,因為,我們是要將帳號密碼都丟到伺服器端。 e. 那在response的data屬性中,有一個success
屬性,用來確認是否登入成功, 如果登入成功的話,我們會利用vm.$router.push('/')
,來將頁面跳轉回首頁。
製作登出頁面 我們直接先在Helloworld.vue元件檔,做一個登出的小功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 <template> <div class="hello"> <h1>{{ msg }}</h1> <h2>Essential Links</h2> <ul> <li> <a href="https://vuejs.org" target="_blank">Core Docs</a> </li> <li> <a href="https://forum.vuejs.org" target="_blank">Forum</a> </li> <li> <a href="https://chat.vuejs.org" target="_blank" > Community Chat </a> </li> <li> <a href="https://twitter.com/vuejs" target="_blank" > Twitter </a> </li> <br> <li> <a href="http://vuejs-templates.github.io/webpack/" target="_blank" > Docs for This Template </a> </li> </ul> <h2>Ecosystem</h2> <ul> <li> <a href="http://router.vuejs.org/" target="_blank" > vue-router </a> </li> <li> <a href="http://vuex.vuejs.org/" target="_blank" > vuex </a> </li> <li> <a href="http://vue-loader.vuejs.org/" target="_blank" > vue-loader </a> </li> <li> <a href="https://github.com/vuejs/awesome-vue" target="_blank" > awesome-vue </a> </li> </ul> <a href="#" class="btn btn-outline-danger text-danger" @click.prevent="signOut">登出</a> </div> </template> <script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } }, methods:{ signOut:function(){ const api = `${process.env.APIPATH}/logout`; const vm = this; this.$http.post(api, vm.user).then((response) => { console.log(response.data); if(response.data.success){ vm.$router.push('/login'); } }) } } } </script>
a. 首先,我們要先將api的網址改成logout的api網址。 b. 你可以看到,我們新增了一個登出的按鈕,然後,連動到signOut的函式,並在成功登出後,跳轉回login頁面。
登入 API 補充說明 (跨域) 為了解決跨域的cookie存取問題,所以,課堂上的signin的api的網址有改。 你要修改的有兩部分
signin裡面的api網址
在main.js裡面加入axios.defaults.withCredentials = true;
語法,來讓網址存取cookie。
你改成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ---login.vue元件檔--- <script> export default { name: 'login', data () { return { user:{ username:'', password:'' } } }, methods:{ signin:function(){ const api = `${process.env.APIPATH}/admin/signin`; const vm = this; this.$http.post(api, vm.user).then((response) => { console.log(response.data); if(response.data.success){ vm.$router.push('/'); } }) } } } </script>
然後,在你的main.js檔案中,加入以下的語法axios.defaults.withCredentials = true;
用來存取每次的cookie值。
注意~~~~~~~~~ 如果,你沒有按照Vue課程中的”課程補充範例程式碼”裡面,所說的要加入像token
和expired
的內容的話,你的頁面是不會有存取cookie的內容的喔。 所以,在singin的內容存取token
和cookie
的方法,你要去看課程”課程補充範例程式碼”的內容,才行。
課程補充範例程式碼-加入token和cookie的方法 因應Chrome大幅度的改版,導致影響原本的Cookies的運作,所以,此小節是來說明怎麼解決以上的狀況。
同源政策導致cookie傳遞的影響 因為,同源政策的影響,讓原本後端要將cookie寫入到前端時,會被擋掉, 所以,導致,前端無法夾帶原本由後端塞入到前端的cookie來向後端索取相對應的資訊。
調整程式碼 在MDN的官網上有提供如何讀出cookie和如何把cookie寫入的範例讀出cookie 寫入cookie
後端會新增傳送data,而這個data裡面有夾帶一個token。 接著,要由前端負責將這個token塞到cookie裡。
再來,前端再將這個token
塞入cookie
,送回後端,來索取回應。
step1. 接下來,我們先把原本在signin的方法中,轉址的程式碼註解掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ---login.vue元件檔--- <script> export default { name: 'login', data () { return { user:{ username:'', password:'' } } }, methods:{ signin:function(){ const api = `${process.env.APIPATH}/admin/signin`; const vm = this; this.$http.post(api, vm.user).then((response) => { if(response.data.success){ const token = response.data.token; const expired = response.data.expired; console.log(token, expired); //vm.$router.push('/'); --- 註解 } }) } } } </script>
接下來,我們可以成功讀到由後端傳入的token
資訊,接著,就將這些資訊寫到cookie
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ---login.vue元件檔--- methods:{ signin:function(){ const api = `${process.env.APIPATH}/admin/signin`; const vm = this; this.$http.post(api, vm.user).then((response) => { if(response.data.success){ const token = response.data.token; // 取得 token const expired = response.data.expired; // 取得 該 cookie 的有效期限 document.cookie = `hextoken=${token}; expires=${new Date(expired)}`; vm.$router.push('/'); } }) } }
上面的例子中,加入寫入cookie的程式碼。 接著,在成功登入之後,打開網頁上儲存的cookies。
可以看到,我們自訂義的hextoken值。 接著,把原本login.vue元件檔註解掉的轉址那行程式碼給回復到原本的樣子。
step2. 接著,我們主動將cookie的內容往後端發送。 這部分要等到到製作DashBoard的時候,才會有實際的作用,就是用來將後台修改的資料,以管理者的身分,傳送到後端。
驗證登入及 Vue Router 的配置 此小節在教導,怎麼阻止用戶,在沒有經過驗證下,直接通過修改網址就可以去到指定頁面的問題。
導航守衛 在vue的官方文件 中,有一個導航守衛的內容 說明了,當頁面有跳轉時,就會觸發router.beforeEach
的方法。可以透過它來檢查用戶接下來要去的位址是否需要驗證,進而防止以上問題。
step1. 首先,先將router.beforeEach
的方法內容貼到main.js檔案中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ---main.js檔案--- import Vue from 'vue' import axios from 'axios' import VueAxios from 'vue-axios' import App from './App' import router from './router' Vue.use(VueAxios, axios) Vue.config.productionTip = false axios.defaults.withCredentials = true; new Vue({ el: '#app', router, components: { App }, template: '<App/>' }) router.beforeEach((to, from, next) => { console.log(to, from, next); next(); })
首先,我們先為這個beforeEach
方法中加入next()
方法,為的是讓我們在測試頁面跳轉的時候,不會被導航守衛擋下來。
接著,可以透過consoloe.log
看到to
是指要往哪個頁面,from
是指從哪個頁面來。 這個時候,可以看到to
的物件裡面有一個meta
值,它的值是空的。 那這個meta
就是路由訊息,接著,我們就可以將這個meta
加到 HelloWorld 元件檔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ---index.js檔案--- import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import Login from '@/components/pages/login' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld, meta: { requiresAuth: true } // 加入meta屬性 }, { path:'/login', name: 'Login', component: Login } ] });
step3.
接著,我們在beforeEach
加入判斷requiresAuth
的部分
1 2 3 4 5 6 7 8 9 ---main.js檔案--- router.beforeEach((to, from, next) => { console.log('to',to, 'from',from, 'next',next); if(to.meta.requiresAuth){ console.log("這個網站需要驗證"); } else { next(); } })
可以看到,假設該頁面需要具有requiresAuth
這個屬性是true
的話,我們就會做相對應的處理, 如果沒有,我們就會直接讓頁面直接跳轉到目標頁面中。
main.js不是vue的元件而造成this使用上的錯誤 step4. 接著,我們在to.meta.requiresAuth
的判斷式裡面再加入新對應的程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 router.beforeEach((to, from, next) => { if(to.meta.requiresAuth){ const api = `${process.env.APIPATH}/api/user/check`; this.$http.post(api).then((response) => { if(response.data.success){ console.log("成功"); } } } else { next(); } })
a. 我們將api改成登入的帳號驗證的網址改成/api/user/check
囉。 b. 這個時候,編譯器應該會報以下的錯誤 會發生這種錯誤的原因是因為,我們將beforeEach
的程式碼放在main.js檔案中, 而beforeEach
的方法中有使用this.$http.post
,但因為main.js並不是vue的元件檔,所以,使用this
來調用post
的方法會報錯。
所以,要直接使用axios套件,故將this
換成axios
就可以使用囉。 即
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ---main.js檔案--- router.beforeEach((to, from, next) => { if(to.meta.requiresAuth){ const api = `${process.env.APIPATH}/api/user/check`; axios.$http.post(api).then((response) => { // 直接用 axios 來調用 post 方法 if(response.data.success){ console.log("成功"); } } ) } else { next(); } })
c. 接著,我們繼續在main.js檔案加入後續要處理的內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 router.beforeEach((to, from, next) => { if(to.meta.requiresAuth){ const api = `${process.env.APIPATH}/api/user/check`; axios.post(api).then((reponse)=>{ if(reponse.data.success){ next(); } else{ next({ path:'/login', }) } }) } else { next(); } })
你可以看到,當reponse.data.success
是true
的時候,頁面就會跳轉到目標頁面。 那如果是失敗的話,就會跳回login頁面。
小問題,在路由後面隨意加東西 假設今天我們在#
後面亂加一些內容,頁面會呈現空白的畫面,這種觀感不是很好。 此時,我們就在index.js中加入新的內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export default new Router({ routes: [ { path:'*', redirect:'login' }, { path: '/', name: 'HelloWorld', component: HelloWorld, meta: { requiresAuth: true } }, { path:'/login', name: 'Login', component: Login } ] });
上面的path
是*
的部分,就是在於若有人在#
後面亂輸入內容,頁面會直接被重新導回login的頁面。