vue中使用ele组件table实现动态列

本文主要是基于element中table拼接动态列并对动态列合计、行合并、加合计行、对table默认样式的修改、table中局部loading效果的实现。

实现效果图:
在这里插入图片描述

数据结构

[
	{
            "id": "1",
            "projectBudgetId": "1",
            "subjectName": "测试名",
            "itemName": "测试名",
            "remark": null,
            "startTime": 2024,
            "endTime": 2026,
            "totalAmount": "2200.0000",
            "proPlanAndItemQuarters": [
                {
                    "capitalOutflowPlanId": null,
                    "year": "2024",
                    "quarter": null,
                    "quarterAmount": null,
                    "totalAmountForYear": "2200"
                },
                {
                    "capitalOutflowPlanId": null,
                    "year": "2024",
                    "quarter": "Q1",
                    "quarterAmount": "1200",
                    "totalAmountForYear": null
                },
            ]
        }
]

动态列主要是拼接proPlanAndItemQuarters中的字段

详细代码

下面为详细代码:

  1. template中
   <div class="table-con">
        <el-table
            :data="tableData"
            :span-method="objectSpanMethod"
            show-summary
            :summary-method="getSummaries" 
            border
            style="width: 100%; height: 100%;"
            v-loading="loading"
            element-loading-text="拼命加载中..."
            element-loading-spinner="el-icon-loading"
          >
          <el-table-column prop="subjectName" label="科目" align="center" fixed />
          <el-table-column prop="itemName" label="成本项" align="center" fixed/>
          <el-table-column prop="totalAmount" label="合计" align="center" fixed/>
          <el-table-column
            v-for="(items, index) in tableCols"
            :key="index + items"
            :label="curLable(items)"
            align="center" >
            <template v-slot="scope">   
            {{ handleReplaceNum(scope.row.proPlanAndItemQuarters[index].quarterAmount, '-') }} 
            </template>  
          </el-table-column>
          </el-table>
      </div>
  1. css
.table-con {  
  height: calc(100% - 21px);
}
 
:deep(.el-table) {
  --el-table-border-color: transparent;
}

:deep(.el-scrollbar__wrap),
:deep(.el-table th), 
:deep(.el-table tr),
:deep(.el-table td),
:deep(.el-table tfoot td.el-table__cell) {
  background-color: #0A3C9C !important;
  border-color: #04113E;
  color: #FFFFFF;
  font-size: 24px;
}

:deep(.el-table th) {
  background-color: #1B66E9 !important;
}

:deep(.el-table th.el-table__cell.is-leaf) {
  border-color: #04113E !important;
}
 
/* 去掉最下面的那一条线  */
.el-table::before {
  height: 0px;
}
 
/* 设置表格行高度 */
:deep(.el-table__body tr),
:deep(.el-table__body td) {
  padding: 0;
  height: 54px;
}
 
/* 修改高亮当前行颜色 */
:deep(.el-table tbody tr:hover>td) {
  background: #063570 !important;
}
 
/* 取消当前行高亮 */
:deep(.el-table tbody tr) {
  pointer-events: none;
}
 
/* 表格斑马自定义颜色 */
:deep(.el-table__row.warning-row) {
  background: #04113E;
}
 
:deep(.el-table .cell),
.el-table th div {
  font-size: 14px;
}

:deep(.el-table--border .el-table__inner-wrapper) {
  height: 100%;
}

:deep(.el-table tfoot td.el-table__cell) {
  border-top: 1px solid #04113E;
}

:deep(.el-table.is-scrolling-left.el-table--border .el-table-fixed-column--left.is-last-column.el-table__cell) {
  border-right: 1px solid #04113E; 
}
  1. script
<script setup>
import EmptyEle from '@/components/EmptyEle/index'; 
import titleCon from "../components/titleCon"
import { planItem } from "@/api/largeScreenProject/projectKanban"
import { changeQuarter, handleReplaceNum } from '../utils/index'

let props = defineProps({
  projectId: {
    type: String,
    required: true
  },
})

let tableData = ref([]); // 表格数据
let tableCols = ref([]); //动态列
let spanArr = ref([]);
let pos = ref(0);
let loading = ref(false);
let totalArr = ref([0, 0, 0]); //合计行数据初始化

