Orion's Studio.

Vue规范(自用)

2017/12/12

借鉴

Vue规范 @viewweiwu

Vue组件编码规范 @我爱吃小丸子

Vue生命周期探究 @曾广营

CSS规范 @mdo

综述

IDE:WebStorm

总框架:Vue

UI框架:ElementUI

JS代码规范:Eslint

CSS预处理器:Less

API文档:ShowDoc

文件夹

现有方案:

  1. components存页面,common存组件,base存公用函数、常量等
  2. 每个页面一个文件夹,文件名和目录下Vue文件同名
  3. 每个路由的子页面都放在同目录Pages或pages文件夹下

可选优化:

  1. views存页面,components存组件,common存公用函数、常量等
  2. 每个模块一个文件夹
  3. 不需要Pages或pages文件夹
  4. 小写开头的名词,最好一个单词
    (good:cart)(bad:shoppingCart、Cart)

文件

现有方案:

  1. 每个页面都单独创建一个文件夹,并在同目录下创建index.js、vue和less文件

可选优化:

  1. 每个页面创建一个vue,放在模块文件夹下,只有一个文件的情况下不需要创建文件夹,直接放在views目录下(如Login、Home)
  2. 大写名词开头,至少两个单词,开头的单词就是所属模块名(CartList、CartDetail)
  3. 常用结尾单词有:List、Edit、Detail、Info
  4. 以Item结尾的代表组件:CartListItem、CartDetailItem

例:文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
src
assets
...
components
...
views
car
CarEdit.vue
CarList.vue
CarDetai.vue
user
UserDetail.vue
UserEdit.vue
UserPasswordRest.vue
customer
CustomerCardItem.vue
CustomerList.vue

方法

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
export default {
// 不要忘记了 name 属性
name: 'SomeName',
// 使用其它组件或选项
components: {},
extends: someOptions,
mixins: [someOptions],
// 组件变量,由父组件传入
props: {
min: {
type: Number,
default: 1
}
},
// 变量
data() {
return {
// 按字母顺序
bar: 0,
for: '',
fooBar: {}
}
},
// 方法
computed: {}, // 跟Vuex相关
filters: {}, // 不改变原数据,只影响显示
methods: {}, // 自定义方法
watch: {}, // 监听具体某个值的变化
// 生命周期钩子,不能使用箭头函数
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {}, // 页面初始化请求写在这里,也可以写到每个路由的beforeRouteEnter钩子里
beforeUpdate() {},
updated() {},
activated() {},
deactivated() {},
beforeDestroy() {},
destroyed() {}
}

自定义方法

命名

现有方案:

  1. AJAX方法以_开头,此外采用驼峰命名

可选优化

  1. 驼峰命名(good:getCartList)(bad:get_cart_list、GetCartList)
  2. 动宾短语(good:jumpCartPage、openCartDetailDialog)(bad:cartPage、go、open、show)
  3. AJAX方法以add、get、put、del开头来分别代表POST、GET、PUT、DELETE请求(good:addFormData、getCartList、putAddressInfo、delOrder)(bad:postForm、takeData、comfirmData、getList)
  4. 事件方法以handle开头(handleTagClick、handleUsernameChange)(或on开头,只要保证单文件内统一)
  5. $emit事件常以send开头
  6. init、refresh单词除外
  7. 尽量以常见单词开头:set、get、open、close、jump

顺序

  1. get相关,页面显示处理
  2. set相关,data设置
  3. 页面跳转
  4. 分页处理
  5. add请求
  6. get请求
  7. put请求
  8. del请求
  9. handle处理
  10. 其他方法
  11. 未注释方法(一般来说在方法名上方输入/**就能自动生成jsdoc注释,用WebStorm在之后都没有*/才能自动生成)

