本文针对本人这两天进行界面开发的过程中遇到的一些坑点和要点进行记录,留待日后备忘。
本篇博客将用一个极简的购物清单的例子讲解,例子的完整代码可见最后。
购物清单的数据结构说明
shoppingList: {
supermarket: '', // 超市名称,非空,字符串
status: true, // 状态位,默认为 true
items: [ // 购物清单项,0 到多个
{
name: '', // 商品名,非空,字符串
quantity: 0 // 数量,非空,整数
}
]
}
了解了购物清单的数据结构之后,就可以开始讲解了。
1. <el-radio>
的 label
属性
根据官方文档,label
属性可以为 String
、Number
或 Boolean
,但官方文档中没有说明的是,如果要给 label
属性设置 Number
或 Boolean
值,则需要加上冒号变成 :label
,不然像 "0"
、"true"
这类的值会被当作 String
处理。
<!--
感兴趣的可以试着去掉 label 前的冒号,
你会发现在页面中没有一个 radio 处于选中状态,
提交表单的时候对应的 status 会变成 String 而非 Boolean
-->
<el-form-item prop="status" label="状态">
<el-radio v-model="shoppingList.status" :label="true">启用</el-radio>
<el-radio v-model="shoppingList.status" :label="false">禁用</el-radio>
</el-form-item>
2. 在 <el-table>
中放入表单组件
从购物清单的数据结构可以看出,清单项部分是可变的,在 element-ui 里不难解决,官方文档中提到过动态增减表单项。但出于项目的需要,我们希望清单项能够像表格那样编辑,于是自然就有了在表格中嵌入表单组件的做法。官方文档中有一个自定义列模板可以用来做这个。在本例中,代码如下:
<el-table :data="shoppingList.items">
<el-table-column type="index"></el-table-column>
<el-table-column label="商品名">
<template slot-scope="scope">
<el-form-item :prop="'items.' + scope.$index + '.name'"
:rules="[{ required: true, message: '请输入商品名称', trigger: 'blur' }]">
<el-input v-model="scope.row.name"></el-input>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="数量">
<template slot-scope="scope">
<el-form-item :prop="'items.' + scope.$index + '.quantity'"
:rules="[{ required: true, message: '请输入商品数量' }, { type: 'number', message: '只能输入数字' }]">
<el-input v-model="scope.row.quantity"></el-input>
</el-form-item>
</template>
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="danger" size="mini" icon="el-icon-delete" @click="removeItem(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
至于表单数据的绑定,在 <el-table-column>
的 <template>
元素中可以定义一个 slot-scope
属性(这里,该属性被定义为 "scope"
)。经过这样的设置之后,就可以通过 scope.$index
得到该行的索引,通过 scope.row
获得该行的元素,表单数据绑定就比较容易了。至于添加和删除表单项,就通过对 shoppingList.items
进行 push
和 splice
来实现(参见完整代码)。
3. 表单验证时填写正确的 prop
属性
在官方文档中,表单验证既可以在 Form 上传递所有的 rules,也可以在单个表单域上传递验证规则。但在动态增减的表单项中进行验证,需要十分注意设置正确的 prop
属性。既然时动态增减的表单项,那么这个 prop
应该也是动态的,于是我们使用 v-bind
指令(简写为 :
)进行绑定,以下是一个例子:
<el-table-column label="商品名">
<template slot-scope="scope">
<el-form-item :prop="'items.' + scope.$index + '.name'"
:rules="[{ required: true, message: '请输入商品名称', trigger: 'blur' }]">
<el-input v-model="scope.row.name"></el-input>
</el-form-item>
</template>
</el-table-column>
上一节中我们知道 scope.$index
可以访问到该行数据对应的索引,于是就可以通过 :prop="'items.' + $index + '.name'"
进行绑定。注意这里不能写 :prop="scope.row.name"
,也不能省略冒号——前者会导致验证规则不起作用,后者会在运行中给出警告,要求填写正确的 prop
值。至于为什么一定要这种写法,个人认为可以类比到 Java 中遍历数组元素的两种方式
// 方式一
for (int i = 0; i < items.length; i++) {
// 遍历操作
}
// 方式二
for (Item item : items) {
// 遍历操作
}
对于方式二来说,item
在遍历过程中是会变化的,无法通过它唯一确定一条记录,而下标可以。对于动态增减的表单项,肯定要把一个验证规则应用到所有表单项,同时又要区分哪个项验证通过,哪个项验证不通过,在这种要求下,自然用下标定位成为了不二之选。
总结
以上为我在这两天的页面开发中遇到的一些坑点和要点,有任何问题或者其它需要商讨的点可以在评论区留言。
附:极简购物清单的完整代码
<template>
<div id="app">
<h2>A simple shopping list.</h2>
<el-form :model="shoppingList" label-width="100px" size="mini">
<el-form-item prop="supermarket" label="超市"
:rules="[{ required: true, message: '请输入超市名称', trigger: 'blur' }]">
<el-input v-model="shoppingList.supermarket"></el-input>
</el-form-item>
<el-form-item prop="status" label="状态">
<el-radio v-model="shoppingList.status" :label="true">启用</el-radio>
<el-radio v-model="shoppingList.status" :label="false">禁用</el-radio>
</el-form-item>
<el-divider></el-divider>
<el-form-item prop="items" label="购物清单">
<el-button type="text" icon="el-icon-plus" @click="addItem">添加</el-button>
<el-table :data="shoppingList.items">
<el-table-column type="index"></el-table-column>
<el-table-column label="商品名">
<template slot-scope="scope">
<el-form-item :prop="'items.' + scope.$index + '.name'"
:rules="[{ required: true, message: '请输入商品名称', trigger: 'blur' }]">
<el-input v-model="scope.row.name"></el-input>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="数量">
<template slot-scope="scope">
<el-form-item :prop="'items.' + scope.$index + '.quantity'"
:rules="[{ required: true, message: '请输入商品数量' }, { type: 'number', message: '只能输入数字' }]">
<el-input v-model="scope.row.quantity"></el-input>
</el-form-item>
</template>
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="danger" size="mini" icon="el-icon-delete" @click="removeItem(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-button type="primary" @click="saveList">Submit!</el-button>
</el-form>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
shoppingList: {
supermarket: '',
status: true,
items: [
{
name: '',
quantity: 0
}
]
}
}
},
methods: {
addItem () {
this.shoppingList.items.push({ name: '', quantity: 0 })
},
removeItem (index) {
this.shoppingList.items.splice(index, 1)
},
saveList () {
console.log('购物列表为:' + JSON.stringify(this.shoppingList))
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>