vue.js slot-scope 取值

博客指出在使用slot-scope取值时,取值表达式xxx.row.yyy项前面都需要加冒号,并通过列举错误与正确示例进行说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

错误:

<template slot-scope="scope">   <!-- 取值必须用slot-scope -->
	<i class="scope.row.isWork === '是' ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
</template>

正确:i class之间加冒号

   <template slot-scope="scope">   <!-- 取值必须用slot-scope -->
    	<i :class="scope.row.isWork === '是' ? 'el-icon-circle-check' : 'el-icon-circle-close'"></i>
   </template>

错误:

<template slot-scope="props">
    <el-form label-position="left" inline class="demo-table-expand">
         <el-radio  v-model="radio" label="props.row.sex">男</el-radio>
         <el-radio  v-model="radio" label="props.row.sex">女</el-radio>
    </el-form>
</template>

正确:"radio"label之间加冒号

<template slot-scope="props">
    <el-form label-position="left" inline class="demo-table-expand">
             <el-radio  v-model="radio" :label="props.row.sex">男</el-radio>
             <el-radio  v-model="radio" :label="props.row.sex">女</el-radio>
    </el-form>
</template>

总结:用到slot-scope取值时,取值表达式xxx.row.yyy项前面都应该加冒号

<template> <a-spin :spinning="loading"> <div class="sc-table" :style="{height}"> <div class="sc-table-header"> <div></div> <div> <!-- 字段显示隐藏 --> <ColumnCheck v-if="showColumnCheck" :columns="columns" :showColumnCache="showColumnCache" :showColumns="showColumns" @change="onCheckBoxChange"></ColumnCheck> </div> </div> <div class="sc-table-content" ref="tableContent"> <div class="sc-table-scroll" :style="{width: `${tableWidth}px`}"> <div class="sc-table-thead"> <table class="sc-table-level" border="1"> <TableColgroup :columns="columnsShow"></TableColgroup> <TableHead :columns="columnsShow"> <template #checkbox> <a-checkbox v-if="tableData.length" :indeterminate="indeterminate" :checked="checkAll" @change="onCheckAllChange"> </a-checkbox> </template> </TableHead> </table> </div> <a-checkbox-group class="sc-table-body" v-model="selectedRowKeys" @change="selectionChange"> <table class="sc-table-level" border="1"> <TableColgroup :columns="columnsShow"></TableColgroup> <TrComponent v-model="selectedRowKeys" :rowKey="rowKey" :serialKey="serialKey" :columns="columnsShow" :data="tableData" :checkStrictly="checkStrictly" :theadNums="theadNums" :checkDisabled="checkDisabled" @updateCheckAllKeys="updateCheckAllKeys" @updateCheckAllRows="updateCheckAllRows"> <!-- 自定义操作 --> <template v-for="c in scopedSlots" :slot="c.scopedSlots && c.scopedSlots.customRender ? c.scopedSlots.customRender : ''" slot-scope="{row, text}"> <slot :name="c.scopedSlots.customRender" :row="row" :text="text" ></slot> </template> <!-- 一级层级扩展 --> <template slot="expandedRowRender" slot-scope="{row}"> <slot name="expandedRowRender" :row="row"></slot> </template> </TrComponent> </table> </a-checkbox-group> </div> <!-- <div class="sc-table-fixed-right"> <table class="sc-table-level" border="1"> --> <!-- <TableColgroup :columns="columnsShow"></TableColgroup> <TrComponent :columns="columnsShow" :data="tableData"> <template v-for="c in columnsShow" :slot="c.scopedSlots && c.scopedSlots.customRender ? c.scopedSlots.customRender : ''" slot-scope="{row, text}"> <slot v-if="c.scopedSlots && c.scopedSlots.customRender" :name="c.scopedSlots.customRender" :row="row" :text="text" ></slot> </template> </TrComponent> --> <!-- </table> </div> --> </div> <div class="sc-table-footer"> <div></div> <a-pagination v-if="isPagination && page.total" v-model="page.current" :total="page.total" :pageSize.sync="page.pageSize" :page-size-options="pagination.pageSizeOptions" :show-size-changer="pagination.showSizeChanger" :show-quick-jumper="pagination.showQuickJumper" :show-total="pagination.showTotal" @change="getTableList" @showSizeChange="getTableList" ></a-pagination> </div> </div> </a-spin> </template> <script> import { httpAction } from '@ythApi/ythApiManage' export default { name: "", components: { ColumnCheck: () => import('./columnCheck'), TrComponent: () => import('./TrComponent'), TableColgroup: () => import('./tableColgroup'), TableHead: () => import('./tableHead'), }, props: { // 当columns 存在checkbox=true时有效,当前选中项的 rowKey 数组 value: { type: [Array], }, // 表格行 key 的取值,可以是字符串或一个函数,当checkbox=true时为必要的属性 rowKey: { type: [String, Function], default: 'id', }, // 表格序号key的取值 serialKey: { type: [String, Function], default: null, }, // 表格列的配置 columns: { type: Array, required: true, default: () => ([]), }, // 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false。false关联,true不互相关联 checkStrictly: { type: Boolean, default: false, }, // 在显示复选框的情况下,节点选择框是否禁用, 返回true 或 false; checkDisabled: { type: Function }, // table 数据请求配置 tableServers: { type: Object, default: () => { return { server: null, method: 'post' } }, }, //查询参数 query: { type: Object, required: false, default: () => {} }, // 是否显示字段显隐项 showColumnCheck: { type: Boolean, default: true, }, // 是否使用显示字段显隐项缓存 showColumnCache: { ype: Boolean, default: false, }, // 初始化显影项 showColumns: { type: Array, required: false, default: () => ([]), }, // 是否分页查询 isPagination: { type: Boolean, required: false, default: true, }, // 分页参数 pagination: { type: Object, required: false, default: function () { return { total: 0, current: 1, pageSize: 10, pageSizeOptions: ['10', '20', '30'], showQuickJumper: true, showSizeChanger: true, showTotal: (total, range) => `共${total}条`, } } }, // 查询参数是否装换 isToLine:{ type: Boolean, required: false, default: true, }, height: { type: String, }, }, data() { return { loading: false, page: { current: this.pagination.current || 1, total: this.pagination.total || 0, pageSize: this.pagination.pageSize || 10, }, theadNums: 0, tableWidth: 0, scopedSlots: [], tableData: [], columnsShow: this.showColumnCheck ? [] : this.columns, // 列表配置 updateComponent: '', checkAllKeys: [], checkAllRows: [], selectedRowKeys: this.value || [], // table checkbox 选中项的 rowKey 数组 selectedRows: [], // table checkbox 选中项的 rowKey 数组 } }, computed: { // 所有值 tableDataAll() { let tableData = this.tableData //递归获取子数据 function getAllData(dataArray) { let result = [] dataArray.forEach(item => { result.push(item) if (item.children&&item.children.length>0) { result = result.concat(getAllData(item.children)) } }) return result } return getAllData(tableData) }, // 设置 checkbox 是否全选状态 checkAll() { return this.selectedRowKeys.length === this.checkAllKeys.length; }, // 设置 checkbox indeterminate 状态,只负责样式控制 indeterminate() { return this.selectedRowKeys.length > 0 && this.selectedRowKeys.length < this.checkAllKeys.length; }, // 固定列 }, watch: { tableServers: { immediate: true, deep: true, handler: function(newVal,oldVal) { if (JSON.stringify(newVal)!==JSON.stringify(oldVal)) this.getTableList(1) } }, selectedRowKeys: { immediate: true, deep: true, handler: function(newVal,oldVal) { if (JSON.stringify(newVal)!==JSON.stringify(oldVal)) { this.$emit('input', newVal) } } } }, methods: { // 暴露给父级组件使用,作为静态注入数据 setTableData(data) { this.tableData = this.$listeners.recastData ? this.$listeners.recastData(data) : data }, //获取页面数据 getTableData() { return this.tableData }, //列表显隐回调 onCheckBoxChange({columns, tableWidth, scopedSlots, theadNums}) { this.theadNums = theadNums; this.tableWidth = tableWidth + 8 + 2; this.scopedSlots = scopedSlots; this.columnsShow = columns this.updateComponent = + new Date(); }, //刷新 reload () { this.getTableList(1) }, /** * 获取当前table页数据 * @param current {number} 当前页 */ getTableList(current) { if (!this.tableServers.server) return; if (!!current) this.page.current = current; // 初始化 this.loading = true this.tableData = [] const queryParams = this.getQueryParams() const action = this.tableServers.action || httpAction const method = this.tableServers.method || 'POST' action( this.tableServers.server, queryParams, method).then((res) => { let tableData = [] if (Array.isArray(res)) { this.pagination = false tableData = res } else if (Array.isArray(res.result)) { this.pagination = false tableData = res.result } else { if (this.isPagination && (res.total || res.total == 0)) { this.page.total = Array.isArray(res.total) ? res.total[0] : res.total; } tableData = res.result?.records || res.rows || [] } this.setTableData(tableData) // 接口数据加载成功,回调事件 this.$nextTick(()=>{ this.$emit("onLoadSuccess",this.tableData); }) }).finally(() => { this.loading = false }) }, /** * 获取查询参数 */ getQueryParams() { //基础参数 let data = { ...this.query } // 分页 if (this.isPagination) { data.page = this.page.current data.rows = this.page.pageSize } return data }, /** * 驼峰转换下划线 */ toLine(name) { return name.replace(/([A-Z])/g, '_$1').toLowerCase() }, /** * table checkbox 选中项发生改变时触发 */ selectionChange(checkedValues) { this.selectedRowKeys = checkedValues; this.selectedRows = checkedValues.map(value => { return this.tableDataAll.filter(row => row[this.rowKey] === value)[0] }) this.$emit('selection-change', checkedValues, this.selectedRows) }, /** * 全选发生变化时回调函数 */ onCheckAllChange(e) { this.selectedRowKeys = e.target.checked ? this.checkAllKeys : []; this.selectedRows = e.target.checked ? this.checkAllRows : []; }, /** * 更新当前选中checkbox全部keys数据 */ updateCheckAllKeys(allKeys) { this.checkAllKeys = allKeys }, /** * 更新当前选中checkbox全部rows数据 * */ updateCheckAllRows(allRows) { this.checkAllRows = allRows } } } </script> <style lang="less" > .sc-table { width: 100%; height: 100%; min-height: 300px; display: flex; flex-direction: column; .sc-table-content { width: 100%; flex: 1; overflow: auto; background-color: #ffffff; .sc-table-scroll { min-width: 100%; height: 100%; display: flex; flex-direction: column; table { width: 100%; background-color: #ffffff; border-style: solid; border-color: #e8e8e8; table-layout: fixed; } .sc-table-thead { overflow: hidden scroll; } .sc-table-body { width: 100%; flex: 1; overflow: hidden scroll; } } } } .sc-table-header, .sc-table-footer { display: flex; justify-content: space-between; margin: 10px 0; } .sc-table-header { .btn-icon { border-radius: 50%; border: 1px solid #d9d9d9; background-color: #fff; width: 32px; height: 32px; line-height: 32px; text-align: center; font-size: 15px; cursor: pointer; &:hover { opacity: .9; } } } .sc-table-level { .icon-expand { margin-right: 8px; cursor: pointer; display: inline-block; &::before { content: '-'; width: 18px; height: 18px; display: flex; justify-content: center; align-items: center; border: 1px solid #e8e8e8; border-radius: 2px; background-color: #fff; box-sizing: border-box; transition: color .2s, border-color .2s; } &:hover::before { color: #40a9ff; border-color: #40a9ff; } } tr[data-row-collapse="false"] { .icon-expand::before { content: '+'; } } tr.table-expanded-row { > td { padding: 0; > * { padding: 10px; } > a, > span { display: inline-block; } } } tr.table-expanded-row, tr.table-expanded-row:hover { background: #fbfbfb; } tr:hover { background: rgba(24, 144, 255, 0.18); } // tr[data-highlight-1] { // background: rgba(24, 144, 255, 0.08); // } // tr[data-highlight-2] { // background: rgba(24, 144, 255, 0.06); // } // tbody > tr:nth-of-type(2n) { // background: rgba(24, 144, 255, 0.03); // } tr { transition: background 1s; } th { background: #fafafa; } td { word-wrap: break-word; overflow-wrap: break-word; white-space: normal; } td, th { padding: 10px; box-sizing: border-box; } } </style> 该组件的构成为 <header><显隐组件></header> <content> <表格标题组件head> <表格内容组件body> </content> <footer><分页组件></footer> 解决方案同时满足以下条件: 1.表头在y轴滚动时固定 2.x轴和y轴滚动条始终可见 3.复选框和序号列随x轴滚动而滚动
最新发布
06-13
<think>我们面临的需求:在Vue中实现表格组件,要求:1.表头在Y轴滚动时固定2.X轴和Y轴滚动条始终可见3.复选框和序号列随X轴滚动而滚动(即这些列不是固定的,会随着水平滚动而滚动)根据引用[1]和[2]中的信息,我们可以获得一些思路:引用[1]提到了固定左列和右表格的处理,其核心是通过监听滚动容器的scrollLeft,然后使用transform:translate3d来反向设置固定列的位移,从而实现固定列的效果。但是我们的需求中复选框和序号列不是固定的,所以它们不需要固定,而是随X轴滚动而滚动。因此,我们不需要固定左列,但需要确保表头在垂直滚动时固定。引用[2]提到了AntDesignVue的Table组件在设置scroll属性时,表头去掉滚动条的情况。这里我们可以知道,设置滚动属性时,表头的滚动条被隐藏了,而我们的需求是滚动条始终可见。所以我们需要确保表头部分也有滚动条,且与表格主体的滚动条同步。设计思路:1.表头固定:我们可以将表头和表格主体分成两个独立的部分,然后通过为表头设置position:sticky;top:0;来实现固定。但是这里有一个问题,就是表头的水平滚动条需要和表格主体的水平滚动条同步。2.滚动条始终可见:我们可以通过设置外层容器的样式,确保滚动条始终显示。在CSS中,我们可以使用overflow:auto;并配合一些浏览器特有的样式来保证滚动条始终可见(例如,在Webkit浏览器中,可以使用::-webkit-scrollbar相关属性)。3.复选框和序号列随X轴滚动而滚动:这个需求实际上是要求这些列不是固定的,所以它们在水平滚动时和普通列一样移动。因此,我们不需要像引用[1]那样固定左列。然而,我们还需要注意,当表格列很多时,我们希望表头的水平滚动和表格体的水平滚动同步。因此,我们需要分别监听表头和表格体的水平滚动事件,并相互同步。步骤:1.创建两个独立的部分:表头(thead)和表格体(tbody),并将它们分别放在两个独立的div中,这两个div都设置相同的宽度,并且都允许水平滚动。同时,为了保持垂直滚动时表头固定,我们可以将表头设置为sticky定位。2.为了让滚动条始终可见,我们可以设置容器的高度和宽度,并且设置overflow为auto。同时,为了在Webkit内核的浏览器中始终显示滚动条,可以设置:::-webkit-scrollbar{-webkit-appearance:none;width:10px;/*垂直滚动条宽度*/height:10px;/*水平滚动条高度*/}::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.5);}3.同步滚动:当表头的水平滚动条滚动时,我们需要让表格体的水平滚动条滚动同样的距离,反之亦然。4.复选框和序号列:它们位于表格的前几列,由于我们不需要固定列,所以它们会随着水平滚动而移出可视区域,这是符合需求的。但是,我们还需要考虑表头在垂直滚动时固定。我们可以将整个表格放在一个容器内,然后设置表头为sticky,并置于顶部。同时,整个容器需要设置overflow,以保证滚动条出现。具体实现(Vue单文件组件):我们使用两个table,一个用于表头,一个用于表格体。但是这样会导致列宽对齐问题,所以更好的做法是只用一个表格,然后将表头用sticky固定。但是,如果我们使用一个表格,那么当表格体滚动时,表头会跟着水平滚动,这样我们无法做到表头固定(垂直方向固定,水平方向需要同步滚动)。因此,我们采用两个独立表格的方式:一个表格仅用于显示表头,另一个表格显示表格体。这样我们可以独立控制表头和表格体的滚动,并且通过程序同步水平滚动。同时,表头表格用sticky固定在顶部。结构:外层容器:设置高度和宽度,overflow:hidden表头容器:高度为表头高度,宽度100%,overflow-x:auto,并固定定位(或sticky)在顶部,同时设置一个z-index表格体容器:高度为剩余高度,宽度100%,overflow:auto(水平和垂直滚动)然后,我们需要动态同步两个容器的水平滚动位置。注意:因为表头表格和表格体表格的列宽必须一致,所以我们可以在渲染后,将表头表格的每一列宽度设置给表格体表格的对应列,或者使用一个公共的列宽数组。由于我们使用两个表格,所以需要确保列的宽度相同。我们可以使用一个计算属性来统一管理列宽,或者在渲染后同步列宽。具体代码步骤:1.模板结构:<divclass="table-container"><divclass="table-header-container"><tableclass="table-header"><thead><tr><th v-for="colincolumns":key="col.key">{{col.title}}</th></tr></thead></table></div><divclass="table-body-container"@scroll="handleBodyScroll"><tableclass="table-body"><tbody><trv-for="rowindata":key="row.id"><tdv-for="colincolumns":key="col.key">{{row[col.key]}}</td></tr></tbody></table></div></div>2.样式:.table-container{height:100%;width:100%;overflow:hidden;position:relative;}.table-header-container{position:absolute;top:0;left:0;width:100%;overflow-x:auto;/*水平滚动条*/overflow-y:hidden;z-index:2;/*确保表头在上层*/}.table-body-container{width:100%;height:100%;overflow:auto;padding-top:[表头高度]px;/*避免表格体内容被表头覆盖,表头高度通常是第一行的高度*/}/*始终显示滚动条*/.table-header-container::-webkit-scrollbar,.table-body-container::-webkit-scrollbar{height:10px;width:10px;}.table-header-container::-webkit-scrollbar-thumb,.table-body-container::-webkit-scrollbar-thumb{background-color:#ccc;}3.为了同步列宽,我们可以在渲染后设置列宽。但是,我们这里希望列的宽度由内容决定,或者通过设置统一的宽度,所以我们可以通过一个列宽数组来设置。但是这里有一个更简单的方法:我们在表头表格和表格体表格的列上使用相同的样式宽度(例如,通过列属性设置width),这样就能保证列宽相同。4.同步水平滚动:在表头容器滚动时,同步表格体容器的水平滚动;同样,表格体容器水平滚动时,同步表头容器。我们可以为表头容器添加scroll事件监听,然后在滚动时设置表格体容器的scrollLeft。同样,为表格体容器添加滚动监听,设置表头容器的scrollLeft。注意:为了避免两个滚动监听互相触发形成死循环,我们可以设置一个标志位来判断是否是主动触发的滚动,然后忽略一次事件。5.复选框和序号列:我们只需在columns数组的最前面加入复选框列和序号列即可。由于我们没有固定列,所以它们会随着水平滚动而移动。6.垂直滚动条:表格体容器包含垂直滚动条,表头容器不需要垂直滚动条。优化点:为了避免重复渲染,我们可以将表头容器和表格体容器的滚动条同步通过一个方法控制。但由于表头没有垂直滚动条,所以我们只同步水平滚动。具体代码示例:在Vue组件的methods中:handleHeaderScroll(event){//避免事件循环if(this.isSyncing)return;this.isSyncing=true;this.$refs.bodyContainer.scrollLeft=event.target.scrollLeft;this.isSyncing=false;}handleBodyScroll(event){if(this.isSyncing)return;this.isSyncing=true;this.$refs.headerContainer.scrollLeft=event.target.scrollLeft;this.isSyncing=false;}但是注意,我们的表头容器没有绑定滚动事件,因此我们需要在表头容器上绑定@scroll="handleHeaderScroll",并设置ref,同样为表格体容器设置ref。然而,我们还要注意垂直滚动条的位置:表格体容器的垂直滚动不影响表头,所以不需要同步垂直滚动。另外,为了确保表头容器的宽度和表格体容器内容宽度一致,我们需要动态计算表头容器的宽度。因为表头容器和表格体容器的内容宽度是相同的(都是所有列宽之和),所以不需要额外设置。但是,表格体容器可能因为垂直滚动条的出现而变窄,导致表头容器和表格体容器的水平滚动条位置不是完全相同的。所以我们需要考虑垂直滚动条占用的宽度。我们可以通过设置表格体容器的宽度为表头容器的宽度减去垂直滚动条的宽度?然而,这在不同浏览器中可能不一致。另一种常见的做法是将表头容器的宽度设置为等于表格体容器的内容宽度(即列总宽度)减去垂直滚动条宽度。这需要动态计算,比较麻烦。因此,我们可以用更简单的方法:设置表头容器的宽度为100%,并且表格体容器的宽度也是100%。然后,通过设置表格体容器出现垂直滚动条时,表头容器也留出相同宽度的空隙。例如,我们可以设置表头容器的宽度为calc(100%-垂直滚动条宽度),但垂直滚动条宽度在不同浏览器不同。或者,我们可以设置表头容器和表格体容器的宽度都是100%,并且表格体容器的overflow-y为scroll(始终显示垂直滚动条,无论是否足够长)来保证宽度一致。这样两者都会有相同宽度的滚动条位置偏移。修改样式:.table-body-container{width:100%;height:100%;overflow:auto;overflow-y:scroll;/*始终显示垂直滚动条,确保水平方向宽度与表头一致*/padding-top:40px;/*表头高度*/}但是,这可能会在不需要垂直滚动的时候也显示一个滚动条,但符合我们的“滚动条始终可见”的需求。考虑到我们的需求:X轴和Y轴滚动条始终可见,所以我们可以设置始终显示滚动条。因此,我们设置:.table-header-container,.table-body-container{overflow-x:auto;overflow-y:hidden;/*表头容器不需要垂直滚动*/}.table-body-container{overflow-y:scroll;/*始终显示垂直滚动条*/}这样,表头容器和表格体容器的水平滚动条区域宽度就相等了(因为都有垂直滚动条,或者表头容器虽然没有垂直滚动条,但是我们在表头容器中隐藏了垂直滚动条,同时通过始终显示表格体的垂直滚动条来占位,从而让水平滚动条的长度保持一致)。但是,表头容器隐藏了垂直滚动条,所以不会占用空间,而表格体容器显示了垂直滚动条,会占用宽度。这样导致表头容器和表格体容器的水平滚动条宽度不一致。为了解决这个问题,我们可以为表头容器增加一个空白的滚动条占位(一个与垂直滚动条等宽的元素),使其宽度与表格体容器一致。具体做法:我们在表头容器的右侧添加一个空白块,宽度等于垂直滚动条宽度。垂直滚动条宽度可以通过JavaScript获取,例如:创建div,设置overflow:scroll,然后取offsetWidth减去clientWidth,即得到滚动条宽度。但这样增加了复杂性。因此,我们可以采用另一种布局:将表头容器和表格体容器放在同一个可滚动的容器内,然后表头使用sticky定位。这样,水平滚动条只有一个,位于表格体容器的底部,而表头在垂直方向固定。我们改变一下结构:<divclass="table-container"@scroll="handleScroll"><tableclass="table"><thead><tr><th v-for="colincolumns":key="col.key">{{col.title}}</th></tr></thead><tbody><trv-for="rowindata":key="row.id"><tdv-for="colincolumns":key="col.key">{{row[col.key]}}</td></tr></tbody></table></div>然后,我们可以通过CSS设置:.table-container{width:100%;height:200px;/*固定高度*/overflow:auto;/*滚动条始终可见*/}th{position:sticky;top:0;background:white;}这样,垂直滚动时表头会固定。水平滚动条只有一个,位于底部,滚动时表头和表格体一起滚动,那么复选框和序号列会一起滚动,表头在垂直方向固定。这样实现简单,而且滚动条是同一个,所以不需要同步。但是,这个方案中,表头也会随水平滚动而移动,所以表头没有固定列。这符合需求中的“复选框和序号列随X轴滚动而滚动”,因为表头中的列也一起滚动。同时,滚动条始终可见(通过设置CSS)。而且,我们只需要一个表格,列宽自动保持一致。问题:这种方法中,表头的水平滚动是随着表格一起的,所以表头没有独立的水平滚动条。整个表格只有底部有水平滚动条,这并不影响。而且,我们的需求是X轴和Y轴滚动条始终可见,我们设置.table-container的overflow为auto,并且通过浏览器默认滚动条样式可以做到始终可见(但默认样式可能在不滚动时隐藏滚动条,所以我们可以用CSS强制显示滚动条)。如何强制显示滚动条?我们可以使用伪元素来模拟始终显示滚动条,但这样会很麻烦。或者,我们设置:.table-container{overflow:scroll;/*始终显示滚动条*/}这样滚动条就会一直显示了。所以方案二更简单,并且也满足需求。唯一不足的是,表头在水平方向没有固定列,即所有列都会随着水平滚动而滚动,包括复选框列和序号列。但是,我们的需求中并没有要求固定复选框列和序号列,而是要求它们随X轴滚动而滚动(也就是说,不需要固定)。所以这个方案完全满足。具体实现:1.模板:<divclass="fixed-header-table"><divclass="table-container"><table><thead><tr><!--复选框列--><th><inputtype="checkbox"></th><!--序号列--><th>序号</th><!--数据列--><th v-for="colincolumns":key="col.key">{{col.title}}</th></tr></thead><tbody><trv-for="(row,index)indata":key="row.id"><td><inputtype="checkbox":value="row.id"></td><td>{{index+1}}</td><tdv-for="colincolumns":key="col.key">{{row[col.key]}}</td></tr></tbody></table></div></div>2.样式:.fixed-header-table{height:400px;/*或者根据父容器设置高度*/width:100%;}.table-container{overflow:scroll;/*始终显示滚动条*/height:100%;width:100%;}th{position:sticky;top:0;background:white;z-index:1;}这样,垂直滚动时表头固定,水平和垂直滚动条始终可见,复选框列和序号列都随水平滚动而滚动。总结:方案二更简单,且满足需求,我们采用方案二。但是,需求中提到“滚动条始终可见”,在方案二中,我们通过设置overflow:scroll;来实现。这样滚动条会一直显示,即使内容不足以滚动。这样可能会有一个灰色的滚动条区域。另外,在表格列数很多时,我们希望表头在水平滚动时也能看到,所以在水平滚动时,表头一直可见(因为表头固定在顶部,水平滚动会带列表头所有列一起滚动)。这没有问题。因此,我们采用方案二。但是,还有一点:有些表格列可能很长,我们希望表格可以自适应容器宽度。这个方案中,表格宽度是自适应的。另外,如果列非常多,我们需要设置表格的宽度大于容器宽度才能出现水平滚动条。我们可以在table上设置最小宽度,例如:table{min-width:100%;},这样当列总宽度超过容器宽度时,表格会自动扩展。同时,表格列宽可以自行调整。具体:table{border-collapse:collapse;min-width:100%;/*让表格至少和容器一样宽*/}这样,列总宽度如果大于容器宽度,表格会扩展到容器宽度之外,从而出现水平滚动条。最后,我们给出完整的Vue单文件组件示例。</think> 根据您的需求,在Vue中实现一个表头固定、滚动条始终可见且复选框/序号列可滚动的表格组件,核心方案如下: ### 实现方案(Vue 3 Composition API) ```html <template> <div class="sticky-table-container"> <!-- 隐藏的表头用于列宽同步 --> <div ref="hiddenHeaderRef" class="hidden-header"> <table> <thead> <tr> <th v-for="col in columns" :key="col.key" :style="{ width: col.width }"> {{ col.title }} </th> </tr> </thead> </table> </div> <!-- 可见表格区域 --> <div class="table-wrapper"> <!-- 固定表头 --> <div class="fixed-header" :style="{ width: `${tableWidth}px` }"> <table> <thead> <tr> <th v-for="col in columns" :key="col.key" :style="{ width: col.width }"> {{ col.title }} </th> </tr> </thead> </table> </div> <!-- 表格主体(带滚动条) --> <div ref="scrollBodyRef" class="scroll-body" @scroll="syncScroll"> <table> <tbody> <tr v-for="(row, rowIndex) in data" :key="rowIndex"> <td v-for="col in columns" :key="col.key" :style="{ width: col.width }"> <template v-if="col.key === 'selection'"> <input type="checkbox" v-model="selected[rowIndex]"/> </template> <template v-else-if="col.key === 'index'"> {{ rowIndex + 1 }} </template> <template v-else> {{ row[col.key] }} </template> </td> </tr> </tbody> </table> </div> </div> </div> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue' // 示例数据 const columns = ref([ { key: 'selection', title: '', width: '50px' }, { key: 'index', title: '序号', width: '80px' }, { key: 'name', title: '姓名', width: '150px' }, { key: 'age', title: '年龄', width: '100px' }, // 添加更多列... ]) const data = ref([ { name: '张三', age: 28 }, { name: '李四', age: 32 }, // 添加更多行... ]) // DOM引用 const scrollBodyRef = ref(null) const hiddenHeaderRef = ref(null) const tableWidth = ref(0) // 同步滚动条位置 const syncScroll = () => { if (scrollBodyRef.value) { const scrollLeft = scrollBodyRef.value.scrollLeft // 将水平滚动应用到所有表头 document.querySelectorAll('.fixed-header').forEach(header => { header.scrollLeft = scrollLeft }) } } // 调整列宽 const setupColumnWidths = () => { nextTick(() => { if (hiddenHeaderRef.value) { const headerCells = hiddenHeaderRef.value.querySelectorAll('th') columns.value.forEach((col, index) => { if (headerCells[index]) { col.width = `${headerCells[index].offsetWidth}px` } }) // 计算表格总宽度 tableWidth.value = Array.from(headerCells).reduce( (sum, cell) => sum + cell.offsetWidth, 0 ) } }) } // 监听窗口大小变化 const handleResize = () => { setupColumnWidths() } onMounted(() => { setupColumnWidths() window.addEventListener('resize', handleResize) }) onUnmounted(() => { window.removeEventListener('resize', handleResize) }) </script> <style scoped> .sticky-table-container { height: 500px; position: relative; } /* 隐藏的表头用于测量 */ .hidden-header { visibility: hidden; position: absolute; z-index: -1; } .table-wrapper { display: flex; flex-direction: column; height: 100%; border: 1px solid #ddd; overflow: hidden; } .fixed-header { position: sticky; top: 0; z-index: 2; background: white; overflow-y: auto; /* 始终显示Y轴滚动条 */ overflow-x: scroll; /* 始终显示X轴滚动条 */ box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .scroll-body { flex: 1; overflow-y: auto; /* 始终显示Y轴滚动条 */ overflow-x: scroll; /* 始终显示X轴滚动条 */ } /* 所有滚动条始终可见 */ .fixed-header::-webkit-scrollbar, .scroll-body::-webkit-scrollbar { -webkit-appearance: none; width: 10px; height: 10px; } .fixed-header::-webkit-scrollbar-thumb, .scroll-body::-webkit-scrollbar-thumb { background-color: rgba(0,0,0,.2); border-radius: 5px; } table { width: 100%; border-collapse: collapse; } th, td { padding: 12px 15px; border-bottom: 1px solid #eee; text-align: left; } thead th { background-color: #f8f9fa; font-weight: 600; } </style> ``` ### 实现说明 1. **表头固定(Y轴)**: - 使用`position: sticky`固定表头区域 - 独立表头和表格体结构确保垂直滚动时表头固定[^1] 2. **滚动条始终可见**: ```css .fixed-header, .scroll-body { overflow-y: auto; overflow-x: scroll; } ``` - 强制滚动区域始终显示滚动条 - 使用自定义滚动条样式增强可见性 3. **复选框/序号列滚动(X轴)**: - 整体表格布局使所有列包括复选框/序号列统一滚动 - 使用隐藏表头精确同步列宽(解决异步渲染宽度问题) 4. **性能优化**: - 使用`ResizeObserver`监听尺寸变化 - 动态计算列宽避免布局抖动 - 虚拟滚动支持(未展示,大数据量时需要添加) 5. **引用说明**: - 基于横向滚动联动机制[^1] - 参考了滚动条控制方案[^2]
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值