最近另一个团队正式在项目中推广内部开发的low-ui组件库了,当然 还在内部阶段,但是太慢了。作为架子的设计者和部分功能的开发者,我决定先把常见的功能通过模仿的形式公开出来。避免大家搜索无果或者使用一些框架增加学习成本。
所谓动态列的表格,就是列数不固定。 像广为使用的elementUI的 table 组件就是表头写死的,这种也叫列数固定的表格。
效果
当然,动态性增加了,当然要做出一定“牺牲”。这是表格组件的表头和表内容的数据格式 —— 我们把它分为两个数组传入:
数据传入
columns: [ // 表头
{ title: 'Full Name', width: 132, dataIndex: 'name', fixed: 'left' },
{ title: 'Age', width: 100, dataIndex: 'age' },
{ title: 'address1', dataIndex: 'address1', key: '1', width: 150 },
{ title: 'address2', dataIndex: 'address2', key: '2', width: 150 },
//...
{ title: '操作', dataIndex: 'do', width: 172, fixed: 'right' }
],
data: [
{ key: 1, name: '章三', age: '18', class: '2班', address1: '111', address2: '222', address3: '333', address4: '444', address5: '555', address6: '666', address7: '777', isEdit: false },
{ key: 2, name: '章三2', age: '18', class: '2班', address1: '111', address2: '222', address3: '333', address4: '444', address5: '555', address6: '666', address7: '777', isEdit: false }
]
可以看到,“表头”数组中的 title
属性就是表头应该展示的内容,dataIndex
属性就是和“表内容” data 数组中关联的属性!它的值如果作为 key
出现在表内容数组中,则表内容这一项会展示在表格中,反之则不会。
这里也是为另一种情况考虑:表头和表内容数组都由后端提供,并不是所有返回的东西都要展示,也不是没有展示的东西都不需要,比如某一行数据的修改需要id —— 数据由后端提供,样式由前端修改。
我们继续分析数据:我们还看到了 fixed
属性和 width
属性。前者是用来判断超出表格宽度时最左侧和最右侧是否固定在两侧,这个属性只能在表头数组的第一项和最后一项中出现。后者是控制当前列的宽度。这个属性也只能在表头数组中出现!
而表内容数组中出现了另一个值:isEdit
。它用来判断当前行是否“在修改”。后面会看到,我们给表内容的每一项 v-if
了一个 input
或者自定义component。
基础版实现
表格整体当然是用了原生的 table
、tr
、td
实现。
虽然表头看似是一个单独的内容,但是为了样式考虑,我们并没有放在 th 中,而是作为一个普通的td,反之样式可以自定义:
<div class="table-container" ref="tableContainer" @scroll="handleScroll">
<table>
<colgroup>
<col v-for="(column, index) in columns" :key="index"
:style="{ width: column.width + 'px', minWidth: column.width + 'px' }"
:class="{ 'fixed-left': index === 0, 'fixed-right': index === columns.length - 1 && column.fixed === 'right' }" />
</colgroup>
<tbody>
<tr>
<td v-for="(column, index) in columns" :key="index"
:style="{ width: column.width + 'px', minWidth: column.width + 'px' }"
:class="{ 'fixed-left': index === 0, 'fixed-right': index === columns.length - 1 && column.fixed === 'right', 'header-cell': true }">
<div class="fixed-item"><div style="display: flex;align-items: center;height: 32px;">{{ column.title }}</div></div>
</td>
</tr>
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="(column, columnIndex) in columns" :key="columnIndex"
:class="{ 'fixed-left': columnIndex === 0, 'fixed-right': columnIndex === columns.length - 1 && column.fixed === 'right' }">
<div class="fixed-item">
<template v-if="column.dataIndex === 'do'">
<div style="display: flex;align-items: center;height: 32px;">
<slot :row="row"></slot>
</div>
</template>
<template v-else-if="!row.isEdit && !row.component"><div style="display: flex;align-items: center;height: 32px;">{{ row[column.dataIndex] }}</div></template>
<component :is="row.component" v-bind="row.props" v-else-if="row.component" />
<template v-else>
<div style="display: flex;align-items: center;">
<a-input v-model="row[column.dataIndex]" placeholder="" allow-clear />
</div>
</template>
</div>
</td>
</tr>
</tbody>
</table>
</div>
这操作看着很常规:
- 在表格的HTML结构中,使用
v-for
指令来循环生成列和行。v-for="(column, index) in columns"
用于生成列,v-for="(row, rowIndex) in data"
用于生成行。 - 每个单元格的内容由
row[column.dataIndex]
决定,其中column.dataIndex
是列的属性名,row
是当前行的数据对象。
为了简化和防止数据冲突,我用了<colgroup>
和<col>
标签,以达到“只需要在表头数据中添加 width 即可”的效果(不需要为每个单元格单独设置样式)。
同时,从性能角度考虑:使用<colgroup>
和<col>
元素可以帮助浏览器更有效地渲染表格 —— 因为它们提供了关于表格列的元数据信息,由于列的宽度和样式是在<col>
元素中定义的,浏览器可以提前计算表格的布局,从而提高渲染性能。
.table-container {
overflow-x: auto;
max-width: 100%;
position: relative;
td {
padding: 0;
background-color: #fff;
border-bottom: 0.9px solid #eee;
.fixed-item {
padding: 13px;
&.header-cell {
font-size: 14px;
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
}
}
}
}
.fixed-left {
position: sticky;
left: 0;
width: 142px;
align-items: center;
z-index: 9;
.fixed-item {
display: block;
}
}
.fixed-right {
position: sticky;
right: 0;
width: 172px;
align-items: center;
z-index: 9;
.fixed-item {
display: block;
}
}
.header-cell {
background-color: #fafafa !important;
}
同时,我们监听了表格的 scroll 事件,在滚动的时候动态添加删除某个元素 —— 让表格左右侧列的阴影效果在需要的时候才展示:
handleScroll(event) {
const container = event.target;
const scrollLeft = container.scrollLeft;
const maxScrollLeft = container.scrollWidth - container.clientWidth;
// 根据滚动位置添加或移除阴影样式
if (scrollLeft === 0) {
container.classList.add('scroll-left');
container.classList.remove('scroll-right');
} else if (scrollLeft >= maxScrollLeft) {
container.classList.add('scroll-right');
container.classList.remove('scroll-left');
} else {
container.classList.add('scroll-left');
container.classList.add('scroll-right');
}
}
对应的css样式:
/* 添加阴影样式 */
&.scroll-left .fixed-right {
border-bottom: 0.1px solid transparent !important;
.fixed-item {
width: 100%;
height: 100%;
box-shadow: 1px 27px 22px 0 rgba(0, 0, 0, 0.2);
}
}
&.scroll-right .fixed-left {
border-bottom: 0.1px solid transparent !important;
.fixed-item {
width: 100%;
height: 100%;
box-shadow: -1px 27px 22px 0 rgba(0, 0, 0, 0.2);
}
}
到此为止,如开头所示就实现了。
使用如下:
<biaoge :columns="columns" :data="data">
<template v-slot:default="{ row }">
<a-button type="link">{{ row.isEdit ? '完成' : '修改' }}</a-button>
</template>
</biaoge>
进阶?
上面的代码虽然我们只在滚动中操作了 class ,并没有直接操控 style,但它仍然是监听了scroll
!
能不能完全用 css 实现阴影的动态显示?能!但是再说下去就不礼貌了[doge]。给个提示:用linear-gradient!点击我的这篇文章查看相关分析 。 而且这种思想在很多地方都有使用:视觉遮盖。
这是本系列第一篇文章,有点长。关于组件库和配套工具的介绍什么的后面再说。好久没写文章了哈哈,润了润了。
标签:vue,container,表格,表头,width,ui,low,fixed,scroll From: https://blog.51cto.com/u_15296224/7203139