Vue中使用render 封装el-table解决多级表头自定义模板
vue中多数情况下使用template封装组件逻辑清晰结构简单。但是在某些情况下,比如非常简单的组件 vue render简单解析或者template无法解决某些场景下,。使用完全javascirpt能力就显得尤为重要。文章目录使用render场景render简单了解createElement 了解模板中 attribute 对应的数据对象render中操作`$attrs`和`$listener`
vue中多数情况下使用
template
封装组件逻辑清晰结构简单。
但是在某些情况下,比如非常简单的组件 vue render简单解析或者template
无法解决某些场景下,。使用完全javascirpt能力就显得尤为重要。
文章目录
使用render场景
平时会对ElementUI
中的部分组件。el-table等
进行封装。以前于增加开发效率。对于el-table
使用template
封装在多级表头的情况下。是无法对多级表头的body
内容进行自定义模板的。因为需要递归二级及以上el-table-column
。所以就造成封装组件会直接引用封装的递归组件。这样就造成具名插槽无法使用。这个情形下使用render
来解决就很nice
~~
render简单了解
render
函数重要参数createElement
render: function (createElement) {
return createElement('h1', this.blogTitle)
}
渲染结果:
<h1>{{ blogTitle }}</h1>
createElement 了解
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
模板中 attribute 对应的数据对象
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute
attrs: {
id: 'foo'
},
// 组件 prop
props: {
myProp: 'bar'
},
// DOM property
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用
// `vm.$emit` 触发的事件。
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
// 赋值,因为 Vue 已经自动为你进行了同步。
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层 property
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
render中操作$attrs
和$listener
el-table
中为了便捷往往只需要写上部分需要的属性或者事件。其他的使用$attrs
和$listener
来让开发人员自定义去使用
- template写法
<div class="ele-table_container">
<el-table
:data="tableList"
:max-height="tableHeight"
:ref="tableRef"
border
fit
stripe
style="width: 100%"
:sum-text="sumText"
highlight-current-row
v-bind="$attrs"
v-on="$listeners"
>
// ...省略
</el-table>
- render 写法
render(h) {
// ele-table的基础信息
// 设置el-table options
const tableOptions = {
style: {
width: "100%"
},
props: {
...this.$attrs,
...this.$props,
data: this.tableList,
cellClassName: "ele-table_cell",
headerCellClassName: "ele-table_header--cell",
border: true,
fit: true,
stripe: true,
sumText: this.sumText,
highlightCurrentRow: true
},
on: {
...this.$listeners
},
ref: this.tableRef
};
return h(
"div",
{
class: "ele-table_container"
},
[
h("el-table", tableOptions]
);
},
render 中 $slots、$scopedSlots、scopedSlots
$slots
场景一:一个为div
。并包裹一个slot
插槽的组件
- template
<div>
<slot></slot>
</div>
- 使用render
render(h){
h("div",this.$slots.default)
}
场景二:一个为div
。并包裹一个slot
名字为btn
的具名插槽的组件,
- template
<div>
<slot name="btn"></slot>
</div>
- 使用render
render(h){
h("div",this.$slots.btn)
}
$scopedSlots
场景一:一个为div
。并包裹一个slot
名字为btn
的具名插槽且包含数据的作用域插槽组件,
- template
<div>
<slot name="btn" :item="{text:'我是render slot'}"></slot>
</div>
- 使用render
render(h){
h("div",this.$scopedSlots.btn({
item:{ text:'我是render slot' }
}))
}
场景二:一个为div
。并包裹一个slot
包含数据的作用域插槽组件,
- template
<div>
<slot :item="{text:'我是render slot'}"></slot>
</div>
- 使用render
render(h){
h("div",this.$scopedSlots.default({
item:{ text:'我是render slot' }
}))
}
scopedSlots
有个组件A:
<div>
<slot name="btn" :item="{text:'我是render slot'}"></slot>
</div>
现在需要对A进行封装为组件B 。
- template
<div class="ele-b">
<A #btn="{item}">
<slot name="b" :item-b="{text:'这是B组件slot'}"></slot>
</A>
</div>
- render
render(h){
h("div",{
class:"ele-b"
},[
h("A",{
scopedSlots:props=>{
return h('span',this.$scopedSlots.b({
'item-b':'这是B组件slot'
}))
}
})
])
}
总结
$slot
一般用于生成非作用域的插槽
即组件中
<slot></slot>
或
<slot name="xxx"></slot>
$scopedSlots
: 一般生成作用域的插槽
即组件中
<slot :xx="xxx"></slot>
或
<slot name="xxx" :xx="xxxx"></slot>
options
中的scopedSlots
一般用于使用组件,在slot内进行下一步操作
完整代码
<script>
import { summaryMethod, spanMethod } from "./utils.js";
import renderPage from "./we-table-page.js";
import renderColumnCheckBox from "./we-checkbox-column";
import { columnOptions, renderColumn } from "./we-table-column";
import commonCss from "@/styles/common.scss";
import Sortable from "sortablejs";
export default {
name: "Table",
props: {
// 表头数据
headers: {
/**
* prop:行数据字典
* label:显示的表头
* width:对应列宽
* headerAlign:表头对齐方式
* align:表数据对齐方式
* fixed:是否固定列
* sortable:是否排序
* min-width:对应最小列宽
*/
type: Array,
default: () => {
return [];
}
},
// 表体数据
list: {
type: [Array, String],
default: () => {
return [];
}
},
// 是否开启根据计算可视范围内,固定表头
// FIXME 非window 改变无法监听!
hasResize: {
type: Boolean,
default: false
},
// 是否增加拖动列.同时开启之后增加row-key.否则会出现渲染问题。
dragDisabled: {
type: Boolean,
default: true
},
// 是否开启checkbox
selection: {
type: Boolean,
default: false
},
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
reserveSelection: {
type: Boolean,
default: false
},
// 表格ref。
tableRef: {
type: String,
default: "we-table"
},
sumText: String,
// 操作列固定
btnFixed: {
type: [Boolean, String],
default: "right"
},
// 操作列名
btnLabel: {
type: String,
default: "操作"
},
// 分页
// 是否开启分页
isPage: {
type: Boolean,
default: true
},
// 当前页数
page: {
type: Number,
default: 1
},
// 每页显示条目个数
rows: {
type: Number,
default: 15
},
// 总条目数
total: {
type: Number,
default: 0
},
// 每页显示个数选择器的选项设置
pagerCount: Number,
// 是否开启多表头的合计。一般来说一级表头,为了性能不去开启。
// TODO 为了性能后面直接提供模板?少量多级性能几乎忽略不计,是否合适待定。
levelColumnsSummary: {
type: Boolean,
default: false
},
// 合并行设置
spanOption: {
type: Object,
default: () => {
return {
spanIndex: [], // 合并列数。默认为空,为空则表示不合并
spanKey: "id" // 数据合并唯一标识。默认以id作为合并标识
};
}
}
},
computed: {
tableList() {
if (this.list == "success") {
return [];
} else {
return this.list;
}
}
},
render(h) {
// ele-table的基础信息
// 设置el-table props
const tableOptions = {
style: {
width: "100%"
},
props: {
...this.$attrs,
...this.$props,
data: this.tableList,
cellClassName: "ele-table_cell",
headerCellClassName: "ele-table_header--cell",
border: true,
fit: true,
stripe: true,
sumText: this.sumText,
highlightCurrentRow: true
},
on: {
...this.$listeners
},
ref: this.tableRef
};
return h(
"div",
{
class: "ele-table_container"
},
[
h("el-table", tableOptions, [
renderColumnCheckBox.call(this, h),
...this.headers.map(item => {
return h("el-table-column", columnOptions.call(this, h, item), [
renderColumn.call(this, h, item)
]);
})
]),
renderPage.call(this, h)
]
);
},
mounted() {
if (this.hasResize) {
this.resizeTable();
}
this.dragTable();
},
methods: {
// 多级表头获取真实子节点数据
getLevelColumns(columns) {
let newColumns = [];
columns.forEach(i => {
if (i.child && i.child.length > 0) {
newColumns = [...newColumns, ...this.getLevelColumns(i.child)];
} else {
newColumns.push(i);
}
});
return newColumns;
},
resizeTable() {
this.$nextTick(function () {
const mainBodyPadding = commonCss.mainBodyPadding.split("p")[0];
// 可视高度减去padding和分页组件的高度
const tableDom = this.$refs[this.tableRef].$el;
const parentDom = tableDom.parentNode.parentNode;
this.tableHeight =
window.innerHeight -
this.$refs[this.tableRef].$el.offsetTop -
(Number(mainBodyPadding) + 45);
let _this = this;
parentDom.onresize = function (e) {
console.log(e);
_this.tableHeight =
window.innerHeight -
_this.$refs[_this.tableRef].$el.offsetTop -
(Number(mainBodyPadding) + 45);
};
});
},
dragTable() {
const el = this.$refs[this.tableRef].$el.querySelectorAll(
".el-table__body-wrapper > table > tbody"
)[0];
this.sortable = Sortable.create(el, {
disabled: this.dragDisabled,
setData: function (dataTransfer) {
dataTransfer.setData("Text", "");
},
onEnd: ({ newIndex, oldIndex }) => {
const currRow = this.list.splice(oldIndex, 1)[0];
this.list.splice(newIndex, 0, currRow);
// 对于某些情况,需要拖拽后的时候,则同时影响父组件list数据
this.$emit("update:list", this.list);
}
});
},
handleSizeChange(val) {
this.$emit("update:rows", val);
this.$emit("page-rows-change");
},
handlePageChange(val) {
this.$emit("update:page", val);
this.$emit("page-rows-change");
},
summaryMethod(param) {
// 合计行
return summaryMethod(
param,
this.headers,
this.sumText,
this.levelColumns
);
},
spanMethod(param) {
if (this.spanOption.spanIndex.length > 0) {
return spanMethod(param, this.spanOption, this.list);
}
},
/**
* @description: 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否
* @param {Number} row 行数据
* @param {Boolean} selectType 是否勾选
*/
toggleRowSelection(row, isSelected = true) {
this.$refs[this.tableRef].toggleRowSelection(row, isSelected);
},
/**
* @description 用于多选表格,清空用户的选择
*/
clearSelection() {
this.$refs[this.tableRef].clearSelection();
},
/**
* @param {Number} row 行数据
* @param {*} index
* @description 用于处理在type selection时,是否可选。所以返回的list数据要清洗数据添加isChecked字段
*/
selectable(row) {
return row.isChecked == undefined || row.isChecked == true ? true : false;
}
}
};
</script>
使用
<Table
ref="cTable"
:is-page="false"
:headers="detailHeaders"
:list="editTable"
:page.sync="page"
:rows.sync="rows"
@page-rows-change="pageChange"
@selection-change="selectionChange"
>
<template #productCode="{ row }">
<el-input v-model="row.productCode"></el-input>
</template>
<template #bdemandQty="{ row }">
<el-input v-model="row.bdemandQty"></el-input>
</template>
<template #sdemandQty="{ row }">
<el-input v-model="row.sdemandQty"></el-input>
</template>
<template #bpric="{ row }">
<el-input v-model="row.bprice"></el-input>
</template>
<template #sprice="{ row }">
<el-input v-model="row.sprice"></el-input>
</template>
<template #zp="{ row }">
<el-checkbox v-model="row.zp"></el-checkbox>
</template>
</Table>
export default{
data(){
return {
detailHeaders: [
{
label: "产品编码",
prop: "productCode",
width: 180
},
{
label: "产品名称",
prop: "productName"
},
{
label: "条码",
prop: "barCode"
},
{
label: "需求数量",
prop: "bdemandQty",
child: [
{
label: "大包装",
prop: "bdemandQty"
},
{
label: "小包装",
prop: "sdemandQty"
}
]
},
{
label: "销售数量",
prop: "borderedQty",
child: [
{
label: "大包装",
prop: "borderedQty"
},
{
label: "小包装",
prop: "sorderedQty"
}
]
},
{
label: "销售价格",
prop: "bprice",
child: [
{
label: "大包装",
prop: "bprice"
},
{
label: "小包装",
prop: "sprice"
}
]
},
{
label: "可供量",
prop: "usableBigQty",
child: [
{
label: "大包装",
prop: "usableBigQty"
},
{
label: "小包装",
prop: "usableSmallQty"
}
]
},
{
label: "金额",
prop: "amount"
},
{
label: "计量单位",
prop: "bigUom",
child: [
{
label: "大",
prop: "bigUom"
},
{
label: "小",
prop: "smallUom"
}
]
},
{
label: "换算",
prop: "convert"
},
{
label: "赠品",
prop: "zp"
}
],
}
}
}
效果
千里之行
始于足下
更多推荐
所有评论(0)