Skip to content

Commit

Permalink
Merge pull request baidu#9652 from nwind/feat-print-element
Browse files Browse the repository at this point in the history
feat: 增加打印事件, 支持打印容器组件 Closes baidu#9475
  • Loading branch information
hsm-lv committed Feb 29, 2024
2 parents eda1809 + f67d344 commit 4b1f18f
Show file tree
Hide file tree
Showing 16 changed files with 267 additions and 18 deletions.
69 changes: 69 additions & 0 deletions docs/zh-CN/concepts/event-action.md
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,75 @@ run action ajax
| copyFormat | `string` | `text/html` | 复制格式 |
| content | [模板](../../docs/concepts/template) | - | 指定复制的内容。可用 `${xxx}` 取值 |

### 打印

> 6.2.0 及以后版本
打印页面中的某个组件,对应的组件需要配置 `testid`,如果要打印多个,可以使用 `"testids": ["x", "y"]` 来打印多个组件

```schema
{
type: 'page',
body: [
{
type: 'button',
label: '打印',
level: 'primary',
className: 'mr-2',
onEvent: {
click: {
actions: [
{
actionType: 'print',
args: {
testid: 'mycrud'
}
}
]
}
}
},
{
"type": "crud",
"api": "/api/mock2/sample",
"testid": "mycrud",
"syncLocation": false,
"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"
}
]
}
]
}
```

| 属性名 | 类型 | 默认值 | 说明 |
| ------- | ---------- | ------ | ----------------- |
| testid | `string` | | 组件的 testid |
| testids | `string[]` | - | 多个组件的 testid |

### 发送邮件

通过配置`actionType: 'email'`和邮件属性实现发送邮件操作。
Expand Down
51 changes: 51 additions & 0 deletions packages/amis-core/src/actions/PrintAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {printElements} from '../utils/printElement';
import {RendererEvent} from '../utils/renderer-event';
import {
RendererAction,
ListenerAction,
ListenerContext,
registerAction
} from './Action';

export interface IPrintAction extends ListenerAction {
actionType: 'copy';
args: {
testid?: string;
testids?: string[];
};
}

/**
* 打印动作
*
* @export
* @class PrintAction
* @implements {Action}
*/
export class PrintAction implements RendererAction {
async run(
action: IPrintAction,
renderer: ListenerContext,
event: RendererEvent<any>
) {
if (action.args?.testid) {
const element = document.querySelector(
`[data-testid='${action.args.testid}']`
);
if (element) {
printElements([element]);
}
} else if (action.args?.testids) {
const elements: Element[] = [];
action.args.testids.forEach(testid => {
const element = document.querySelector(`[data-testid='${testid}']`);
if (element) {
elements.push(element);
}
});
printElements(elements);
}
}
}

registerAction('print', new PrintAction());
1 change: 1 addition & 0 deletions packages/amis-core/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ import './EmailAction';
import './LinkAction';
import './ToastAction';
import './PageAction';
import './PrintAction';

export * from './Action';
6 changes: 4 additions & 2 deletions packages/amis-core/src/renderers/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import LazyComponent from '../components/LazyComponent';
import {isAlive} from 'mobx-state-tree';

import type {LabelAlign} from './Item';
import {injectObjectChain} from '../utils';
import {buildTestId, injectObjectChain} from '../utils';
import {reaction} from 'mobx';

export interface FormHorizontal {
Expand Down Expand Up @@ -1808,7 +1808,8 @@ export default class Form extends React.Component<FormProps, object> {
render,
staticClassName,
static: isStatic = false,
loadingConfig
loadingConfig,
testid
} = this.props;

const {restError} = store;
Expand Down Expand Up @@ -1840,6 +1841,7 @@ export default class Form extends React.Component<FormProps, object> {
)}
onSubmit={this.handleFormSubmit}
noValidate
{...buildTestId(testid)}
>
{/* 实现回车自动提交 */}
<input type="submit" style={{display: 'none'}} />
Expand Down
76 changes: 76 additions & 0 deletions packages/amis-core/src/utils/printElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 打印元素,参考 https://github.com/szepeshazi/print-elements 里的实现
* 原理就是遍历节点加上打印样式,然后打印完了再清理掉
* 对代码做了改造和优化
*/

const hideFromPrintClass = 'pe-no-print';
const preservePrintClass = 'pe-preserve-print';
const preserveAncestorClass = 'pe-preserve-ancestor';
const bodyElementName = 'BODY';

function hide(element: Element) {
if (!element.classList.contains(preservePrintClass)) {
element.classList.add(hideFromPrintClass);
}
}

function preserve(element: Element, isStartingElement: boolean) {
element.classList.remove(hideFromPrintClass);
element.classList.add(preservePrintClass);
if (!isStartingElement) {
element.classList.add(preserveAncestorClass);
}
}

function clean(element: Element) {
element.classList.remove(hideFromPrintClass);
element.classList.remove(preservePrintClass);
element.classList.remove(preserveAncestorClass);
}

function walkSiblings(element: Element, callback: (element: Element) => void) {
let sibling = element.previousElementSibling;
while (sibling) {
callback(sibling);
sibling = sibling.previousElementSibling;
}
sibling = element.nextElementSibling;
while (sibling) {
callback(sibling);
sibling = sibling.nextElementSibling;
}
}

function attachPrintClasses(element: Element, isStartingElement: boolean) {
preserve(element, isStartingElement);
walkSiblings(element, hide);
}

function cleanup(element: Element, isStartingElement: boolean) {
clean(element);
walkSiblings(element, clean);
}

