0%

Vue-vue出一個電商網站2-製作出登入頁面

製作登入介面

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">&copy; 2017-2021</p>
</form>
</div>
</template>

step4.
但是,剛剛引入的畫面樣式是不正確的,所以,在剛剛的 Bootstrap 的公版的 HTML 原始碼內容,可以看到在 App 以下引入該login內容的專用css
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">&copy; 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">&copy; 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的網址有改。
你要修改的有兩部分

  1. signin裡面的api網址
  2. 在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課程中的”課程補充範例程式碼”裡面,所說的要加入像tokenexpired的內容的話,你的頁面是不會有存取cookie的內容的喔。
所以,在singin的內容存取tokencookie的方法,你要去看課程”課程補充範例程式碼”的內容,才行。

課程補充範例程式碼-加入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。
網頁上的cookie
網頁上的cookie2

可以看到,我們自訂義的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.
這個時候,編譯器應該會報以下的錯誤
因為不是vue的元件
會發生這種錯誤的原因是因為,我們將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.successtrue的時候,頁面就會跳轉到目標頁面。
那如果是失敗的話,就會跳回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的頁面。