|
@@ -37,7 +37,7 @@
|
|
|
:data="pageData"
|
|
:data="pageData"
|
|
|
border
|
|
border
|
|
|
stripe
|
|
stripe
|
|
|
- row-key="questionId"
|
|
|
|
|
|
|
+ :row-key="(row) => row._rowKey"
|
|
|
:show-header="pIndex === 0"
|
|
:show-header="pIndex === 0"
|
|
|
:span-method="SpanMethod"
|
|
:span-method="SpanMethod"
|
|
|
:row-class-name="ErrorTableRowClassName"
|
|
:row-class-name="ErrorTableRowClassName"
|
|
@@ -158,6 +158,7 @@ import domtoimage from "dom-to-image";
|
|
|
// 定义数据类型
|
|
// 定义数据类型
|
|
|
interface TableItem {
|
|
interface TableItem {
|
|
|
rowKey: number | string;
|
|
rowKey: number | string;
|
|
|
|
|
+ questionId?: string | number; // 行唯一标识(模板 row-key 使用;拆分片段克隆自同一行,questionId 相同)
|
|
|
questionName?: string;
|
|
questionName?: string;
|
|
|
questionType?: string;
|
|
questionType?: string;
|
|
|
answerValue?: string;
|
|
answerValue?: string;
|
|
@@ -174,6 +175,12 @@ interface TableItem {
|
|
|
hiddenStu?: boolean;
|
|
hiddenStu?: boolean;
|
|
|
rowspan?: number;
|
|
rowspan?: number;
|
|
|
colspan?: number;
|
|
colspan?: number;
|
|
|
|
|
+ // ---- 以下为本组件内部字段,请勿由上游数据传入 ----
|
|
|
|
|
+ _rowKey?: string; // 分页行唯一 key(拆分片段也生成新 key,避免 row-key 重复)
|
|
|
|
|
+ _fromSplit?: boolean; // 拆分片段标记:并入所在题块,不独立成合并块
|
|
|
|
|
+ _anchorRowspan?: number; // 合并锚点行跨行数
|
|
|
|
|
+ _anchorColspan?: number; // 合并锚点行跨列数(恒为 1)
|
|
|
|
|
+ _anchorBlockIndex?: number; // 合并块全局序号(斑马纹按块交替底色)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 定义 Props
|
|
// 定义 Props
|
|
@@ -320,9 +327,11 @@ const TablePagesData = () => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 行唯一 key 自增序号:buildPages 每次重建时重置,保证同一页内 _rowKey 全局唯一
|
|
|
|
|
+ let rowSeq = 0;
|
|
|
// 放入页面的行一律克隆,避免改动原数据
|
|
// 放入页面的行一律克隆,避免改动原数据
|
|
|
const cloneRows = (rows: TableItem[]): TableItem[] =>
|
|
const cloneRows = (rows: TableItem[]): TableItem[] =>
|
|
|
- rows.map((r) => ({ ...r }));
|
|
|
|
|
|
|
+ rows.map((r) => ({ ...r, _rowKey: `${r.questionId}_${rowSeq++}` }));
|
|
|
|
|
|
|
|
// 行内拆分:整行放不下时,把学号名单内容按条目边界切分为
|
|
// 行内拆分:整行放不下时,把学号名单内容按条目边界切分为
|
|
|
// [当前页部分, 续排部分];前部分填满当前页剩余空白,剩余部分在下一页继续。
|
|
// [当前页部分, 续排部分];前部分填满当前页剩余空白,剩余部分在下一页继续。
|
|
@@ -353,15 +362,15 @@ const TablePagesData = () => {
|
|
|
}
|
|
}
|
|
|
const rowA = {
|
|
const rowA = {
|
|
|
...row,
|
|
...row,
|
|
|
|
|
+ _rowKey: `${row.questionId}_a_${rowSeq++}`,
|
|
|
registrationCodeList: String(code).slice(0, cut),
|
|
registrationCodeList: String(code).slice(0, cut),
|
|
|
- rowspan: 1,
|
|
|
|
|
- colspan: 1,
|
|
|
|
|
|
|
+ _fromSplit: true, // 拆分片段:并入所在题块,不开启新合并块
|
|
|
};
|
|
};
|
|
|
const rowB = {
|
|
const rowB = {
|
|
|
...row,
|
|
...row,
|
|
|
- registrationCodeList: String(code).slice(cut),
|
|
|
|
|
- rowspan: 1,
|
|
|
|
|
- colspan: 1,
|
|
|
|
|
|
|
+ _rowKey: `${row.questionId}_b_${rowSeq++}`,
|
|
|
|
|
+ registrationCodeList: String(code).slice(cut).replace(/、+$/, ""), // 去除末尾残留顿号
|
|
|
|
|
+ _fromSplit: true,
|
|
|
};
|
|
};
|
|
|
// rowA 行数 = ceil(fitItems/codeItemsPerLine) <= fitLines,其高度必 <= availableHeight
|
|
// rowA 行数 = ceil(fitItems/codeItemsPerLine) <= fitLines,其高度必 <= availableHeight
|
|
|
return [rowA, rowB];
|
|
return [rowA, rowB];
|
|
@@ -479,20 +488,31 @@ const TablePagesData = () => {
|
|
|
|
|
|
|
|
// 统一修正每页合并单元格:
|
|
// 统一修正每页合并单元格:
|
|
|
// 每页第一行必须作为合并锚点,保证前 8 列(小题名称~正确答案)在每一页都可见;
|
|
// 每页第一行必须作为合并锚点,保证前 8 列(小题名称~正确答案)在每一页都可见;
|
|
|
- // 页内遇到新的组起点(rowKey==0)或拆分片段(rowspan)时重新开启一个锚点,
|
|
|
|
|
|
|
+ // 页内遇到新的组起点(rowKey==0)时闭合当前锚点块、开启新锚点;
|
|
|
|
|
+ // 拆分片段(_fromSplit)并入所在题块、不开启新锚点,避免同一页出现
|
|
|
|
|
+ // 相邻合并块导致前 8 列题信息重复显示;
|
|
|
// 锚点 rowspan 覆盖到下一个锚点前的所有行,其余行被合并隐藏
|
|
// 锚点 rowspan 覆盖到下一个锚点前的所有行,其余行被合并隐藏
|
|
|
|
|
+ let blockIndex = 0; // 合并块全局序号(跨页递增),供斑马纹按题块交替底色
|
|
|
pages.forEach((page) => {
|
|
pages.forEach((page) => {
|
|
|
if (!page.length) return;
|
|
if (!page.length) return;
|
|
|
let anchorIdx = 0;
|
|
let anchorIdx = 0;
|
|
|
for (let i = 1; i < page.length; i++) {
|
|
for (let i = 1; i < page.length; i++) {
|
|
|
- if (Number(page[i].rowKey) === 0 || page[i].rowspan) {
|
|
|
|
|
- page[anchorIdx].rowspan = i - anchorIdx;
|
|
|
|
|
- page[anchorIdx].colspan = 1;
|
|
|
|
|
|
|
+ // 拆分片段:并入当前块,不开启新锚点
|
|
|
|
|
+ if (page[i]._fromSplit) continue;
|
|
|
|
|
+ if (Number(page[i].rowKey) === 0) {
|
|
|
|
|
+ page[anchorIdx]._anchorRowspan = i - anchorIdx;
|
|
|
|
|
+ page[anchorIdx]._anchorColspan = 1;
|
|
|
|
|
+ page.slice(anchorIdx, i).forEach(
|
|
|
|
|
+ (r) => (r._anchorBlockIndex = blockIndex),
|
|
|
|
|
+ );
|
|
|
|
|
+ blockIndex++;
|
|
|
anchorIdx = i;
|
|
anchorIdx = i;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- page[anchorIdx].rowspan = page.length - anchorIdx;
|
|
|
|
|
- page[anchorIdx].colspan = 1;
|
|
|
|
|
|
|
+ page[anchorIdx]._anchorRowspan = page.length - anchorIdx;
|
|
|
|
|
+ page[anchorIdx]._anchorColspan = 1;
|
|
|
|
|
+ page.slice(anchorIdx).forEach((r) => (r._anchorBlockIndex = blockIndex));
|
|
|
|
|
+ blockIndex++;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
return pages;
|
|
return pages;
|
|
@@ -827,11 +847,13 @@ const SpanMethod = ({ row, column }: any) => {
|
|
|
"studentNum",
|
|
"studentNum",
|
|
|
"registrationCodeList",
|
|
"registrationCodeList",
|
|
|
];
|
|
];
|
|
|
|
|
+ // 合并信息读取组件内部字段(_anchorRowspan/_anchorColspan),
|
|
|
|
|
+ // 与上游数据可能自带的 rowspan/colspan 字段解耦
|
|
|
if (props.indexOf(column.property) == -1) {
|
|
if (props.indexOf(column.property) == -1) {
|
|
|
- if (row.rowspan) {
|
|
|
|
|
|
|
+ if (row._anchorRowspan) {
|
|
|
return {
|
|
return {
|
|
|
- rowspan: row.rowspan,
|
|
|
|
|
- colspan: row.colspan,
|
|
|
|
|
|
|
+ rowspan: row._anchorRowspan,
|
|
|
|
|
+ colspan: row._anchorColspan || 1,
|
|
|
};
|
|
};
|
|
|
} else {
|
|
} else {
|
|
|
return {
|
|
return {
|
|
@@ -844,7 +866,9 @@ const SpanMethod = ({ row, column }: any) => {
|
|
|
|
|
|
|
|
// 定义行样式
|
|
// 定义行样式
|
|
|
const ErrorTableRowClassName = ({ row }: any) => {
|
|
const ErrorTableRowClassName = ({ row }: any) => {
|
|
|
- if (row.rowspanIndex % 2 === 0) {
|
|
|
|
|
|
|
+ // 按合并块序号交替底色:同一题块内(含拆分片段、被合并隐藏行)保持同色
|
|
|
|
|
+ const idx = row._anchorBlockIndex ?? 0;
|
|
|
|
|
+ if (idx % 2 === 0) {
|
|
|
return "row_color_FFFFFF";
|
|
return "row_color_FFFFFF";
|
|
|
} else {
|
|
} else {
|
|
|
return "row_color_FAFAFA";
|
|
return "row_color_FAFAFA";
|