watch(() => props.projectId, (newValue, oldValue) => {
  handlePlanItem();
})

// 获取列表数据  
const handlePlanItem = () => { 
  tableData.value = [];
  tableCols.value = []; 
  totalArr = ref([0, 0, 0]);
  loading.value = true;  
  planItem(props.projectId).then(res => {   
    if(res?.code === 200) {  
      tableData.value = res?.data;  
      tableCols.value = tableData.value?.[0]?.proPlanAndItemQuarters || [];
      handleResData();    
      calcQuarterTotal();  
    }  
  }).catch(error => {  
    console.error('获取计划项数据发生错误', error);  
  }).finally(() => {  
    loading.value = false;  
  });  
}  
  
// 对后台返回数据的处理,用于合并行  
const handleResData = () => {  
  spanArr.value = []; 
  pos.value = 0;  
  for (var i = 0; i < tableData.value.length; i++) {  
    if (i === 0) {  
      spanArr.value.push(1);  
    } else {  
      // 判断当前元素与上一个元素subjectName是否相同  
      if (tableData.value[i].subjectName === tableData.value[i - 1].subjectName) {  
        spanArr.value[pos.value] += 1;  
        spanArr.value.push(0);  
      } else {  
        spanArr.value.push(1);  
        pos.value = i;  
      }  
    }  
  }   
}  
  
// 合并行的计算规则(当前行row、当前列column、当前行号rowIndex、当前列号columnIndex)  
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {  
  if (columnIndex === 0) {  
    const _row = spanArr.value[rowIndex];  
    return {  
      rowspan: _row,  
      colspan: 1  
    };  
  }  
  return {  
    rowspan: 1,  
    colspan: 1  
  };  
}

// 季度求和
const calcQuarterTotal = () => {
  const quarterSums = {};   
  tableData.value.forEach(item => {  
      item.proPlanAndItemQuarters.forEach(quarter => {  
          const key = quarter.year + '-' + quarter.quarter;  
          quarterSums[key] = (quarterSums[key] || 0) + Number(quarter.quarterAmount);  
      });  
  });  
    
  for (const key in quarterSums) {  
      totalArr.value.push(`${quarterSums[key].toFixed(2)}`);
  }
}

const getSummaries = (param) => {  
  const { columns, data } = param;  
  const summaries = {};  
  columns.forEach((column, index) => {  
    if (index === 0) {  
      summaries[index] = '合计';  
    } else if (index === 2) {    
      const values = data.map((item) => Number(item[column.property]));  
      const sum = values.reduce((prev, curr) => { 
        const value = Number(curr);  
        return Number.isNaN(value) ? Number(prev) : Number(prev) + value;  
      }, 0);  
      summaries[index] = sum.toFixed(2);  
    } else if (index >= 3) {
      summaries[index] = handleReplaceNum(totalArr.value[index], '-');
    }  
  });  
  return summaries;  
};

// 动态列label
const curLable = (items) => {
  return items.quarter != -1 ? items.year +'年度第'+ changeQuarter(items.quarter) + '季度' : items.year +'年度';
}


onMounted(() => {
  handlePlanItem();
})
</script>

具体代码块-动态列实现

<el-table>
	<!-- 这块可写其它固定的列展示 -->
	
	<el-table-column
            v-for="(items, index) in tableCols"
            :key="index + items"
            :label="curLable(items)"
            align="center" >
            <template v-slot="scope">   
            {{ handleReplaceNum(scope.row.proPlanAndItemQuarters[index].quarterAmount, '-') }} 
            </template>  
          </el-table-column>
</el-table>

对上面代码的简单说明:
1, 我这块tableCols取的后端返回数据取的第一条数据中的proPlanAndItemQuarters字段,因为动态拼接的列头都一样,所以随便遍历一条即可。【列头label显示需要前端自行转换拼接为:X年度第X季度。若quarter为-1,则标识只有年度】
2. 插槽进行具体数据显示,这里注意与vue2插槽用法的区别,这里只能用v-slot
3. handleReplaceNum这个封装的方法主要是将为0的数据或者没有的数据替换为 - 显示(可不要)

