本案例中,编写了一个名为stockAdd的vue(v2.5.17)自定义组件,使用a-table组件的插槽功能,创建了一个可编辑的数据表格:
表格用于添加采购的物品,点“新增物品”按键,表格添加一行,新增的行内容为空。
一、a-table组件基本操作
a-table组件实现表格框架和数据填充,接收columns和data-source两个参数:
<a-table :columns="columns" :data-source="dataList">
columns内容为数组:
columns () {
return [{
title: '物品名称',
dataIndex: 'name',
scopedSlots: {customRender: 'nameShow'}
}, {
title: '所属类型',
dataIndex: 'typeId',
width: 200,
scopedSlots: {customRender: 'typeIdShow'}
}, {
title: '型号',
dataIndex: 'type',
scopedSlots: {customRender: 'typeShow'}
}, {
title: '采购量',
dataIndex: 'amount',
scopedSlots: {customRender: 'amountShow'}
}, {
title: '消耗量',
dataIndex: 'consumption',
scopedSlots: {customRender: 'consumptionShow'}
}, {
title: '单位',
dataIndex: 'unit',
scopedSlots: {customRender: 'unitShow'}
}, {
title: '单价',
dataIndex: 'price',
scopedSlots: {customRender: 'priceShow'}
}]
}
数组中每一项代表一列,每列各个属性的具体含义为:
参数 | 说明 | 类型 | 默认值 | 版本 |
---|---|---|---|---|
dataIndex | 列数据在数据项中对应的 key,支持 a.b.c 的嵌套写法 | string | - | |
customRender | 生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引,@return 里面可以设置表格行/列合并,可参考 demo 表格行/列合并 | Function(text, record, index) {}|slot-scope | - | |
title | 列头显示文字 | string|slot | - | |
width | 列宽度 | string|number | - | |
scopedSlots | 使用 columns 时,可以通过该属性配置支持 slot-scope 的属性,如 scopedSlots: { customRender: 'XXX'} | object | - |
上面是官方解释,通俗描述为:
title——显示在列头(每列第一行)的文字标题,表面这一列数据是什么内容,如物品名称、单位、单价等
dataIndex——该列数据在data-source传入数组dataList [{name:"白菜", price:"4.00"},{name:"土豆", price:"2.50"}] 的一个数据项(表示一行)中 的键值对的键名(key)。如物品名称列的dataIndex为 'name',表示该列的每行数据是dataList中每个数据项的name对应的取值,第一行数据是白菜,第二行是土豆,依次类推。
scopedSlots: {customRender: 'nameShow'}——表示该列的样式是一个名为nameShow的插槽:
:data-source="dataList"绑定的是行数据项数组,其结构为:
[{name: '白菜', type: '', typeId: '', unit: '', amount: 0, consumption: 0, price: 0},
{name: '土豆', type: '', typeId: '', unit: '', amount: 0, consumption: 0, price: 0},
{name: '物品名称', type: '', typeId: '', unit: '', amount: 0, consumption: 0, price: 0}]
每行对应数组的一项,每行数据含有若干 以列的dataIndex为键名的键值对,键值对数量等于列的数量。
键为name的是物品名称列的数据
键为type的是型号列的数据
键为typeId的是所属类型列的数据
键为unit的是单位列的数据
键为amount的是采购量列的数据
键为consumption的是消耗量列的数据
键为price的是单价列的数据(图里标的是价格列,改不了 不改了)
二、a-table组件内的插槽
不加插槽的话,a-table组件显示的数据表是只读的。想要变成交互式的输入框,需要使用具名作用域插槽。在stockAdd组件中(本例中的最顶层组件),定义的dataList数组的数据需要传递到a-table组件,以及我们为a-table组件添加的插槽中。
数据传递路径:stockAdd组件 ——》a-table组件 ——》具名作用域插槽
注意:虽然插槽的定义在stockAdd组件中,但结构上插槽是a-table组件的子组件(stockAdd组件的孙组件),插槽不能直接获取stockAdd组件中定义的数据。
因为 a-table组件的结构不像自定义组件那样方便查看,这里不去深究a-table组件源码如何实现插槽。插槽从a-table组件接收参数需要使用slot-scope属性,slot-scope="text, record"是固定写法,探究a-table源码超出本例的实用原则,有精力再研究。
<template slot="unitShow" slot-scope="text, record">
slot="unitShow" 表示插槽名(见具名插槽)
slot-scope="text, record" 表示作用域插槽接收从<a-table>组件传递进来的两个参数,其中:
text是<a-table>组件定义的 当前插槽slot="unitShow"对应的数据项'unit'的文本值
{
title: '单位',
dataIndex: 'unit',
scopedSlots: {customRender: 'unitShow'}
},
record是<a-table>组件定义的 当前行的全部数据
Function(text, record, index) {}|slot-scope
text, record, index分别代表:当前行的值,当前行数据,行索引
Ant Design Vue
通过输出{{ text }}和{{ record }}可以看出:
<h4>text内容{{ text }}</h4>
<h4>record内容{{ record }}</h4>
在插槽中加入input元素,并使用v-model双向绑定,实现了单元格输入功能。
<template slot="nameShow" slot-scope="text, record">
<a-input v-model="record.name"></a-input>
</template>
源码如下:
<template>
<a-drawer
title="物品入库"
:maskClosable="false"
placement="right"
:closable="false"
:visible="show"
:width="1200"
@close="onClose"
style="height: calc(100% - 55px);overflow: auto;padding-bottom: 53px;"
>
<a-form :form="form" layout="horizontal">
<a-row :gutter="32">
<a-col :span="12">
<a-form-item label='保管人' v-bind="formItemLayout">
<a-input v-decorator="[
'custodian',
{ rules: [{ required: true, message: '请输入保管人!' }] }
]"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label='入库人' v-bind="formItemLayout">
<a-input v-decorator="[
'putUser',
{ rules: [{ required: true, message: '请输入入库人!' }] }
]"/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label='备注消息' v-bind="formItemLayout">
<a-textarea :rows="4" v-decorator="[
'content',
{ rules: [{ required: true, message: '请输入名称!' }] }
]"/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-table :columns="columns" :data-source="dataList">
<template slot="nameShow" slot-scope="text, record">
<a-input v-model="record.name"></a-input>
</template>
<template slot="typeShow" slot-scope="text, record">
<a-input v-model="record.type"></a-input>
</template>
<template slot="typeIdShow" slot-scope="text, record">
<a-select v-model="record.typeId" style="width: 100%">
<a-select-option v-for="(item, index) in consumableType" :value="item.id" :key="index">{{ item.name }}</a-select-option>
</a-select>
</template>
<template slot="unitShow" slot-scope="text, record">
<h4>text内容{{ text }}</h4>
<h4>record内容{{ record }}</h4>
<a-input v-model="record.unit"></a-input>
</template>
<template slot="amountShow" slot-scope="text, record">
<a-input-number v-model="record.amount" :min="1" :step="1"/>
</template>
<template slot="priceShow" slot-scope="text, record">
<a-input-number v-model="record.price" :min="1"/>
</template>
</a-table>
<a-button @click="dataAdd" type="primary" ghost size="large" style="margin-top: 10px;width: 100%">
新增物品
</a-button>
</a-col>
</a-row>
</a-form>
<div class="drawer-bootom-button">
<a-popconfirm title="确定放弃编辑?" @confirm="onClose" okText="确定" cancelText="取消">
<a-button style="margin-right: .8rem">取消</a-button>
</a-popconfirm>
<a-button @click="handleSubmit" type="primary" :loading="loading">提交</a-button>
</div>
</a-drawer>
</template>
<script>
import {mapState} from 'vuex'
const formItemLayout = {
labelCol: { span: 24 },
wrapperCol: { span: 24 }
}
export default {
name: 'stockAdd',
props: {
stockAddVisiable: {
default: false
}
},
computed: {
...mapState({
currentUser: state => state.account.user
}),
show: {
get: function () {
return this.stockAddVisiable
},
set: function () {
}
},
columns () {
return [{
title: '物品名称',
dataIndex: 'name',
scopedSlots: {customRender: 'nameShow'}
}, {
title: '型号',
dataIndex: 'type',
scopedSlots: {customRender: 'typeShow'}
}, {
title: '数量',
dataIndex: 'amount',
scopedSlots: {customRender: 'amountShow'}
}, {
title: '所属类型',
dataIndex: 'typeId',
width: 200,
scopedSlots: {customRender: 'typeIdShow'}
}, {
title: '单位',
dataIndex: 'unit',
scopedSlots: {customRender: 'unitShow'}
}, {
title: '单价',
dataIndex: 'price',
scopedSlots: {customRender: 'priceShow'}
}]
}
},
mounted () {
this.getConsumableType()
},
data () {
return {
dataList: [],
formItemLayout,
form: this.$form.createForm(this),
loading: false,
consumableType: []
}
},
methods: {
getConsumableType () {
this.$get('/cos/consumable-type/list').then((r) => {
this.consumableType = r.data.data
})
},
dataAdd () {
this.dataList.push({name: '', type: '', typeId: '', unit: '', amount: '', price: ''})
},
reset () {
this.loading = false
this.form.resetFields()
},
onClose () {
this.reset()
this.$emit('close')
},
handleSubmit () {
let price = 0
this.dataList.forEach(item => {
price += item.price * item.amount
})
this.form.validateFields((err, values) => {
values.price = price
values.goods = JSON.stringify(this.dataList)
if (!err) {
this.loading = true
this.$post('/cos/stock-info/put', {
...values
}).then((r) => {
this.reset()
this.$emit('success')
}).catch(() => {
this.loading = false
})
}
})
}
}
}
</script>
<style scoped>
</style>
参考
Ant Design Vue封装a-drawer
https://1x.antdv.com/components/drawer-cn/
a-form
https://1x.antdv.com/components/form-cn/#components-form-demo-advanced-search
a-col a-row
https://1x.antdv.com/components/grid-cn/
<a-row :gutter="50">
https://1x.antdv.com/components/grid-cn/#components-grid-demo-grid-gutter
a-table
https://1x.antdv.com/components/table-cn/
vue slot插槽
P102102_尚硅谷Vue技术_默认插槽
https://www.bilibili.com/video/BV1Zy4y1K7SH?p=102
P103103_尚硅谷Vue技术_具名插槽
P104104_尚硅谷Vue技术_作用域插槽
插槽 — Vue.js
后续:dataIndex会传递给后台服务端,与实体类中的属性名称形成一一对应关系。