mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:58:05 +08:00
parent
1885b3390b
commit
54c13b9809
126
docs/zh-CN/components/form/input-excel.md
Normal file
126
docs/zh-CN/components/form/input-excel.md
Normal file
@ -0,0 +1,126 @@
|
||||
---
|
||||
title: InputExcel 解析 Excel
|
||||
description:
|
||||
type: 0
|
||||
group: null
|
||||
menuName: InputExcel
|
||||
icon:
|
||||
order: 14
|
||||
---
|
||||
|
||||
这个组件是通过前端对 Excel 进行解析,将结果作为表单项,使用它有两个好处:
|
||||
|
||||
1. 节省后端开发成本,无需再次解析 Excel
|
||||
2. 可以前端实时预览效果,比如配合 input-table 组件进行二次修改
|
||||
|
||||
## 基本使用
|
||||
|
||||
默认情况下只解析第一个 sheet 的内容,下面的例子中,选择上传文件后,就能知道最终会解析成什么数据
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "input-excel",
|
||||
"name": "excel",
|
||||
"label": "上传 Excel"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
默认模式是解析成对象数组,将第一行作为对象里的键,可以上传一个类似这样的 Excel 内容试试
|
||||
|
||||
```
|
||||
|名称|网址|
|
||||
|amis|https://baidu.gitee.io/amis|
|
||||
|百度|https://www.baidu.com|
|
||||
```
|
||||
|
||||
解析后的的数据格式将会是
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"名称": "amis",
|
||||
"网址": "https://baidu.gitee.io/amis"
|
||||
},
|
||||
{
|
||||
"名称": "百度",
|
||||
"网址": "https://www.baidu.com"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 二维数组模式
|
||||
|
||||
除了默认配置的对象数组格式,还可以使用二维数组方式,方法是设置 `"parseMode": "array"`
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "input-excel",
|
||||
"name": "excel",
|
||||
"parseMode": "array",
|
||||
"label": "上传 Excel"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
如果是前面的例子,解析结果将会是
|
||||
|
||||
```json
|
||||
[
|
||||
["名称", "网址"],
|
||||
["amis", "https://baidu.gitee.io/amis"],
|
||||
["百度", "https://www.baidu.com"]
|
||||
]
|
||||
```
|
||||
|
||||
## 解析多个 sheet
|
||||
|
||||
默认配置只解析第一个 sheet,如果要解析多个 sheet,可以通过 `"allSheets": true` 开启多个 sheet 的读取,这时的数据会增加一个层级。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "/api/mock2/form/saveForm",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "input-excel",
|
||||
"name": "excel",
|
||||
"allSheets": true,
|
||||
"label": "上传 Excel"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
如果按之前的例子,结果将会是
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"sheetName": "Sheet1",
|
||||
"data": [
|
||||
{
|
||||
"名称": "amis",
|
||||
"网址": "https://baidu.gitee.io/amis"
|
||||
},
|
||||
{
|
||||
"名称": "百度",
|
||||
"网址": "https://www.baidu.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
@ -383,9 +383,9 @@ export const components = [
|
||||
path: '/zh-CN/components/form/input-datetime-range',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/zh-CN/components/form/input-datetime-range.md').then(
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
import(
|
||||
'../../docs/zh-CN/components/form/input-datetime-range.md'
|
||||
).then(makeMarkdownRenderer)
|
||||
},
|
||||
{
|
||||
label: 'InputMonthRange 月份范围',
|
||||
@ -423,6 +423,15 @@ export const components = [
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'InputExcel Excel 解析',
|
||||
path: '/zh-CN/components/form/input-excel',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/zh-CN/components/form/input-excel.md').then(
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'InputFile 文件上传',
|
||||
path: '/zh-CN/components/form/input-file',
|
||||
|
42
scss/components/form/_excel.scss
Normal file
42
scss/components/form/_excel.scss
Normal file
@ -0,0 +1,42 @@
|
||||
// Excel 上传的配置,基于 https://react-dropzone.js.org/
|
||||
|
||||
.#{$ns}ExcelControl {
|
||||
&-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: sans-serif;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-container > p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&-container > em {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
&-dropzone {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--gap-md);
|
||||
border-width: 2px;
|
||||
border-radius: 2px;
|
||||
border-color: #eeeeee;
|
||||
border-style: dashed;
|
||||
background-color: #fafafa;
|
||||
color: #bdbdbd;
|
||||
outline: none;
|
||||
transition: border 0.24s ease-in-out;
|
||||
}
|
||||
|
||||
&-dropzone:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
&-dropzone.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
@ -86,6 +86,7 @@
|
||||
@import '../components/form/image';
|
||||
|
||||
@import '../components/form/file';
|
||||
@import '../components/form/excel';
|
||||
@import '../components/form/editor';
|
||||
@import '../components/form/rich-text';
|
||||
@import '../components/form/tinymce';
|
||||
|
@ -221,6 +221,7 @@ export type SchemaType =
|
||||
| 'input-date-range'
|
||||
| 'input-time-range'
|
||||
| 'input-datetime-range'
|
||||
| 'input-excel'
|
||||
| 'diff-editor'
|
||||
|
||||
// editor 系列
|
||||
|
@ -103,6 +103,7 @@ import './renderers/Form/InputArray';
|
||||
import './renderers/Form/Combo';
|
||||
import './renderers/Form/ConditionBuilder';
|
||||
import './renderers/Form/InputSubForm';
|
||||
import './renderers/Form/InputExcel';
|
||||
import './renderers/Form/InputRichText';
|
||||
import './renderers/Form/Editor';
|
||||
import './renderers/Form/DiffEditor';
|
||||
|
@ -80,6 +80,7 @@ register('en-US', {
|
||||
'Dialog.close': 'Close',
|
||||
'Embed.invalidRoot': 'Invalid root selector',
|
||||
'Embed.downloading': 'Start downloading',
|
||||
'Excel.placeholder': `Drag 'n' drop excel here, or click to select`,
|
||||
'fetchFailed': 'Fetch api failed',
|
||||
'File.continueAdd': 'Continue add',
|
||||
'File.dragDrop': `Drag 'n' drop some files here`,
|
||||
|
@ -82,6 +82,7 @@ register('zh-CN', {
|
||||
'Dialog.close': '关闭',
|
||||
'Embed.invalidRoot': '选择器不对,页面上没有此元素',
|
||||
'Embed.downloading': '文件即将开始下载。。',
|
||||
'Excel.placeholder': '拖拽 Excel 到这,或点击上传',
|
||||
'fetchFailed': '初始化失败',
|
||||
'File.continueAdd': '继续添加',
|
||||
'File.dragDrop': '将文件拖拽到此处',
|
||||
|
156
src/renderers/Form/InputExcel.tsx
Normal file
156
src/renderers/Form/InputExcel.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import React, {Suspense} from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import {FileRejection} from 'react-dropzone';
|
||||
import {autobind} from '../../utils/helper';
|
||||
import {FormItem, FormControlProps, FormBaseControl} from './Item';
|
||||
|
||||
/**
|
||||
* Excel 解析
|
||||
* 文档:https://baidu.gitee.io/amis/docs/components/form/input-excel
|
||||
*/
|
||||
export interface InputExcelControlSchema extends FormBaseControl {
|
||||
/**
|
||||
* 指定为 Excel 解析
|
||||
*/
|
||||
type: 'input-excel';
|
||||
|
||||
/**
|
||||
* 是否解析所有 sheet,默认情况下只解析第一个
|
||||
*/
|
||||
allSheets: boolean;
|
||||
|
||||
/**
|
||||
* 解析模式,array 是解析成二维数组,object 是将第一列作为字段名,解析为对象数组
|
||||
*/
|
||||
parseMode: 'array' | 'object';
|
||||
|
||||
/**
|
||||
* 是否包含空内容,主要用于二维数组模式
|
||||
*/
|
||||
includeEmpty: boolean;
|
||||
}
|
||||
|
||||
export interface ExcelProps
|
||||
extends FormControlProps,
|
||||
Omit<
|
||||
InputExcelControlSchema,
|
||||
'type' | 'className' | 'descriptionClassName' | 'inputClassName'
|
||||
> {}
|
||||
|
||||
export interface ExcelControlState {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export default class ExcelControl extends React.PureComponent<
|
||||
ExcelProps,
|
||||
ExcelControlState
|
||||
> {
|
||||
static defaultProps: Partial<ExcelProps> = {
|
||||
allSheets: false,
|
||||
parseMode: 'object',
|
||||
includeEmpty: true
|
||||
};
|
||||
state: ExcelControlState = {
|
||||
open: false
|
||||
};
|
||||
|
||||
@autobind
|
||||
handleDrop(files: File[]) {
|
||||
const {allSheets, onChange} = this.props;
|
||||
const excel = files[0];
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(excel);
|
||||
reader.onload = async () => {
|
||||
if (reader.result) {
|
||||
import('exceljs').then(async (ExcelJS: any) => {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.load(reader.result);
|
||||
if (allSheets) {
|
||||
const sheetsResult: any[] = [];
|
||||
workbook.eachSheet((worksheet: any) => {
|
||||
sheetsResult.push({
|
||||
sheetName: worksheet.name,
|
||||
data: this.readWorksheet(worksheet)
|
||||
});
|
||||
onChange(sheetsResult);
|
||||
});
|
||||
} else {
|
||||
const worksheet = workbook.worksheets[0];
|
||||
onChange(this.readWorksheet(worksheet));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取单个 sheet 的内容
|
||||
*/
|
||||
readWorksheet(worksheet: any) {
|
||||
const result: any[] = [];
|
||||
const {parseMode} = this.props;
|
||||
|
||||
if (parseMode === 'array') {
|
||||
worksheet.eachRow((row: any, rowNumber: number) => {
|
||||
const values = row.values;
|
||||
values.shift(); // excel 返回的值是从 1 开始的,0 节点永远是 null
|
||||
result.push(values);
|
||||
});
|
||||
return result;
|
||||
} else {
|
||||
let firstRowValues: any[] = [];
|
||||
worksheet.eachRow((row: any, rowNumber: number) => {
|
||||
// 将第一列作为字段名
|
||||
if (rowNumber == 1) {
|
||||
firstRowValues = row.values;
|
||||
} else {
|
||||
const data: any = {};
|
||||
row.eachCell((cell: any, colNumber: any) => {
|
||||
if (firstRowValues[colNumber]) {
|
||||
data[firstRowValues[colNumber]] = cell.value;
|
||||
}
|
||||
});
|
||||
result.push(data);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
classnames: cx,
|
||||
classPrefix: ns,
|
||||
value,
|
||||
disabled,
|
||||
translate: __
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={cx('ExcelControl', className)}>
|
||||
<Dropzone
|
||||
key="drop-zone"
|
||||
onDrop={this.handleDrop}
|
||||
accept=".xlsx"
|
||||
multiple={false}
|
||||
disabled={disabled}
|
||||
>
|
||||
{({getRootProps, getInputProps}) => (
|
||||
<section className={cx('ExcelControl-container', className)}>
|
||||
<div {...getRootProps({className: cx('ExcelControl-dropzone')})}>
|
||||
<input {...getInputProps()} />
|
||||
<p>{__('Excel.placeholder')}</p>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</Dropzone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@FormItem({
|
||||
type: 'input-excel'
|
||||
})
|
||||
export class ExcelControlRenderer extends ExcelControl {}
|
Loading…
Reference in New Issue
Block a user