diff --git a/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue b/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue
index cb4932a..6a268c0 100644
--- a/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue
+++ b/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue
@@ -61,7 +61,7 @@
@@ -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();
+ 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();
+ 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 `
+`;
+ },
+ };
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(
diff --git a/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts b/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts
index 738452a..ad54491 100644
--- a/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts
+++ b/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts
@@ -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 || {};