优化PrintDesigner组件,新增列的顺序属性,调整列排序逻辑,确保列在打印预览中的顺序与设计一致。

This commit is contained in:
geht
2026-04-08 17:40:52 +08:00
parent adc0631569
commit 1aee848c6a
2 changed files with 148 additions and 47 deletions

View File

@@ -61,7 +61,7 @@
<a-textarea
v-model:value="tableColumnsJson"
:rows="6"
placeholder='例如:[{"title":"物料","field":"name","width":90},{"title":"数量","field":"qty","width":45}]'
placeholder='例如:[{"order":0,"title":"物料","field":"name","width":90},{"order":1,"title":"数量","field":"qty","width":45}]'
@focus="onColumnsEditorFocus"
@blur="onColumnsEditorBlur"
/>
@@ -254,6 +254,7 @@
}
type SimpleColumn = {
order?: number;
title: string;
field: string;
width?: number;
@@ -261,11 +262,24 @@
};
function normalizeColumns(cols: any[]): any[] {
return cols.map((c) => ({
title: c.title || c.field || '列',
field: c.field || '',
width: Number(c.width || 60),
align: c.align || 'left',
const normalized = (Array.isArray(cols) ? cols : []).map((c, idx) => {
const orderNum = Number(c?.order);
return {
_idx: idx,
_order: Number.isFinite(orderNum) ? orderNum : idx,
title: c?.title || c?.field || '列',
field: c?.field || '',
width: Number(c?.width || 60),
align: c?.align || 'left',
};
});
normalized.sort((a, b) => a._order - b._order || a._idx - b._idx);
return normalized.map((c, idx) => ({
title: c.title,
order: idx,
field: c.field,
width: c.width,
align: c.align,
colspan: 1,
rowspan: 1,
}));
@@ -274,6 +288,7 @@
function compactColumnsForEditor(cols: any[]) {
return normalizeColumns(cols).map((c) => ({
title: c.title,
order: c.order,
field: c.field,
width: c.width,
align: c.align,
@@ -296,10 +311,17 @@ function(t,e,printData){
var list = printData && Array.isArray(printData[opts.field || 'table']) ? printData[opts.field || 'table'] : [];
var globalCols = printData && Array.isArray(printData.__qhmesTableColumns) ? printData.__qhmesTableColumns : [];
var columns = Array.isArray(opts.columns) && opts.columns.length ? opts.columns : (globalCols.length ? globalCols : [
{ title: '物料', field: 'name', width: 90, align: 'left' },
{ title: '数量', field: 'qty', width: 45, align: 'right' },
{ title: '金额', field: 'amount', width: 45, align: 'right' }
{ title: '物料', order: 0, field: 'name', width: 90, align: 'left' },
{ title: '数量', order: 1, field: 'qty', width: 45, align: 'right' },
{ title: '金额', order: 2, field: 'amount', width: 45, align: 'right' }
]);
columns = columns.slice().sort(function(a,b){
var ao = Number(a && a.order);
var bo = Number(b && b.order);
var av = isFinite(ao) ? ao : 9999;
var bv = isFinite(bo) ? bo : 9999;
return av - bv;
});
var globalGroups = printData && Array.isArray(printData.__qhmesGroupFields) ? printData.__qhmesGroupFields : [];
var groupFields = Array.isArray(opts.groupFields) && opts.groupFields.length ? opts.groupFields : globalGroups;
var style = opts.__qhmesStyle || {};
@@ -502,9 +524,11 @@ function(t,e,printData){
}
const mergedCols = normalizeColumns(parsedCols).map((c) => ({
title: c.title,
order: c.order,
field: c.field,
width: c.width,
align: c.align,
headerBg: (c as any).headerBg || '',
}));
const mergedGroups = groupEnabled.value ? [...groupFields.value] : [];
let changedCount = 0;
@@ -649,6 +673,7 @@ function(t,e,printData){
}
const mergedCols = normalizeColumns(parsedCols).map((c) => ({
title: c.title,
order: c.order,
field: c.field,
width: c.width,
align: c.align,
@@ -656,18 +681,30 @@ function(t,e,printData){
const mergedGroups = groupEnabled.value ? [...groupFields.value] : [];
const readCanvasTableStyle = () => {
try {
const tables = Array.from(document.querySelectorAll('#hiprint-printTemplate table'));
const tables = Array.from(document.querySelectorAll('#hiprint-printTemplate table')) as HTMLTableElement[];
if (!tables.length) return null;
const tb = tables[0] as HTMLTableElement;
// 选择“最像目标明细表”的 table表头列最多、且可见
const scoreTable = (tb: HTMLTableElement) => {
const thCount = tb.querySelectorAll('thead th').length;
const hasBody = tb.querySelectorAll('tbody tr').length > 0 ? 5 : 0;
const visible = (tb as HTMLElement).offsetParent ? 3 : 0;
return thCount * 10 + hasBody + visible;
};
const tb = [...tables].sort((a, b) => scoreTable(b) - scoreTable(a))[0];
const ths = Array.from(tb.querySelectorAll('thead th')) as HTMLElement[];
const th = ths.length > 0 ? ths[0] : null;
const trHead = tb.querySelector('thead tr') as HTMLElement | null;
const td = tb.querySelector('tbody td') as HTMLElement | null;
const ref = th || td;
if (!ref) return null;
const cs = window.getComputedStyle(ref);
const tbcs = window.getComputedStyle(tb);
const validColor = (c: string) => !!c && c !== 'rgba(0, 0, 0, 0)' && c !== 'transparent';
const headerColors = ths.map((cell) => window.getComputedStyle(cell).backgroundColor || '');
const trHeadBg = trHead ? window.getComputedStyle(trHead).backgroundColor || '' : '';
const headerColors = ths.map((cell) => {
const c = window.getComputedStyle(cell).backgroundColor || '';
return validColor(c) ? c : trHeadBg;
});
const firstValidHeaderBg = headerColors.find((c) => validColor(c)) || '';
const bg = firstValidHeaderBg || (th ? window.getComputedStyle(th).backgroundColor : '');
const headerTitles = ths.map((cell) => (cell.textContent || '').trim());
@@ -753,32 +790,29 @@ function(t,e,printData){
};
// 保真模式:优先用画布真实样式兜底(含表头填充色)
if (preserveDesignStyle.value && domStyle) {
// 保真模式:列顺序与表头标题优先按画布 DOM 对齐
if (Array.isArray(domStyle.headerTitles) && domStyle.headerTitles.length > 0) {
const srcCols = Array.isArray(node.options.columns) ? [...node.options.columns] : [];
const unused = [...srcCols];
const ordered = domStyle.headerTitles.map((title: string, idx: number) => {
let hitIdx = -1;
if (title) {
hitIdx = unused.findIndex((c: any) => (c?.title || '').trim() === title);
}
let base: any;
if (hitIdx >= 0) {
base = unused.splice(hitIdx, 1)[0];
} else if (unused.length > 0) {
base = unused.shift();
} else {
base = srcCols[idx] || mergedCols[idx] || { title: title || `${idx + 1}`, field: '' };
}
return {
...base,
title: title || base?.title || base?.field || `${idx + 1}`,
headerBg: Array.isArray(domStyle.headerColors) ? domStyle.headerColors[idx] || '' : '',
};
});
if (ordered.length > 0) {
node.options.columns = ordered;
// 保真模式:预览列顺序优先沿用画布组件本身的字段顺序,避免被 JSON 列配置重排
// 仅当画布拿不到列定义时,才回退到列配置 JSON。
const baseCols = originColumns.length > 0 ? originColumns : mergedCols;
if (baseCols.length > 0) {
let orderedCols = [...baseCols];
// 进一步按画布可见表头文本重排,保证预览列顺序与画布视觉顺序一致
if (Array.isArray(domStyle.headerTitles) && domStyle.headerTitles.length === baseCols.length) {
const used = new Set<number>();
orderedCols = domStyle.headerTitles.map((ht: string, idx: number) => {
const hitIdx = baseCols.findIndex(
(c: any, i: number) => !used.has(i) && String(c?.title || '').trim() === String(ht || '').trim()
);
if (hitIdx >= 0) {
used.add(hitIdx);
return { ...baseCols[hitIdx], title: ht || baseCols[hitIdx].title };
}
return { ...baseCols[idx], title: ht || baseCols[idx].title };
});
}
node.options.columns = orderedCols.map((c: any, idx: number) => ({
...c,
headerBg: Array.isArray(domStyle.headerColors) ? domStyle.headerColors[idx] || (c as any).headerBg || '' : (c as any).headerBg || '',
}));
}
node.options.__qhmesStyle = {
fontSize: node.options.__qhmesStyle.fontSize || domStyle.fontSize,
@@ -829,6 +863,7 @@ function(t,e,printData){
type.options = type.options || {};
type.options.columns = mergedCols.map((c) => ({
title: c.title,
order: c.order,
field: c.field,
width: c.width,
align: c.align,
@@ -989,7 +1024,45 @@ function(t,e,printData){
}
};
const keys = Object.keys(firstRow);
// 字段顺序优先使用“画布可见表头顺序”映射,其次用画布明细列顺序,最后回退 JSON 键顺序
const canvasFieldOrder =
existingCols
.map((c: any) => c?.field)
.filter((f: any) => !!f && Object.prototype.hasOwnProperty.call(firstRow, f)) || [];
const domHeaderTitles = (() => {
try {
const tables = Array.from(document.querySelectorAll('#hiprint-printTemplate table'));
if (!tables.length) return [] as string[];
// 与预览逻辑一致:选表头列最多的表,减少抓错表的概率
const scoreTable = (tb: Element) => tb.querySelectorAll('thead th').length * 10 + tb.querySelectorAll('tbody tr').length;
const tb = [...tables].sort((a, b) => scoreTable(b) - scoreTable(a))[0];
return Array.from(tb.querySelectorAll('thead th')).map((th) => (th.textContent || '').trim());
} catch {
return [] as string[];
}
})();
let keys = canvasFieldOrder.length > 0 ? [...canvasFieldOrder] : Object.keys(firstRow);
// 当 DOM 表头数量与画布列一致时,按表头标题重排字段顺序(避免 existingCols 顺序异常)
if (domHeaderTitles.length > 0 && existingCols.length === domHeaderTitles.length) {
const used = new Set<number>();
const byDom = domHeaderTitles
.map((ht) => {
const idx = existingCols.findIndex(
(c: any, i: number) => !used.has(i) && String(c?.title || '').trim() === String(ht || '').trim()
);
if (idx < 0) return '';
used.add(idx);
return existingCols[idx]?.field || '';
})
.filter((f: string) => !!f && Object.prototype.hasOwnProperty.call(firstRow, f));
if (byDom.length > 0) {
keys = byDom;
}
}
// 补齐画布中没有但数据里有的字段
Object.keys(firstRow).forEach((k) => {
if (!keys.includes(k)) keys.push(k);
});
const allEnglishLike = keys.every((k) => {
const t = titleMap.get(k);
return !t || t === k;
@@ -1027,12 +1100,20 @@ function(t,e,printData){
price: '单价',
total: '合计',
};
const cols = keys.map((k) => ({
const cols = keys.map((k, idx) => ({
title: titleMap.get(k) || zhFallback[k.toLowerCase()] || k,
order: idx,
field: k,
width: 60,
align: typeof firstRow[k] === 'number' ? 'right' : 'left',
headerBg: '',
}));
// 若画布表头数量一致,则进一步按表头文本覆盖 title以画布视觉为准
if (domHeaderTitles.length === cols.length) {
cols.forEach((c, idx) => {
if (domHeaderTitles[idx]) c.title = domHeaderTitles[idx];
});
}
tableColumnsJson.value = JSON.stringify(cols, null, 2);
createMessage.success('已根据 table[0] 推导列配置');
}
@@ -1152,6 +1233,19 @@ function(t,e,printData){
}
function handlePreview() {
const printExt = {
styleHandler: () => {
return `
<style>
@media print {
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
}
</style>`;
},
};
if (!hiprintTemplate) {
return;
}
@@ -1173,10 +1267,10 @@ function(t,e,printData){
const previewTemplate = buildPreviewTemplateWithGrouping();
if (previewTemplate) {
const previewPrintTpl = new (hiprint as any).PrintTemplate({ template: previewTemplate });
previewPrintTpl.print(data, {}, { callback: () => {} });
previewPrintTpl.print(data, {}, { ...printExt, callback: () => {} });
return;
}
hiprintTemplate.print(data, {}, { callback: () => {} });
hiprintTemplate.print(data, {}, { ...printExt, callback: () => {} });
}
watch(

View File

@@ -75,9 +75,9 @@ export function createQhmesProvider() {
height: 60,
__qhmesManaged: true,
columns: [
{ title: '物料', field: 'name', width: 90, align: 'left' },
{ title: '数量', field: 'qty', width: 45, align: 'right' },
{ title: '金额', field: 'amount', width: 45, align: 'right' },
{ title: '物料', order: 0, field: 'name', width: 90, align: 'left' },
{ title: '数量', order: 1, field: 'qty', width: 45, align: 'right' },
{ title: '金额', order: 2, field: 'amount', width: 45, align: 'right' },
],
groupFields: [],
formatter: `
@@ -86,10 +86,17 @@ function(t,e,printData){
var list = printData && Array.isArray(printData[opts.field || 'table']) ? printData[opts.field || 'table'] : [];
var globalCols = printData && Array.isArray(printData.__qhmesTableColumns) ? printData.__qhmesTableColumns : [];
var columns = Array.isArray(opts.columns) && opts.columns.length ? opts.columns : (globalCols.length ? globalCols : [
{ title: '物料', field: 'name', width: 90, align: 'left' },
{ title: '数量', field: 'qty', width: 45, align: 'right' },
{ title: '金额', field: 'amount', width: 45, align: 'right' }
{ title: '物料', order: 0, field: 'name', width: 90, align: 'left' },
{ title: '数量', order: 1, field: 'qty', width: 45, align: 'right' },
{ title: '金额', order: 2, field: 'amount', width: 45, align: 'right' }
]);
columns = columns.slice().sort(function(a,b){
var ao = Number(a && a.order);
var bo = Number(b && b.order);
var av = isFinite(ao) ? ao : 9999;
var bv = isFinite(bo) ? bo : 9999;
return av - bv;
});
var globalGroups = printData && Array.isArray(printData.__qhmesGroupFields) ? printData.__qhmesGroupFields : [];
var groupFields = Array.isArray(opts.groupFields) && opts.groupFields.length ? opts.groupFields : globalGroups;
var style = opts.__qhmesStyle || {};