例:购物车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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
<template>
<div>
<div class="cart">
<el-table
:data="cartInfo.cartProductVoList"
border
@select-all="handleSelectAll"
@select="handleSelect"
@selection-change="handleSelectionChange"
class="cart-table"
ref="multipleTable">
<el-table-column
type="selection"
width="38px">
</el-table-column>
<el-table-column
label="商品名称"
width="360px">
<template scope="scope">
<div class="pruduct-name">
<div><img :src="cartInfo.imageHost + scope.row.productMainImage"></div>
<div class="pruduct-subname">{{scope.row.productName + ' ' + scope.row.productSubtitle}}</div>
</div>
</template>
</el-table-column>
<el-table-column
label="单价"
width="110px">
<template scope="scope">
<div>
<span>{{scope.row.productPrice | showCurrency}}</span>
</div>
</template>
</el-table-column>
<el-table-column
label="数量"
width="190px">
<template scope="scope">
<div>
<el-input-number size="small" v-model="scope.row.quantity" :disabled="!scope.row.productStock"
@change="(value) => handleInput(value, scope.row)"
:min="1" :max="scope.row.productStock" label="商品数量">
</el-input-number>
</div>
</template>
</el-table-column>
<el-table-column
label="小计"
width="110px">
<template scope="scope">
<div>
<span>{{scope.row.productTotalPrice | showCurrency}}</span>
</div>
</template>
</el-table-column>
<el-table-column
label="操作"
width="100px">
<template scope="scope">
<el-button type="danger" @click="handleDelete(scope.$index, scope.row)" class="delete-button">
删除<i class="el-icon-delete el-icon--right"></i>
</el-button>
</template>
</el-table-column>
</el-table>
<div class="cart-total">
<el-button type="danger" @click="handleDeleteTotal">
删除<i class="el-icon-delete el-icon--right"></i>
</el-button>
<el-button type="success" :disabled="!cartInfo.cartTotalPrice" @click="jumpAddress">商品总额:{{cartInfo.cartTotalPrice | showCurrency }}</el-button>
</div>
</div>
<div class="last" v-show="isLast">已经没有更多了...</div>
<div class="loading" v-show="isLoading">正在加载...</div>
<div class="error" v-show="isError" @click="getcartProductList">加载错误,点击 <span class="font-blue">这里</span> 重试</div>
</div>
</template>

