暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

el-table合并表头、动态合并列、合并尾部合计

原创 浮游 2023-06-14
7029

在有些情况下,我们会有合并表头、合并列、合并尾部合计的需求,这篇文章是为了记录,如何进行合并,方便日后翻阅。

效果图

el-table合并表头

image.png

el-table合并列(动态合并)

image.png

el-table合并尾部合计

image.png

el-table合并表头的实现

这个地方是用的两个表格实现的,即两个el-table,上方的全选、规格、购买时长等属于一个表,下面的阿里云以及数据属于另一个表,将两个表的列宽设为一致,即可实现。

// 主表 <template> <div class="pdlr20 h-100" v-if="state.goodsList.length > 0"> <div class="sticky-top"> <el-table class="shopping-cart-table" //最上面的表头,需要把表身隐藏 style="width: 99.9%;" :header-row-style="{border: 'none'}" :header-cell-style="{border: 'none',height:'60px',fontSize: '14px',fontWeight: 600,color: '#333333',background:'#FFFFFF'}" :data="[]"> <el-table-column width="32px" label=""> <template #header> <el-checkbox v-model="state.checkAll" :disabled="state.goodsList.length === 0" :indeterminate="state.isIndeterminate"> </el-checkbox> </template> </el-table-column> <el-table-column width="268px" label="全选"></el-table-column> <el-table-column width="180px" label="规格"></el-table-column> <el-table-column label="购买时长" align="center"></el-table-column> <el-table-column width="100px" align="center" label="单价"></el-table-column> <el-table-column width="150px" align="center" label="台数"></el-table-column> <el-table-column width="120px" align="center" label="小计"></el-table-column> <el-table-column width="190px" align="center" label="操作"></el-table-column> </el-table> </div> <div v-for="(item, index) of state.cloudProvider" :key="index"> // 表身中一个个的表 <cloud-provider-merchant v-if="state.goodsClassify[item].length > 0" // 判断子表中的商品长度是否大于0 :cloudProvider="item" //用于判断当前表是'ali', 'tencent', 'huawei', 'ct', 'baidu', 'jd', 'ks'中的哪一个 :checkAll="state.checkAll"//是否全选 :index="index" @selection-change="handleSelectionChange"> // 用于计算选中项的预付金额和按需金额 </cloud-provider-merchant> </div> </div> <el-empty v-if="state.goodsList.length === 0" description="暂无云资源" :image="state.emptyImage" :image-size="240"></el-empty> <div class="c6 shopping-cart-footer pdl30r20 font-size-normal font-weight-400 d-flex align-items-center justify-content-between"> <div class="d-flex align-items-center"> <el-checkbox v-model="state.checkAll" :disabled="state.goodsList.length === 0" :indeterminate="state.isIndeterminate">全选 </el-checkbox> <el-button class="ml50 font-size-normal font-weight-400 c6 pad0 op1" type="text" @click="deleteCheckedGoods">删除 </el-button> </div> <div class="d-flex align-items-center"> <div class="mr40 d-flex align-items-center" v-if="[].concat(...Object.values(state.checkedGoods)).length > 0"> <div class="mr20">总计:</div> <div class="d-flex text-amount font-size-mini"> <div class="mr30" v-if="state.reservedTotalPrice > 0"> <span class="c3 mr6">预付:</span> <span class="text-amount">{{ state.reservedTotalPrice.toFixed(2) }} 元</span> </div> <div v-if="state.onDemandTotalPrice > 0"> <span class="c3 mr6">按需:</span> <span class="text-amount">{{ state.onDemandTotalPrice.toFixed(2) }} 元/小时</span> </div> </div> </div> </div> </div> </template> <script setup> import { useStore } from 'vuex' import { reactive, onMounted, getCurrentInstance, watch } from 'vue' import CloudProviderMerchant from './CloudProviderMerchant' const store = useStore() const { proxy } = getCurrentInstance() const goodsClassifyInitData = { ali: [], tencent: [], huawei: [], ct: [], baidu: [], jd: [], ks: [] } const state = reactive({ checkAll: false, isIndeterminate: false, goodsList: [], goodsClassify: JSON.parse(JSON.stringify(goodsClassifyInitData)), cloudProvider: ['ali', 'tencent', 'huawei', 'ct', 'baidu', 'jd', 'ks'], reservedTotalPrice: 0, onDemandTotalPrice: 0, emptyImage: require('@assets/images/no-data.png'), shoppingCartLoading: false, checkedGoods: JSON.parse(JSON.stringify(goodsClassifyInitData)) }) onMounted(() => { getGoodsList() getTotalPrice() }) watch(() => store.state.shoppingCartChange, () => { getGoodsList() getTotalPrice() getCheckAllStatus() }) watch(state.checkedGoods, () => { getCheckAllStatus() }) const getCheckAllStatus = () => { if (state.goodsList.length === 0) { state.checkAll = false state.isIndeterminate = false return } const checkedNum = Object.values(state.checkedGoods).map(item => item.length).reduce((pre, val) => { return pre + val }, 0) if (checkedNum === state.goodsList.length) { state.checkAll = true state.isIndeterminate = false } else if (checkedNum > 0 && checkedNum < state.goodsList.length) { state.isIndeterminate = true } else { state.checkAll = false state.isIndeterminate = false } } const getGoodsList = () => { const goodsClassify = { ali: [], tencent: [], huawei: [], baidu: [], ct: [], jd: [], ks: [] } state.goodsList = JSON.parse(localStorage.getItem('goodsList')) || [] state.goodsList.forEach(goods => { goodsClassify[goods.cloudProvider].push(goods) }) state.goodsClassify = goodsClassify } const getTotalPrice = () => { const checkedGoods = [].concat(...Object.values(state.checkedGoods)) // Object.values()返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同,此处是用于找到选中的商品 const filteredList = state.goodsList.filter(goods => { return checkedGoods.find(goodsHash => { // 从商品列表中,筛选出选中的商品 return goodsHash === goods.goodsHash }) }) state.reservedTotalPrice = formatFloat(filteredList.filter(item => { //选中商品计算预付金额 return item.pricingType === 'reserved' }).reduce((pre, item) => { return pre + (item.goodsNum * item.price) }, 0), 2) state.onDemandTotalPrice = formatFloat(filteredList.filter(item => { //选中商品计算按需金额 return item.pricingType === 'onDemand' }).reduce((pre, item) => { return pre + (item.goodsNum * item.price) }, 0), 2) } const formatFloat = (num, pos = 2) => { return parseFloat(num * (pos * 10), 10) / (pos * 10) // parseFloat() 函数可解析一个字符串,并返回一个浮点数。 } const deleteCheckedGoods = () => { // 删除已选的厂家 if ([].concat(...Object.values(state.checkedGoods)).length === 0) { proxy.$notify.error({ title: '错误', message: '没有选中的云资源' }) return } proxy.$confirm('确定要删除云资源吗?', '删除云资源').then(result => { if (result === 'confirm') { const checkedGoods = [].concat(...Object.values(state.checkedGoods)) const filteredList = state.goodsList.filter(goods => { return !checkedGoods.find(goodsHash => { return goodsHash === goods.goodsHash }) }) state.checkedGoods = JSON.parse(JSON.stringify(goodsClassifyInitData)) updateGoodsList(filteredList) } }) } const updateGoodsList = (goodsList) => { // 删除已选商家时更新商品列表 goodsList.forEach((item) => { item.input = '' item.detailsInput = '' }) localStorage.setItem('goodsList', JSON.stringify(goodsList)) state.goodsList = goodsList proxy.$store.commit('setShoppingCartChange') } const handleSelectionChange = (cloudProvider, val) => { // 子表调用这方法计算已选商品金额 state.checkedGoods[cloudProvider] = val getTotalPrice() } </script> <style scoped lang="scss"> @import "../../assets/styles/vendor/element-variables"; .shopping-cart-footer { width: 100%; height: 80px; position: absolute; left: 0; bottom: 0; z-index: 9999; background: $color-white; } ::v-deep .el-input__inner { height: 32px !important; line-height: 32px !important; padding: 0; border: 1px solid #dcdfe6; } ::v-deep.el-input__inner:focus { background-color: #fff !important; } .sticky-top{ position: sticky; top: 0; z-index: 99; } .shop-plan-btn{ width: 124px; height: 34px; line-height: 34px; background: #4C66CE; border-radius: 17px; color: #FFFFFF; font-size: 14px; } </style>
复制
// 子表 <template> <div class="mb10 goods-widget"> <el-collapse-transition name="el-fade-in"> <div class="goods-widget-body"> <!-- 购物车表格内部内容 --> <el-table style="width: width: 99.9%;" :row-style="{height:'38px',fontSize: '12px',color: '#666666',fontWeight: 400}" :header-cell-style="handerMethod" //用于合并表头的方法 row-key="goodsHash" :key="index" ref="goods-list-table" class="goods-widget-table" :class="{'goods-widget-body': !state.goodsDetailVisible}" :data="state.cloudProviderGoodsList" @selection-change="handleSelectionChange"> <el-table-column width="32px" type="selection" :reserve-selection="true"/> <el-table-column width="268px"> <template #header> <div class="d-flex align-items-center"> <div class="d-flex align-items-center justify-content-between"> <div v-if="state.cloudProvider === 'ali'" class="text-amount d-flex align-items-center"> <svg-icon class="mr-2" data="@icon/ali_cloud_logo.svg"></svg-icon> <span>阿里云</span> </div> <div v-if="state.cloudProvider === 'tencent'" class="text-primary-blue d-flex align-items-center"> <svg-icon class="mr-2" data="@icon/tencent_cloud_logo.svg"></svg-icon> <span>腾讯云</span> </div> <div v-if="state.cloudProvider === 'huawei'" class="text-danger d-flex align-items-center"> <svg-icon class="mr-2" data="@icon/huawei_logo.svg"></svg-icon> <span>华为云</span> </div> <div v-if="state.cloudProvider === 'ct'" class="text-ct d-flex align-items-center"> <svg-icon class="mr-2" data="@icon/tianyi_cloud_logo.svg"></svg-icon> <span>天翼云</span> </div> <div v-if="state.cloudProvider === 'baidu'" class="d-flex align-items-center"> <el-image class="mr-2" :src="require('@assets/images/baidu_logo.png')" style="width: 16px;height: 16px;"/> <span>百度云</span> </div> <div v-if="state.cloudProvider === 'jd'" class="text-ct d-flex align-items-center"> <svg-icon class="mr-2" data="@icon/jd_cloud_logo.svg"></svg-icon> <span>京东云</span> </div> <div v-if="state.cloudProvider === 'ks'" class="text-ct d-flex align-items-center"> <svg-icon class="mr-2" data="@icon/ks_cloud_logo.svg"></svg-icon> <span>金山云</span> </div> <div>(共 {{ goodsTotalNum }} 台)</div> </div> </div> </template> <template #default="scope"> <el-row> {{ pricingTypeMap[scope.row.pricingType] }},{{ scope.row.cpu }}核 {{ scope.row.mem }}GiB,{{ scope.row.zoneName }} </el-row> <el-row> 操作系统:{{ scope.row.systemImage }} </el-row> <el-row> 流量带宽:{{ scope.row.netBrandWidth > 0 ? `${scope.row.netBrandWidth}Mbps` : '--'}} </el-row> </template> </el-table-column> <el-table-column width="180px"> <template #default="scope"> <el-row> 系统盘:{{ scope.row.systemDisk ? getSystemDiskDescription(scope.row.systemDisk) : '--'}} </el-row> <el-row> 数据盘: <span v-if="scope.row.dataDisk.length === 0"> -- </span> <span v-else-if="scope.row.dataDisk.length === 1">{{ getDataDiskDescription(scope.row.dataDisk)[0] }}</span> <div v-else-if="scope.row.dataDisk.length > 1"> {{ getDataDiskSize(scope.row) }} <el-popover class="data-disk-popover" effect="dark" placement="right" :width="90" trigger="click" > <template #reference> <el-button class="data-disk-btn" type="text">详情</el-button> </template> <div v-for="(item, index) of getDataDiskDescription(scope.row.dataDisk)" :key="index">{{ item }}</div> </el-popover> </div> </el-row> <el-row class="data-disk-blank">-</el-row> </template> </el-table-column> <el-table-column align="center"> <template #default="scope"> <span class="mr-1">{{ scope.row.duration }}</span> <span class="" v-if="scope.row.durationUnit === 'Year'"></span> <span class="" v-else-if="scope.row.durationUnit === 'Month'">个月</span> <span class="" v-else>小时</span> </template> </el-table-column> <el-table-column width="100px" align="center"> <template #default="scope"> <div v-if="scope.row.price"> <div v-if="scope.row.pricingType === 'onDemand'"> <span class="c3">{{ priceDataFormatter(scope.row.price) }}元/小时</span> </div> <div v-else> <span class="c3">{{ priceDataFormatter(scope.row.price) }}元</span> </div> </div> <div v-else>--</div> </template> </el-table-column> <el-table-column width="150px" align="center"> <template #default="scope"> <el-tooltip content="可选范围 1 ~ 999" placement="top-start"> <el-input-number class="input-number-box c6" v-model="scope.row.goodsNum" :min="1" :max="999" style="width: 130px;border-radius: 4px;" @change="goodsNumChange(scope.row)"> </el-input-number> </el-tooltip> </template> </el-table-column> <el-table-column width="120px" align="center"> <template #default="scope"> <div class="text-amount">{{ getTotalPrice(scope.row) }}</div> </template> </el-table-column> <el-table-column width="190px" align="center"> <template #header> <div class="d-flex justify-content-end align-items-center"> <div class="d-flex mr20" v-if="reservedTotalPrice > 0"> <div class="mr4">预付:</div> <div class="text-amount">{{ reservedTotalPrice }}元</div> </div> <div class="d-flex ml28 mr20" v-if="onDemandTotalPrice > 0"> <div class="mr4">按需:</div> <div class="text-amount">{{ onDemandTotalPrice }}元/小时</div> </div> <el-tooltip content="展开/收起" placement="top" :enterable="false"> <el-button type="text" @click="goodsDetailVisibleToggle"> <svg-icon v-if="state.goodsDetailVisible" data="@icon/unfold.svg" style="width: 14px; height: 14px;"></svg-icon> <svg-icon v-else data="@icon/unfold.svg" style="width: 14px; height: 14px;"></svg-icon> </el-button> </el-tooltip> </div> </template> <template #default="scope"> <el-button class="el-button-operate" type="primary" @click="buyNow(scope.row)">立即购买</el-button> <el-button class="el-button-del" type="info" @click="deleteGoods(scope.row)">删除</el-button> </template> </el-table-column> </el-table> </div> </el-collapse-transition> </div> </template> <script setup> import { useStore } from 'vuex' import { reactive, defineProps, defineEmits, getCurrentInstance, ref, onMounted, watch, computed } from 'vue' const store = useStore() const { proxy } = getCurrentInstance() const emit = defineEmits(['selection-change']) const props = defineProps({ checkAll: Boolean, cloudProvider: String, index: Number }) const state = reactive({ cloudProvider: props.cloudProvider, goodsList: [], cloudProviderGoodsList: [], checkedGoodsItem: [], goodsDetailVisible: true, multipleSelection: [] }) const reservedTotalPrice = computed(() => { return getTotalPricingTypePrice('reserved') }) const onDemandTotalPrice = computed(() => { return getTotalPricingTypePrice('onDemand') }) const goodsTotalNum = computed(() => { return state.cloudProviderGoodsList.map(item => item.goodsNum).reduce((pre, val) => { return pre + val }, 0) }) watch(() => store.state.shoppingCartChange, () => { getGoodsList() }) watch(() => store.state.shoppingCartDeleteAction, () => { proxy.$refs['goods-list-table'].clearSelection() }) watch(() => props.checkAll, (val) => { if (val) { checkAllAction() } else { clearChecked() } }) onMounted(() => { getGoodsList() }) const getTotalPricingTypePrice = (pricingType) => { return state.cloudProviderGoodsList.filter(item => { return item.pricingType === pricingType }).map(goods => { return Number(goods.price).floatMul(goods.goodsNum) }).reduce((pre, val) => { // reduce() 方法对数组中的每个元素执行一个由您提供的reduce函数(升序执行),将其结果汇总为单个返回值。reduce方法可做的事情特别多,就是循环遍历能做的,reduce都可以做,比如数组求和、数组求积、数组中元素出现的次数、数组去重等等。 return pre.floatAdd(val) // 相加,计算单个商品小计的总额 }, 0) } const pricingTypeMap = ref({ reserved: '预付实例', onDemand: '按需实例' }) const diskTypeMap = { standard: '标准性能', efficient: '高性能' } const deleteGoods = (goodsItem) => { //删除某项商品 proxy.$confirm('确定要删除云资源吗?', '删除云资源').then(result => { if (result === 'confirm') { const index = state.goodsList.findIndex(item => { return item.goodsHash === goodsItem.goodsHash }) state.goodsList.splice(index, 1) updateGoodsList() proxy.$message.success({ message: '成功删除云资源' }) } }) } const getGoodsList = () => { state.goodsList = JSON.parse(localStorage.getItem('goodsList')) || [] state.cloudProviderGoodsList = state.goodsList.filter(goods => { return goods.cloudProvider === props.cloudProvider }) } const updateGoodsList = () => { //改变父组件中价格 localStorage.setItem('goodsList', JSON.stringify(state.goodsList)) proxy.$store.commit('setShoppingCartChange') } const goodsNumChange = (goodsItem) => { //操作台数时,父表中价格做相应计算 state.goodsList.forEach(item => { if (item.goodsHash === goodsItem.goodsHash && item.goodsNum !== goodsItem.goodsNum) { //只对选中的商品价格做相应计算 item.goodsNum = goodsItem.goodsNum } }) updateGoodsList() } const getSystemDiskDescription = ({ type, size }) => { return `${ diskTypeMap[type] } | ${ size }GB` } const getDataDiskDescription = (dataDisks) => { return dataDisks?.map(item => { return `${ diskTypeMap[item.type] } | ${ item.size }GB` }) } const getDataDiskSize = (dataDisks) => { //计算数据盘大小 let size = 0 dataDisks.dataDisk.map(item => { size += item.size }) return `共 ${size} G` } const priceDataFormatter = (price) => { //单价保留两位小数 return Number(price).toFixed(2) } const getTotalPrice = (item) => { /单价保留两位小数,floatMul是防止精度丢失的问题 return `${ Number(item.price).floatMul(item.goodsNum, 2) }${ (item.pricingType === 'reserved' ? '元' : '元/小时') }` } const handleSelectionChange = (val) => { // 点击选中/取消选中时调父表中的计算金额的方法 state.multipleSelection = val emit('selection-change', props.cloudProvider, state.multipleSelection.map(item => item.goodsHash)) } const goodsDetailVisibleToggle = () => { //展开和收起 state.goodsDetailVisible = !state.goodsDetailVisible proxy.$nextTick(() => { proxy.$refs['goods-list-table'].doLayout() // 对 Table 进行重新布局。当 Table 或其祖先元素由隐藏切换为显示时,可能需要调用此方法 }) } const checkAllAction = () => { // 全选 state.cloudProviderGoodsList.forEach(item => { proxy.$refs['goods-list-table'].toggleRowSelection(item, true) // 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中) }) } const clearChecked = () => { // 用于多选表格,清空用户的选择 proxy.$refs['goods-list-table'].clearSelection() } // 立即购买 const buyNow = (row) => { } const handerMethod = ({ row, column, rowIndex, columnIndex }) => { //合并表头 if (row[0].level == 1) { // //这里有个非常坑的bug 必须是row[0]=0 row[1]=2才会生效 row[4].colSpan = 0 // 表头索引为4、5、6时合并到7 row[5].colSpan = 0 row[6].colSpan = 0 row[7].colSpan = 4 if (columnIndex === 4 || columnIndex === 5 || columnIndex === 6) { // columnIndex 代表列号/列索引,隐藏 return { display: 'none' } } } } </script> <style scoped lang="scss"> @import "../../assets/styles/vendor/element-variables"; .el-table td.el-table__cell div { line-height: 32px; .data-disk-btn { color: #409EFF; } .data-disk-blank { color: #FFF; } } .text-ct{ color: $color-tianyi; } .el-checkbox { --el-checkbox-checked-background-color: #fd852d; --el-checkbox-checked-input-border-color: #fd852d; --el-checkbox-input-border-color-hover: #fd852d; } .goods-widget { background: #fff; border-radius: 4px; &-footer, &-body { box-sizing: border-box; border-top: 1px solid var(--el-border-color-base); } ::v-deep(.el-form-item) { margin-bottom: 8px; } } ::v-deep .input-number-box{ height: 32px; border: 1px solid #EAEBEF; .el-input-number__increase, .el-input-number__decrease{ font-size: 12px; font-weight: 400; } .el-input__inner{ height: 28px !important; line-height: 28px !important; font-size: 12px; border: none; } } ::v-deep.el-button-operate{ width: 80px; height: 32px; line-height: 32px; background-color: #EBEFFB; color: #4C66CE; font-size: 12px; border-radius: 4px; border: none; &:hover{ background-color: #4C66CE !important; color: #FFFFFF; } } ::v-deep.el-button-del{ width: 52px; height: 32px; line-height: 32px; background-color: #F2F2F4; color: #666666; font-size: 12px; border-radius: 4px; border: none; &:hover{ background-color: #F2F2F4 !important; color: #666666; } } </style>
复制

