使用 Nuxt 2.0 實作「簽到簿」應用程式

環境

  • Windows 7
  • node 8.11.1
  • npm 5.6.0

建立專案

建立專案。

1
npx create-nuxt-app guestbook-nuxt

選擇:

  • Express
  • Bootstrap
  • Universal
  • 添加 axios module
  • 添加 EsLint
  • 暫不添加 Prettier

啟動

1
npm run dev

安裝套件

安裝 Lodash 套件。

1
npm install --save vue-lodash

nuxt.config.js 檔引入相關套件。

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
// ...
env: {
baseUrl: process.env.BASE_URL || 'http://guestbook.test' //設定 API 基礎位址
},
// ...
plugins: [
'~/plugins/lodash'
],
// ...
}

新增 plugins\lodash.js 檔。

1
2
3
4
5
6
import Vue from 'vue'
import VueLodash from 'vue-lodash'

Vue.use(VueLodash, {
name: 'lodash'
})

重新編譯。

1
rs

建立視圖

新增 layouts\app.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
<template>
<div>
<b-navbar
toggleable
type="dark"
variant="dark"
class="mb-3">
<b-navbar-toggle target="nav_text_collapse"/>
<b-navbar-brand>Guestbook</b-navbar-brand>
<b-collapse
id="nav_text_collapse"
is-nav>
<b-navbar-nav class="ml-auto">
<b-nav-item
to="/"
exact>
Home
</b-nav-item>
<b-nav-item
to="/signatures"
exact>
Signatures
</b-nav-item>
<b-nav-item
to="/signatures/create"
exact>
Sign
</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>

<b-container>
<nuxt/>
</b-container>
</div>
</template>

新增 pages\signatures\index.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
100
101
102
103
<template>
<div>
<b-row class="mb-3">
<b-col
md="3"
offset-md="9">
<b-form-select
v-model="meta.per_page"
:options="options"/>
</b-col>
</b-row>

<b-table
:items="signatures"
:fields="fields"
striped
hover>
<template
slot="action"
slot-scope="signature">
<b-button
size="sm"
variant="danger"
@click.prevent="destroy(signature.item.id)">刪除</b-button>
</template>
</b-table>

<b-pagination
v-model="meta.current_page"
:first-text="meta.first_text"
:prev-text="meta.prev_text"
:next-text="meta.next_text"
:last-text="meta.last_text"
:limit="meta.limit"
:per-page="meta.per_page"
:total-rows="meta.total_rows"
align="center"/>
</div>
</template>

<script>
export default {
layout: 'app',
data() {
return {
url: process.env.baseUrl + '/signatures',
signatures: [],
meta: {
current_page: 1,
first_text: '第一頁',
prev_text: '前一頁',
next_text: '下一頁',
last_text: '最後頁',
limit: 7,
per_page: 10,
total_rows: 0
},
options: [
{ value: 5, text: 5 },
{ value: 10, text: 10 },
{ value: 15, text: 15 }
],
fields: [
{ key: 'id', label: '編號' },
{ key: 'name', label: '名字' },
{ key: 'content', label: '內容' },
{ key: 'action', label: '' }
]
}
},
watch: {
'meta.current_page': function() {
this.fetch()
},
'meta.per_page': function() {
this.fetch()
}
},
created() {
this.fetch()
},
methods: {
fetch() {
this.$axios.get(this.url + '?page=' + this.meta.current_page + '&per_page=' + this.meta.per_page)
.then(({data}) => {
this.signatures = data.data
this.meta.total_rows = data.meta.total
})
},
destroy(id) {
if (confirm('確定刪除?')) {
this.$axios.delete(this.url + '/' + id)
.then(() => {
this.data = this.lodash.remove(this.data, function (data) {
return data.id !== id
})
this.fetch(this.url + '?page=' + this.meta.current_page)
})
}
}
}
}
</script>

新增 pages\signatures\create.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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<template>
<div>
<b-form
class="mb-3"
@submit.prevent="onSubmit"
@reset="onReset">
<b-form-group
label="名字"
label-for="name"
description="">
<b-form-input
id="name"
v-model="signature.name"
:class="{
'is-valid': nameIsValid,
'is-invalid': nameIsInvalid
}"
type="text"
required/>
<span
v-if="nameIsInvalid"
class="invalid-feedback">
{{ errors.name[0] }}
</span>
</b-form-group>

<b-form-group
label="信箱"
label-for="email"
description="">
<b-form-input
id="email"
v-model="signature.email"
:class="{
'is-valid': emailIsValid,
'is-invalid': emailIsInvalid
}"
type="email"
required/>
<span
v-if="emailIsInvalid"
class="invalid-feedback">
{{ errors.email[0] }}
</span>
</b-form-group>

<b-form-group
label="內容"
label-for="content"
description="">
<b-form-textarea
id="content"
v-model="signature.content"
:class="{
'is-valid': contentIsValid,
'is-invalid': contentIsInvalid
}"
:rows="3"
:max-rows="6"
required/>
<span
v-if="contentIsInvalid"
class="invalid-feedback">
{{ errors.content[0] }}
</span>
</b-form-group>

<b-button
type="submit"
variant="primary">送出</b-button>&nbsp;
<b-button
type="reset"
variant="danger">重設</b-button>
</b-form>

<b-alert
v-if="saved"
show
dismissible
fade>
表單已送出!
</b-alert>
</div>
</template>

<script>
export default {
layout: 'app',
data() {
return {
url: process.env.baseUrl + '/signatures',
signature: {
name: '',
email: '',
content: ''
},
validation: {
name: /^[a-zA-Z0-9\u4e00-\u9fa5]{3,30}$/,
email: /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$/,
content: /^.{3,30}$/
},
saved: false,
errors: []
};
},
computed: {
nameIsValid: function() {
return this.validation.name.test(this.signature.name.trim());
},
nameIsInvalid: function() {
return (!this.validation.name.test(this.signature.name.trim()) && this.errors.name);
},
emailIsValid: function() {
return this.validation.email.test(this.signature.email.trim());
},
emailIsInvalid: function() {
return (!this.validation.email.test(this.signature.email.trim()) && this.errors.email);
},
contentIsValid: function() {
return this.validation.content.test(this.signature.content.trim());
},
contentIsInvalid: function() {
return (!this.validation.content.test(this.signature.content.trim()) && this.errors.content);
}
},
methods: {
onSubmit() {
this.saved = false;
this.$axios.post(this.url, this.signature)
.then(({data}) => {
this.success()
})
.catch(({response}) => {
this.error(response.data)
});
},
success() {
this.saved = true;
this.onReset();
},
error(data) {
this.errors = data;
},
onReset() {
this.errors = [];
this.signature = {
name: '',
email: '',
content: ''
};
}
}
}
</script>

程式碼