<template>
<a-table
:columns="columns"
:data-source="processedData"
:scroll="{ x: '100%' }"
:pagination="false"
bordered
size="small"
>
<template #summary>
<a-table-summary-row>
<a-table-summary-cell :index="0" :col-span="5">
<span style="font-weight: bold">DDR汇总</span>
</a-table-summary-cell>
<a-table-summary-cell
v-for="(month, index) in newData.head"
:key="month"
:index="index + 5"
align="center"
>
<a-typography-text
:type="totals[month] < 0 ? 'danger' : undefined"
style="font-weight: bold; font-size: 10px"
>
{{ totals[month] }}
</a-typography-text>
</a-table-summary-cell>
</a-table-summary-row>
</template>
</a-table>
</template>
<script setup lang="ts">
import { computed } from "vue";
import type { TableColumnType } from "ant-design-vue";
interface TableItem {
productFamily: string;
data: Record<string, number>;
}
interface TableGroup {
productType: string;
techNode: string;
nickName: string;
qg: string;
items: TableItem[];
}
const getConsecutiveCount = (
array: any[],
startIndex: number,
key: string
): number => {
const value = array[startIndex][key];
let count = 1;
for (let i = startIndex + 1; i < array.length; i++) {
if (array[i][key] === value) {
count++;
} else {
break;
}
}
return count;
};
const newData = {
head: [
"202410",
"202411",
"202412",
"2024H1",
"2024H2",
"2024 TTL",
"2025H1",
"2025H2",
"2025TTL",
],
items: [
{
productType: "DDR",
techNodes: [
{
techNode: "G1",
nicknames: [
{
nickname: "DQRMB",
qgs: [
{
qg: "Non-QG5",
subFamilys: [
{
subFamily: "Wafer",
items: [
{ name: "202410", outputValue: 0 },
{ name: "202411", outputValue: 0 },
{ name: "202412", outputValue: 0 },
{ name: "2024H1", outputValue: 0 },
{ name: "2024H2", outputValue: 0 },
{ name: "2024 TTL", outputValue: 0 },
{ name: "2025H1", outputValue: 0 },
{ name: "2025H2", outputValue: 0 },
{ name: "2025TTL", outputValue: 0 },
],
},
{
subFamily: "4Gb x16 DG",
items: [
{ name: "202410", outputValue: -9 },
{ name: "202411", outputValue: -2 },
{ name: "202412", outputValue: 10 },
{ name: "2024H1", outputValue: 0 },
{ name: "2024H2", outputValue: 0 },
{ name: "2024 TTL", outputValue: 29 },
{ name: "2025H1", outputValue: 126 },
{ name: "2025H2", outputValue: 125 },
{ name: "2025TTL", outputValue: 0 },
],
},
{
subFamily: "8Gb x16",
items: [
{ name: "202410", outputValue: -27 },
{ name: "202411", outputValue: 93 },
{ name: "202412", outputValue: 120 },
{ name: "2024H1", outputValue: 0 },
{ name: "2024H2", outputValue: 0 },
{ name: "2024 TTL", outputValue: 126 },
{ name: "2025H1", outputValue: 126 },
{ name: "2025H2", outputValue: 0 },
{ name: "2025TTL", outputValue: 0 },
],
},
{
subFamily: "8Gb x4",
items: [
{ name: "202410", outputValue: 2 },
{ name: "202411", outputValue: -100 },
{ name: "202412", outputValue: -10 },
{ name: "2024H1", outputValue: -108 },
{ name: "2024H2", outputValue: -108 },
{ name: "2024 TTL", outputValue: 458 },
{ name: "2025H1", outputValue: 458 },
{ name: "2025H2", outputValue: 0 },
{ name: "2025TTL", outputValue: 458 },
],
},
],
},
{
qg: "QG5",
subFamilys: [
{
subFamily: "x8 DG&Hwe",
items: [
{ name: "202410", outputValue: 107 },
{ name: "202411", outputValue: -237 },
{ name: "202412", outputValue: -190 },
{ name: "2024H1", outputValue: -320 },
{ name: "2024H2", outputValue: -320 },
{ name: "2024 TTL", outputValue: -122 },
{ name: "2025H1", outputValue: -122 },
{ name: "2025H2", outputValue: -122 },
{ name: "2025TTL", outputValue: 0 },
],
},
],
},
],
},
],
},
{
techNode: "G3",
nicknames: [
{
nickname: "DGPQA",
qgs: [
{
qg: "Non-QG5",
subFamilys: [
{
subFamily: "16Gb x8",
items: [
{ name: "202410", outputValue: 0 },
{ name: "202411", outputValue: 0 },
{ name: "202412", outputValue: 80 },
{ name: "2024H1", outputValue: 80 },
{ name: "2024H2", outputValue: 80 },
{ name: "2024 TTL", outputValue: 148 },
{ name: "2025H1", outputValue: 148 },
{ name: "2025H2", outputValue: 0 },
{ name: "2025TTL", outputValue: 0 },
],
},
],
},
{
qg: "QG5",
subFamilys: [
{
subFamily: "16Gb x16",
items: [
{ name: "202410", outputValue: 6 },
{ name: "202411", outputValue: 6 },
{ name: "202412", outputValue: 0 },
{ name: "2024H1", outputValue: 0 },
{ name: "2024H2", outputValue: 0 },
{ name: "2024 TTL", outputValue: -1068 },
{ name: "2025H1", outputValue: -1068 },
{ name: "2025H2", outputValue: -147 },
{ name: "2025TTL", outputValue: -218 },
],
},
{
subFamily: "16Gb x4",
items: [
{ name: "202410", outputValue: 380 },
{ name: "202411", outputValue: 449 },
{ name: "202412", outputValue: -1684 },
{ name: "2024H1", outputValue: 2140 },
{ name: "2024H2", outputValue: 2140 },
{ name: "2024 TTL", outputValue: -8825 },
{ name: "2025H1", outputValue: -8825 },
{ name: "2025H2", outputValue: -2522 },
{ name: "2025TTL", outputValue: 818 },
],
},
],
},
],
},
],
},
],
},
],
};
const processedData = computed(() => {
const result: any[] = [];
let currentIndex = 0;
const flattenData = (data: typeof newData) => {
data.items.forEach((productTypeGroup) => {
productTypeGroup.techNodes.forEach((techNodeGroup) => {
techNodeGroup.nicknames.forEach((nicknameGroup) => {
nicknameGroup.qgs.forEach((qgGroup) => {
qgGroup.subFamilys.forEach((subFamily) => {
const monthValues: Record<string, number> = {};
subFamily.items.forEach((item) => {
monthValues[item.name] = item.outputValue;
});
result.push({
key: currentIndex++,
productType: productTypeGroup.productType,
techNode: techNodeGroup.techNode,
nickName: nicknameGroup.nickname,
qg: qgGroup.qg,
productFamily: subFamily.subFamily,
...monthValues,
});
});
});
});
});
});
};
flattenData(newData);
const processRowSpans = (rows: any[]) => {
rows.forEach((row, index) => {
row._isFirst = {
productType:
index === 0 || rows[index - 1].productType !== row.productType,
techNode: index === 0 || rows[index - 1].techNode !== row.techNode,
nickName: index === 0 || rows[index - 1].nickName !== row.nickName,
qg: index === 0 || rows[index - 1].qg !== row.qg,
};
row._rowSpan = {};
if (row._isFirst.productType) {
row._rowSpan.productType = getConsecutiveCount(
rows,
index,
"productType"
);
}
if (row._isFirst.techNode) {
row._rowSpan.techNode = getConsecutiveCount(rows, index, "techNode");
}
if (row._isFirst.nickName) {
row._rowSpan.nickName = getConsecutiveCount(rows, index, "nickName");
}
if (row._isFirst.qg) {
row._rowSpan.qg = getConsecutiveCount(rows, index, "qg");
}
});
};
processRowSpans(result);
return result;
});
const totals = computed(() => {
const sums: Record<string, number> = {};
newData.head.forEach((month) => {
sums[month] = 0;
});
processedData.value.forEach((row) => {
newData.head.forEach((month) => {
sums[month] += Number(row[month]) || 0;
});
});
return sums;
});
const columns = [
{
title: "分类",
dataIndex: "productType",
fixed: "left",
width: 120,
align: "center",
customRender: ({ text, record }: { text: string; record: any }) => ({
children: text,
props: {
rowSpan: record._isFirst.productType ? record._rowSpan.productType : 0,
},
}),
},
{
title: "Tech Node",
dataIndex: "techNode",
fixed: "left",
width: 120,
align: "center",
customRender: ({ text, record }: { text: string; record: any }) => ({
children: text,
props: {
rowSpan: record._isFirst.techNode ? record._rowSpan.techNode : 0,
},
}),
},
{
title: "Nick Name",
dataIndex: "nickName",
fixed: "left",
width: 120,
align: "center",
customRender: ({ text, record }: { text: string; record: any }) => ({
children: text,
props: {
rowSpan: record._isFirst.nickName ? record._rowSpan.nickName : 0,
},
}),
},
{
title: "QG分类",
dataIndex: "qg",
fixed: "left",
width: 120,
align: "center",
customRender: ({ text, record }: { text: string; record: any }) => ({
children: text,
props: {
rowSpan: record._isFirst.qg ? record._rowSpan.qg : 0,
},
}),
},
{
title: "Product分类",
dataIndex: "productFamily",
fixed: "left",
width: 150,
align: "center",
},
...newData.head.map((month) => ({
title: month,
dataIndex: month,
align: "center",
width: 100,
className: /(H1|H2|TTL)/.test(month) ? "statistics-column1" : "",
})),
];
</script>
<style scoped>
:deep(.ant-table-thead > tr > th.statistics-column1) {
background-color: rgb(255, 128, 0) !important;
font-weight: bold;
}
:deep(.ant-table-thead > tr > th.statistics-column2) {
background-color: #g5g5g5 !important;
font-weight: bold;
}
:deep(.ant-table-thead > tr > th) {
text-align: center;
background: #e6f4ff !important;
padding: 0px;
border-top: 1px solid black !important;
border-bottom: 1px solid black !important;
}
:deep(.ant-table-tbody > tr > td) {
padding: 0px;
}
:deep(.ant-table-tbody > tr:hover > td) {
background: #f0f7ff;
}
:deep(.ant-table-summary) {
background: #e6f4ff !important;
font-size: 10px;
}
:deep(.ant-table-summary > tr > td) {
padding: 0px;
border-top: 1px solid black !important;
border-bottom: 1px solid black !important;
}
:deep(.ant-table) {
border-radius: 0 !important;
}
:deep(.ant-table) {
border-radius: 0 !important;
}
:deep(.ant-table-container) {
border-radius: 0 !important;
}
:deep(.ant-table-content) {
border-radius: 0 !important;
}
:deep(.ant-table-thead > tr:first-child > th:first-child) {
border-top-left-radius: 0 !important;
}
:deep(.ant-table-thead > tr:first-child > th:last-child) {
border-top-right-radius: 0 !important;
}
:deep(.ant-table-cell) {
white-space: nowrap;
}
:deep(table) {
font-size: 10px !important;
}
:deep(.ant-table-thead > tr > th.ant-table-cell) {
padding: 0 !important;
height: 24px !important;
}
:deep(.ant-table-tbody > tr > td.ant-table-cell) {
padding: 0 !important;
height: 24px !important;
}
:deep(.ant-table-summary > tr > td.ant-table-cell) {
height: 24px !important;
}
:deep(.ant-table-cell-content) {
padding: 0 !important;
}
:deep(.ant-table-cell) {
padding-left: 4px !important;
padding-right: 4px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
</style>