mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-04 12:58:38 +08:00
feat: export-excel 支持自定义导出列 (#3783)
This commit is contained in:
parent
efaab83f65
commit
ad54c29450
@ -1858,6 +1858,61 @@ crud 组件支持通过配置`headerToolbar`和`footerToolbar`属性,实现在
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 自定义导出列
|
||||||
|
|
||||||
|
> 1.8.0 及以上版本
|
||||||
|
|
||||||
|
除了简单隐藏某些列,还可以通过 `exportColumns` 完全控制导出列,比如新增某些列,它的配置项和 `columns` 一致
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "crud",
|
||||||
|
"syncLocation": false,
|
||||||
|
"api": "/api/mock2/sample",
|
||||||
|
"headerToolbar": [{
|
||||||
|
"type": "export-excel",
|
||||||
|
"label": "导出 Excel",
|
||||||
|
"exportColumns": [
|
||||||
|
{
|
||||||
|
"name": "engine",
|
||||||
|
"label": "Engine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tpl",
|
||||||
|
"label": "tpl",
|
||||||
|
"tpl": "${browser}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### 通过 api 导出 Excel
|
#### 通过 api 导出 Excel
|
||||||
|
|
||||||
> 1.1.6 以上版本支持
|
> 1.1.6 以上版本支持
|
||||||
|
@ -26,7 +26,7 @@ export interface PullRefreshProps {
|
|||||||
loadingDuration?: number;
|
loadingDuration?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type statusText = 'normal' | 'pulling' | 'loosing' |'success' | 'loading';
|
type statusText = 'normal' | 'pulling' | 'loosing' | 'success' | 'loading';
|
||||||
|
|
||||||
export interface PullRefreshState {
|
export interface PullRefreshState {
|
||||||
status: statusText;
|
status: statusText;
|
||||||
@ -41,7 +41,6 @@ const defaultProps = {
|
|||||||
const defaultHeaderHeight = 28;
|
const defaultHeaderHeight = 28;
|
||||||
|
|
||||||
const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
translate: __,
|
translate: __,
|
||||||
@ -59,11 +58,11 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
|||||||
|
|
||||||
const touch = useTouch();
|
const touch = useTouch();
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if (props.loading === false) {
|
if (props.loading === false) {
|
||||||
loadSuccess();
|
loadSuccess();
|
||||||
}
|
}
|
||||||
},[props.loading]);
|
}, [props.loading]);
|
||||||
|
|
||||||
const [state, updateState] = useSetState({
|
const [state, updateState] = useSetState({
|
||||||
status: 'normal',
|
status: 'normal',
|
||||||
@ -71,8 +70,12 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
|||||||
} as PullRefreshState);
|
} as PullRefreshState);
|
||||||
|
|
||||||
const isTouchable = () => {
|
const isTouchable = () => {
|
||||||
return !props.disabled && state.status !== 'loading' && state.status !== 'success';
|
return (
|
||||||
}
|
!props.disabled &&
|
||||||
|
state.status !== 'loading' &&
|
||||||
|
state.status !== 'success'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ease = (distance: number) => {
|
const ease = (distance: number) => {
|
||||||
const pullDistance = defaultHeaderHeight;
|
const pullDistance = defaultHeaderHeight;
|
||||||
@ -118,13 +121,13 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onTouchStart = (event: any) => {
|
const onTouchStart = (event: any) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
|
|
||||||
if (isTouchable() && state.offsetY === 0) {
|
if (isTouchable() && state.offsetY === 0) {
|
||||||
touch.start(event);
|
touch.start(event);
|
||||||
updateState({});
|
updateState({});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const onTouchMove = (event: any) => {
|
const onTouchMove = (event: any) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -137,7 +140,7 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const onTouchEnd = (event: any) => {
|
const onTouchEnd = (event: any) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -154,7 +157,7 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
|||||||
setStatus(0);
|
setStatus(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const transformStyle = {
|
const transformStyle = {
|
||||||
transform: `translate3d(0, ${state.offsetY}px, 0)`,
|
transform: `translate3d(0, ${state.offsetY}px, 0)`,
|
||||||
@ -166,7 +169,7 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return props[`${status}Text`] || refreshText[`${status}Text`];
|
return props[`${status}Text`] || refreshText[`${status}Text`];
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -178,7 +181,9 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
|
|||||||
>
|
>
|
||||||
<div className={cx('PullRefresh-wrap')} style={transformStyle}>
|
<div className={cx('PullRefresh-wrap')} style={transformStyle}>
|
||||||
<div className={cx('PullRefresh-header')}>
|
<div className={cx('PullRefresh-header')}>
|
||||||
{state.status === 'loading' && <Icon icon="loading-outline" className="icon loading-icon" />}
|
{state.status === 'loading' && (
|
||||||
|
<Icon icon="loading-outline" className="icon loading-icon" />
|
||||||
|
)}
|
||||||
{getStatusText(state.status)}
|
{getStatusText(state.status)}
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
|
@ -228,7 +228,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||||||
'change',
|
'change',
|
||||||
createObject(data, {
|
createObject(data, {
|
||||||
value: newValue,
|
value: newValue,
|
||||||
options,
|
options
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (rendererEvent?.prevented) {
|
if (rendererEvent?.prevented) {
|
||||||
@ -410,7 +410,9 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||||||
onFocus={(e: any) => this.dispatchEvent('focus', e)}
|
onFocus={(e: any) => this.dispatchEvent('focus', e)}
|
||||||
onAdd={() => this.dispatchEvent('add')}
|
onAdd={() => this.dispatchEvent('add')}
|
||||||
onEdit={(item: any) => this.dispatchEvent('edit', {value: item})}
|
onEdit={(item: any) => this.dispatchEvent('edit', {value: item})}
|
||||||
onDelete={(item: any) => this.dispatchEvent('delete', {value: item})}
|
onDelete={(item: any) =>
|
||||||
|
this.dispatchEvent('delete', {value: item})
|
||||||
|
}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
noResultsText={noResultsText}
|
noResultsText={noResultsText}
|
||||||
renderMenu={menuTpl ? this.renderMenu : undefined}
|
renderMenu={menuTpl ? this.renderMenu : undefined}
|
||||||
|
@ -744,28 +744,30 @@ export default class Page extends React.Component<PageProps> {
|
|||||||
|
|
||||||
const styleVar = buildStyle(style, data);
|
const styleVar = buildStyle(style, data);
|
||||||
|
|
||||||
const pageContent = <div className={cx('Page-content')}>
|
const pageContent = (
|
||||||
<div className={cx('Page-main')}>
|
<div className={cx('Page-content')}>
|
||||||
{this.renderHeader()}
|
<div className={cx('Page-main')}>
|
||||||
<div className={cx(`Page-body`, bodyClassName)}>
|
{this.renderHeader()}
|
||||||
<Spinner size="lg" overlay key="info" show={store.loading} />
|
<div className={cx(`Page-body`, bodyClassName)}>
|
||||||
|
<Spinner size="lg" overlay key="info" show={store.loading} />
|
||||||
|
|
||||||
{store.error && showErrorMsg !== false ? (
|
{store.error && showErrorMsg !== false ? (
|
||||||
<Alert
|
<Alert
|
||||||
level="danger"
|
level="danger"
|
||||||
showCloseButton
|
showCloseButton
|
||||||
onClose={store.clearMessage}
|
onClose={store.clearMessage}
|
||||||
>
|
>
|
||||||
{store.msg}
|
{store.msg}
|
||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{(Array.isArray(regions) ? ~regions.indexOf('body') : body)
|
{(Array.isArray(regions) ? ~regions.indexOf('body') : body)
|
||||||
? render('body', body || '', subProps)
|
? render('body', body || '', subProps)
|
||||||
: null}
|
: null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -799,15 +801,17 @@ export default class Page extends React.Component<PageProps> {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{pullRefresh && !pullRefresh.disabled
|
{pullRefresh && !pullRefresh.disabled ? (
|
||||||
? <PullRefresh
|
<PullRefresh
|
||||||
{...pullRefresh}
|
{...pullRefresh}
|
||||||
translate={__}
|
translate={__}
|
||||||
onRefresh={this.handleRefresh}
|
onRefresh={this.handleRefresh}
|
||||||
>
|
>
|
||||||
{pageContent}
|
{pageContent}
|
||||||
</PullRefresh>
|
</PullRefresh>
|
||||||
: pageContent}
|
) : (
|
||||||
|
pageContent
|
||||||
|
)}
|
||||||
|
|
||||||
{render(
|
{render(
|
||||||
'dialog',
|
'dialog',
|
||||||
|
@ -370,6 +370,7 @@ export interface TableProps extends RendererProps {
|
|||||||
type ExportExcelToolbar = SchemaNode & {
|
type ExportExcelToolbar = SchemaNode & {
|
||||||
api?: SchemaApi;
|
api?: SchemaApi;
|
||||||
columns?: string[];
|
columns?: string[];
|
||||||
|
exportColumns?: any[];
|
||||||
filename?: string;
|
filename?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2261,11 +2262,12 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
classPrefix: ns,
|
classPrefix: ns,
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
translate: __,
|
translate: __,
|
||||||
columns,
|
|
||||||
data,
|
data,
|
||||||
render
|
render
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
let columns = store.filteredColumns || [];
|
||||||
|
|
||||||
if (!columns) {
|
if (!columns) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -2318,19 +2320,28 @@ export default class Table extends React.Component<TableProps, object> {
|
|||||||
});
|
});
|
||||||
worksheet.views = [{state: 'frozen', xSplit: 0, ySplit: 1}];
|
worksheet.views = [{state: 'frozen', xSplit: 0, ySplit: 1}];
|
||||||
|
|
||||||
let exportColumns = toolbar.columns;
|
let exportColumnNames = toolbar.columns;
|
||||||
if (isPureVariable(exportColumns)) {
|
|
||||||
exportColumns = resolveVariableAndFilter(
|
if (isPureVariable(exportColumnNames)) {
|
||||||
exportColumns,
|
exportColumnNames = resolveVariableAndFilter(
|
||||||
|
exportColumnNames,
|
||||||
data,
|
data,
|
||||||
'| raw'
|
'| raw'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredColumns = exportColumns
|
// 自定义导出列配置
|
||||||
|
if (toolbar.exportColumns && Array.isArray(toolbar.exportColumns)) {
|
||||||
|
columns = toolbar.exportColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredColumns = exportColumnNames
|
||||||
? columns.filter(column => {
|
? columns.filter(column => {
|
||||||
const filterColumnsNames = exportColumns!;
|
const filterColumnsNames = exportColumnNames!;
|
||||||
if (filterColumnsNames.indexOf(column.name) !== -1) {
|
if (
|
||||||
|
column.name &&
|
||||||
|
filterColumnsNames.indexOf(column.name) !== -1
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -253,7 +253,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(value => {
|
.then(value => {
|
||||||
this.handleInitEvent(store.data)
|
this.handleInitEvent(store.data);
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
currentStep:
|
currentStep:
|
||||||
@ -340,14 +340,19 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
async dispatchEvent(action: string, value?: object) {
|
async dispatchEvent(action: string, value?: object) {
|
||||||
const {dispatchEvent, data} = this.props;
|
const {dispatchEvent, data} = this.props;
|
||||||
|
|
||||||
const rendererEvent = await dispatchEvent(action, createObject(data, value ? value : {}));
|
const rendererEvent = await dispatchEvent(
|
||||||
|
action,
|
||||||
|
createObject(data, value ? value : {})
|
||||||
|
);
|
||||||
|
|
||||||
return rendererEvent?.prevented ?? false;
|
return rendererEvent?.prevented ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleInitEvent(data: any) {
|
async handleInitEvent(data: any) {
|
||||||
const {onInit} = this.props;
|
const {onInit} = this.props;
|
||||||
(await this.dispatchEvent('inited', {formData: data})) && onInit && onInit(data);
|
(await this.dispatchEvent('inited', {formData: data})) &&
|
||||||
|
onInit &&
|
||||||
|
onInit(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@ -383,8 +388,13 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
index = Math.max(Math.min(steps.length, index), 1);
|
index = Math.max(Math.min(steps.length, index), 1);
|
||||||
|
|
||||||
if (index != this.state.currentStep) {
|
if (index != this.state.currentStep) {
|
||||||
if (await this.dispatchEvent('stepChange', {step: index, formData: this.props.store.data})) {
|
if (
|
||||||
return
|
await this.dispatchEvent('stepChange', {
|
||||||
|
step: index,
|
||||||
|
formData: this.props.store.data
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -537,7 +547,11 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
) {
|
) {
|
||||||
const {onAction, store, env, steps} = this.props;
|
const {onAction, store, env, steps} = this.props;
|
||||||
|
|
||||||
if (action.actionType === 'next' || action.type === 'submit' || action.actionType === 'step-submit') {
|
if (
|
||||||
|
action.actionType === 'next' ||
|
||||||
|
action.type === 'submit' ||
|
||||||
|
action.actionType === 'step-submit'
|
||||||
|
) {
|
||||||
this.form.doAction(
|
this.form.doAction(
|
||||||
{
|
{
|
||||||
...action,
|
...action,
|
||||||
@ -593,11 +607,15 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
} else if (action.actionType === 'goto-step') {
|
} else if (action.actionType === 'goto-step') {
|
||||||
const targetStep = (data as any).step;
|
const targetStep = (data as any).step;
|
||||||
|
|
||||||
if (targetStep !== undefined && targetStep <= steps.length && targetStep >= 0) {
|
if (
|
||||||
|
targetStep !== undefined &&
|
||||||
|
targetStep <= steps.length &&
|
||||||
|
targetStep >= 0
|
||||||
|
) {
|
||||||
this.gotoStep((data as any).step);
|
this.gotoStep((data as any).step);
|
||||||
}
|
}
|
||||||
} else if (action.actionType === 'submit') {
|
} else if (action.actionType === 'submit') {
|
||||||
this.finalSubmit()
|
this.finalSubmit();
|
||||||
} else if (onAction) {
|
} else if (onAction) {
|
||||||
onAction(e, action, data, throwErrors, delegate || this.context);
|
onAction(e, action, data, throwErrors, delegate || this.context);
|
||||||
}
|
}
|
||||||
@ -630,14 +648,13 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
async handleChange(values: object) {
|
async handleChange(values: object) {
|
||||||
const {store} = this.props;
|
const {store} = this.props;
|
||||||
|
|
||||||
const previous = store.data;
|
const previous = store.data;
|
||||||
const final = {...previous, ...values};
|
const final = {...previous, ...values};
|
||||||
|
|
||||||
if (await this.dispatchEvent('change', {formData: final})) {
|
if (await this.dispatchEvent('change', {formData: final})) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
store.updateData(values);
|
store.updateData(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,7 +702,6 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
const step = steps[this.state.currentStep - 1];
|
const step = steps[this.state.currentStep - 1];
|
||||||
store.updateData(values);
|
store.updateData(values);
|
||||||
|
|
||||||
|
|
||||||
// 最后一步
|
// 最后一步
|
||||||
if (target) {
|
if (target) {
|
||||||
this.submitToTarget(target, store.data);
|
this.submitToTarget(target, store.data);
|
||||||
@ -766,7 +782,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
return value;
|
return value;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.dispatchEvent('submitFail', {error})
|
this.dispatchEvent('submitFail', {error});
|
||||||
store.markSaving(false);
|
store.markSaving(false);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
@ -779,12 +795,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
// 接管里面 form 的提交,不能直接让 form 提交,因为 wizard 自己需要知道进度。
|
// 接管里面 form 的提交,不能直接让 form 提交,因为 wizard 自己需要知道进度。
|
||||||
@autobind
|
@autobind
|
||||||
async handleSubmit(values: object, action: Action) {
|
async handleSubmit(values: object, action: Action) {
|
||||||
const {
|
const {store, steps, finishedField} = this.props;
|
||||||
store,
|
|
||||||
steps,
|
|
||||||
finishedField
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
|
|
||||||
if (this.state.currentStep < steps.length) {
|
if (this.state.currentStep < steps.length) {
|
||||||
const step = steps[this.state.currentStep - 1];
|
const step = steps[this.state.currentStep - 1];
|
||||||
|
@ -127,14 +127,14 @@ export function calculatePosition(
|
|||||||
atX === 'left'
|
atX === 'left'
|
||||||
? childOffset.left
|
? childOffset.left
|
||||||
: atX === 'right'
|
: atX === 'right'
|
||||||
? childOffset.left + childOffset.width
|
? childOffset.left + childOffset.width
|
||||||
: childOffset.left + childOffset.width / 2;
|
: childOffset.left + childOffset.width / 2;
|
||||||
positionTop =
|
positionTop =
|
||||||
atY === 'top'
|
atY === 'top'
|
||||||
? childOffset.top
|
? childOffset.top
|
||||||
: atY === 'bottom'
|
: atY === 'bottom'
|
||||||
? childOffset.top + childOffset.height
|
? childOffset.top + childOffset.height
|
||||||
: childOffset.top + childOffset.height / 2;
|
: childOffset.top + childOffset.height / 2;
|
||||||
|
|
||||||
positionLeft -=
|
positionLeft -=
|
||||||
myX === 'left' ? 0 : myX === 'right' ? overlayWidth : overlayWidth / 2;
|
myX === 'left' ? 0 : myX === 'right' ? overlayWidth : overlayWidth / 2;
|
||||||
@ -142,8 +142,8 @@ export function calculatePosition(
|
|||||||
myY === 'top'
|
myY === 'top'
|
||||||
? 0
|
? 0
|
||||||
: myY === 'bottom'
|
: myY === 'bottom'
|
||||||
? overlayHeight
|
? overlayHeight
|
||||||
: overlayHeight / 2;
|
: overlayHeight / 2;
|
||||||
|
|
||||||
// 如果还有其他可选项,则做位置判断,是否在可视区域,不完全在则继续看其他定位情况。
|
// 如果还有其他可选项,则做位置判断,是否在可视区域,不完全在则继续看其他定位情况。
|
||||||
if (tests.length) {
|
if (tests.length) {
|
||||||
|
Loading…
Reference in New Issue
Block a user