一. 实现效果
二. 实现方法
2.1 版本:ant-design-vue:“^2.1.2”
2.2 方法:a-table组件里面的customCell功能
2.3 注意点
若定义的列表columns中需要合并的列用到了slots插槽属性,那么用customRender方法进行列合并是没有效果的,需要用customCell方法。
2.4 数据情况
① 下面的列子中数据是一个数组,数组里面包含各个table列表数据,每个table列表数据是一个对象
② 每个table对象数据中,需要table展示的数据在三级子目录中。
2.5 实现
将每一个table下面的展示子数据,单独拿出来放在一个数组里面,table就取这个数组进行展示。类别相同的子数据rowSpan值设为相同,customCell会把rowSpan值相同的数据进行合并。
三. 示例代码(直接可用)
3.1 数据源,获取所有的列表数据(data.js)
import { toRefs, reactive } from 'vue';
import { mergeRowSpan } from './rowSpan';
export function tableDataList() {
const dataList = [
{
advertiserName: 'Z-测试-1户-yjr-滴滴-账户-上下滑',
bidding: 10,
promotionTarget: '提升应用安装',
advertisingPlanList: [
{
campaign_name: '快乐滴滴-0328快乐滴滴01123',
promotionTarget: '提升应用安装',
day_budget: 0,
campaignContent: {
campaign_name: '快乐滴滴-0328快乐滴滴01123',
type: 2,
day_budget: 1913000,
},
adPlanCreative: [
{
rowOrder: 1,
name: '0328_141705_01',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试包-测试-0328',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
],
},
},
{
rowOrder: 2,
name: '0328_141705_02',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试包-测试-0328',
},
creativeInformation: [1],
copywriting: [],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程滴滴滴序化2[日期]',
},
{
action_bar_text: '立即下载',
description: '快滴测试程五个大镜子序化1[日期]',
},
],
},
},
{
rowOrder: 3,
name: '0328_141705_03',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试包-测试-0328',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:45',
titleName: '快滴测试程序化2[日期]',
},
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化2[日期]',
},
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
{
action_bar_text: '立即下载',
description: '快滴测试程序化2[日期]',
},
],
},
},
],
},
{
campaign_name: '快乐滴滴-0328快乐滴滴02123',
promotionTarget: '提升应用安装',
day_budget: 1913000,
campaignContent: {
campaign_name: '快乐滴滴-0328快乐滴滴02123',
type: 2,
day_budget: 1913000,
},
adPlanCreative: [
{
rowOrder: 4,
name: '0328_141705_04',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试-定向-0209',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
],
},
},
{
rowOrder: 5,
name: '0328_141705_05',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试-定向-0209',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:45',
titleName: '快滴测试程序化2[日期]',
},
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化2[日期]',
},
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
],
},
},
],
},
],
},
{
advertiserName: 'Z-谨焱-1户-yjr-快乐滴滴-安卓-上下滑',
bidding: 10,
promotionTarget: '提升应用安装',
advertisingPlanList: [
{
campaign_name: '快乐滴滴-0328快乐滴滴01123',
promotionTarget: '提升应用安装',
day_budget: 0,
campaignContent: {
campaign_name: '快乐滴滴-0328快乐滴滴01123',
type: 2,
day_budget: 1913000,
},
adPlanCreative: [
{
rowOrder: 1,
name: '0328_141705_01',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试包-测试-0328',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
],
},
},
{
rowOrder: 2,
name: '0328_141705_02',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试包-测试-0328',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:45',
titleName: '快滴测试程序化2[日期]',
},
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化2[日期]',
},
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
],
},
},
{
rowOrder: 3,
name: '0328_141705_03',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试包-测试-0328',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:45',
titleName: '快滴测试程序化2[日期]',
},
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化2[日期]',
},
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
{
action_bar_text: '立即下载',
description: '快滴测试程序化2[日期]',
},
],
},
},
],
},
{
campaign_name: '快乐滴滴-0328快乐滴滴02123',
promotionTarget: '提升应用安装',
day_budget: 1913000,
campaignContent: {
campaign_name: '快乐滴滴-0328快乐滴滴02123',
type: 2,
day_budget: 1913000,
},
adPlanCreative: [
{
rowOrder: 4,
name: '0328_141705_04',
optimizeTarget: '激活',
optimizeApplication: '快乐滴滴',
optimizeActivity: '快乐滴滴',
budget: 123000,
bidding: 10000,
depthBidding: 20000,
packageObj: {
template_name: '测试-定向-0209',
},
creativeInformation: [1],
copywriting: [
{
createTime: '2022-03-08 17:46:34',
titleName: '快滴测试程序化1[日期]',
},
],
landingPage: {
applicationDetailsPage: 'close',
directLink: false,
},
playable_switch: '',
adPlan: {},
adCreative: {
creatives: [
{
action_bar_text: '立即下载',
description: '快滴测试程序化1[日期]',
},
],
},
},
],
},
],
},
];
return {
...toRefs(reactive({ dataList })),
};
}
export function getTableDataList(arrData) {
const newDataList = arrData.map((item) => {
item.isShowData = true;
item.isSelectAll = false;
item.failList = [];
item.showList = [];
item.advertisingPlanList.forEach((el, idx) => {
el.advertisingPlanType = 0;
el.isSelectAllItem = false;
el.selectedAdvertising = null;
el.adPlanCreative.forEach((e) => {
e.isChecked = false;
e.rowOrder = idx;
e.commitStatus = 0;
});
item.showList.push(...el.adPlanCreative);
});
item.showList.forEach((el, index) => {
el.idx = index;
});
return item;
});
mergeRowSpan(newDataList, 'advertisingInfo', 'showList', 'rowOrder');
const columns = [
{
title: '测试计划',
width: 150,
fixed: 'left',
align: 'center',
children: [
{
title: '测试计划信息',
width: 150,
align: 'center',
key: 'advertisingInfoRowSpan',
slots: { customRender: 'advertisingInfoRowSpan' },
customCell: (record, rowIndex) => {
return {
style: { display: record.advertisingInfoRowSpan !== 0 ? '' : 'none' },
rowSpan: record.advertisingInfoRowSpan,
};
},
},
],
},
{
title: '测试组',
align: 'center',
children: [
{
title: '',
width: 50,
key: 'selected',
align: 'center',
slots: { customRender: 'selected' },
},
{
title: '测试组名称',
dataIndex: 'name',
key: 'name',
align: 'center',
slots: { customRender: 'name' },
},
{
title: '测化目标',
dataIndex: 'optimizeTarget',
key: 'optimizeTarget',
align: 'center',
slots: { customRender: 'optimizeTarget' },
},
{
title: '测算与出价',
dataIndex: 'budgetBidding',
key: 'budgetBidding',
align: 'center',
slots: { customRender: 'budgetBidding' },
},
{
title: '测试包',
dataIndex: ' packageObj',
key: ' packageObj',
align: 'center',
slots: { customRender: 'packageObj' },
},
],
},
{
title: '测试测意',
align: 'center',
children: [
{
title: '测意信息',
dataIndex: 'creativeInformation',
key: 'creativeInformation',
align: 'center',
slots: { customRender: 'creativeInformation' },
},
{
title: '测意素材',
dataIndex: 'creativeMaterial',
key: 'creativeMaterial',
align: 'center',
slots: { customRender: 'creativeMaterial' },
},
{
title: '文案',
dataIndex: 'copywriting',
key: 'copywriting',
align: 'center',
slots: { customRender: 'copywriting' },
},
{
title: '测试页',
dataIndex: 'landingPage',
key: 'landingPage',
align: 'center',
slots: { customRender: 'landingPage' },
},
{
title: '测试链接',
dataIndex: 'directLinkContent',
key: 'directLinkContent',
align: 'center',
slots: { customRender: 'directLinkContent' },
},
{
title: '测试素材',
dataIndex: 'playable_switch',
key: 'playable_switch',
align: 'center',
slots: { customRender: 'playable_switch' },
},
{
title: '提交状态',
dataIndex: 'commitStatus',
key: 'commitStatus',
align: 'center',
slots: { customRender: 'commitStatus' },
},
],
},
{
title: '操作',
dataIndex: 'gender',
key: 'gender',
width: 80,
align: 'center',
fixed: 'right',
slots: { customRender: 'gender' },
},
];
return {
newDataList,
columns,
};
}
3.2 对数据源进行处理,对需要合并的列设置相同的rowSpan值 (rowSpan.js)
const rowSpan = (key, data, numKey) => {
const arrData = data
.reduce((result, item) => {
if (result.indexOf(item[key]) < 0) {
result.push(item[key]);
}
return result;
}, [])
.reduce((results, keys) => {
const children = data.filter((item) => item[key] === keys);
const numKeyCount = countNumberGoals(children, numKey);
let numKeyArray = [];
numKeyCount.forEach((item) => {
numKeyArray.push(children.splice(0, item));
});
numKeyArray.forEach((item) => {
results = results.concat(
item.map((el, index) => {
el[`${key}RowSpan`] = index === 0 ? item.length : 0;
return el;
})
);
});
return results;
}, []);
return arrData;
};
function countNumberGoals(arrayData, key) {
if (!key) {
return [arrayData.length];
}
let arr = JSON.parse(JSON.stringify(arrayData));
let newArray = [];
let obj = {};
for (var i = 0; i < arr.length; i++) {
let temp = arr[i][key];
let count = 0;
for (var j = 0; j < arr.length; j++) {
if (arr[j][key] === temp) {
count++;
arr[j][key] = -1;
}
}
if (temp !== -1) {
newArray.push(count);
}
}
return newArray;
}
const mergeRowSpan = (dataList, rowIndex, dataIndex, numKey) => {
if (dataIndex) {
dataList.forEach((item, index) => {
dataList[index][dataIndex] = rowSpan(rowIndex, item[dataIndex], numKey);
});
} else {
rowSpan(rowIndex, dataList);
}
};
export { rowSpan, mergeRowSpan };
3.3 页面展示部分 (TableRowSpan.vue)
<template>
<div class="table-row-span">
<div class="preview-data-box" ref="newDataListBox">
<template v-for="(item, index) in newDataList" :key="index">
<div class="data-item" v-if="newDataList[index].showList.length > 0">
<div class="data-title common">
<div class="title-left">
<span>{{
'账户' + (index + 1) + ':' + item.advertiserName
}}</span>
<a-checkbox :checked="item.isSelectAll" style="margin: 0 20px"
>全选</a-checkbox
>
<span>已选择 {{ 10 }} 条测试组</span>
</div>
<div class="title-right">
<span>
测试计划数量:<span style="color: #3d73e2">{{
newDataList[index].advertisingPlanList.length
}}</span>
</span>
<span style="margin-left: 20px">
测试组数量:<span style="color: #3d73e2">{{
newDataList[index].showList.length
}}</span>
</span>
<CaretDownOutlined
v-if="item.isShowData"
style="color: #c5c8ce; margin-left: 20px; cursor: pointer"
/>
<CaretUpOutlined
v-else
style="color: #c5c8ce; margin-left: 20px; cursor: pointer"
/>
</div>
</div>
<div class="data-list">
<a-table
:row-key="(record) => record.idx"
:data-source="newDataList[index].showList"
:columns="columns"
bordered
size="middle"
:pagination="false"
:scroll="{ x: 'calc(700px + 50%)' }"
>
<!-- 共用部分 -->
<template #advertisingInfoRowSpan="{ record }">
<div class="advertising-program-information">
<div class="common-style">
<a-select
v-model:value="
newDataList[index].advertisingPlanList[record.rowOrder]
.advertisingPlanType
"
>
<a-select-option :value="0">新建测试计划</a-select-option>
<a-select-option :value="1">已有测试计划</a-select-option>
</a-select>
</div>
<div
class="choose-new"
v-if="
newDataList[index].advertisingPlanList[record.rowOrder]
.advertisingPlanType === 0
"
>
<div class="common-style">
<p>名称:</p>
<p>
{{
newDataList[index].advertisingPlanList[
record.rowOrder
].campaign_name
}}
<FormOutlined
style="
color: #3d73e2;
cursor: pointer;
margin-left: 5px;
"
/>
</p>
</div>
<div class="common-style">
<p>测试目标:</p>
<p>
{{
newDataList[index].advertisingPlanList[
record.rowOrder
].promotionTarget
}}
</p>
</div>
<div class="common-style">
<p>测算:</p>
<p>
{{
newDataList[index].advertisingPlanList[
record.rowOrder
].day_budget == 0
? '不限'
: newDataList[index].advertisingPlanList[
record.rowOrder
].day_budget / 1000
}}
</p>
</div>
</div>
<div class="select-existing" v-else>
<div class="common-style">
<a-button type="primary">选择测试计划</a-button>
</div>
<div
v-if="
newDataList[index].advertisingPlanList[record.rowOrder]
.selectedAdvertising
"
class="common-style"
>
已选:{{
newDataList[index].advertisingPlanList[record.rowOrder]
.selectedAdvertising.campaign_name
}}
</div>
</div>
<div>
<a-checkbox
:checked="
newDataList[index].advertisingPlanList[record.rowOrder]
.isSelectAllItem
"
>全选</a-checkbox
>
</div>
</div>
</template>
<!-- 选择部分 -->
<template #selected="{ record }">
<a-checkbox :checked="record.isChecked"></a-checkbox>
</template>
<!-- 测试组名称 -->
<template #name="{ record }">
<span>{{ record.name }}</span>
<FormOutlined
style="color: #3d73e2; cursor: pointer; margin-left: 5px"
/>
</template>
<!-- 测化目标 -->
<template #optimizeTarget="{ record }">
<p v-show="record.optimizeTarget">
测化目标:{{ record.optimizeTarget }}
</p>
<p v-show="record.optimizeApplication">
快滴应用:{{ record.optimizeApplication }}
</p>
<p v-show="record.optimizeActivity">
监测活动:{{ record.optimizeActivity }}
</p>
</template>
<!-- 测算与出价 -->
<template #budgetBidding="{ record }">
<p>
测算:{{ record.budget == 0 ? '不限' : record.budget / 1000 }}
</p>
<p v-show="record.bidding">出价:{{ record.bidding / 1000 }}</p>
<p v-show="record.depthBidding">
深度出价:{{ record.depthBidding / 1000 }}
</p>
</template>
<!-- 定向包 -->
<template #packageObj="{ record }">
<span>{{ record.packageObj.template_name }}</span>
</template>
<!-- 定向包 -->
<!-- 创意信息 -->
<template #creativeInformation="{ record }">
<span v-if="record.creativeInformation.length === 0"
>优选测试位</span
>
<p
v-else
v-for="(item, index) in record.creativeInformation"
:key="index"
>
{{ index + 1 + '.' + item }}
</p>
</template>
<!-- 创意素材 -->
<template #creativeMaterial="{ record }">
<span class="common-operate"
>已选{{ record.adCreative.creatives.length }}个视频</span
>
</template>
<!-- 文案 -->
<template #copywriting="{ record }">
<p>作品测试语:</p>
<template
v-if="record.copywriting && record.copywriting.length > 0"
>
<p v-for="(item, index) in record.copywriting" :key="index">
{{ index + 1 + '.' + item.titleName }}
</p>
</template>
<template v-else>
<p
v-for="(item, index) in record.adCreative.creatives"
:key="index"
>
{{ index + 1 + '.' + item.description }}
</p>
</template>
</template>
<!-- 落地页 -->
<template #landingPage="{ record }">
<span>{{ record.landingPage.landingPage || '无' }}</span>
</template>
<!-- 直达链接 -->
<template #directLinkContent="{ record }">
<span>{{ record.landingPage.directLinkContent || '无' }}</span>
</template>
<!-- 试玩素材 -->
<template #playable_switch="{ record }">
<span>{{ record.playable_switch || '默认关闭' }}</span>
</template>
<!-- 提交状态 -->
<template #commitStatus="{ record }">
<span v-if="+record.commitStatus === 0">待提交</span>
<span v-else-if="+record.commitStatus === 1">提交中</span>
<span
v-else-if="+record.commitStatus === 2"
style="color: #00a265"
>
<CheckCircleOutlined style="margin-right: 5px" />
<span>提交成功</span>
</span>
<span
v-else-if="+record.commitStatus === 3"
style="color: #f00"
>
<a-tooltip placement="top" color="#f00">
<CloseCircleOutlined style="margin-right: 5px" />
<span>提交失败</span>
<template #title>
<span>{{ record.errorMessage }}</span>
</template>
</a-tooltip>
</span>
<span
v-else-if="+record.commitStatus === 7"
style="color: #00a265"
>
<CheckCircleOutlined style="margin-right: 5px" />
<span>异步提交成功</span>
</span>
</template>
<!-- 操作 -->
<template #gender="{ record }">
<span class="common-operate" v-show="record">删除</span>
</template>
</a-table>
</div>
</div>
</template>
</div>
</div>
</template>
<script>
import { tableDataList } from './data'
import { getTableDataList } from './data'
export default {
name: 'table-row-span',
setup() {
const { dataList } = tableDataList()
console.log(dataList)
const { newDataList, columns } = getTableDataList(
JSON.parse(JSON.stringify(dataList.value))
)
return {
newDataList,
columns,
}
},
}
</script>
<style lang="less" scoped>
.table-row-span {
.common {
height: 56px;
padding: 18px 24px;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #e8eaec;
border-bottom: 0px solid #e8eaec;
}
.preview-header-box {
.preview-title {
.left {
font-size: 15px;
font-weight: 600;
}
}
}
.preview-data-box {
.data-item {
margin-bottom: 20px;
overflow: hidden;
.data-list {
p {
margin-bottom: 8px;
text-align: left;
}
.common-operate {
color: #1577f6;
cursor: pointer;
}
}
}
:deep(.ant-table-tbody > tr > td),
:deep(.ant-table-thead > tr > th) {
vertical-align: middle !important;
}
:deep(.ant-table-tbody .ant-table-row-hover > td) {
background-color: #fff !important;
}
:deep(.ant-table-fixed-left) {
.ant-table-tbody > tr > td {
vertical-align: top !important;
}
}
:deep(.ant-table-row-cell-break-word) {
.advertising-program-information {
text-align: left;
height: 100%;
.common-style {
margin-bottom: 15px;
}
p {
margin-bottom: 5px;
}
}
}
}
:deep(.ant-checkbox-wrapper) {
.ant-checkbox-checked + span {
color: #5e75ee;
font-weight: 600;
}
}
}
.no-data-info {
height: 450px;
background-color: #fff;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 1px dashed #e8eaec;
border-radius: 3px;
.title {
font-size: 30px;
font-weight: 600;
color: #e1e3e7;
}
.content {
font-size: 13px;
font-weight: 600;
color: #818694;
}
}
</style>
data.js、rowSpan.js、TableRowSpan.vue三个文件在同一层。
(完)
|