style(cargo-type): 美化货物类型导入模板Excel样式及失败明细

- 表头底色调整为统一灰色 #D9D9D9,字体改为黑色加粗
- 表头行高从42调整为30,增加视觉紧凑感
- 表头与数据区统一添加细边框,居中对齐并自动换行
- 编码列保持文本格式,防止格式识别错误
- 导入失败明细中「导入失败原因」列字体设置为红色
- 失败原因按 A./B./C. 格式编号,使用分号分隔
- 去除后端重复的「第N行:」前缀,避免冗余信息
- 通过 Node + exceljs 验证样式和格式变更正确生效
This commit is contained in:
2026-07-28 00:37:42 +08:00
parent c695d72ec8
commit 8002fe3d50
2 changed files with 36 additions and 21 deletions

View File

@@ -2,5 +2,7 @@
## 货物类型导入模板美化 ## 货物类型导入模板美化
- 文件:`src/views/base/cargo-type.vue``handleTemplate()` - 文件:`src/views/base/cargo-type.vue``handleTemplate()`
- 改动:表头行加主色底色 `#409EFF`argb `FF409EFF`+ 白色加粗字体 + 居中对齐 + 自动换行;表头与数据区统一加细边框(`FFD0D7E2`);表头行高设为 42编码列保持文本格式`numFmt='@'` - 改动(第一次):表头行加主色底色 `#409EFF` + 白色加粗字体 + 居中对齐 + 细边框;行高 42
- 验证Node + exceljs 4.4.0 跑通生成逻辑,确认表头 fill/font/alignment/border 与行高正确 - 改动(调整后):按用户反馈,表头底色改为统一灰色 `#D9D9D9` + 黑色加粗字体,行高降为 30
- 导入失败明细 `decorateCargoTypeFailDetail` 调整:仅「导入失败原因」列字体红色;错误按 `A./B./C.` 编号分号隔开并去掉后端重复的「第N行」前缀最终格式示例 `第2行A.该货物类型编码已存在,请更换唯一编码`
- 验证Node + exceljs 4.4.0 跑通生成逻辑,确认表头 fill/font/alignment/height、失败原因编号格式与红色字体均正确。

View File

@@ -183,9 +183,9 @@ const CARGO_TYPE_FAIL_COL_WIDTH = {
/** /**
* 加工后端返回的导入失败明细 Excel * 加工后端返回的导入失败明细 Excel
* - 「导入失败原因」列改写为第N行错误规则格式便于定位 * - 「导入失败原因」列按 A. B. C. 编号列出,多条以分号隔开
* - 每个错误后追加对应规则说明,仅失败原因列标红
* - 适当加宽各列、原因列自动换行,避免内容拥挤 * - 适当加宽各列、原因列自动换行,避免内容拥挤
* 不再对单元格标红,仅通过文字清晰说明错误
*/ */
export const decorateCargoTypeFailDetail = async workbook => { export const decorateCargoTypeFailDetail = async workbook => {
const ws = workbook.worksheets[0]; const ws = workbook.worksheets[0];
@@ -200,19 +200,32 @@ export const decorateCargoTypeFailDetail = async workbook => {
if (!reasonCol) return; if (!reasonCol) return;
ws.eachRow((row, rowNumber) => { ws.eachRow((row, rowNumber) => {
if (rowNumber === 1) return; // 跳过表头 if (rowNumber === 1) return; // 跳过表头
const reasonCell = row.getCell(reasonCol); // 仅「导入失败原因」列允许红色,其他列红色字体统一清除
const raw = String(reasonCell.value == null ? '' : reasonCell.value).trim(); row.eachCell((cell, colNumber) => {
if (!raw) return; if (colNumber === reasonCol) return;
const parts = raw.split(/[;\n\r]+/).map(s => s.trim()).filter(Boolean); if (cell.font && cell.font.color) {
if (parts.length === 0) return; const font = { ...cell.font };
const decorated = parts.map(part => { delete font.color;
const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match)); cell.font = font;
if (rule) {
return `${rowNumber}行:${part}${rule.rule}`;
} }
return `${rowNumber}行:${part}`;
}); });
reasonCell.value = decorated.join(''); const reasonCell = row.getCell(reasonCol);
let raw = String(reasonCell.value == null ? '' : reasonCell.value).trim();
if (!raw) return;
// 去掉后端可能已经存在的「第N行」前缀避免重复
raw = raw.replace(/^第\s*\d+\s*行[:]\s*/, '');
const parts = raw
.split(/[;\n\r]+/)
.map(s => s.trim().replace(/^第\s*\d+\s*行[:]\s*/, ''))
.filter(Boolean);
if (parts.length === 0) return;
const decorated = parts.map((part, index) => {
const label = `${String.fromCharCode(65 + index)}.`; // A. B. C.
const rule = CARGO_TYPE_FAIL_RULES.find(r => part.includes(r.match));
return rule ? `${label}${part}${rule.rule}` : `${label}${part}`;
});
reasonCell.value = `${rowNumber}行:${decorated.join('')}`;
reasonCell.font = { color: { argb: 'FFFF0000' } };
reasonCell.alignment = { wrapText: true, vertical: 'top' }; reasonCell.alignment = { wrapText: true, vertical: 'top' };
}); });
// 加宽各列(未列在映射中的列给默认宽度) // 加宽各列(未列在映射中的列给默认宽度)
@@ -585,11 +598,11 @@ export default {
const col = ws.getColumn(idx); const col = ws.getColumn(idx);
if (col) col.numFmt = '@'; if (col) col.numFmt = '@';
}); });
// 表头与数据区样式美化:主色底色表头 + 白字加粗居中 + 细边框 // 表头与数据区样式:统一灰色表头 + 黑色加粗居中 + 细边框
const headerFill = { const headerFill = {
type: 'pattern', type: 'pattern',
pattern: 'solid', pattern: 'solid',
fgColor: { argb: 'FF409EFF' }, fgColor: { argb: 'FFD9D9D9' },
}; };
const thinBorder = { const thinBorder = {
top: { style: 'thin', color: { argb: 'FFD0D7E2' } }, top: { style: 'thin', color: { argb: 'FFD0D7E2' } },
@@ -601,9 +614,9 @@ export default {
row.eachCell(cell => { row.eachCell(cell => {
cell.border = thinBorder; cell.border = thinBorder;
if (row.number === 1) { if (row.number === 1) {
// 表头:色底色 + 字加粗 + 居中对齐 // 表头:色底色 + 字加粗 + 居中对齐
cell.fill = headerFill; cell.fill = headerFill;
cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; cell.font = { bold: true, color: { argb: 'FF000000' } };
cell.alignment = { cell.alignment = {
wrapText: true, wrapText: true,
vertical: 'middle', vertical: 'middle',
@@ -619,8 +632,8 @@ export default {
} }
}); });
}); });
// 表头行高,确保长说明换行后完整显示 // 表头行高适度,兼顾两行说明与视觉紧凑
ws.getRow(1).height = 42; ws.getRow(1).height = 30;
return wb.xlsx.writeBuffer(); return wb.xlsx.writeBuffer();
}) })
.then(buf => { .then(buf => {