From f9be2996af88c09366342e816aee40d26ea0ca67 Mon Sep 17 00:00:00 2001 From: yupeng12 Date: Wed, 28 Feb 2024 20:00:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81PDF=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh-CN/components/pdf-viewer.md | 54 +++++ examples/components/Components.tsx | 7 + examples/components/Example.jsx | 8 + examples/components/PdfViewer.jsx | 23 +++ examples/static/simple.pdf | Bin 0 -> 17160 bytes fis-conf.js | 9 +- packages/amis-ui/package.json | 3 +- packages/amis-ui/rollup.config.js | 3 +- .../amis-ui/scss/components/_pdf_viewer.scss | 45 +++++ packages/amis-ui/scss/themes/_common.scss | 1 + packages/amis-ui/src/components/PdfViewer.tsx | 143 +++++++++++++ .../amis-ui/src/components/TimelineItem.tsx | 2 +- packages/amis/package.json | 2 +- packages/amis/src/Schema.ts | 1 + packages/amis/src/index.tsx | 1 + packages/amis/src/renderers/PdfViewer.tsx | 189 ++++++++++++++++++ 16 files changed, 486 insertions(+), 5 deletions(-) create mode 100644 docs/zh-CN/components/pdf-viewer.md create mode 100644 examples/components/PdfViewer.jsx create mode 100644 examples/static/simple.pdf create mode 100644 packages/amis-ui/scss/components/_pdf_viewer.scss create mode 100644 packages/amis-ui/src/components/PdfViewer.tsx create mode 100644 packages/amis/src/renderers/PdfViewer.tsx diff --git a/docs/zh-CN/components/pdf-viewer.md b/docs/zh-CN/components/pdf-viewer.md new file mode 100644 index 000000000..d5baa5ad0 --- /dev/null +++ b/docs/zh-CN/components/pdf-viewer.md @@ -0,0 +1,54 @@ +--- +title: PDF Viewer +description: +type: 0 +group: ⚙ 组件 +menuName: PDFViewer 渲染 +icon: +order: 24 +--- + +## 基本用法 + +```schema: scope="body" +{ + "type": "pdf-viewer", + "id": "pdf-viewer", + "src": "/examples/static/simple.pdf" +} +``` + +## 配合文件上传实现预览功能 + +配置和 `input-file` 相同的 `name` 即可 + +```schema: scope="body" +{ + "type": "form", + "title": "", + "wrapWithPanel": false, + "body": [ + { + "type": "input-file", + "name": "file", + "label": "File", + "asBlob": true, + "accept": ".pdf" + }, + { + "type": "pdf-viewer", + "id": "pdf-viewer", + "name": "file" + } + ] +} +``` + +## 属性表 + +| 属性名 | 类型 | 默认值 | 说明 | +| ---------- | ------ | ------ | ---------- | +| src | Api | | 文档地址 | +| width | number | 500 | 宽度 | +| height | number | - | 高度 | +| background | string | #fff | PDF 背景色 | diff --git a/examples/components/Components.tsx b/examples/components/Components.tsx index 4bbc1b029..c6853b0a9 100644 --- a/examples/components/Components.tsx +++ b/examples/components/Components.tsx @@ -987,6 +987,13 @@ export const components = [ import('../../docs/zh-CN/components/office-viewer.md').then(wrapDoc) ) }, + { + label: 'PDFViewer 渲染', + path: '/zh-CN/components/pdf-viewer', + component: React.lazy(() => + import('../../docs/zh-CN/components/pdf-viewer.md').then(wrapDoc) + ) + }, { label: 'Progress 进度条', path: '/zh-CN/components/progress', diff --git a/examples/components/Example.jsx b/examples/components/Example.jsx index fa19ec4c4..3efa54bcc 100644 --- a/examples/components/Example.jsx +++ b/examples/components/Example.jsx @@ -130,6 +130,7 @@ import Tab3Schema from './Tabs/Tab3'; import Loading from './Loading'; import CodeSchema from './Code'; import OfficeViewer from './OfficeViewer'; +import PdfViewer from './PdfViewer'; import InputTableEvent from './EventAction/cmpt-event-action/InputTableEvent'; import WizardPage from './WizardPage'; @@ -912,6 +913,13 @@ export const examples = [ component: makeSchemaRenderer(OfficeViewer) }, + { + label: 'Pdf 预览', + icon: 'fa fa-file-pdf', + path: '/examples/pdf-viewer', + component: makeSchemaRenderer(PdfViewer) + }, + { label: '多 loading', icon: 'fa fa-spinner', diff --git a/examples/components/PdfViewer.jsx b/examples/components/PdfViewer.jsx new file mode 100644 index 000000000..e9d1afbf1 --- /dev/null +++ b/examples/components/PdfViewer.jsx @@ -0,0 +1,23 @@ +export default { + type: 'page', + body: { + type: 'form', + id: 'form', + debug: true, + wrapWithPanel: false, + body: [ + { + type: 'input-file', + name: 'file.test', + label: '选择 PDF 文件预览效果(不会上传到服务器)', + asBlob: true, + accept: '.pdf' + }, + { + type: 'pdf-viewer', + id: 'pdf-viewer', + name: 'file.test' + } + ] + } +}; diff --git a/examples/static/simple.pdf b/examples/static/simple.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a64ea050bec9298cd452cf23c06d6681c7832344 GIT binary patch literal 17160 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Ξ) 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 do8>R>jFYp0qw}BDWMpIGV1y = props => { + const {classnames: cx, className, width = 500} = props; + const [file, setFile] = React.useState(props.file); + const [loaded, setLoaded] = React.useState(false); + const [page, setPage] = React.useState(1); + const [scale, setScale] = React.useState(1); + const [total, setTotal] = React.useState(1); + const inputRef = React.useRef(); + + React.useEffect(() => { + if (props.file instanceof ArrayBuffer && props.file.byteLength > 0) { + setFile(props.file); + } else { + setFile(undefined); + } + }, [props.file]); + + function handleLoadSuccess({numPages}: {numPages: number}) { + setLoaded(true); + setTotal(numPages); + } + + function handleChangePage(idx: number) { + const newPage = page + idx; + if (newPage <= 0 || newPage > total) { + return; + } + setPage(newPage); + } + + function handlePageBlur(event: React.ChangeEvent) { + const newPage = +event.target.value; + if (isNaN(newPage) || newPage <= 0 || newPage > total) { + if (inputRef.current) { + inputRef.current.value = page + ''; + } + return; + } + setPage(newPage); + } + + function handleChangeScale(t: number) { + setScale(scale * t); + } + + if (!file) { + return null; + } + + function renderLoading() { + return ( +
+ +
+ ); + } + + function renderTool() { + return ( +
+ handleChangePage(-1)} + /> + + / + {total} + handleChangePage(1)} + /> + handleChangeScale(1.2)} + /> + handleChangeScale(0.8)} + /> +
+ ); + } + + return ( +
+
+ + No PDF data
} + scale={scale} + renderTextLayer={false} + renderAnnotationLayer={false} + /> + +
+ {loaded ? renderTool() : null} + + ); +}; + +export default themeable(PdfViewer); diff --git a/packages/amis-ui/src/components/TimelineItem.tsx b/packages/amis-ui/src/components/TimelineItem.tsx index 8262e1fe0..ae116843c 100644 --- a/packages/amis-ui/src/components/TimelineItem.tsx +++ b/packages/amis-ui/src/components/TimelineItem.tsx @@ -3,7 +3,7 @@ import {localeable, LocaleProps} from 'amis-core'; import {themeable, ThemeProps} from 'amis-core'; import {Icon} from './icons'; -import type {IconCheckedSchema} from 'amis-ui'; +import type {IconCheckedSchema} from '../index'; export interface TimelineItemProps { /** diff --git a/packages/amis/package.json b/packages/amis/package.json index 0b6269fc1..67501f898 100644 --- a/packages/amis/package.json +++ b/packages/amis/package.json @@ -244,4 +244,4 @@ "react-dom": ">=16.8.6" }, "gitHead": "37d23b4a8eb1c663bc38e8dd9040889ea1526ec4" -} \ No newline at end of file +} diff --git a/packages/amis/src/Schema.ts b/packages/amis/src/Schema.ts index 5f813bc7d..89fabe370 100644 --- a/packages/amis/src/Schema.ts +++ b/packages/amis/src/Schema.ts @@ -253,6 +253,7 @@ export type SchemaType = | 'input-formula' | 'diff-editor' | 'office-viewer' + | 'pdf-viewer' // editor 系列 | 'editor' diff --git a/packages/amis/src/index.tsx b/packages/amis/src/index.tsx index 4b8d4900d..906ac577e 100644 --- a/packages/amis/src/index.tsx +++ b/packages/amis/src/index.tsx @@ -153,6 +153,7 @@ import './renderers/Password'; import './renderers/DateRange'; import './renderers/MultilineText'; import './renderers/OfficeViewer'; +import './renderers/PdfViewer'; import './renderers/AMIS'; import './compat'; diff --git a/packages/amis/src/renderers/PdfViewer.tsx b/packages/amis/src/renderers/PdfViewer.tsx new file mode 100644 index 000000000..2390e3e4a --- /dev/null +++ b/packages/amis/src/renderers/PdfViewer.tsx @@ -0,0 +1,189 @@ +/** + * @file PdfViewer.tsx PDF 预览 + * + * @created: 2024/02/26 + */ + +import React from 'react'; +import { + autobind, + getVariable, + isApiOutdated, + IScopedContext, + Renderer, + RendererProps, + resolveVariableAndFilter, + ScopedContext +} from 'amis-core'; +import {BaseSchema} from '../Schema'; + +export const PdfView = React.lazy( + () => import('amis-ui/lib/components/PdfViewer') +); + +export interface PdfViewerSchema extends BaseSchema { + type: 'pdf-viewer'; + /** + * 文件地址 + */ + src?: string; + /** + * 文件取值,一般配个表单使用 + */ + name?: string; + width?: number; + height?: number; + background?: string; +} + +export interface PdfViewerProps extends RendererProps {} + +interface PdfViewerState { + loading: boolean; +} + +export default class PdfViewer extends React.Component< + PdfViewerProps, + PdfViewerState +> { + file?: ArrayBuffer; + reader?: FileReader; + fetchCancel?: Function; + constructor(props: PdfViewerProps) { + super(props); + this.state = { + loading: false + }; + } + + componentDidMount() { + this.renderPdf(); + } + + componentDidUpdate(prevProps: PdfViewerProps) { + const props = this.props; + + if (isApiOutdated(prevProps.src, props.src, prevProps.data, props.data)) { + this.abortLoad(); + this.fetchPdf(); + } + + if (getVariable(props.data, props.name)) { + if ( + getVariable(prevProps.data, prevProps.name) !== + getVariable(props.data, props.name) + ) { + this.abortLoad(); + this.renderPdf(); + } + } + } + + @autobind + abortLoad() { + if (this.fetchCancel) { + this.fetchCancel('load canceled'); + this.fetchCancel = undefined; + } + if (this.reader) { + this.reader.abort(); + this.reader = undefined; + } + } + + @autobind + async renderPdf() { + const {src, name, data} = this.props; + // src 优先级高于 name + if (src) { + if (!this.file) { + await this.fetchPdf(); + } + } else if (getVariable(data, name)) { + await this.renderFormFile(); + } + } + + @autobind + async fetchPdf() { + const {env, src, data, translate: __} = this.props; + const finalSrc = src + ? resolveVariableAndFilter(src, data, '| raw') + : undefined; + + if (!finalSrc) { + console.warn('file src is empty'); + return; + } + + this.setState({ + loading: true + }); + + try { + const res = await env.fetcher(finalSrc, data, { + responseType: 'arraybuffer', + cancelExecutor: (executor: Function) => (this.fetchCancel = executor) + }); + this.file = res.data; + this.forceUpdate(); + } catch (error) { + console.error(error); + } finally { + this.setState({ + loading: false + }); + } + } + + @autobind + async renderFormFile() { + const {name, data} = this.props; + const file = getVariable(data, name); + if (file instanceof File) { + const reader = new FileReader(); + reader.onload = _e => { + const data = reader.result as ArrayBuffer; + this.file = data; + this.forceUpdate(); + }; + reader.readAsArrayBuffer(file); + this.reader = reader; + } + } + + render() { + const {className, classnames: cx, width, height, background} = this.props; + + return ( + + ); + } +} + +@Renderer({ + type: 'pdf-viewer' +}) +export class PdfViewerRenderer extends PdfViewer { + static contextType = ScopedContext; + + constructor(props: PdfViewerProps, context: IScopedContext) { + super(props); + + const scoped = context; + scoped.registerComponent(this); + } + + componentWillUnmount() { + super.componentWillUnmount?.(); + const scoped = this.context as IScopedContext; + scoped.unRegisterComponent(this); + } +}