el-table合并列(动态合并)的实现

<template> <el-table class="procurement-plan-table procurement-plan-table-noborder" :row-style="{height: '48px',fontSize: '12px',color: '#666666'}" :header-cell-style="{height: '48px',background: '#F6F6F8',fontSize: '14px',color: '#333333',fontWeight: 400}" :cell-class-name="cellClassName" //因为设计稿上是只有首位才需要左右边框,合并的单元格需要去除右侧的边框 :data="state.tempGoodList" :span-method="objectSpanMethod" //合并单元格 ref="table" border :summary-method="getSummaries" show-summary :style="{borderColor: '#E6E6E6'}" > <el-table-column label="云厂商" width="120px" align="center" prop="cloudProvider"> <template #default="scope"> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'ali'"> <div class="text-center">阿里云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'tencent'"> <div class="text-center">腾讯云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'huawei'"> <div class="text-center">华为云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'ct'"> <div class="text-center">天翼云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'baidu'"> <div class="text-center">百度云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'jd'"> <div class="text-center">京东云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'ks'"> <div class="text-center">金山云</div> </div> </template> </el-table-column> <el-table-column label="类型" width="120px" align="center"> 云服务器 </el-table-column> <el-table-column label="付费方式" width="110px" align="center"> <template #default="scope"> {{ scope.row.pricingType === 'reserved' ? '预付' : '按需'}} </template> </el-table-column> <el-table-column label="数量" width="110px" align="center"> <template #default="scope"> {{ scope.row.goodsNum }} </template> </el-table-column> <el-table-column label="小计" width="130px"> <template #default="scope"> <div> <span>{{ scope.row.price * scope.row.goodsNum }}</span> <span v-if="scope.row.pricingType === 'reserved'"></span> <span v-else>元/小时</span> </div> </template> </el-table-column> <el-table-column label="备注" align="center"> <template #default="scope"> <el-input class="procurement-plan-table-input" v-model="scope.row.input" type="textarea" placeholder="请输入备注" @blur="saveMemo" /> </template> </el-table-column> </el-table> </template> <script setup> import { reactive, onMounted, getCurrentInstance } from 'vue' import { ArrowLeft } from '@element-plus/icons-vue' import transform from './common/toExcel.js' import moment from 'moment' import AppMenuChange from '@component/AppMenuChange' const { proxy } = getCurrentInstance() onMounted(() => { getGoodsList() getCollectTableData() }) const diskTypeMap = { standard: '标准性能', efficient: '高性能' } const durationUnitMap = { Hour: '小时', Month: '月', Year: '年' } const state = reactive({ cloudProvider: { ali: [], tencent: [], huawei: [], ct: [], baidu: [], jd: [], ks: [] }, goodsList: [], collectTableData: [ { cloudProvider: '', reserved: { num: 0, price: 0 }, onDemand: { num: 0, price: 0 } } ], tableData: [], purchasePurpose: '部署高可用的云上网站架构,支持业务流量跨可用区进行并发,并具备跨可用区故障容灾能力。' || JSON.parse(localStorage.getItem('purchasePurpose')), printObj: { id: 'pdf', popTitle: '', // extraHead: '打印', // 最上方的头部文字,附加在head标签上的额外标签,使用逗号分割 preview: false, // 是否启动预览模式,默认是false previewTitle: ' ', // 打印预览的标题 extraCss: '', extraHead: '<meta http-equiv="Content-Language"content="zh-cn"/>,<style> #pdf { width: 100%; height: auto !important; } <style>' }, detailsForm: [], tempStr: '', cloudProviderInfo: [ { id: 'ali', name: '阿里云' }, { id: 'baidu', name: '百度云' }, { id: 'huawei', name: '华为云' }, { id: 'ct', name: '天翼云' }, { id: 'tencent', name: '腾讯云' }, { id: 'ks', name: '金山云' }, { id: 'jd', name: '京东' } ], tempCloudName: null, tempGoodList: [] }) const typeNameArr = [] let typeNamePos = 0 // 导出为excel const toExcel = () => { state.detailsForm = [] state.tempStr = '' state.goodsList.forEach((item) => { let tempCloudName = '' state.cloudProviderInfo.filter((subitem) => { if (subitem.id === item.cloudProvider) { tempCloudName = subitem.name return subitem.name } }) if (item.dataDisk) { state.tempStr = getDataDiskDescription(item.dataDisk) } state.detailsForm.push( { cloudProvider: tempCloudName, standardID: item.standardID, info: `${item.cpu}核${item.mem}GiB, ${item.zoneName}, 系统盘:${item.systemDisk ? getSystemDiskDescription(item.systemDisk) : '--'}, 数据盘:${state.tempStr},固定带宽:${item.netBrandWidth}M`, time: `${getDuration(item) ? getDuration(item) : '按需'}`, price: `${item.price} ${item.pricingType === 'reserved' ? '元' : '元/小时'}`, memo: item.detailsInput ? item.detailsInput : '' } ) }) transform(state.detailsForm, '云服务器采购清单') } // 合并单元格 const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => { if (columnIndex === 0) { const _row = typeNameArr[rowIndex] const _col = _row > 0 ? 1 : 0 return { rowspan: _row, colspan: _col } } proxy.$nextTick(() => { if (proxy.$refs.table.$el) { const current = proxy.$refs.table.$el.querySelector('.el-table__footer-wrapper').querySelector('.el-table__footer') const cell = current.rows[0].cells cell[1].style.display = 'none' cell[2].classList.remove('is-left') cell[2].colSpan = '2' cell[3].style.display = 'none' cell[4].classList.remove('is-left') cell[4].colSpan = '2' } }) } // 设置cell样式 const cellClassName = ({ row, column, rowIndex, columnIndex }) => { if (columnIndex !== 0) { return 'noRightBorderClass' } } // 在本地存储里获取购物清单 const getGoodsList = () => { state.goodsList = JSON.parse(localStorage.getItem('checkedGoodsList')) || [] const tempGoodObject = { ali: [], tencent: [], huawei: [], baidu: [], ks: [], ct: [], jd: [] } // tempGoodList.push(state.goodsList[0]) state.goodsList.forEach((item) => { if (item.cloudProvider === 'ali') tempGoodObject.ali.push(item) if (item.cloudProvider === 'tencent') tempGoodObject.tencent.push(item) if (item.cloudProvider === 'huawei') tempGoodObject.huawei.push(item) if (item.cloudProvider === 'baidu') tempGoodObject.baidu.push(item) if (item.cloudProvider === 'ks') tempGoodObject.ks.push(item) if (item.cloudProvider === 'ct') tempGoodObject.ct.push(item) if (item.cloudProvider === 'jd') tempGoodObject.jd.push(item) }) state.tempGoodList = [ ...tempGoodObject.ali, ...tempGoodObject.tencent, ...tempGoodObject.huawei, ...tempGoodObject.baidu, ...tempGoodObject.ks, ...tempGoodObject.ct, ...tempGoodObject.jd ] for (let i = 0; i < state.tempGoodList.length; i += 1) { if (i === 0) { typeNameArr.push(1) typeNamePos = 0 } else { if (state.tempGoodList[i].cloudProvider === state.tempGoodList[i - 1].cloudProvider) { typeNameArr[typeNamePos] += 1 typeNameArr.push(0) } else { typeNameArr.push(1) typeNamePos = i } } } } </script>
复制