function walkTree(
element: Element,
callback: (element: Element, isStartingElement: boolean) => void
) {
let currentElement: Element | null = element;
callback(currentElement, true);
currentElement = currentElement.parentElement;
while (currentElement && currentElement.nodeName !== bodyElementName) {
callback(currentElement, false);
currentElement = currentElement.parentElement;
}
}

export function printElements(elements: Element[]) {
for (let i = 0; i < elements.length; i++) {
walkTree(elements[i], attachPrintClasses);
}
window.print();
for (let i = 0; i < elements.length; i++) {
walkTree(elements[i], cleanup);
}
}
13 changes: 13 additions & 0 deletions packages/amis-ui/scss/components/_print.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@media print {
.pe-no-print {
display: none !important;
}

.pe-preserve-ancestor {
display: block !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
box-shadow: none !important;
}
}
2 changes: 2 additions & 0 deletions packages/amis-ui/scss/themes/_common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,5 @@
@import '../components/debug';
@import '../components/menu';
@import '../components/overflow-tpl';

@import '../components/print';
5 changes: 4 additions & 1 deletion packages/amis/src/renderers/CRUD.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
RendererProps,
evalExpressionWithConditionBuilder,
filterTarget,
mapTree
mapTree,
buildTestId
} from 'amis-core';
import {SchemaNode, Schema, ActionObject, PlainObject} from 'amis-core';
import {CRUDStore, ICRUDStore} from 'amis-core';
Expand Down Expand Up @@ -2516,6 +2517,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
onSearchableFromInit,
headerToolbarRender,
footerToolbarRender,
testid,
...rest
} = this.props;

Expand All @@ -2526,6 +2528,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
'is-mobile': isMobile()
})}
style={style}
{...buildTestId(testid)}
>
{filter && (!store.filterTogggable || store.filterVisible)
? render(
Expand Down
5 changes: 4 additions & 1 deletion packages/amis/src/renderers/CRUD2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
isApiOutdated,
isPureVariable,
resolveVariableAndFilter,
parsePrimitiveQueryString
parsePrimitiveQueryString,
buildTestId
} from 'amis-core';
import {Html, SpinnerExtraProps} from 'amis-ui';
import {
Expand Down Expand Up @@ -1308,6 +1309,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
columnsTogglable,
headerToolbarClassName,
footerToolbarClassName,
testid,
...rest
} = this.props;

Expand All @@ -1317,6 +1319,7 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
'is-loading': store.loading
})}
style={style}
{...buildTestId(testid)}
>
<div className={cx('Crud2-filter')}>
{this.renderFilter(filterSchema)}
Expand Down
7 changes: 5 additions & 2 deletions packages/amis/src/renderers/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
isPureVariable,
resolveVariableAndFilter,
CustomStyle,
setThemeClassName
setThemeClassName,
buildTestId
} from 'amis-core';
import {DndContainer as DndWrapper} from 'amis-ui';
import {BaseSchema, SchemaClassName, SchemaCollection} from '../Schema';
Expand Down Expand Up @@ -195,7 +196,8 @@ export default class Container<T> extends React.Component<
wrapperCustomStyle,
env,
themeCss,
baseControlClassName
baseControlClassName,
testid
} = this.props;
const finalDraggable: boolean = isPureVariable(draggable)
? resolveVariableAndFilter(draggable, data, '| raw')
Expand Down Expand Up @@ -231,6 +233,7 @@ export default class Container<T> extends React.Component<
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={buildStyle(style, data)}
{...buildTestId(testid)}
>
{this.renderBody()}
<CustomStyle
Expand Down
7 changes: 5 additions & 2 deletions packages/amis/src/renderers/Flex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
Renderer,
RendererProps,
CustomStyle,
setThemeClassName
setThemeClassName,
buildTestId
} from 'amis-core';
import {Schema} from 'amis-core';
import {BaseSchema, SchemaCollection, SchemaObject} from '../Schema';
Expand Down Expand Up @@ -111,7 +112,8 @@ export default class Flex extends React.Component<FlexProps, object> {
wrapperCustomStyle,
env,
themeCss,
classnames: cx
classnames: cx,
testid
} = this.props;
const styleVar = buildStyle(style, data);
const flexStyle = {
Expand Down Expand Up @@ -150,6 +152,7 @@ export default class Flex extends React.Component<FlexProps, object> {
themeCss: wrapperCustomStyle
})
)}
{...buildTestId(testid)}
>
{(Array.isArray(items) ? items : items ? [items] : []).map(
(item, key) =>
Expand Down
7 changes: 5 additions & 2 deletions packages/amis/src/renderers/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
RendererProps,
buildStyle,
CustomStyle,
setThemeClassName
setThemeClassName,
buildTestId
} from 'amis-core';
import pick from 'lodash/pick';
import {BaseSchema, SchemaClassName, SchemaCollection} from '../Schema';
Expand Down Expand Up @@ -212,7 +213,8 @@ export default class Grid<T> extends React.Component<GridProps & T, object> {
id,
wrapperCustomStyle,
env,
themeCss
themeCss,
testid
} = this.props;
const styleVar = buildStyle(style, data);
return (
Expand All @@ -239,6 +241,7 @@ export default class Grid<T> extends React.Component<GridProps & T, object> {
})
)}
style={styleVar}
{...buildTestId(testid)}
>
{this.renderColumns(this.props.columns)}
<Spinner loadingConfig={loadingConfig} overlay show={loading} />
Expand Down

0 comments on commit 4b1f18f

Please sign in to comment.