mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
* feat: CRUD 支持导出 Excel 模板 Closes #9157 * 更新版本
This commit is contained in:
parent
eb5299e645
commit
eb55199ef1
@ -2743,6 +2743,49 @@ interface CRUDMatchFunc {
|
||||
"filename": "自定义文件名${test}",
|
||||
"api": "/api/mock2/sample"
|
||||
}],
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Rendering engine"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "Browser"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"label": "Platform(s)"
|
||||
},
|
||||
{
|
||||
"name": "version",
|
||||
"label": "Engine version"
|
||||
},
|
||||
{
|
||||
"name": "grade",
|
||||
"label": "CSS grade"
|
||||
}
|
||||
]`
|
||||
}
|
||||
```
|
||||
|
||||
### 导出 Excel 模板
|
||||
|
||||
> 6.1 及以上版本
|
||||
|
||||
配置是 `export-excel-template` 和前面 `export-excel` 不同,这个功能只导出表头,主要用于线下填数据,可以配合 input-excel 组件来上传填好的内容。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "crud",
|
||||
"syncLocation": false,
|
||||
"headerToolbar": [{
|
||||
"type": "export-excel-template",
|
||||
"label": "导出 Excel 模板",
|
||||
}],
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -2,7 +2,7 @@ export default {
|
||||
title: 'CSV 导出的是原始数据,而 Excel 是尽可能还原展现效果',
|
||||
body: {
|
||||
type: 'crud',
|
||||
headerToolbar: ['export-excel', 'export-csv'],
|
||||
headerToolbar: ['export-excel', 'export-excel-template', 'export-csv'],
|
||||
data: {
|
||||
mapping_type: {
|
||||
'*': '其他'
|
||||
|
@ -38,6 +38,7 @@ register('de-DE', {
|
||||
'Copyable.tip': 'Kopieren',
|
||||
'CRUD.exportCSV': 'In CSV exportieren',
|
||||
'CRUD.exportExcel': 'In Excel exportieren',
|
||||
'CRUD.exportExcelTemplate': 'In Excel-Vorlage exportieren',
|
||||
'CRUD.fetchFailed': 'Fehler beim Abrufen',
|
||||
'CRUD.filter': 'Filtern',
|
||||
'CRUD.selected': 'Ausgewählte {{total}} Elemente: ',
|
||||
|
@ -33,6 +33,7 @@ register('en-US', {
|
||||
'Copyable.tip': 'Copy',
|
||||
'CRUD.exportCSV': 'Export CSV',
|
||||
'CRUD.exportExcel': 'Export Excel',
|
||||
'CRUD.exportExcelTemplate': 'Export Excel Template',
|
||||
'CRUD.fetchFailed': 'Fetch failed',
|
||||
'CRUD.filter': 'Filter',
|
||||
'CRUD.selected': 'selected {{total}} items: ',
|
||||
|
@ -36,6 +36,7 @@ register('zh-CN', {
|
||||
'Copyable.tip': '点击复制',
|
||||
'CRUD.exportCSV': '导出 CSV',
|
||||
'CRUD.exportExcel': '导出 Excel',
|
||||
'CRUD.exportExcelTemplate': '导出 Excel 模板',
|
||||
'CRUD.fetchFailed': '获取失败',
|
||||
'CRUD.filter': '筛选',
|
||||
'CRUD.selected': '已选{{total}}条:',
|
||||
|
@ -46,7 +46,7 @@
|
||||
"echarts": "5.4.0",
|
||||
"echarts-stat": "^1.2.0",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"exceljs": "^4.3.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"hls.js": "1.1.3",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
@ -244,4 +244,4 @@
|
||||
"react-dom": ">=16.8.6"
|
||||
},
|
||||
"gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4"
|
||||
}
|
||||
}
|
@ -206,10 +206,56 @@ const renderSummary = (
|
||||
return rowIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取 map 的映射数据
|
||||
* @param remoteMappingCache 缓存
|
||||
* @param env mobx env
|
||||
* @param column 列配置
|
||||
* @param data 上下文数据
|
||||
* @param rowData 当前行数据
|
||||
* @returns
|
||||
*/
|
||||
async function getMap(
|
||||
remoteMappingCache: any,
|
||||
env: any,
|
||||
column: any,
|
||||
data: any,
|
||||
rowData: any
|
||||
) {
|
||||
let map = column.pristine.map as Record<string, any>;
|
||||
const source = column.pristine.source;
|
||||
if (source) {
|
||||
let sourceValue = source;
|
||||
if (isPureVariable(source)) {
|
||||
map = resolveVariableAndFilter(source as string, rowData, '| raw');
|
||||
} else if (isEffectiveApi(source, data)) {
|
||||
const mapKey = JSON.stringify(source);
|
||||
if (mapKey in remoteMappingCache) {
|
||||
map = remoteMappingCache[mapKey];
|
||||
} else {
|
||||
const res = await env.fetcher(sourceValue, rowData);
|
||||
if (res.data) {
|
||||
remoteMappingCache[mapKey] = res.data;
|
||||
map = res.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 Excel
|
||||
* @param ExcelJS ExcelJS 对象
|
||||
* @param props Table 组件的 props
|
||||
* @param toolbar 导出 Excel 的 toolbar 配置
|
||||
* @param withoutData 如果为 true 就不导出数据,只导出表头
|
||||
*/
|
||||
export async function exportExcel(
|
||||
ExcelJS: any,
|
||||
props: TableProps,
|
||||
toolbar: ExportExcelToolbar
|
||||
toolbar: ExportExcelToolbar,
|
||||
withoutData: boolean = false
|
||||
) {
|
||||
const {
|
||||
store,
|
||||
@ -333,6 +379,17 @@ export async function exportExcel(
|
||||
column: firstRowLabels.length
|
||||
}
|
||||
};
|
||||
|
||||
if (withoutData) {
|
||||
return exportExcelWithoutData(
|
||||
workbook,
|
||||
worksheet,
|
||||
filteredColumns,
|
||||
filename,
|
||||
env,
|
||||
data
|
||||
);
|
||||
}
|
||||
// 用于 mapping source 的情况
|
||||
const remoteMappingCache: any = {};
|
||||
// 数据从第二行开始
|
||||
@ -443,27 +500,10 @@ export async function exportExcel(
|
||||
hyperlink: absoluteURL
|
||||
};
|
||||
} else if (type === 'mapping' || (type as any) === 'static-mapping') {
|
||||
let map = column.pristine.map;
|
||||
let map = await getMap(remoteMappingCache, env, column, data, rowData);
|
||||
|
||||
const valueField = column.pristine.valueField || 'value';
|
||||
const labelField = column.pristine.labelField || 'label';
|
||||
const source = column.pristine.source;
|
||||
if (source) {
|
||||
let sourceValue = source;
|
||||
if (isPureVariable(source)) {
|
||||
map = resolveVariableAndFilter(source as string, rowData, '| raw');
|
||||
} else if (isEffectiveApi(source, data)) {
|
||||
const mapKey = JSON.stringify(source);
|
||||
if (mapKey in remoteMappingCache) {
|
||||
map = remoteMappingCache[mapKey];
|
||||
} else {
|
||||
const res = await env.fetcher(sourceValue, rowData);
|
||||
if (res.data) {
|
||||
remoteMappingCache[mapKey] = res.data;
|
||||
map = res.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(map)) {
|
||||
map = map.reduce((res, now) => {
|
||||
@ -575,6 +615,10 @@ export async function exportExcel(
|
||||
// 后置总结行
|
||||
renderSummary(worksheet, data, affixRow, rowIndex);
|
||||
|
||||
downloadFile(workbook, filename);
|
||||
}
|
||||
|
||||
async function downloadFile(workbook: any, filename: string) {
|
||||
const buffer = await workbook.xlsx.writeBuffer();
|
||||
|
||||
if (buffer) {
|
||||
@ -584,3 +628,47 @@ export async function exportExcel(
|
||||
saveAs(blob, filename + '.xlsx');
|
||||
}
|
||||
}
|
||||
|
||||
function numberToLetters(num: number) {
|
||||
let letters = '';
|
||||
while (num >= 0) {
|
||||
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[num % 26] + letters;
|
||||
num = Math.floor(num / 26) - 1;
|
||||
}
|
||||
return letters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 只导出表头
|
||||
*/
|
||||
async function exportExcelWithoutData(
|
||||
workbook: any,
|
||||
worksheet: any,
|
||||
filteredColumns: any[],
|
||||
filename: string,
|
||||
env: any,
|
||||
data: any
|
||||
) {
|
||||
let index = 0;
|
||||
const rowNumber = 100;
|
||||
const mapCache: any = {};
|
||||
for (const column of filteredColumns) {
|
||||
index += 1;
|
||||
if (column.pristine?.type === 'mapping') {
|
||||
const map = await getMap(mapCache, env, column, data, {});
|
||||
if (map && isObject(map)) {
|
||||
const keys = Object.keys(map);
|
||||
for (let rowIndex = 1; rowIndex < rowNumber; rowIndex++) {
|
||||
worksheet.getCell(numberToLetters(index) + rowIndex).dataValidation =
|
||||
{
|
||||
type: 'list',
|
||||
allowBlank: true,
|
||||
formulae: [`"${keys.join(',')}"`]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
downloadFile(workbook, filename);
|
||||
}
|
||||
|
@ -2215,6 +2215,9 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
} else if (type === 'export-excel') {
|
||||
this.renderedToolbars.push(type);
|
||||
return this.renderExportExcel(toolbar);
|
||||
} else if (type === 'export-excel-template') {
|
||||
this.renderedToolbars.push(type);
|
||||
return this.renderExportExcelTemplate(toolbar);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
@ -2377,15 +2380,7 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
}
|
||||
|
||||
renderExportExcel(toolbar: ExportExcelToolbar) {
|
||||
const {
|
||||
store,
|
||||
env,
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
translate: __,
|
||||
data,
|
||||
render
|
||||
} = this.props;
|
||||
const {store, translate: __, render} = this.props;
|
||||
let columns = store.filteredColumns || [];
|
||||
|
||||
if (!columns) {
|
||||
@ -2418,6 +2413,38 @@ export default class Table extends React.Component<TableProps, object> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 Excel 模板
|
||||
*/
|
||||
renderExportExcelTemplate(toolbar: ExportExcelToolbar) {
|
||||
const {store, translate: __, render} = this.props;
|
||||
let columns = store.filteredColumns || [];
|
||||
|
||||
if (!columns) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return render(
|
||||
'exportExcelTemplate',
|
||||
{
|
||||
label: __('CRUD.exportExcelTemplate'),
|
||||
...(toolbar as any),
|
||||
type: 'button'
|
||||
},
|
||||
{
|
||||
onAction: () => {
|
||||
import('exceljs').then(async (ExcelJS: any) => {
|
||||
try {
|
||||
await exportExcel(ExcelJS, this.props, toolbar, true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
renderActions(region: string) {
|
||||
let {actions, render, store, classnames: cx, data} = this.props;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user