diff --git a/examples/components/CRUD/ExportCSVExcel.jsx b/examples/components/CRUD/ExportCSVExcel.jsx index b21785e50..6a6d5330f 100644 --- a/examples/components/CRUD/ExportCSVExcel.jsx +++ b/examples/components/CRUD/ExportCSVExcel.jsx @@ -158,7 +158,8 @@ export default { }, { name: 'engine.name', - label: '引擎' + label: '引擎', + className: 'text-primary' }, { name: 'browser', @@ -170,9 +171,11 @@ export default { }, { name: 'engine.version', - label: 'CSS版本', + label: '引擎版本', type: 'tpl', - tpl: '${engine.version}' + tpl: '${engine.version}', + classNameExpr: + "<%= data.engine.version > 4 ? 'bg-green-100' : 'bg-red-50' %>" }, { name: 'grade', diff --git a/packages/amis/src/renderers/Table/exportExcel.ts b/packages/amis/src/renderers/Table/exportExcel.ts index 4250daaea..369542287 100644 --- a/packages/amis/src/renderers/Table/exportExcel.ts +++ b/packages/amis/src/renderers/Table/exportExcel.ts @@ -15,10 +15,8 @@ import { import {isPureVariable, resolveVariableAndFilter} from 'amis-core'; import {BaseSchema} from '../../Schema'; import {toDataURL, getImageDimensions} from 'amis-core'; -import {TplSchema} from '../Tpl'; -import {MappingSchema} from '../Mapping'; +import memoize from 'lodash/memoize'; import {getSnapshot} from 'mobx-state-tree'; -import {DateSchema} from '../Date'; import moment from 'moment'; import type {TableProps, ExportExcelToolbar} from './index'; @@ -38,6 +36,123 @@ const getAbsoluteUrl = (function () { }; })(); +interface CellStyleFont { + name?: string; + color?: {argb: string}; + underline?: boolean; + bold?: boolean; + italic?: boolean; +} + +interface CellStyleFill { + type?: string; + pattern?: string; + fgColor?: {argb: string}; +} + +interface CellStyle { + font?: CellStyleFont; + fill?: CellStyleFill; +} + +/** + * 将 computedStyle 的 rgba 转成 argb hex + */ +const rgba2argb = memoize((rgba: string) => { + const color = `${rgba + .match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/)! + .slice(1) + .map((n, i) => + (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)) + .toString(16) + .padStart(2, '0') + .replace('NaN', '') + ) + .join('')}`; + if (color.length === 6) { + return 'FF' + color; + } + return color; +}); + +/** + * 将 classname 转成对应的 excel 样式,只支持字体颜色、粗细、背景色 + */ +const getCellStyleByClassName = memoize((className: string): CellStyle => { + if (!className) return {}; + const classNameElm = document.getElementsByClassName(className).item(0); + if (classNameElm) { + const computedStyle = getComputedStyle(classNameElm); + const font: CellStyleFont = {}; + let fill: CellStyleFill = {}; + if (computedStyle.color && computedStyle.color.indexOf('rgb') !== -1) { + const color = rgba2argb(computedStyle.color); + // 似乎不支持完全透明的情况,所以就不设置 + if (!color.startsWith('00')) { + font['color'] = {argb: color}; + } + } + if (computedStyle.fontWeight && parseInt(computedStyle.fontWeight) >= 700) { + font['bold'] = true; + } + if ( + computedStyle.backgroundColor && + computedStyle.backgroundColor.indexOf('rgb') !== -1 + ) { + const color = rgba2argb(computedStyle.backgroundColor); + if (!color.startsWith('00')) { + fill = { + type: 'pattern', + pattern: 'solid', + fgColor: {argb: color} + }; + } + } + + return {font, fill}; + } + return {}; +}); + +/** + * 设置单元格样式 + */ +const applyCellStyle = ( + sheetRow: any, + columIndex: number, + schema: any, + data: any +) => { + let cellStyle: CellStyle = {}; + if (schema.className) { + for (const className of schema.className.split(/\s+/)) { + const style = getCellStyleByClassName(className); + if (style) { + cellStyle = {...cellStyle, ...style}; + } + } + } + + if (schema.classNameExpr) { + const classNames = filter(schema.classNameExpr, data); + if (classNames) { + for (const className of classNames.split(/\s+/)) { + const style = getCellStyleByClassName(className); + if (style) { + cellStyle = {...cellStyle, ...style}; + } + } + } + } + + if (cellStyle.font && Object.keys(cellStyle.font).length > 0) { + sheetRow.getCell(columIndex).font = cellStyle.font; + } + if (cellStyle.fill && Object.keys(cellStyle.fill).length > 0) { + sheetRow.getCell(columIndex).fill = cellStyle.fill; + } +}; + export async function exportExcel( ExcelJS: any, props: TableProps, @@ -173,6 +288,8 @@ export async function exportExcel( } } + applyCellStyle(sheetRow, columIndex, column.pristine, rowData); + const type = (column as BaseSchema).type || 'plain'; // TODO: 这里很多组件都是拷贝对应渲染的逻辑实现的,导致每种都得实现一遍 if ((type === 'image' || (type as any) === 'static-image') && value) {