UNPKG

vxe-table

Version:

A PC-end table component based on Vxe UI, supporting copy-paste, data pivot table, and high-performance virtual list table solution.

1,096 lines (1,043 loc) 77.4 kB
import { nextTick } from 'vue' import XEUtils from 'xe-utils' import { VxeUI } from '../../../ui' import { isColumnInfo, getCellValue, createHandleGetRowId } from '../../src/util' import { parseFile, formatText, eqEmptyValue } from '../../../ui/src/utils' import { hasClass } from '../../../ui/src/dom' import { createHtmlPage, getExportBlobByContent } from './util' import { warnLog, errLog } from '../../../ui/src/log' import type { VxeTablePropTypes, VxeColumnPropTypes, TableExportMethods, VxeGridPropTypes, VxeTableDefines, VxeTableConstructor, VxeTablePrivateMethods } from '../../../../types' const { getI18n, hooks, renderer } = VxeUI let htmlCellElem: any const csvBOM = '\ufeff' const enterSymbol = '\r\n' function defaultFilterExportColumn (column: VxeTableDefines.ColumnInfo) { return !!column.field || ['seq', 'checkbox', 'radio'].indexOf(column.type || '') === -1 } const getConvertColumns = (columns: any) => { const result: any = [] columns.forEach((column: any) => { if (column.childNodes && column.childNodes.length) { result.push(column) result.push(...getConvertColumns(column.childNodes)) } else { result.push(column) } }) return result } const convertToRows = (originColumns: any): any[][] => { let maxLevel = 1 const traverse = (column: any, parent?: any) => { if (parent) { column._level = parent._level + 1 if (maxLevel < column._level) { maxLevel = column._level } } if (column.childNodes && column.childNodes.length) { let colSpan = 0 column.childNodes.forEach((subColumn: any) => { traverse(subColumn, column) colSpan += subColumn._colSpan }) column._colSpan = colSpan } else { column._colSpan = 1 } } originColumns.forEach((column: any) => { column._level = 1 traverse(column) }) const rows: any = [] for (let i = 0; i < maxLevel; i++) { rows.push([]) } const allColumns = getConvertColumns(originColumns) allColumns.forEach((column: any) => { if (column.childNodes && column.childNodes.length) { column._rowSpan = 1 } else { column._rowSpan = maxLevel - column._level + 1 } rows[column._level - 1].push(column) }) return rows } function toTableBorder (border: VxeTablePropTypes.Border | undefined) { if (border === true) { return 'full' } if (border) { return border } return 'default' } function getBooleanValue (cellValue: any) { return cellValue === 'TRUE' || cellValue === 'true' || cellValue === true } function getFooterData ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, opts: VxeTablePropTypes.ExportHandleOptions, footerTableData: any[]) { const $xeGrid = $xeTable.xeGrid const $xeGantt = $xeTable.xeGantt const { footerFilterMethod } = opts return footerFilterMethod ? footerTableData.filter((items, index) => footerFilterMethod({ $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, items, $rowIndex: index })) : footerTableData } function getCsvCellTypeLabel (column: any, cellValue: any) { if (cellValue) { if (column.type === 'seq') { return `\t${cellValue}` } switch (column.cellType) { case 'string': if (!isNaN(cellValue)) { return `\t${cellValue}` } break case 'number': break default: if (cellValue.length >= 12 && !isNaN(cellValue)) { return `\t${cellValue}` } break } } return cellValue } function toTxtCellLabel (val: any) { if (/[",\s\n]/.test(val)) { return `"${val.replace(/"/g, '""')}"` } return val } function getElementsByTagName (elem: any, qualifiedName: any) { return elem.getElementsByTagName(qualifiedName) as HTMLElement[] } function getTxtCellKey (now: number) { return `#${now}@${XEUtils.uniqueId()}` } function replaceTxtCell (cell: any, vMaps: any) { return cell.replace(/#\d+@\d+/g, (key: any) => XEUtils.hasOwnProp(vMaps, key) ? vMaps[key] : key) } function getTxtCellValue (val: any, vMaps: any) { const rest = replaceTxtCell(val, vMaps) return rest.replace(/^"+$/g, (qVal: any) => '"'.repeat(Math.ceil(qVal.length / 2))) } function toExportField (tableConf: { fieldMaps: Record<string, VxeTableDefines.ColumnInfo> titleMaps: Record<string, VxeTableDefines.ColumnInfo> }, field: string) { const { fieldMaps, titleMaps } = tableConf // title 转 field if (!fieldMaps[field]) { const teCol = titleMaps[field] if (teCol && teCol.field) { field = teCol.field } } return field } function parseCsvAndTxt (tableConf: { fieldMaps: Record<string, VxeTableDefines.ColumnInfo> titleMaps: Record<string, VxeTableDefines.ColumnInfo> }, content: string, cellSeparator: string) { const list = content.split(enterSymbol) const rows: any[] = [] let fields: string[] = [] if (list.length) { const vMaps: any = {} const now = Date.now() list.forEach((rVal) => { if (rVal) { const item: any = {} rVal = rVal.replace(/("")|(\n)/g, (text: string, dVal: string) => { const key = getTxtCellKey(now) vMaps[key] = dVal ? '"' : '\n' return key }).replace(/"(.*?)"/g, (text: string, cVal: string) => { const key = getTxtCellKey(now) vMaps[key] = replaceTxtCell(cVal, vMaps) return key }) const cells: string[] = rVal.split(cellSeparator) if (!fields.length) { fields = cells.map((val: string) => toExportField(tableConf, getTxtCellValue(val.trim(), vMaps))) } else { cells.forEach((val, colIndex) => { if (colIndex < fields.length) { item[fields[colIndex]] = getTxtCellValue(val.trim(), vMaps) } }) rows.push(item) } } }) } return { fields, rows } } function parseCsv (tableConf: { fieldMaps: Record<string, VxeTableDefines.ColumnInfo> titleMaps: Record<string, VxeTableDefines.ColumnInfo> }, content: string) { return parseCsvAndTxt(tableConf, content, ',') } function parseTxt (tableConf: { fieldMaps: Record<string, VxeTableDefines.ColumnInfo> titleMaps: Record<string, VxeTableDefines.ColumnInfo> }, content: string) { return parseCsvAndTxt(tableConf, content, '\t') } function parseHTML (tableConf: { fieldMaps: Record<string, VxeTableDefines.ColumnInfo> titleMaps: Record<string, VxeTableDefines.ColumnInfo> }, content: string) { const domParser = new DOMParser() const xmlDoc = domParser.parseFromString(content, 'text/html') const bodyNodes = getElementsByTagName(xmlDoc, 'body') const rows: any[] = [] const fields: string[] = [] if (bodyNodes.length) { const tableNodes = getElementsByTagName(bodyNodes[0], 'table') if (tableNodes.length) { const theadNodes = getElementsByTagName(tableNodes[0], 'thead') if (theadNodes.length) { XEUtils.arrayEach(getElementsByTagName(theadNodes[0], 'tr'), rowNode => { XEUtils.arrayEach(getElementsByTagName(rowNode, 'th'), cellNode => { fields.push(toExportField(tableConf, cellNode.textContent || '')) }) }) const tbodyNodes = getElementsByTagName(tableNodes[0], 'tbody') if (tbodyNodes.length) { XEUtils.arrayEach(getElementsByTagName(tbodyNodes[0], 'tr'), rowNode => { const item: any = {} XEUtils.arrayEach(getElementsByTagName(rowNode, 'td'), (cellNode, colIndex) => { if (fields[colIndex]) { item[fields[colIndex]] = cellNode.textContent || '' } }) rows.push(item) }) } } } } return { fields, rows } } function parseXML (tableConf: { fieldMaps: Record<string, VxeTableDefines.ColumnInfo> titleMaps: Record<string, VxeTableDefines.ColumnInfo> }, content: string) { const domParser = new DOMParser() const xmlDoc = domParser.parseFromString(content, 'application/xml') const sheetNodes = getElementsByTagName(xmlDoc, 'Worksheet') const rows: any[] = [] const fields: string[] = [] if (sheetNodes.length) { const tableNodes = getElementsByTagName(sheetNodes[0], 'Table') if (tableNodes.length) { const rowNodes = getElementsByTagName(tableNodes[0], 'Row') if (rowNodes.length) { XEUtils.arrayEach(getElementsByTagName(rowNodes[0], 'Cell'), cellNode => { fields.push(toExportField(tableConf, cellNode.textContent || '')) }) XEUtils.arrayEach(rowNodes, (rowNode, index) => { if (index) { const item: any = {} const cellNodes = getElementsByTagName(rowNode, 'Cell') XEUtils.arrayEach(cellNodes, (cellNode, colIndex) => { if (fields[colIndex]) { item[fields[colIndex]] = cellNode.textContent } }) rows.push(item) } }) } } } return { fields, rows } } function clearColumnConvert (columns: any) { XEUtils.eachTree(columns, (column: any) => { delete column._level delete column._colSpan delete column._rowSpan delete column._children delete column.childNodes }, { children: 'children' }) } const tableExportMethodKeys: (keyof TableExportMethods)[] = ['exportData', 'importByFile', 'importData', 'saveFile', 'readFile', 'print', 'getPrintHtml', 'openImport', 'closeImport', 'openExport', 'closeExport', 'openPrint', 'closePrint'] hooks.add('tableExportModule', { setupTable ($xeTable) { const { props, reactData, internalData } = $xeTable const { computeTreeOpts, computePrintOpts, computeExportOpts, computeImportOpts, computeCustomOpts, computeSeqOpts, computeRadioOpts, computeCheckboxOpts, computeColumnOpts, computeAggregateOpts } = $xeTable.getComputeMaps() const getSeq = (cellValue: any, row: any, $rowIndex: number, column: VxeTableDefines.ColumnInfo, $columnIndex: number) => { const seqOpts = computeSeqOpts.value const seqMd = seqOpts.seqMethod || (column as any).seqMethod if (seqMd) { return seqMd({ $table: $xeTable, row, rowIndex: $xeTable.getRowIndex(row), _rowIndex: $xeTable.getVTRowIndex(row), $rowIndex, column, columnIndex: $xeTable.getColumnIndex(column), _columnIndex: $xeTable.getVTColumnIndex(column), $columnIndex }) } return cellValue } function getHeaderTitle (opts: VxeTablePropTypes.ExportHandleOptions, column: VxeTableDefines.ColumnInfo) { const columnOpts = computeColumnOpts.value const headExportMethod = column.headerExportMethod || columnOpts.headerExportMethod return headExportMethod ? headExportMethod({ column, options: opts, $table: $xeTable }) : ((opts.isTitle ? column.getTitle() : column.field) || '') } const toBooleanValue = (cellValue: any) => { return XEUtils.isBoolean(cellValue) ? (cellValue ? 'TRUE' : 'FALSE') : cellValue } const toStringValue = (cellValue: any) => { return eqEmptyValue(cellValue) ? '' : `${cellValue}` } const getBodyLabelData = (opts: VxeTablePropTypes.ExportHandleOptions, columns: VxeTableDefines.ColumnInfo[], datas: any[]) => { const { isTreeAllExpanded, isRowGroupAllExpanded, mode: expMode } = opts const { treeConfig } = props const { isRowGroupStatus } = reactData const { fullColumnFieldData } = internalData const radioOpts = computeRadioOpts.value const checkboxOpts = computeCheckboxOpts.value const treeOpts = computeTreeOpts.value const columnOpts = computeColumnOpts.value const aggregateOpts = computeAggregateOpts.value if (!htmlCellElem) { htmlCellElem = document.createElement('div') } const { handleGetRowId } = createHandleGetRowId($xeTable) if (isRowGroupStatus) { // 如果是数据分组 const { mode: aggMode, showTotal, totalMethod, countFields, contentMethod, formatValuesMethod, mapChildrenField } = aggregateOpts const rest: any[] = [] const expandMaps: Record<string, boolean> = {} const useMaps: Record<string, boolean> = {} XEUtils.eachTree(datas, (item, $rowIndex, items, path, parentItem, nodes) => { const row = item._row || item const rowid = handleGetRowId(row) if (useMaps[rowid]) { return } const parentRow = parentItem && parentItem._row ? parentItem._row : parentItem const pRowid = parentRow ? handleGetRowId(parentRow) : '' if ((isRowGroupAllExpanded || !parentRow || (expandMaps[pRowid] && $xeTable.isRowExpandByRow(parentRow)))) { const hasRowChild = mapChildrenField && row[mapChildrenField] && row[mapChildrenField].length const item: any = { _row: row, _level: nodes.length - 1, _hasChild: hasRowChild, _expand: hasRowChild && $xeTable.isRowExpandByRow(row) } columns.forEach((column, $columnIndex) => { const { field, editRender, cellRender, aggFunc, rowGroupNode } = column let cellValue: string | number | boolean | null = '' const renderOpts = editRender || cellRender let bodyExportMethod: VxeColumnPropTypes.ExportMethod | undefined = column.exportMethod || columnOpts.exportMethod if (!bodyExportMethod && renderOpts && renderOpts.name) { const compConf = renderer.get(renderOpts.name) if (compConf) { bodyExportMethod = compConf.tableExportMethod || compConf.exportMethod } } if (!bodyExportMethod) { bodyExportMethod = columnOpts.exportMethod } if (bodyExportMethod) { cellValue = bodyExportMethod({ $table: $xeTable, row, column, options: opts }) } else { switch (column.type) { case 'seq': { const seqVal = path.map((num, i) => i % 2 === 0 ? (Number(num) + 1) : '.').join('') cellValue = expMode === 'all' ? seqVal : getSeq(seqVal, row, $rowIndex, column, $columnIndex) break } case 'checkbox': { cellValue = toBooleanValue($xeTable.isCheckedByCheckboxRow(row)) item._checkboxLabel = checkboxOpts.labelField ? XEUtils.get(row, checkboxOpts.labelField) : '' item._checkboxDisabled = checkboxOpts.checkMethod && !checkboxOpts.checkMethod({ $table: $xeTable, row }) break } case 'radio': { cellValue = toBooleanValue($xeTable.isCheckedByRadioRow(row)) item._radioLabel = radioOpts.labelField ? XEUtils.get(row, radioOpts.labelField) : '' item._radioDisabled = radioOpts.checkMethod && !radioOpts.checkMethod({ $table: $xeTable, row }) break } default: { if (field && row.isAggregate) { const aggRow: VxeTableDefines.AggregateRowInfo = row const aggData = aggRow.aggData const currAggData = aggData ? aggData[field] : null const groupField = aggRow.groupField const groupContent = aggRow.groupContent const childList = mapChildrenField ? (aggRow[mapChildrenField] || []) : [] const childCount = aggRow.childCount const colRest = fullColumnFieldData[groupField] || {} const ctParams = { $table: $xeTable, groupField, groupColumn: (colRest ? colRest.column : null) as VxeTableDefines.ColumnInfo, column, groupValue: groupContent, childList, childCount, aggValue: null as any, /** * 已废弃 * @deprecated */ children: childList, /** * 已废弃 * @deprecated */ totalValue: childCount } if (aggMode === 'column' ? field === aggRow.groupField : rowGroupNode) { cellValue = groupContent if (contentMethod) { cellValue = `${contentMethod(ctParams)}` } if (showTotal) { cellValue = getI18n('vxe.table.rowGroupContentTotal', [cellValue, totalMethod ? totalMethod(ctParams) : childCount, childCount]) } } else if ($xeTable.getPivotTableAggregateCellAggValue) { const aggParams = { $table: $xeTable, row, column } cellValue = $xeTable.getPivotTableAggregateCellAggValue(aggParams) } else if (aggFunc === true || (countFields && countFields.includes(field))) { cellValue = currAggData ? currAggData.value : childCount ctParams.aggValue = cellValue if (formatValuesMethod) { cellValue = formatValuesMethod(ctParams) } } } else if (opts.original) { cellValue = getCellValue(row, column) } else { cellValue = $xeTable.getCellLabel(row, column) if (column.type === 'html') { htmlCellElem.innerHTML = cellValue cellValue = htmlCellElem.innerText.trim() } else { const cell = $xeTable.getCellElement(row, column) if (cell && !hasClass(cell, 'is--progress')) { cellValue = cell.innerText.trim() } } } break } } } item[column.id] = toStringValue(cellValue) }) useMaps[rowid] = true if (pRowid) { expandMaps[pRowid] = true } rest.push(Object.assign(item, row)) } }, { children: mapChildrenField }) return rest } if (treeConfig) { // 如果是树结构 const childrenField = treeOpts.children || treeOpts.childrenField const rest: any[] = [] const expandMaps: Record<string, boolean> = {} const useMaps: Record<string, boolean> = {} XEUtils.eachTree(datas, (item, $rowIndex, items, path, parentItem, nodes) => { const row = item._row || item const rowid = handleGetRowId(row) if (useMaps[rowid]) { return } const parentRow = parentItem && parentItem._row ? parentItem._row : parentItem const pRowid = parentRow ? handleGetRowId(parentRow) : '' if ((isTreeAllExpanded || !parentRow || (expandMaps[pRowid] && $xeTable.isTreeExpandByRow(parentRow)))) { const hasRowChild = row[childrenField] && row[childrenField].length const item: any = { _row: row, _level: nodes.length - 1, _hasChild: hasRowChild, _expand: hasRowChild && $xeTable.isTreeExpandByRow(row) } columns.forEach((column, $columnIndex) => { let cellValue: string | number | boolean | null = '' const renderOpts = column.editRender || column.cellRender let bodyExportMethod: VxeColumnPropTypes.ExportMethod | undefined = column.exportMethod || columnOpts.exportMethod if (!bodyExportMethod && renderOpts && renderOpts.name) { const compConf = renderer.get(renderOpts.name) if (compConf) { bodyExportMethod = compConf.tableExportMethod || compConf.exportMethod } } if (!bodyExportMethod) { bodyExportMethod = columnOpts.exportMethod } if (bodyExportMethod) { cellValue = bodyExportMethod({ $table: $xeTable, row, column, options: opts }) } else { switch (column.type) { case 'seq': { const seqVal = path.map((num, i) => i % 2 === 0 ? (Number(num) + 1) : '.').join('') cellValue = expMode === 'all' ? seqVal : getSeq(seqVal, row, $rowIndex, column, $columnIndex) break } case 'checkbox': cellValue = toBooleanValue($xeTable.isCheckedByCheckboxRow(row)) item._checkboxLabel = checkboxOpts.labelField ? XEUtils.get(row, checkboxOpts.labelField) : '' item._checkboxDisabled = checkboxOpts.checkMethod && !checkboxOpts.checkMethod({ $table: $xeTable, row }) break case 'radio': cellValue = toBooleanValue($xeTable.isCheckedByRadioRow(row)) item._radioLabel = radioOpts.labelField ? XEUtils.get(row, radioOpts.labelField) : '' item._radioDisabled = radioOpts.checkMethod && !radioOpts.checkMethod({ $table: $xeTable, row }) break default: if (opts.original) { cellValue = getCellValue(row, column) } else { cellValue = $xeTable.getCellLabel(row, column) if (column.type === 'html') { htmlCellElem.innerHTML = cellValue cellValue = htmlCellElem.innerText.trim() } else { const cell = $xeTable.getCellElement(row, column) if (cell && !hasClass(cell, 'is--progress')) { cellValue = cell.innerText.trim() } } } } } item[column.id] = toStringValue(cellValue) }) useMaps[rowid] = true if (pRowid) { expandMaps[pRowid] = true } rest.push(Object.assign(item, row)) } }, { children: childrenField }) return rest } return datas.map((row, $rowIndex) => { const item: any = { _row: row } columns.forEach((column, $columnIndex) => { let cellValue: string | number | boolean | null = '' const renderOpts = column.editRender || column.cellRender let bodyExportMethod: VxeColumnPropTypes.ExportMethod | undefined = column.exportMethod || columnOpts.exportMethod if (!bodyExportMethod && renderOpts && renderOpts.name) { const compConf = renderer.get(renderOpts.name) if (compConf) { bodyExportMethod = compConf.tableExportMethod || compConf.exportMethod } } if (bodyExportMethod) { cellValue = bodyExportMethod({ $table: $xeTable, row, column, options: opts }) } else { switch (column.type) { case 'seq': { const seqValue = $rowIndex + 1 cellValue = expMode === 'all' ? seqValue : getSeq(seqValue, row, $rowIndex, column, $columnIndex) break } case 'checkbox': cellValue = toBooleanValue($xeTable.isCheckedByCheckboxRow(row)) item._checkboxLabel = checkboxOpts.labelField ? XEUtils.get(row, checkboxOpts.labelField) : '' item._checkboxDisabled = checkboxOpts.checkMethod && !checkboxOpts.checkMethod({ $table: $xeTable, row }) break case 'radio': cellValue = toBooleanValue($xeTable.isCheckedByRadioRow(row)) item._radioLabel = radioOpts.labelField ? XEUtils.get(row, radioOpts.labelField) : '' item._radioDisabled = radioOpts.checkMethod && !radioOpts.checkMethod({ $table: $xeTable, row }) break default: if (opts.original) { cellValue = getCellValue(row, column) } else { cellValue = $xeTable.getCellLabel(row, column) if (column.type === 'html') { htmlCellElem.innerHTML = cellValue cellValue = htmlCellElem.innerText.trim() } else { const cell = $xeTable.getCellElement(row, column) if (cell && !hasClass(cell, 'is--progress')) { cellValue = cell.innerText.trim() } } } } } item[column.id] = toStringValue(cellValue) }) return item }) } const getExportData = (opts: VxeTablePropTypes.ExportHandleOptions) => { const $xeGrid = $xeTable.xeGrid const $xeGantt = $xeTable.xeGantt const { columns, dataFilterMethod } = opts let datas = opts.data if (dataFilterMethod) { datas = datas.filter((row, index) => dataFilterMethod({ $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt, row, $rowIndex: index })) } return getBodyLabelData(opts, columns, datas) } const getFooterCellValue = (opts: VxeTablePropTypes.ExportHandleOptions, row: any, column: VxeTableDefines.ColumnInfo) => { const columnOpts = computeColumnOpts.value const renderOpts = column.editRender || column.cellRender let footLabelMethod: VxeColumnPropTypes.FooterExportMethod | undefined = column.footerExportMethod if (!footLabelMethod && renderOpts && renderOpts.name) { const compConf = renderer.get(renderOpts.name) if (compConf) { footLabelMethod = compConf.tableFooterExportMethod || compConf.footerExportMethod } } if (!footLabelMethod) { footLabelMethod = columnOpts.footerExportMethod } if (footLabelMethod) { const _columnIndex = $xeTable.getVTColumnIndex(column) return footLabelMethod({ $table: $xeTable, items: row, itemIndex: _columnIndex, row, _columnIndex, column, options: opts }) } const cellValue = $xeTable.getFooterCellLabel(row, column) return cellValue } const toCsv = ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, opts: VxeTablePropTypes.ExportHandleOptions, columns: VxeTableDefines.ColumnInfo[], datas: any[]) => { let content = csvBOM if (opts.isHeader) { content += columns.map((column) => toTxtCellLabel(getHeaderTitle(opts, column))).join(',') + enterSymbol } datas.forEach((row) => { content += columns.map((column) => toTxtCellLabel(getCsvCellTypeLabel(column, row[column.id]))).join(',') + enterSymbol }) if (opts.isFooter) { const { footerTableData } = reactData const footers = getFooterData($xeTable, opts, footerTableData) footers.forEach((row) => { content += columns.map((column: any) => toTxtCellLabel(getFooterCellValue(opts, row, column))).join(',') + enterSymbol }) } return content } const toTxt = ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, opts: VxeTablePropTypes.ExportHandleOptions, columns: VxeTableDefines.ColumnInfo[], datas: any[]) => { let content = '' if (opts.isHeader) { content += columns.map((column) => toTxtCellLabel(getHeaderTitle(opts, column))).join('\t') + enterSymbol } datas.forEach((row) => { content += columns.map((column) => toTxtCellLabel(row[column.id])).join('\t') + enterSymbol }) if (opts.isFooter) { const { footerTableData } = reactData const footers = getFooterData($xeTable, opts, footerTableData) footers.forEach((row) => { content += columns.map((column: any) => toTxtCellLabel(getFooterCellValue(opts, row, column))).join('\t') + enterSymbol }) } return content } const hasEllipsis = (column: VxeTableDefines.ColumnInfo, property: 'showOverflow' | 'showHeaderOverflow', allColumnOverflow: VxeTablePropTypes.ShowOverflow | undefined) => { const columnOverflow = column[property] const headOverflow = XEUtils.isUndefined(columnOverflow) || XEUtils.isNull(columnOverflow) ? allColumnOverflow : columnOverflow const showEllipsis = headOverflow === 'ellipsis' const showTitle = headOverflow === 'title' const showTooltip = headOverflow === true || headOverflow === 'tooltip' let isEllipsis = showTitle || showTooltip || showEllipsis // 虚拟滚动不支持动态高度 const { scrollXLoad, scrollYLoad } = reactData if ((scrollXLoad || scrollYLoad) && !isEllipsis) { isEllipsis = true } return isEllipsis } const toHtml = (opts: VxeTablePropTypes.ExportHandleOptions, columns: VxeTableDefines.ColumnInfo[], datas: any[]) => { const { id, border, treeConfig, headerAlign: allHeaderAlign, align: allAlign, footerAlign: allFooterAlign, showOverflow: allColumnOverflow, showHeaderOverflow: allColumnHeaderOverflow } = props const { isAllSelected, isIndeterminate } = reactData const { mergeBodyCellMaps } = internalData const treeOpts = computeTreeOpts.value const { print: isPrint, isHeader, isFooter, isColgroup, isMerge, colgroups, original } = opts const allCls = 'check-all' const clss = [ 'vxe-table', `border--${toTableBorder(border)}`, isPrint ? 'is--print' : '', isHeader ? 'is--header' : '' ].filter(cls => cls) const tables = [ `<table class="${clss.join(' ')}" border="0" cellspacing="0" cellpadding="0">`, `<colgroup>${columns.map((column: any) => `<col style="width:${column.renderWidth}px">`).join('')}</colgroup>` ] if (isHeader) { tables.push('<thead>') if (isColgroup && !original) { colgroups.forEach((cols: any) => { tables.push( `<tr>${cols.map((column: any) => { const headAlign = column.headerAlign || column.align || allHeaderAlign || allAlign const classNames = hasEllipsis(column, 'showHeaderOverflow', allColumnHeaderOverflow) ? ['col--ellipsis'] : [] const cellTitle = getHeaderTitle(opts, column) let childWidth = 0 let countChild = 0 XEUtils.eachTree([column], item => { if (!item.childNodes || !column.childNodes.length) { countChild++ } childWidth += item.renderWidth }, { children: 'childNodes' }) const cellWidth = childWidth - countChild if (headAlign) { classNames.push(`col--${headAlign}`) } if (column.type === 'checkbox') { return `<th class="${classNames.join(' ')}" colspan="${column._colSpan}" rowspan="${column._rowSpan}"><div ${isPrint ? '' : `style="width: ${cellWidth}px"`}><input type="checkbox" class="${allCls}" ${isAllSelected ? 'checked' : ''}><span>${cellTitle}</span></div></th>` } return `<th class="${classNames.join(' ')}" colspan="${column._colSpan}" rowspan="${column._rowSpan}" title="${cellTitle}"><div ${isPrint ? '' : `style="width: ${cellWidth}px"`}><span>${formatText(cellTitle, true)}</span></div></th>` }).join('')}</tr>` ) }) } else { tables.push( `<tr>${columns.map((column: any) => { const headAlign = column.headerAlign || column.align || allHeaderAlign || allAlign const classNames = hasEllipsis(column, 'showHeaderOverflow', allColumnHeaderOverflow) ? ['col--ellipsis'] : [] const cellTitle = getHeaderTitle(opts, column) if (headAlign) { classNames.push(`col--${headAlign}`) } if (column.type === 'checkbox') { return `<th class="${classNames.join(' ')}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><input type="checkbox" class="${allCls}" ${isAllSelected ? 'checked' : ''}><span>${cellTitle}</span></div></th>` } return `<th class="${classNames.join(' ')}" title="${cellTitle}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><span>${formatText(cellTitle, true)}</span></div></th>` }).join('')}</tr>` ) } tables.push('</thead>') } if (datas.length) { tables.push('<tbody>') if (treeConfig) { datas.forEach((item: any) => { tables.push( '<tr>' + columns.map((column: any) => { const colid = column.id const cellAlign = column.align || allAlign const classNames = hasEllipsis(column, 'showOverflow', allColumnOverflow) ? ['col--ellipsis'] : [] const cellValue = item[colid] if (cellAlign) { classNames.push(`col--${cellAlign}`) } if (column.treeNode) { let treeIcon = '' if (item._hasChild) { treeIcon = `<i class="${item._expand ? 'vxe-table--tree-fold-icon' : 'vxe-table--tree-unfold-icon'}"></i>` } classNames.push('vxe-table--tree-node') if (column.type === 'radio') { return `<td class="${classNames.join(' ')}" title="${cellValue}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><div class="vxe-table--tree-node-wrapper" style="padding-left: ${item._level * treeOpts.indent}px"><div class="vxe-table--tree-icon-wrapper">${treeIcon}</div><div class="vxe-table--tree-cell"><input type="radio" name="radio_${id}" ${item._radioDisabled ? 'disabled ' : ''}${getBooleanValue(cellValue) ? 'checked' : ''}><span>${item._radioLabel}</span></div></div></div></td>` } else if (column.type === 'checkbox') { return `<td class="${classNames.join(' ')}" title="${cellValue}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><div class="vxe-table--tree-node-wrapper" style="padding-left: ${item._level * treeOpts.indent}px"><div class="vxe-table--tree-icon-wrapper">${treeIcon}</div><div class="vxe-table--tree-cell"><input type="checkbox" ${item._checkboxDisabled ? 'disabled ' : ''}${getBooleanValue(cellValue) ? 'checked' : ''}><span>${item._checkboxLabel}</span></div></div></div></td>` } return `<td class="${classNames.join(' ')}" title="${cellValue}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><div class="vxe-table--tree-node-wrapper" style="padding-left: ${item._level * treeOpts.indent}px"><div class="vxe-table--tree-icon-wrapper">${treeIcon}</div><div class="vxe-table--tree-cell">${cellValue}</div></div></div></td>` } if (column.type === 'radio') { return `<td class="${classNames.join(' ')}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><input type="radio" name="radio_${id}" ${item._radioDisabled ? 'disabled ' : ''}${getBooleanValue(cellValue) ? 'checked' : ''}><span>${item._radioLabel}</span></div></td>` } else if (column.type === 'checkbox') { return `<td class="${classNames.join(' ')}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><input type="checkbox" ${item._checkboxDisabled ? 'disabled ' : ''}${getBooleanValue(cellValue) ? 'checked' : ''}><span>${item._checkboxLabel}</span></div></td>` } return `<td class="${classNames.join(' ')}" title="${cellValue}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}>${formatText(cellValue, true)}</div></td>` }).join('') + '</tr>' ) }) } else { datas.forEach((item) => { tables.push( '<tr>' + columns.map((column) => { const cellAlign = column.align || allAlign const classNames = hasEllipsis(column, 'showOverflow', allColumnOverflow) ? ['col--ellipsis'] : [] const cellValue = item[column.id] let rowSpan = 1 let colSpan = 1 if (isMerge) { const _rowIndex = $xeTable.getVTRowIndex(item._row) const _columnIndex = $xeTable.getVTColumnIndex(column) const spanRest = mergeBodyCellMaps[`${_rowIndex}:${_columnIndex}`] if (spanRest) { const { rowspan, colspan } = spanRest if (!rowspan || !colspan) { return '' } if (rowspan > 1) { rowSpan = rowspan } if (colspan > 1) { colSpan = colspan } } } if (cellAlign) { classNames.push(`col--${cellAlign}`) } if (column.type === 'radio') { return `<td class="${classNames.join(' ')}" rowspan="${rowSpan}" colspan="${colSpan}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><input type="radio" name="radio_${id}" ${item._radioDisabled ? 'disabled ' : ''}${getBooleanValue(cellValue) ? 'checked' : ''}><span>${item._radioLabel}</span></div></td>` } else if (column.type === 'checkbox') { return `<td class="${classNames.join(' ')}" rowspan="${rowSpan}" colspan="${colSpan}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}><input type="checkbox" ${item._checkboxDisabled ? 'disabled ' : ''}${getBooleanValue(cellValue) ? 'checked' : ''}><span>${item._checkboxLabel}</span></div></td>` } return `<td class="${classNames.join(' ')}" rowspan="${rowSpan}" colspan="${colSpan}" title="${cellValue}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}>${formatText(cellValue, true)}</div></td>` }).join('') + '</tr>' ) }) } tables.push('</tbody>') } if (isFooter) { const { footerTableData } = reactData const footers = getFooterData($xeTable, opts, footerTableData) if (footers.length) { tables.push('<tfoot>') footers.forEach((row: any) => { tables.push( `<tr>${columns.map((column: any) => { const footAlign = column.footerAlign || column.align || allFooterAlign || allAlign const classNames = hasEllipsis(column, 'showOverflow', allColumnOverflow) ? ['col--ellipsis'] : [] const cellValue = getFooterCellValue(opts, row, column) if (footAlign) { classNames.push(`col--${footAlign}`) } return `<td class="${classNames.join(' ')}" title="${cellValue}"><div ${isPrint ? '' : `style="width: ${column.renderWidth}px"`}>${formatText(cellValue, true)}</div></td>` }).join('')}</tr>` ) }) tables.push('</tfoot>') } } // 是否半选状态 const script = !isAllSelected && isIndeterminate ? `<script>(function(){var a=document.querySelector(".${allCls}");if(a){a.indeterminate=true}})()</script>` : '' tables.push('</table>', script) return isPrint ? tables.join('') : createHtmlPage(opts, tables.join('')) } const toXML = (opts: any, columns: any, datas: any) => { let xml = [ '<?xml version="1.0"?>', '<?mso-application progid="Excel.Sheet"?>', '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">', '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">', '<Version>16.00</Version>', '</DocumentProperties>', '<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">', '<WindowHeight>7920</WindowHeight>', '<WindowWidth>21570</WindowWidth>', '<WindowTopX>32767</WindowTopX>', '<WindowTopY>32767</WindowTopY>', '<ProtectStructure>False</ProtectStructure>', '<ProtectWindows>False</ProtectWindows>', '</ExcelWorkbook>', `<Worksheet ss:Name="${opts.sheetName}">`, '<Table>', columns.map((column: any) => `<Column ss:Width="${column.renderWidth}"/>`).join('') ].join('') if (opts.isHeader) { xml += `<Row>${columns.map((column: any) => `<Cell><Data ss:Type="String">${getHeaderTitle(opts, column)}</Data></Cell>`).join('')}</Row>` } datas.forEach((row: any) => { xml += '<Row>' + columns.map((column: any) => `<Cell><Data ss:Type="String">${row[column.id]}</Data></Cell>`).join('') + '</Row>' }) if (opts.isFooter) { const { footerTableData } = reactData const footers = getFooterData($xeTable, opts, footerTableData) footers.forEach((row: any) => { xml += `<Row>${columns.map((column: any) => `<Cell><Data ss:Type="String">${getFooterCellValue(opts, row, column)}</Data></Cell>`).join('')}</Row>` }) } return `${xml}</Table></Worksheet></Workbook>` } const getContent = ($xeTable: VxeTableConstructor & VxeTablePrivateMethods, opts: VxeTablePropTypes.ExportHandleOptions, columns: VxeTableDefines.ColumnInfo[], datas: any[]) => { if (columns.length) { switch (opts.type) { case 'csv': return toCsv($xeTable, opts, columns, datas) case 'txt': return toTxt($xeTable, opts, columns, datas) case 'html': return toHtml(opts, columns, datas) case 'xml': return toXML(opts, columns, datas) } } return '' } const downloadFile = (opts: any, content: any) => { const { filename, type, download } = opts if (!download) { const blob = getExportBlobByContent(content, opts) return Promise.resolve({ type, content, blob }) } if (VxeUI.saveFile) { VxeUI.saveFile({ filename, type, content }).then(() => { if (opts.message !== false) { if (VxeUI.modal) { VxeUI.modal.message({ content: getI18n('vxe.table.expSuccess'), status: 'success' }) } } }) } } const handleExport = (opts: VxeTablePropTypes.ExportHandleOptions) => { const $xeGrid = $xeTable.xeGrid const $xeGantt = $xeTable.xeGantt const { remote, columns, colgroups, exportMethod, afterExportMethod } = opts return new Promise(resolve => { if (remote) { const params = { options: opts, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt } resolve(exportMethod ? exportMethod(params) : params) } else { const datas = getExportData(opts) resolve( $xeTable.preventEvent(null, 'event.export', { options: opts, columns, colgroups, datas }, () => { return downloadFile(opts, getContent($xeTable, opts, columns, datas)) }) ) } }).then((params: any) => { clearColumnConvert(columns) if (!opts.print) { if (afterExportMethod) { afterExportMethod({ status: true, options: opts, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }) } } return Object.assign({ status: true }, params) }).catch(() => { clearColumnConvert(columns) if (!opts.print) { if (afterExportMethod) { afterExportMethod({ status: false, options: opts, $table: $xeTable, $grid: $xeGrid, $gantt: $xeGantt }) } } const params = { status: false } return Promise.reject(params) }) } const handleImport = (content: any, opts: any) => { const { tableFullColumn, _importResolve, _importReject } = internalData let rest: { fields: string[]; rows: any[]; } = { fields: [], rows: [] } const tableFieldMaps: Record<string, VxeTableDefines.ColumnInfo> = {} const tableTitleMaps: Record<string, VxeTableDefines.ColumnInfo> = {} tableFullColumn.forEach((column) => { const field = column.field const title = column.getTitle() if (field) { tableFieldMaps[field] = column } if (title) { tableTitleMaps[column.getTitle()] = column } }) const tableConf = { fieldMaps: tableFieldMaps, titleMaps: tableTitleMaps } switch (opts.type) { case 'csv': rest = parseCsv(tableConf, content) break case 'txt': rest = parseTxt(tableConf, content) break case 'html': rest = parseHTML(tableConf, content) break case 'xml': rest = parseXML(tableConf, content) break } const { fields, rows } = rest const status = fields.some(field => tableFieldMaps[field] || tableTitleMaps[field]) if (status) { $xeTable.createData(rows) .then((data: any) => { let loadRest if (opts.mode === 'insert' || opts.mode === 'insertBottom') { loadRest = $xeTable.insertAt(data, -1) } else if (opts.mode === 'insertTop') { loadRest = $xeTable.insert(data) } else { loadRest = $xeTable.reloadData(data) } if (opts.message !== false) { if (VxeUI.modal) { VxeUI.modal.message({ content: getI18n('vxe.table.impSuccess', [rows.length]), status: 'success' }) } } return loadRest.then(() => { if (_importResolve) { _importResolve({ status: true }) } }) }) } else if (opts.message !== false) { if (VxeUI.modal) { VxeUI.modal.message({ content: getI18n('vxe.error.impFields'), status: 'error' }) } if (_importReject) { _importReject({ status: false }) } } } const handleFileImport = (file: File, opts: any) => { const { importMethod, afterImportMethod } = opts const { type, filename } = parseFile(file) const importOpts = computeImportOpts.value // 检查类型,如果为自定义导出,则不需要校验类型 if (!importMethod && !XEUtils.includes(XEUtils.keys(importOpts._typeMaps), type)) { if (opts.message !== false) { if (VxeUI.modal) { VxeUI.modal.message({ content: getI18n('vxe.error.notType', [type]), status: 'error' }) } } const params = { status: false } return Promise.reject(params) } const rest = new Promise((resolve, reject) => { const _importResolve = (params?: any) => { resolve(params) internalData._importResolve = null internalData._importReject = null } const _importReject = (params?: any) => { reject(params) internalData._importResolve = null internalData._importReject = null } internalData._importResolve = _importResolve internalData._importReject = _importReject if (window.FileReader) { const options = Object.assign({ mode: 'insertTop' }, opts, { type, filename }) if (options.remote) { if (importMethod) { Promise.resolve(importMethod({ file, options, $table: $xeTable })).then(() => { _importResolve({ status: true }) }).catch(() => { _importResolve({ status: true }) }) } else { _importResolve({ status: true })