diff --git a/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue b/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue index 6a268c0..c0f3a09 100644 --- a/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue +++ b/jeecgboot-vue3/src/views/print/template/PrintDesigner.vue @@ -31,6 +31,8 @@
普通明细表(默认列)
+
单行表头表格
+
原生表格(官方)
默认普通表头;字段/绑定在右侧属性里修改。
@@ -271,6 +273,7 @@ field: c?.field || '', width: Number(c?.width || 60), align: c?.align || 'left', + headerBg: c?.headerBg || '', }; }); normalized.sort((a, b) => a._order - b._order || a._idx - b._idx); @@ -280,6 +283,7 @@ field: c.field, width: c.width, align: c.align, + headerBg: c.headerBg, colspan: 1, rowspan: 1, })); @@ -407,14 +411,26 @@ function(t,e,printData){ return json || null; } + function mapColumnsKeepSequence(rawCols: any[]) { + if (!Array.isArray(rawCols)) return []; + return rawCols.map((c, idx) => ({ + title: c?.title || c?.field || '列', + order: Number.isFinite(Number(c?.order)) ? Number(c.order) : idx, + field: c?.field || '', + width: Number(c?.width || 60), + align: c?.align || 'left', + headerBg: c?.headerBg || '', + })); + } + function extractColumnsFromElement(el: any) { if (Array.isArray(el?.options?.columns) && el.options.columns.length) { - return compactColumnsForEditor(el.options.columns); + return mapColumnsKeepSequence(el.options.columns); } if (Array.isArray(el?.columns) && el.columns.length) { const headerRow = Array.isArray(el.columns[0]) ? el.columns[0] : []; if (headerRow.length) { - return compactColumnsForEditor(headerRow); + return mapColumnsKeepSequence(headerRow); } } return []; @@ -513,6 +529,87 @@ function(t,e,printData){ return [...elements].sort((a, b) => score(b) - score(a))[0]; } + // 目标识别层:统一提列并打分(extractColumnsFromElement 同时兼容 options.columns/columns[0]) + function pickBestColumnsFromTemplate(templateObj: any, dataKeySet: Set) { + if (!templateObj || typeof templateObj !== 'object') return []; + const candidates: Array<{ node: any; cols: any[] }> = []; + const visited = new Set(); + const walk = (node: any) => { + if (!node || typeof node !== 'object' || visited.has(node)) return; + visited.add(node); + const cols = extractColumnsFromElement(node); + if (Array.isArray(cols) && cols.length > 0) { + candidates.push({ node, cols }); + } + Object.keys(node).forEach((k) => { + const v = node[k]; + if (Array.isArray(v)) v.forEach((it) => walk(it)); + else if (v && typeof v === 'object') walk(v); + }); + }; + walk(templateObj); + if (candidates.length === 0) return []; + + const score = (item: { node: any; cols: any[] }) => { + let hit = 0; + let valid = 0; + item.cols.forEach((c: any) => { + const f = String(c?.field || '').trim(); + if (!f) return; + valid++; + if (dataKeySet.has(f)) hit++; + }); + const tidBonus = item?.node?.tid === 'qhmesModule.tableSimple' ? 50 : 0; + const tableFieldBonus = item?.node?.options?.field === 'table' ? 20 : 0; + return hit * 100 + valid * 20 + item.cols.length + tidBonus + tableFieldBonus; + }; + + const meaningful = candidates.filter((it) => it.cols.some((c: any) => !!String(c?.field || '').trim())); + const pool = meaningful.length > 0 ? meaningful : candidates; + const best = [...pool].sort((a, b) => score(b) - score(a))[0]; + return compactColumnsForEditor(best?.cols || []).filter((c: any) => !!String(c?.field || '').trim()); + } + + function pickBestTableElementByDataKeys(templateObj: any, dataKeySet: Set) { + if (!templateObj || typeof templateObj !== 'object') return null; + const candidates: Array<{ node: any; cols: any[] }> = []; + const visited = new Set(); + const walk = (node: any) => { + if (!node || typeof node !== 'object' || visited.has(node)) return; + visited.add(node); + const cols = extractColumnsFromElement(node); + if (Array.isArray(cols) && cols.length > 0) { + candidates.push({ node, cols }); + } + Object.keys(node).forEach((k) => { + const v = node[k]; + if (Array.isArray(v)) v.forEach((it) => walk(it)); + else if (v && typeof v === 'object') walk(v); + }); + }; + walk(templateObj); + if (candidates.length === 0) return null; + + const score = (item: { node: any; cols: any[] }) => { + let hit = 0; + let valid = 0; + item.cols.forEach((c: any) => { + const f = String(c?.field || '').trim(); + if (!f) return; + valid++; + if (dataKeySet.has(f)) hit++; + }); + const tidBonus = item?.node?.tid === 'qhmesModule.tableSimple' ? 50 : 0; + const tableFieldBonus = item?.node?.options?.field === 'table' ? 20 : 0; + return hit * 100 + valid * 20 + item.cols.length + tidBonus + tableFieldBonus; + }; + + const meaningful = candidates.filter((it) => it.cols.some((c: any) => !!String(c?.field || '').trim())); + const pool = meaningful.length > 0 ? meaningful : candidates; + const best = [...pool].sort((a, b) => score(b) - score(a))[0]; + return best?.node || null; + } + function patchTableSimpleElements(templateObj: any): number { if (!templateObj) return 0; @@ -677,6 +774,7 @@ function(t,e,printData){ field: c.field, width: c.width, align: c.align, + headerBg: (c as any).headerBg || '', })); const mergedGroups = groupEnabled.value ? [...groupFields.value] : []; const readCanvasTableStyle = () => { @@ -837,6 +935,29 @@ function(t,e,printData){ return previewTemplate; } + function logTableColumnsOnBoot() { + if (!hiprintTemplate) return; + const selectedEl = findCurrentTableElementFromCanvas(); + const allEls = findAllTableElementsFromCanvas(); + const bestEl = selectedEl || pickBestHeaderSource(allEls); + const cols = bestEl ? extractColumnsFromElement(bestEl) : []; + const normalized = normalizeColumns(cols).map((c) => ({ + title: c.title, + order: c.order, + field: c.field, + width: c.width, + align: c.align, + headerBg: (c as any).headerBg || '', + })); + console.log('[PrintDesigner] 进入页面-强逻辑表格明细', { + source: selectedEl ? 'selected' : bestEl ? 'bestMatched' : 'none', + tid: bestEl?.tid || '', + type: bestEl?.type || '', + count: normalized.length, + columns: normalized, + }); + } + function applyTableColumnsConfig(silent = false) { let cols: SimpleColumn[] = []; try { @@ -867,6 +988,7 @@ function(t,e,printData){ field: c.field, width: c.width, align: c.align, + headerBg: (c as any).headerBg || '', })); type.options.groupFields = groupEnabled.value ? [...groupFields.value] : []; return type; @@ -882,14 +1004,9 @@ function(t,e,printData){ } } - async function buildColumnsFromSampleData() { - const data = parsePrintData(); - const firstRow = Array.isArray(data?.table) && data.table.length > 0 ? data.table[0] : null; - if (!firstRow || Object.prototype.toString.call(firstRow) !== '[object Object]') { - createMessage.warning('预览数据中未找到 table[0] 对象,无法推导'); - return; - } - // 先强制提交右侧属性面板输入框,避免“已编辑但未提交”导致仍读到旧值 + async function buildColumnsFromSampleData(opts?: { silent?: boolean }) { + const silent = !!opts?.silent; + // 与进入页面后的强逻辑保持一致:优先读画布组件本身的列定义,不再做字段推导和猜测 const commitPropertyPanelEdits = async () => { try { const container = document.querySelector('#PrintElementOptionSetting'); @@ -915,207 +1032,129 @@ function(t,e,printData){ } }; await commitPropertyPanelEdits(); - - // title 优先取画布里“最像人工表头”的明细组件 + const templateObj = parseTemplateObjectFromInstance(); + const firstRow = parsePrintData()?.table?.[0] || {}; + const dataKeySet = new Set(Object.keys(firstRow || {})); const selectedEl = findCurrentTableElementFromCanvas(); const allEls = findAllTableElementsFromCanvas(); - const bestEl = selectedEl || pickBestHeaderSource(allEls); - const existingCols = bestEl ? extractColumnsFromElement(bestEl) : []; - const titleMap = new Map(); - existingCols.forEach((c: any) => { - if (c?.field && c?.title) titleMap.set(c.field, c.title); - }); - - // 一级优先:从右侧属性面板读取“标题”输入框(用户刚编辑但尚未回写时也能取到) - const readTitleFromPropertyPanel = () => { - try { - const container = document.querySelector('#PrintElementOptionSetting'); - if (!container) return [] as string[]; - const isColorLike = (v: string) => { - const s = (v || '').trim().toLowerCase(); - return ( - /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(s) || - /^rgba?\(/.test(s) || - /^hsla?\(/.test(s) || - /^(transparent|inherit|initial|unset|currentcolor)$/.test(s) - ); - }; - const rows = Array.from(container.querySelectorAll('.hiprint-option-item')); - const titles: string[] = []; - rows.forEach((row) => { - const labelEl = row.querySelector('.hiprint-option-item-label'); - const label = (labelEl?.textContent || '').trim(); - if (!label || !/(标题|title)/i.test(label)) return; - const inputEl = - (row.querySelector('input.auto-submit') as HTMLInputElement | null) || - (row.querySelector('input') as HTMLInputElement | null) || - (row.querySelector('textarea.auto-submit') as HTMLTextAreaElement | null) || - (row.querySelector('textarea') as HTMLTextAreaElement | null); - const v = (inputEl?.value || '').trim(); - if (v && !isColorLike(v)) titles.push(v); - }); - // 兜底:如果上面没抓到,直接从属性面板抓取可见文本输入值(按顺序) - if (titles.length === 0) { - const allInputs = Array.from( - container.querySelectorAll('input[type="text"], input:not([type]), textarea') - ) as Array; - allInputs.forEach((el) => { - const v = (el.value || '').trim(); - if (!v) return; - // 过滤明显不是标题的值:纯数字、字段变量名样式 - if (/^\d+(\.\d+)?$/.test(v)) return; - if (/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(v)) return; - if (isColorLike(v)) return; - titles.push(v); - }); - } - return titles; - } catch (e) { - console.warn(e); - return [] as string[]; - } - }; - - const panelTitles = readTitleFromPropertyPanel(); - if (panelTitles.length > 0) { - // 优先按当前组件列顺序映射 - if (existingCols.length > 0 && panelTitles.length >= existingCols.length) { - existingCols.forEach((c: any, idx: number) => { - const t = panelTitles[idx]; - if (c?.field && t) titleMap.set(c.field, t); - }); - } else { - // 组件列拿不到时,按预览数据字段顺序映射 - Object.keys(firstRow).forEach((k, idx) => { - const t = panelTitles[idx]; - if (t) titleMap.set(k, t); - }); - } + const bestDataEl = pickBestTableElementByDataKeys(templateObj, dataKeySet); + const bestEl = bestDataEl || selectedEl || pickBestHeaderSource(allEls); + let cols = bestEl ? extractColumnsFromElement(bestEl) : []; + if ((!Array.isArray(cols) || cols.length === 0) && templateObj) { + cols = pickBestColumnsFromTemplate(templateObj, dataKeySet); } - // 二级兜底:使用当前文本框已有 title(用户手动维护过时) + if (Array.isArray(cols) && cols.length > 0) { + const readHeaderColorByField = () => { + try { + const tables = Array.from(document.querySelectorAll('#hiprint-printTemplate table')) as HTMLTableElement[]; + if (!tables.length) return new Map(); + const isValidColor = (c: string) => !!c && c !== 'rgba(0, 0, 0, 0)' && c !== 'transparent'; + const targetFields = new Set(cols.map((c: any) => String(c?.field || '').trim()).filter((f: string) => !!f)); + const scoreTable = (tb: HTMLTableElement) => { + const cells = Array.from(tb.querySelectorAll('thead [colnum-id]')) as HTMLElement[]; + const ids = cells.map((it) => String(it.getAttribute('colnum-id') || '').trim()).filter((v) => !!v); + const overlap = ids.filter((id) => targetFields.has(id)).length; + return overlap * 100 + ids.length * 10; + }; + const tb = [...tables].sort((a, b) => scoreTable(b) - scoreTable(a))[0]; + const thead = tb.querySelector('thead') as HTMLElement | null; + const trHead = tb.querySelector('thead tr') as HTMLElement | null; + const theadInlineBg = thead ? (thead.style.background || thead.style.backgroundColor || '') : ''; + const theadBg = thead ? window.getComputedStyle(thead).backgroundColor || '' : ''; + const trHeadBg = trHead ? window.getComputedStyle(trHead).backgroundColor || '' : ''; + const map = new Map(); + const cells = Array.from(tb.querySelectorAll('thead [colnum-id]')) as HTMLElement[]; + cells.forEach((cell) => { + const field = String(cell.getAttribute('colnum-id') || '').trim(); + if (!field) return; + const inlineBg = (cell.style.background || cell.style.backgroundColor || '').trim(); + const c = window.getComputedStyle(cell).backgroundColor || ''; + const bg = isValidColor(inlineBg) + ? inlineBg + : isValidColor(c) + ? c + : isValidColor(theadInlineBg) + ? theadInlineBg + : isValidColor(theadBg) + ? theadBg + : isValidColor(trHeadBg) + ? trHeadBg + : ''; + if (bg) map.set(field, bg); + }); + return map; + } catch (e) { + console.warn(e); + return new Map(); + } + }; + const readUniformHeaderColor = () => { + try { + const tables = Array.from(document.querySelectorAll('#hiprint-printTemplate table')) as HTMLTableElement[]; + if (!tables.length) return ''; + const isValidColor = (c: string) => !!c && c !== 'rgba(0, 0, 0, 0)' && c !== 'transparent'; + const tb = tables[0]; + const thead = tb.querySelector('thead') as HTMLElement | null; + const trHead = tb.querySelector('thead tr') as HTMLElement | null; + const theadInlineBg = thead ? (thead.style.background || thead.style.backgroundColor || '') : ''; + const theadBg = thead ? window.getComputedStyle(thead).backgroundColor || '' : ''; + const trHeadBg = trHead ? window.getComputedStyle(trHead).backgroundColor || '' : ''; + if (isValidColor(theadInlineBg)) return theadInlineBg; + if (isValidColor(theadBg)) return theadBg; + if (isValidColor(trHeadBg)) return trHeadBg; + return ''; + } catch (e) { + console.warn(e); + return ''; + } + }; + const headerColorMap = readHeaderColorByField(); + const uniformHeaderColor = headerColorMap.size === 0 ? readUniformHeaderColor() : ''; + const normalized = normalizeColumns(cols).map((c) => ({ + title: c.title, + order: c.order, + field: c.field, + width: c.width, + align: c.align, + headerBg: c.headerBg || headerColorMap.get(String(c.field || '').trim()) || uniformHeaderColor || '', + })); + const strongHeaders = normalized.map((c) => String(c?.title || '').trim()).filter((t) => !!t); + console.log('[PrintDesigner] 数据命中明细列(强匹配)', normalized); + console.log('[PrintDesigner] 数据命中明细表头(强匹配)', strongHeaders); + tableColumnsJson.value = JSON.stringify(normalized, null, 2); + lastSyncedColumnsKey.value = JSON.stringify(normalized); + if (!silent) { + createMessage.success('已按画布表格配置同步列 JSON'); + } + return; + } + + // 兼容场景:部分表格(如仅 content 的 HTML 表)无法反解析列,回退到当前已保存 JSON try { - const currentCols = JSON.parse(tableColumnsJson.value || '[]'); - if (Array.isArray(currentCols)) { - currentCols.forEach((c: any) => { - if (c?.field && c?.title && !titleMap.has(c.field)) { - titleMap.set(c.field, c.title); - } - }); + const cached = JSON.parse(tableColumnsJson.value || '[]'); + if (Array.isArray(cached) && cached.length > 0) { + const normalizedCached = normalizeColumns(cached).map((c) => ({ + title: c.title, + order: c.order, + field: c.field, + width: c.width, + align: c.align, + headerBg: c.headerBg || '', + })); + tableColumnsJson.value = JSON.stringify(normalizedCached, null, 2); + lastSyncedColumnsKey.value = JSON.stringify(normalizedCached); + if (!silent) { + createMessage.success('已按当前列 JSON 同步'); + } + return; } } catch (e) { console.warn(e); } - // 三级兜底:从画布可见表头 DOM 抓取标题(解决 title 一直是英文变量名) - const readHeaderTitlesFromCanvas = () => { - try { - const tables = Array.from(document.querySelectorAll('#hiprint-printTemplate table')); - let best: string[] = []; - tables.forEach((tb) => { - const ths = Array.from(tb.querySelectorAll('thead th')); - const titles = ths - .map((th) => (th.textContent || '').trim()) - .filter((t) => !!t); - if (titles.length > best.length) best = titles; - }); - return best; - } catch (e) { - console.warn(e); - return [] as string[]; - } - }; - // 字段顺序优先使用“画布可见表头顺序”映射,其次用画布明细列顺序,最后回退 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; - } + if (!silent) { + createMessage.warning('未识别到可读取列配置的表格组件,且当前列 JSON 为空'); } - // 补齐画布中没有但数据里有的字段 - 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; - }); - if (allEnglishLike) { - const domTitles = readHeaderTitlesFromCanvas(); - if (domTitles.length > 0) { - // 优先按 existingCols 字段顺序映射(更接近画布列顺序) - if (existingCols.length > 0 && domTitles.length === existingCols.length) { - existingCols.forEach((c: any, idx: number) => { - if (c?.field && domTitles[idx]) { - titleMap.set(c.field, domTitles[idx]); - } - }); - } else if (domTitles.length === keys.length) { - // 次选:按预览数据字段顺序映射 - keys.forEach((k, idx) => { - if (domTitles[idx]) titleMap.set(k, domTitles[idx]); - }); - } - } - } - // 最终兜底:常见字段中文名映射(确保按钮点击后可见变化) - const zhFallback: Record = { - fbillno: '单号', - billno: '单号', - orderno: '订单号', - order_no: '订单号', - name: '物料', - material: '物料', - qty: '数量', - quantity: '数量', - amount: '金额', - unit: '单位', - price: '单价', - total: '合计', - }; - 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] 推导列配置'); } function parseTemplatePayload(str: string | undefined) { @@ -1139,6 +1178,59 @@ function(t,e,printData){ } } + function migrateSingleHeaderTableElements(templateObj: any) { + if (!templateObj || typeof templateObj !== 'object') { + return; + } + const visited = new Set(); + const toTableColumns = (cols: any[]) => { + const normalized = normalizeColumns(Array.isArray(cols) ? cols : []); + if (normalized.length === 0) { + return [ + [ + { title: '单号', field: 'fbillno', width: 55, align: 'left', colspan: 1, rowspan: 1 }, + { title: '物料', field: 'name', width: 75, align: 'left', colspan: 1, rowspan: 1 }, + { title: '数量', field: 'qty', width: 35, align: 'right', colspan: 1, rowspan: 1 }, + { title: '金额', field: 'amount', width: 35, align: 'right', colspan: 1, rowspan: 1 }, + ], + ]; + } + return [ + normalized.map((c) => ({ + title: c.title, + field: c.field, + width: c.width, + align: c.align, + colspan: 1, + rowspan: 1, + })), + ]; + }; + const walk = (node: any) => { + if (!node || typeof node !== 'object' || visited.has(node)) return; + visited.add(node); + if (node?.tid === 'qhmesModule.tableSingleHeader' && node?.type !== 'table') { + node.type = 'table'; + node.columns = toTableColumns(node?.options?.columns); + if (node.options && typeof node.options === 'object') { + delete node.options.formatter; + delete node.options.columns; + delete node.options.groupFields; + delete node.options.__qhmesManaged; + } + } + Object.keys(node).forEach((k) => { + const v = node[k]; + if (Array.isArray(v)) { + v.forEach((item) => walk(item)); + } else if (v && typeof v === 'object') { + walk(v); + } + }); + }; + walk(templateObj); + } + async function bootDesigner() { const id = route.query.id as string; if (!id) { @@ -1166,6 +1258,7 @@ function(t,e,printData){ const payload = parseTemplatePayload(record.templateJson); const template = payload.template; + migrateSingleHeaderTableElements(template); if (payload.ext) { if (typeof payload.ext.printDataJson === 'string' && payload.ext.printDataJson.trim()) { printDataJson.value = payload.ext.printDataJson; @@ -1187,6 +1280,13 @@ function(t,e,printData){ hiprintTemplate.design('#hiprint-printTemplate'); applyFieldsToTemplate(); applyTableColumnsConfig(true); + setTimeout(() => { + logTableColumnsOnBoot(); + }, 120); + // 进入页面后自动执行一次与按钮一致的强匹配同步,确保顺序/颜色来自真实画布 + setTimeout(() => { + buildColumnsFromSampleData({ silent: true }); + }, 260); // 实时从画布反向同步列配置(不打断手输) if (columnsSyncTimer) clearInterval(columnsSyncTimer); columnsSyncTimer = setInterval(() => { @@ -1262,6 +1362,7 @@ function(t,e,printData){ field: c.field, width: c.width, align: c.align, + headerBg: (c as any).headerBg || '', })); data.__qhmesGroupFields = groupEnabled.value ? [...groupFields.value] : []; const previewTemplate = buildPreviewTemplateWithGrouping(); diff --git a/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts b/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts index ad54491..35a977f 100644 --- a/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts +++ b/jeecgboot-vue3/src/views/print/template/hiprint/qhmesProvider.ts @@ -180,6 +180,26 @@ function(t,e,printData){ `, }, }, + { + tid: `${key}.tableSingleHeader`, + title: '单行表头表格', + type: 'table', + options: { + title: '单行表头表格', + field: 'table', + testData: '', + width: 180, + height: 60, + }, + columns: [ + [ + { title: '单号', field: 'fbillno', width: 55, align: 'left', colspan: 1, rowspan: 1 }, + { title: '物料', field: 'name', width: 75, align: 'left', colspan: 1, rowspan: 1 }, + { title: '数量', field: 'qty', width: 35, align: 'right', colspan: 1, rowspan: 1 }, + { title: '金额', field: 'amount', width: 35, align: 'right', colspan: 1, rowspan: 1 }, + ], + ], + }, ]; // 分组(左侧面板展示更友好)