el-table合并尾部合计的实现

<template> <el-table class="procurement-plan-table procurement-plan-table-noborder" :row-style="{height: '48px',fontSize: '12px',color: '#666666'}" :header-cell-style="{height: '48px',background: '#F6F6F8',fontSize: '14px',color: '#333333',fontWeight: 400}" :cell-class-name="cellClassName" //因为设计稿上是只有首位才需要左右边框,合并的单元格需要去除右侧的边框 :data="state.tempGoodList" :span-method="objectSpanMethod" //合并单元格 ref="table" border :summary-method="getSummaries" //底部合计行 show-summary :style="{borderColor: '#E6E6E6'}" > <el-table-column label="云厂商" width="120px" align="center" prop="cloudProvider"> <template #default="scope"> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'ali'"> <div class="text-center">阿里云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'tencent'"> <div class="text-center">腾讯云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'huawei'"> <div class="text-center">华为云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'ct'"> <div class="text-center">天翼云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'baidu'"> <div class="text-center">百度云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'jd'"> <div class="text-center">京东云</div> </div> <div class="d-flex align-items-center justify-content-center" v-if="scope.row.cloudProvider === 'ks'"> <div class="text-center">金山云</div> </div> </template> </el-table-column> <el-table-column label="类型" width="120px" align="center"> 云服务器 </el-table-column> <el-table-column label="付费方式" width="110px" align="center"> <template #default="scope"> {{ scope.row.pricingType === 'reserved' ? '预付' : '按需'}} </template> </el-table-column> <el-table-column label="数量" width="110px" align="center"> <template #default="scope"> {{ scope.row.goodsNum }} </template> </el-table-column> <el-table-column label="小计" width="130px"> <template #default="scope"> <div> <span>{{ scope.row.price * scope.row.goodsNum }}</span> <span v-if="scope.row.pricingType === 'reserved'"></span> <span v-else>元/小时</span> </div> </template> </el-table-column> <el-table-column label="备注" align="center"> <template #default="scope"> <el-input class="procurement-plan-table-input" v-model="scope.row.input" type="textarea" placeholder="请输入备注" @blur="saveMemo" /> </template> </el-table-column> </el-table> </template> <script setup> import { reactive, onMounted, getCurrentInstance } from 'vue' import { ArrowLeft } from '@element-plus/icons-vue' import transform from './common/toExcel.js' import moment from 'moment' import AppMenuChange from '@component/AppMenuChange' const { proxy } = getCurrentInstance() onMounted(() => { getGoodsList() getCollectTableData() }) const diskTypeMap = { standard: '标准性能', efficient: '高性能' } const durationUnitMap = { Hour: '小时', Month: '月', Year: '年' } const state = reactive({ cloudProvider: { ali: [], tencent: [], huawei: [], ct: [], baidu: [], jd: [], ks: [] }, goodsList: [], collectTableData: [ { cloudProvider: '', reserved: { num: 0, price: 0 }, onDemand: { num: 0, price: 0 } } ], tableData: [], purchasePurpose: '部署高可用的云上网站架构,支持业务流量跨可用区进行并发,并具备跨可用区故障容灾能力。' || JSON.parse(localStorage.getItem('purchasePurpose')), printObj: { id: 'pdf', popTitle: '', // extraHead: '打印', // 最上方的头部文字,附加在head标签上的额外标签,使用逗号分割 preview: false, // 是否启动预览模式,默认是false previewTitle: ' ', // 打印预览的标题 extraCss: '', extraHead: '<meta http-equiv="Content-Language"content="zh-cn"/>,<style> #pdf { width: 100%; height: auto !important; } <style>' }, detailsForm: [], tempStr: '', cloudProviderInfo: [ { id: 'ali', name: '阿里云' }, { id: 'baidu', name: '百度云' }, { id: 'huawei', name: '华为云' }, { id: 'ct', name: '天翼云' }, { id: 'tencent', name: '腾讯云' }, { id: 'ks', name: '金山云' }, { id: 'jd', name: '京东' } ], tempCloudName: null, tempGoodList: [] }) const typeNameArr = [] let typeNamePos = 0 // 导出为excel const toExcel = () => { state.detailsForm = [] state.tempStr = '' state.goodsList.forEach((item) => { let tempCloudName = '' state.cloudProviderInfo.filter((subitem) => { if (subitem.id === item.cloudProvider) { tempCloudName = subitem.name return subitem.name } }) if (item.dataDisk) { state.tempStr = getDataDiskDescription(item.dataDisk) } state.detailsForm.push( { cloudProvider: tempCloudName, standardID: item.standardID, info: `${item.cpu}核${item.mem}GiB, ${item.zoneName}, 系统盘:${item.systemDisk ? getSystemDiskDescription(item.systemDisk) : '--'}, 数据盘:${state.tempStr},固定带宽:${item.netBrandWidth}M`, time: `${getDuration(item) ? getDuration(item) : '按需'}`, price: `${item.price} ${item.pricingType === 'reserved' ? '元' : '元/小时'}`, memo: item.detailsInput ? item.detailsInput : '' } ) }) transform(state.detailsForm, '云服务器采购清单') } // 合并单元格 const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => { if (columnIndex === 0) { const _row = typeNameArr[rowIndex] const _col = _row > 0 ? 1 : 0 return { rowspan: _row, colspan: _col } } proxy.$nextTick(() => { if (proxy.$refs.table.$el) { const current = proxy.$refs.table.$el.querySelector('.el-table__footer-wrapper').querySelector('.el-table__footer') const cell = current.rows[0].cells cell[1].style.display = 'none' cell[2].classList.remove('is-left') cell[2].colSpan = '2' cell[3].style.display = 'none' cell[4].classList.remove('is-left') cell[4].colSpan = '2' } }) } // 设置cell样式 const cellClassName = ({ row, column, rowIndex, columnIndex }) => { if (columnIndex !== 0) { return 'noRightBorderClass' } } // 在本地存储里获取购物清单 const getGoodsList = () => { state.goodsList = JSON.parse(localStorage.getItem('checkedGoodsList')) || [] const tempGoodObject = { ali: [], tencent: [], huawei: [], baidu: [], ks: [], ct: [], jd: [] } // tempGoodList.push(state.goodsList[0]) state.goodsList.forEach((item) => { if (item.cloudProvider === 'ali') tempGoodObject.ali.push(item) if (item.cloudProvider === 'tencent') tempGoodObject.tencent.push(item) if (item.cloudProvider === 'huawei') tempGoodObject.huawei.push(item) if (item.cloudProvider === 'baidu') tempGoodObject.baidu.push(item) if (item.cloudProvider === 'ks') tempGoodObject.ks.push(item) if (item.cloudProvider === 'ct') tempGoodObject.ct.push(item) if (item.cloudProvider === 'jd') tempGoodObject.jd.push(item) }) state.tempGoodList = [ ...tempGoodObject.ali, ...tempGoodObject.tencent, ...tempGoodObject.huawei, ...tempGoodObject.baidu, ...tempGoodObject.ks, ...tempGoodObject.ct, ...tempGoodObject.jd ] for (let i = 0; i < state.tempGoodList.length; i += 1) { if (i === 0) { typeNameArr.push(1) typeNamePos = 0 } else { if (state.tempGoodList[i].cloudProvider === state.tempGoodList[i - 1].cloudProvider) { typeNameArr[typeNamePos] += 1 typeNameArr.push(0) } else { typeNameArr.push(1) typeNamePos = i } } } } const getSummaries = () => { // 底部合计行 const reservedNum = getSummariesNum('reserved') const reservedPrice = getSummariesPrice('reserved') const onDemandNum = getSummariesNum('onDemand') return ['合计', '', `按需实例: ${onDemandNum}台,预付实例: ${reservedNum}台`, '', `预付: ${reservedPrice}元`, '按需实例为后付费,云账户有一定与余额即可'] } const getSummariesNum = (type) => { // 计算按需/预付多少台 return state.collectTableData.map(item => { return item[type].num }).reduce((pre, value) => pre + value) } const getSummariesPrice = (type) => { // 计算预付价格 return state.collectTableData.map(item => { return item[type].price }).reduce((pre, value) => { return (parseInt((pre + value) * 100, 10) / 100) }) } </script>
复制
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论