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

View File

@ -26,7 +26,7 @@ export interface PullRefreshProps {
loadingDuration?: number;
}
type statusText = 'normal' | 'pulling' | 'loosing' |'success' | 'loading';
type statusText = 'normal' | 'pulling' | 'loosing' | 'success' | 'loading';
export interface PullRefreshState {
status: statusText;
@ -41,7 +41,6 @@ const defaultProps = {
const defaultHeaderHeight = 28;
const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
const {
classnames: cx,
translate: __,
@ -59,11 +58,11 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
const touch = useTouch();
useEffect(()=>{
useEffect(() => {
if (props.loading === false) {
loadSuccess();
}
},[props.loading]);
}, [props.loading]);
const [state, updateState] = useSetState({
status: 'normal',
@ -71,8 +70,12 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
} as PullRefreshState);
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 pullDistance = defaultHeaderHeight;
@ -118,13 +121,13 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
};
const onTouchStart = (event: any) => {
event.stopPropagation()
event.stopPropagation();
if (isTouchable() && state.offsetY === 0) {
touch.start(event);
updateState({});
}
}
};
const onTouchMove = (event: any) => {
event.stopPropagation();
@ -137,7 +140,7 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
}
}
return false;
}
};
const onTouchEnd = (event: any) => {
event.stopPropagation();
@ -154,7 +157,7 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
setStatus(0);
}
}
}
};
const transformStyle = {
transform: `translate3d(0, ${state.offsetY}px, 0)`,
@ -166,7 +169,7 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
return '';
}
return props[`${status}Text`] || refreshText[`${status}Text`];
}
};
return (
<div
@ -178,7 +181,9 @@ const PullRefresh = forwardRef<{}, PullRefreshProps>((props, ref) => {
>
<div className={cx('PullRefresh-wrap')} style={transformStyle}>
<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)}
</div>
{children}

View File

@ -228,7 +228,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
'change',
createObject(data, {
value: newValue,
options,
options
})
);
if (rendererEvent?.prevented) {
@ -410,7 +410,9 @@ export default class SelectControl extends React.Component<SelectProps, any> {
onFocus={(e: any) => this.dispatchEvent('focus', e)}
onAdd={() => this.dispatchEvent('add')}
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}
noResultsText={noResultsText}
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 pageContent = <div className={cx('Page-content')}>
<div className={cx('Page-main')}>
{this.renderHeader()}
<div className={cx(`Page-body`, bodyClassName)}>
<Spinner size="lg" overlay key="info" show={store.loading} />
const pageContent = (
<div className={cx('Page-content')}>
<div className={cx('Page-main')}>
{this.renderHeader()}
<div className={cx(`Page-body`, bodyClassName)}>
<Spinner size="lg" overlay key="info" show={store.loading} />
{store.error && showErrorMsg !== false ? (
<Alert
level="danger"
showCloseButton
onClose={store.clearMessage}
>
{store.msg}
</Alert>
) : null}
{store.error && showErrorMsg !== false ? (
<Alert
level="danger"
showCloseButton
onClose={store.clearMessage}
>
{store.msg}
</Alert>
) : null}
{(Array.isArray(regions) ? ~regions.indexOf('body') : body)
? render('body', body || '', subProps)
: null}
{(Array.isArray(regions) ? ~regions.indexOf('body') : body)
? render('body', body || '', subProps)
: null}
</div>
</div>
</div>
</div>;
);
return (
<div
@ -799,15 +801,17 @@ export default class Page extends React.Component<PageProps> {
</div>
) : null}
{pullRefresh && !pullRefresh.disabled
? <PullRefresh
{...pullRefresh}
translate={__}
onRefresh={this.handleRefresh}
>
{pageContent}
</PullRefresh>
: pageContent}
{pullRefresh && !pullRefresh.disabled ? (
<PullRefresh
{...pullRefresh}
translate={__}
onRefresh={this.handleRefresh}
>
{pageContent}
</PullRefresh>
) : (
pageContent
)}
{render(
'dialog',

View File

@ -370,6 +370,7 @@ export interface TableProps extends RendererProps {
type ExportExcelToolbar = SchemaNode & {
api?: SchemaApi;
columns?: string[];
exportColumns?: any[];
filename?: string;
};
@ -2261,11 +2262,12 @@ export default class Table extends React.Component<TableProps, object> {
classPrefix: ns,
classnames: cx,
translate: __,
columns,
data,
render
} = this.props;
let columns = store.filteredColumns || [];
if (!columns) {
return null;
}
@ -2318,19 +2320,28 @@ export default class Table extends React.Component<TableProps, object> {
});
worksheet.views = [{state: 'frozen', xSplit: 0, ySplit: 1}];
let exportColumns = toolbar.columns;
if (isPureVariable(exportColumns)) {
exportColumns = resolveVariableAndFilter(
exportColumns,
let exportColumnNames = toolbar.columns;
if (isPureVariable(exportColumnNames)) {
exportColumnNames = resolveVariableAndFilter(
exportColumnNames,
data,
'| raw'
);
}
const filteredColumns = exportColumns
// 自定义导出列配置
if (toolbar.exportColumns && Array.isArray(toolbar.exportColumns)) {
columns = toolbar.exportColumns;
}
const filteredColumns = exportColumnNames
? columns.filter(column => {
const filterColumnsNames = exportColumns!;
if (filterColumnsNames.indexOf(column.name) !== -1) {
const filterColumnsNames = exportColumnNames!;
if (
column.name &&
filterColumnsNames.indexOf(column.name) !== -1
) {
return true;
}
return false;

View File

@ -253,7 +253,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
}
})
.then(value => {
this.handleInitEvent(store.data)
this.handleInitEvent(store.data);
const state = {
currentStep:
@ -340,14 +340,19 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
async dispatchEvent(action: string, value?: object) {
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;
}
async handleInitEvent(data: any) {
const {onInit} = this.props;
(await this.dispatchEvent('inited', {formData: data})) && onInit && onInit(data);
(await this.dispatchEvent('inited', {formData: data})) &&
onInit &&
onInit(data);
}
@autobind
@ -383,8 +388,13 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
index = Math.max(Math.min(steps.length, index), 1);
if (index != this.state.currentStep) {
if (await this.dispatchEvent('stepChange', {step: index, formData: this.props.store.data})) {
return
if (
await this.dispatchEvent('stepChange', {
step: index,
formData: this.props.store.data
})
) {
return;
}
this.setState({
@ -537,7 +547,11 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
) {
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(
{
...action,
@ -593,11 +607,15 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
} else if (action.actionType === 'goto-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);
}
} else if (action.actionType === 'submit') {
this.finalSubmit()
this.finalSubmit();
} else if (onAction) {
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) {
const {store} = this.props;
const previous = store.data;
const previous = store.data;
const final = {...previous, ...values};
if (await this.dispatchEvent('change', {formData: final})) {
return;
}
store.updateData(values);
}
@ -685,7 +702,6 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
const step = steps[this.state.currentStep - 1];
store.updateData(values);
// 最后一步
if (target) {
this.submitToTarget(target, store.data);
@ -766,7 +782,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
return value;
})
.catch(error => {
this.dispatchEvent('submitFail', {error})
this.dispatchEvent('submitFail', {error});
store.markSaving(false);
console.error(error);
});
@ -779,12 +795,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
// 接管里面 form 的提交,不能直接让 form 提交,因为 wizard 自己需要知道进度。
@autobind
async handleSubmit(values: object, action: Action) {
const {
store,
steps,
finishedField
} = this.props;
const {store, steps, finishedField} = this.props;
if (this.state.currentStep < steps.length) {
const step = steps[this.state.currentStep - 1];

View File

@ -127,14 +127,14 @@ export function calculatePosition(
atX === 'left'
? childOffset.left
: atX === 'right'
? childOffset.left + childOffset.width
: childOffset.left + childOffset.width / 2;
? childOffset.left + childOffset.width
: childOffset.left + childOffset.width / 2;
positionTop =
atY === 'top'
? childOffset.top
: atY === 'bottom'
? childOffset.top + childOffset.height
: childOffset.top + childOffset.height / 2;
? childOffset.top + childOffset.height
: childOffset.top + childOffset.height / 2;
positionLeft -=
myX === 'left' ? 0 : myX === 'right' ? overlayWidth : overlayWidth / 2;
@ -142,8 +142,8 @@ export function calculatePosition(
myY === 'top'
? 0
: myY === 'bottom'
? overlayHeight
: overlayHeight / 2;
? overlayHeight
: overlayHeight / 2;
// 如果还有其他可选项,则做位置判断,是否在可视区域,不完全在则继续看其他定位情况。
if (tests.length) {