具体代码块-行合并

:span-method="objectSpanMethod"
// 在获取到数据后对数据进行合并
const handleResData = () => {  
  spanArr.value = []; // 用于存储跨越行数
  pos.value = 0;  // 该变量用于跟踪 spanArr数组中最后一个非零元素的位置
  for (var i = 0; i < tableData.value.length; i++) {  
    if (i === 0) {  
     // 数组中的第一个元素, 添加到 spanArr数组中。这是因为第一个单元格不需要合并,所以它的跨越行数为 1 
      spanArr.value.push(1);
    } else {  
      // 数组中的其他数据,代码检查当前元素的 subjectName 属性是否与前一个元素的 subjectName 属性相同
      if (tableData.value[i].subjectName === tableData.value[i - 1].subjectName) {  
       // 相同则意味着当前单元格需要与上一个单元格合并,将 spanArr数组中对应位置的值加 1。然后在 spanArr数组末尾添加一个 0,表示接下来的单元格不需要跨越任何行
        spanArr.value[pos.value] += 1;  
        spanArr.value.push(0);  
      } else {
      // 如果不同,则在 spanArr数组末尾添加一个 1,表示当前单元格不需要跨越任何行,并更新 pos为当前索引 i,以便在下次迭代时能够正确更新跨越行数  
        spanArr.value.push(1);  
        pos.value = i;  
      }  
    }  
  }   
}  


const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {  
  if (columnIndex === 0) { // 对第一列数据进行处理  
    const _row = spanArr.value[rowIndex];  // 从 spanArr数组中获取与当前行索引对应的值,并将其存储在 _row 变量中,列不需要合并,因此值均为1
    return {  
      rowspan: _row,  
      colspan: 1  
    };  
  }  
  return {  
    rowspan: 1,  
    colspan: 1  
  };  
}

若想合并多列,这里举个栗子 合workName 和 firstLevelNodeName两列

const handleResData = () => {  
  spanArr.value = []; 
  pos.value = 0;  
  for (var i = 0; i < dataSource.value.length; i++) {  
    if (i === 0) {  
      spanArr.value.push(1);  
    } else {   
      if (dataSource.value[i].workName === dataSource.value[i - 1].workName ||
          dataSource.value[i].firstLevelNodeName === dataSource.value[i - 1].firstLevelNodeName) {  
        spanArr.value[pos.value] += 1;  
        spanArr.value.push(0);  
      } else {  
        spanArr.value.push(1);  
        pos.value = i;  
      }  
    }  
  }  
}

const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {  
   // 对这两列数据进行合并
  if (['workName', 'firstLevelNodeName'].includes(column.property)) {
    const _row = spanArr.value[rowIndex];
    return {
      rowspan: _row,
      colspan: 1
    };
  }  
  return {
    rowspan: 1,
    colspan: 1
  };
}

具体代码块-列合计

// 首先table先开启这两属性
show-summary
:summary-method="getSummaries" 
let totalArr = ref([0, 0, 0]); //合计行数据初始化(这块初始化主要是为下面取数据方便,因为我前三列不需要取这块计算到的值,到时候直接使用对应的index取对应列和即可

// 季度求和--获取到table数据后就可执行
const calcQuarterTotal = () => {
  const quarterSums = {};   
  tableData.value.forEach(item => {  
      item.proPlanAndItemQuarters.forEach(quarter => {  
          const key = quarter.year + '-' + quarter.quarter;  // 将年度和季度拼接 作为key值
          quarterSums[key] = (quarterSums[key] || 0) + Number(quarter.quarterAmount);  //对当前这个季度数据求和
      });  
  });  
    
  for (const key in quarterSums) {  
      totalArr.value.push(`${quarterSums[key].toFixed(2)}`); //将求和的数据push到一个数组中
  }
}

