feat: export-excel 支持自定义导出列 (#3783)

This commit is contained in:
吴多益 2022-03-17 12:33:28 +08:00 committed by GitHub
parent efaab83f65
commit ad54c29450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 161 additions and 73 deletions

View File

@ -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 以上版本支持

View File

@ -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}

View File

@ -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}

View File

@ -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',

View File

@ -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;

View File

@ -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];

View File

@ -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) {