BootstrapVue表格拖拽列异常:仅表头可拖,表格内容无变化
问题描述
- 在BootstrapVue表格中实现列拖拽时,仅表头可拖拽,表格内容无法同步更新,打印
updatedHeaders显示为undefined - 需要实现复选框选择显示列的功能
现有代码
<template> <div class="container mt-5"> <div class="row"> <h3>Managing Existing Accounts</h3> </div> <div class="row mt-3"> This section shows all accounts you currently manage.<br> Click on a row to open up details about that account.<br> Accounts that have expired will have all their personally identifiable information scrubbed in 90 days. </div> <!--toggle for column customization--> <div> <b-button v-b-toggle.column-collapse variant="primary">Column</b-button> <b-collapse id="column-collapse" class="mt-2"> <b-card> <p class="card-text">Choose the column</p> <b-form-checkbox-group v-model="selectedColumns"> <b-form-checkbox value="Renew Date">Renew Date</b-form-checkbox> <b-form-checkbox value="Account Name">Account Name</b-form-checkbox> <b-form-checkbox value="Location">Location</b-form-checkbox> <b-form-checkbox value="Type">Type</b-form-checkbox> <b-form-checkbox value="Account Type">Account Type</b-form-checkbox> </b-form-checkbox-group> </b-card> </b-collapse> <div> <table class="table table-striped"> <thead class="thead-dark"> <draggable v-model="updatedHeaders" tag="tr" @end="dragged"> <th v-for="header in headers" :key="header" scope="col"> {{ header }} </th> </draggable> </thead> <tbody> <tr v-for="item in items" :key="item.gymID"> <td v-for="header in headers" :key="header">{{ item[header] }}</td> </tr> </tbody> </table> </div> <rawDisplayer class="col-2" :value="items" title="Items"/> <rawDisplayer class="col-2" :value="updatedHeaders" title="Headers"/> </div> </div> </template> <script> import draggable from 'vuedraggable'; export default { components: { draggable, }, data() { return { dataLoaded: false, accountList: [], items: [], selectedColumns: ['Renew Date', 'Account Name', 'Location', 'Type', 'Account Type'], _xsrf: "", updatedHeaders: [] }; }, created() { // Load the saved column order from local storage const headers = localStorage.getItem('tableHeaders'); if (headers !== "undefined" && headers) { const selectedFields = JSON.parse(headers); this.selectedColumns = selectedFields.map(field => field.label); this.updatedHeaders = selectedFields.map(field => field.key); } fetch('/distributor/api/managing-existing-accounts') .then(response => response.json()) .then(data => { this.accountList = data.accountList; this.items = data.accountList; this.dataLoaded = true; this._xsrf = data._xsrf; if(!this.updatedHeaders.length) { this.updatedHeaders = this.filteredFields?.map(field => field.key) || []; } }) .catch(error => { console.error('Error fetching data:', error); }); }, computed: { filteredFields() { if (this.dataLoaded) { return Object.keys(this.items[0] ? this.items[0] : {}) .filter(field => this.selectedColumns.includes(field)) .map(field => ({ key: field, label: field })); } return []; }, headers() { return this.updatedHeaders.length ? this.updatedHeaders : this.filteredFields.map(field => field.key); }, }, methods: { dragged() { // Map the new order of headers to their corresponding labels const updatedLabels = this.updatedHeaders.map(header => this.filteredFields.find(field => field.key === header).label); // Save the new order of headers to local storage localStorage.setItem('tableHeaders', JSON.stringify(this.updatedHeaders.map(key => ({key, label: key})))); } } } </script> <style> /* Add your styles here */ </style>
解决方案
一、修复列拖拽同步问题
核心问题
draggable组件绑定的updatedHeaders初始为空,数据加载时的赋值逻辑依赖延迟计算的filteredFields,导致拖拽时数组未正确初始化- 表头和内容循环的数据源不一致,拖拽更新
updatedHeaders后,内容未跟随新顺序渲染
修复步骤
- 修正
updatedHeaders初始化逻辑
去掉依赖filteredFields的赋值,直接用selectedColumns初始化,避免异步加载导致的延迟:
// 接口数据返回后 if(!this.updatedHeaders.length) { this.updatedHeaders = [...this.selectedColumns]; }
- 统一拖拽与渲染的数据源
将draggable内部的表头循环、表格内容的单元格循环都改为使用updatedHeaders,确保拖拽顺序直接同步到内容:
<!-- 表头部分 --> <draggable v-model="updatedHeaders" tag="tr" @end="dragged"> <th v-for="header in updatedHeaders" :key="header" scope="col"> {{ header }} </th> </draggable> <!-- 内容部分 --> <tr v-for="item in items" :key="item.gymID"> <td v-for="header in updatedHeaders" :key="header">{{ item[header] }}</td> </tr>
- 简化本地存储逻辑
去掉冗余的key/label结构,直接存储表头数组,避免解析错误:
// 拖拽结束后保存 dragged() { localStorage.setItem('tableHeaders', JSON.stringify(this.updatedHeaders)); this.selectedColumns = [...this.updatedHeaders]; // 同步复选框选中状态 } // 初始化读取 const savedHeaders = localStorage.getItem('tableHeaders'); if (savedHeaders) { const parsedHeaders = JSON.parse(savedHeaders); this.updatedHeaders = parsedHeaders; this.selectedColumns = [...parsedHeaders]; }
二、实现复选框选择显示列功能
核心逻辑
监听选中列变化,保留已有的拖拽顺序,只添加/移除选中状态变更的列,避免重置用户调整后的顺序:
- 添加监听逻辑
添加watch监听selectedColumns,更新updatedHeaders时维持原有顺序:
watch: { selectedColumns(newCols) { // 保留现有表头中仍被选中的列 const keptHeaders = this.updatedHeaders.filter(header => newCols.includes(header)); // 添加新选中的列(按默认顺序) const newAddedCols = newCols.filter(col => !keptHeaders.includes(col)); this.updatedHeaders = [...keptHeaders, ...newAddedCols]; // 保存到本地存储 localStorage.setItem('tableHeaders', JSON.stringify(this.updatedHeaders)); } }
- 修正计算属性
简化filteredFields,直接基于selectedColumns生成:
filteredFields() { if (!this.dataLoaded || !this.items.length) return []; return this.selectedColumns.map(field => ({ key: field, label: field })); }
完整修正代码
<template> <div class="container mt-5"> <div class="row"> <h3>管理现有账户</h3> </div> <div class="row mt-3"> 此部分显示您当前管理的所有账户。<br> 点击行可查看该账户的详细信息。<br> 已过期的账户,其所有个人身份信息将在90天后被清除。 </div> <!-- 列设置开关 --> <div> <b-button v-b-toggle.column-collapse variant="primary">列设置</b-button> <b-collapse id="column-collapse" class="mt-2"> <b-card> <p class="card-text">选择要显示的列</p> <b-form-checkbox-group v-model="selectedColumns"> <b-form-checkbox value="Renew Date">续费日期</b-form-checkbox> <b-form-checkbox value="Account Name">账户名称</b-form-checkbox> <b-form-checkbox value="Location">位置</b-form-checkbox> <b-form-checkbox value="Type">类型</b-form-checkbox> <b-form-checkbox value="Account Type">账户类型</b-form-checkbox> </b-form-checkbox-group> </b-card> </b-collapse> <div> <table class="table table-striped"> <thead class="thead-dark"> <draggable v-model="updatedHeaders" tag="tr" @end="dragged"> <th v-for="header in updatedHeaders" :key="header" scope="col"> {{ header }} </th> </draggable> </thead> <tbody> <tr v-for="item in items" :key="item.gymID"> <td v-for="header in updatedHeaders" :key="header">{{ item[header] }}</td> </tr> </tbody> </table> </div> <rawDisplayer class="col-2" :value="items" title="数据项"/> <rawDisplayer class="col-2" :value="updatedHeaders" title="表头顺序"/> </div> </div> </template> <script> import draggable from 'vuedraggable'; export default { components: { draggable, }, data() { return { dataLoaded: false, accountList: [], items: [], selectedColumns: ['Renew Date', 'Account Name', 'Location', 'Type', 'Account Type'], _xsrf: "", updatedHeaders: [] }; }, created() { // 读取本地存储的表头顺序 const savedHeaders = localStorage.getItem('tableHeaders'); if (savedHeaders) { const parsedHeaders = JSON.parse(savedHeaders); this.updatedHeaders = parsedHeaders; this.selectedColumns = [...parsedHeaders]; } fetch('/distributor/api/managing-existing-accounts') .then(response => response.json()) .then(data => { this.accountList = data.accountList; this.items = data.accountList; this.dataLoaded = true; this._xsrf = data._xsrf; // 无存储数据时用默认选中列初始化 if(!this.updatedHeaders.length) { this.updatedHeaders = [...this.selectedColumns]; } }) .catch(error => { console.error('获取数据失败:', error); }); }, computed: { filteredFields() { if (!this.dataLoaded || !this.items.length) return []; return this.selectedColumns.map(field => ({ key: field, label: field })); } }, watch: { selectedColumns(newCols) { const keptHeaders = this.updatedHeaders.filter(header => newCols.includes(header)); const newAddedCols = newCols.filter(col => !keptHeaders.includes(col)); this.updatedHeaders = [...keptHeaders, ...newAddedCols]; localStorage.setItem('tableHeaders', JSON.stringify(this.updatedHeaders)); } }, methods: { dragged() { localStorage.setItem('tableHeaders', JSON.stringify(this.updatedHeaders)); this.selectedColumns = [...this.updatedHeaders]; } } } </script> <style> /* 自定义样式可在此添加 */ </style>
内容的提问来源于stack exchange,提问作者Sandy Zhao
相关产品推荐
相关产品推荐