// 合计行中数据-第0列显示“合并”, 第1列都是文字不需要,第2列对后端返回的数据求和,从第3列开始就取我们计算好的值
const getSummaries = (param) => {  
  const { columns, data } = param;  
  const summaries = {};  //储存最终的合计数据
  columns.forEach((column, index) => {  
    if (index === 0) {  
      summaries[index] = '合计';  
    } else if (index === 2) {    
      const values = data.map((item) => Number(item[column.property]));  
      const sum = values.reduce((prev, curr) => { 
        const value = Number(curr);  
        return Number.isNaN(value) ? Number(prev) : Number(prev) + value;  
      }, 0);  
      summaries[index] = sum.toFixed(2);  
    } else if (index >= 3) {
      summaries[index] = handleReplaceNum(totalArr.value[index], '-');
    }  
  });  
  return summaries;  
};

具体代码块-修改el-table默认样式

:deep(.el-table) {
  --el-table-border-color: transparent;
}

:deep(.el-scrollbar__wrap),
:deep(.el-table th), 
:deep(.el-table tr),
:deep(.el-table td),
:deep(.el-table tfoot td.el-table__cell) {
  background-color: #0A3C9C !important;
  border-color: #04113E;
  color: #FFFFFF;
  font-size: 24px;
}

:deep(.el-table th) {
  background-color: #1B66E9 !important;
}

:deep(.el-table th.el-table__cell.is-leaf) {
  border-color: #04113E !important;
}
 
/* 去掉最下面的那一条线  */
.el-table::before {
  height: 0px;
}
 
/* 设置表格行高度 */
:deep(.el-table__body tr),
:deep(.el-table__body td) {
  padding: 0;
  height: 54px;
}
 
/* 修改高亮当前行颜色 */
:deep(.el-table tbody tr:hover>td) {
  background: #063570 !important;
}
 
/* 取消当前行高亮 */
:deep(.el-table tbody tr) {
  pointer-events: none;
}
 
/* 表格斑马自定义颜色 */
:deep(.el-table__row.warning-row) {
  background: #04113E;
}
 
:deep(.el-table .cell),
.el-table th div {
  font-size: 14px;
}

:deep(.el-table--border .el-table__inner-wrapper) {
  height: 100%;
}

:deep(.el-table tfoot td.el-table__cell) {
  border-top: 1px solid #04113E;
}

:deep(.el-table.is-scrolling-left.el-table--border .el-table-fixed-column--left.is-last-column.el-table__cell) {
  border-right: 1px solid #04113E; 
}

table中局部loading效果

v-loading="loading"
element-loading-text="拼命加载中..."
element-loading-spinner="el-icon-loading"         
  1. 下面定义变量loading
  2. 在发请求时可开启loading效果

表格高度根据页面自适应

表格高设置100% 即可,若设置不正确则排查它的父以及再往上是不是都100%布的

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 3,你可以使用动态组件实现组件动态渲染。你可以使用`<component>`标签,并通过`v-bind:is`属性来决定要渲染的实际组件。这个`is`属性可以是一个字符串,可以是HTML标签名,也可以是已注册的组件名。另外,你也可以直接将组件定义传递给`is`属性,而不是组件的名称。内置组件都可以传递给`is`属性,但如果你想通过名称传递组件,则需要先对其进行注册。如果将组件本身传递给`is`属性而不是其名称,则不需要注册。[2] 具体实现时,你可以在页面上注册需要使用组件,然后使用`<component>`标签来动态渲染组件。你可以通过`v-bind:is`属性来指定要渲染的组件名称或组件定义。[3] 以下是一个示例代码,展示了如何在Vue 3使用动态组件: ```html <template> <div> <button @click="switchComponent('A')">A组件</button> <button @click="switchComponent('B')">B组件</button> <button @click="switchComponent('C')">C组件</button> <component :is="currentComponent"></component> </div> </template> <script setup> import { ref } from 'vue'; import AComponent from './components/AComponent.vue'; import BComponent from './components/BComponent.vue'; import CComponent from './components/CComponent.vue'; const currentComponent = ref(''); const switchComponent = (componentName) => { currentComponent.value = componentName; }; </script> ``` 在上面的代码,我们使用`<component>`标签来动态渲染组件。通过`v-bind:is`属性绑定了`currentComponent`变量,该变量保存了当前要渲染的组件名称。当点击不同的按钮时,调用`switchComponent`方法来切换要渲染的组件。[1] 希望这个例子能帮助你理解如何在Vue 3使用动态组件。如果你有任何其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值