<script type="text/ecmascript-6">
export default {
name: 'cartProductVoList',
components: {},
data () {
return {
// 后台购物车返回格式
cartInfo: {
cartProductVoList: [], // 当前购物车列表
cartTotalPrice: 0, // 总价
imageHost: '' // 图片前缀
},
isError: false, // 当错误时显示
isLast: false, // 当没有数据时显示
isLoading: false, // 加载中显示
multipleSelection: [] // 选择商品列表
}
},
filters: {
/**
* 价格前补上¥符号并保留两位小数,0直接显示0
* 考虑到value可能为null,先用parseFloat转值,防止直接null.toFixed报错
* @param value 价格
* @returns {*}
*/
showCurrency (value) {
return value ? '¥' + parseFloat(value).toFixed(2) : 0
}
},
methods: {
/**
* 设置购物车
* @param data 后台返回数据
*/
setCart (data) {
this.cartInfo = data
this.$nextTick(() => {
this.cartInfo.cartProductVoList.forEach(row => {
if (row.productChecked) {
this.$refs.multipleTable.toggleRowSelection(row)
}
})
})
},
/**
* 跳转到地址管理页面
*/
jumpAddress () {
this.$router.push('/Home/mine/address')
},
/**
* 获取当前用户购物车列表
*/
getcartProductList () {
return this.CrudApi.getInfo({
url: 'cart/list',
data: {},
callback: (res) => {
this.setCart(res.data.data)
}
})
},
/**
* 选择某商品请求
* @param productId
*/
selectProduct (productId) {
return this.CrudApi.addInfo({
url: 'cart/select',
data: {
productId: productId
},
callback: (res) => {
this.setCart(res.data.data)
}
})
},
/**
* 取消选择某商品请求
* @param productId
*/
unselectProduct (productId) {
return this.CrudApi.addInfo({
url: 'cart/un_select',
data: {
productId: productId
},
callback: (res) => {
this.setCart(res.data.data)
}
})
},
/**
* 全选商品请求
*/
selectAllProduct () {
return this.CrudApi.addInfo({
url: 'cart/select_all',
data: {},
callback: (res) => {
this.setCart(res.data.data)
}
})
},
/**
* 取消全选商品请求
*/
unselectAllProduct () {
return this.CrudApi.addInfo({
url: 'cart/un_select_all',
data: {},
callback: (res) => {
this.setCart(res.data.data)
}
})
},
/**
* 修改商品数量请求
* @param productId
* @param count
*/
putCartProduct (productId, count) {
return this.CrudApi.addInfo({
url: 'cart/update',
data: {
productId: productId,
count: count
}
})
},
/**
* 根据Id删除商品请求
* @param productArray 要删除的商品Id数组
*/
delProduct (productArray) {
return this.CrudApi.putInfo({
url: 'cart/delete_product',
data: {
productIds: productArray.join(',')
}
})
},
/**
* 手动修改商品数量请求
* @param value 商品
*/
handleInput (number = 1, value) {
this.putCartProduct(value.productId, number).then((res) => {
this.setCart(res.data.data)
})
},
/**
* 手动选择商品
* @param selection 已经选择的商品们
* @param row 当前选择行
*/
handleSelect (selection, row) {
if (selection.map(item => item.id).indexOf(row.id) > -1) {
this.selectProduct(row.productId)
} else {
this.unselectProduct(row.productId)
}
},
/**
* 全选商品
* @param selection
*/
handleSelectAll (selection) {
if (selection.length) {
this.selectAllProduct()
} else {
this.unselectAllProduct()
}
},
/**
* 选择商品改变
* @param selection
*/
handleSelectionChange (selection) {
this.multipleSelection = selection
},
/**
* 删除商品按钮
* @param index 行数
* @param row 每行内容
*/
handleDelete (index, row) {
this.$confirm('确定删除该商品?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.delProduct(Array.of(row.productId)).then(() => {
// 删除数组中指定的元素
this.cartInfo.cartProductVoList.splice(index, 1)
this.$message({
type: 'success',
message: '删除成功'
})
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
/**
* 删除全部选择商品
*/
handleDeleteTotal () {
this.$confirm('确定删除所选商品?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.delProduct(this.multipleSelection.map(item => item.productId)).then((res) => {
// 删除数组中指定的元素
this.setCart(res.data.data)
this.$message({
type: 'success',
message: '删除成功'
})
})
}).catch((res) => {
console.warn(res)
this.$message({
type: 'info',
message: '已取消删除'
})
})
}
},
mounted () {
this.getcartProductList()
}
}
</script>

<style scoped lang="less">
@import url('../../../../../less/base.less');
.cart{
margin-bottom: 50px;
.cart-table{
.pruduct-name{
display: flex;
align-items: center;
justify-content: center;
img{
width: 60px;
height: 60px;
}
.pruduct-subname{
margin-left: 20px;
}
}
.delete-button{
width: 80px;
}
}
.cart-total{
margin-top: 20px;
text-align: right;
}
}
</style>

其他

  1. 避免this.$parent
  2. 谨慎使用this.$refs

常见问题

绑定的DOM值没有更新

使用this.$nextTick(() => {})this.$refs['refName']

CATALOG
  1. 1. 借鉴
  2. 2. 综述
  3. 3. 文件夹
  4. 4. 文件
    1. 4.1. 例:文件路径
  5. 5. 方法
    1. 5.1. vue方法顺序
  6. 6. 自定义方法
    1. 6.1. 命名
    2. 6.2. 顺序
    3. 6.3. 例:购物车Vue文件
  7. 7. 其他
  8. 8. 常见问题
    1. 8.1. 绑定的DOM值没有更新