首页 > 其他分享 >low-ui-vue前置解读|实现一个动态列的表格组件

low-ui-vue前置解读|实现一个动态列的表格组件

时间:2023-08-23 15:01:18浏览次数:39  
标签:vue container 表格 表头 width ui low fixed scroll

最近另一个团队正式在项目中推广内部开发的low-ui组件库了,当然 还在内部阶段,但是太慢了。作为架子的设计者和部分功能的开发者,我决定先把常见的功能通过模仿的形式公开出来。避免大家搜索无果或者使用一些框架增加学习成本。

所谓动态列的表格,就是列数不固定。 像广为使用的elementUI的 table 组件就是表头写死的,这种也叫列数固定的表格。

效果

low-ui-vue前置解读|实现一个动态列的表格组件_vue

当然,动态性增加了,当然要做出一定“牺牲”。这是表格组件的表头和表内容的数据格式 —— 我们把它分为两个数组传入:

数据传入

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。

基础版实现

表格整体当然是用了原生的 tabletrtd 实现。

虽然表头看似是一个单独的内容,但是为了样式考虑,我们并没有放在 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

相关文章

  • [AHK2-UI] 实现自己的Show()方法
    为什么这其实是一种两阶段XX的设计模式,比如两阶段终止:调用终止方法时并不立即终止,而是设置终止信号,由别人自身决定终止的操作。同样,实现Show()方法算是一种两阶段启动:外部调用Show()方法时,由自身决定show前做什么,show后又做什么,以及如何show。例子这是一个Show()方法:staticS......
  • Vue项目原本原本http请求变成了https
    Vue项目http请求变更为https处理方式在index.html中添加如下代码 <metahttp-equiv="Content-Security-Policy"content="upgrade-insecure-requests">如果向去去掉s就注释<metahttp-equiv="Content-Security-Policy"content="upgrade-insecure-requests......
  • Python基础入门学习笔记 077 GUI的终极选择:Tkinter14
    Tkinter提供了三种标准对话框模块,分别是:messagebox、filedialog、colorchoosermessagebox(消息对话框)实例1:askokcancel函数1fromtkinterimport*23print(messagebox.askokcancel("FishCDemo","发射核弹?"))45mainloop() 实例2:askquestion函数 实例3:asire......
  • vue3 使用 router 进行跳转备忘
    1.在画面中添加子画面,使用el-menu菜单进行跳转,只更新子画面a.首先在配置router路径的js文件中配置画面的路径,子画面的路径要在父画面的children下面 在父节点下设置redirect属性,打开父画面时会默认打开相应子画面,否则子画面默认显示为空白 b.在画面显示区域添加......
  • [AHK2-UI] 使用#Include
    #Include是什么一句话介绍:可以将一个脚本的代码插入到Include语句的位置。作用使用#Include可以实现分模块开发,对于代码组织有十分重要的作用。通常使用小型脚本(只有些热键和热字串)不需要使用;但当脚本不仅仅是这些,还要写ui界面或更繁杂的功能时,我们最好将ui和数据处理的逻辑分......
  • Python基础入门学习笔记 074 GUI的终极选择:Tkinter11
    事件绑定对于每个组件来说,可以通过bind()方法将函数或方法绑定到具体的事件上。当被触发的事件满足该组件绑定的事件时,Tkinter就会带着事件描述去调用handler()方法实例1:捕获单击鼠标位置1fromtkinterimport*23root=Tk()45defcallback(event):6prin......
  • 直播平台软件开发,vue-全局过滤器时间
    直播平台软件开发,vue-全局过滤器时间步骤一:处理数据 exportconstformatYmd=(date)=>{ lettime=newDate(date)  lety=time.getFullYear() letm=time.getMonth()+1 letd=time.getDate()  return[y,m,d].map((v)=>String(v).padStart(2,'0......
  • Python基础入门学习笔记 075 GUI的终极选择:Tkinter12
    Message组件Message(消息)组件是Label组件的变体,用于显示多行文本信息。Message组件能够自动换行,并调整文本的尺寸使其适应给定得尺寸。实例1:1fromtkinterimport*23root=Tk()4w1=Message(root,text="这是一则消息",width=100)5w1.pack()6w2=Message(root,......
  • Python基础入门学习笔记 071 GUI的终极选择:Tkinter8
    Canvas(画布)组件一个可以让你随心所欲绘制界面的组件。通常用于显示和编辑图形,可以用它来绘制直线、图形、多边形,甚至是绘制其他组件。实例1:1fromtkinterimport*2root=Tk()3#创建canvas对象框,设置其宽度、高度与背景色4w=Canvas(root,width=200,height=100,b......
  • Python基础入门学习笔记 073 GUI的终极选择:Tkinter10
    Munu组件Tkinter提供了一个Menu组件,用于实现顶级菜单、下拉菜单和弹出菜单。实例1:创建一个顶级菜单(或称窗口主菜单)1fromtkinterimport*23defcallback():4print("被调用了")56root=Tk()789menubar=Menu(root)#创建一个顶级菜单10m......