在有些情况下,我们会有合并表头、合并列、合并尾部合计的需求,这篇文章是为了记录,如何进行合并,方便日后翻阅。
效果图
el-table合并表头
el-table合并列(动态合并)
el-table合并尾部合计
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-checkboxv-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-merchantv-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-checkboxv-model="state.checkAll":disabled="state.goodsList.length === 0":indeterminate="state.isIndeterminate">全选</el-checkbox><el-buttonclass="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 = falsestate.isIndeterminate = falsereturn}const checkedNum = Object.values(state.checkedGoods).map(item => item.length).reduce((pre, val) => {return pre + val}, 0)if (checkedNum === state.goodsList.length) {state.checkAll = truestate.isIndeterminate = false} else if (checkedNum > 0 && checkedNum < state.goodsList.length) {state.isIndeterminate = true} else {state.checkAll = falsestate.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 = goodsListproxy.$store.commit('setShoppingCartChange')
}const handleSelectionChange = (cloudProvider, val) => { // 子表调用这方法计算已选商品金额state.checkedGoods[cloudProvider] = valgetTotalPrice()
}
</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-tablestyle="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-popoverclass="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-numberclass="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 = 0dataDisks.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 = valemit('selection-change', props.cloudProvider, state.multipleSelection.map(item => item.goodsHash))
}const goodsDetailVisibleToggle = () => { //展开和收起state.goodsDetailVisible = !state.goodsDetailVisibleproxy.$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时合并到7row[5].colSpan = 0row[6].colSpan = 0row[7].colSpan = 4if (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-tableclass="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, // 是否启动预览模式,默认是falsepreviewTitle: ' ', // 打印预览的标题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.namereturn 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 : 0return {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].cellscell[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] += 1typeNameArr.push(0)} else {typeNameArr.push(1)typeNamePos = i}}}
}
</script>
el-table合并尾部合计的实现
<template><el-tableclass="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, // 是否启动预览模式,默认是falsepreviewTitle: ' ', // 打印预览的标题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.namereturn 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 : 0return {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].cellscell[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] += 1typeNameArr.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>