feat: 优化pdf-view、office-view 取值逻辑 支持从对象中获取

This commit is contained in:
yupeng12 2024-05-22 16:59:56 +08:00
parent 6b148a1773
commit 7a3e6908e0
6 changed files with 411 additions and 371 deletions

View File

@ -14,7 +14,7 @@ import {
} from 'amis';
import {eachTree} from 'amis-core';
import 'amis-ui/lib/locale/en-US';
import {withRouter} from 'react-router';
import {withRouter} from 'react-router-dom';
// @ts-ignore
import DocSearch from './DocSearch';
import Doc from './Doc';
@ -142,6 +142,8 @@ class BackTop extends React.PureComponent {
}
}
// @ts-ignore
@withRouter
export class App extends React.PureComponent<{
location: Location;
}> {

View File

@ -3,7 +3,7 @@ import {render, toast, makeTranslator, LazyComponent, Drawer} from 'amis';
import axios from 'axios';
import Portal from 'react-overlays/Portal';
import {normalizeLink} from 'amis-core';
import {withRouter} from 'react-router';
import {withRouter} from 'react-router-dom';
import copy from 'copy-to-clipboard';
import {qsparse, parseQuery, attachmentAdpator} from 'amis-core';
import isPlainObject from 'lodash/isPlainObject';
@ -31,391 +31,395 @@ export default function (schema, schemaProps, showCode, envOverrides) {
};
}
return class extends React.Component {
static displayName = 'SchemaRenderer';
iframeRef;
state = {open: false, schema: {}};
originalTitle = document.title;
toggleCode = () =>
this.setState({
open: !this.state.open
});
copyCode = () => {
copy(JSON.stringify(schema, null, 2));
toast.success('页面配置JSON已复制到粘贴板');
};
close = () =>
this.setState({
open: false
});
constructor(props) {
super(props);
return withRouter(
class extends React.Component {
static displayName = 'SchemaRenderer';
iframeRef;
state = {open: false, schema: {}};
originalTitle = document.title;
toggleCode = () =>
this.setState({
open: !this.state.open
});
copyCode = () => {
copy(JSON.stringify(schema, null, 2));
toast.success('页面配置JSON已复制到粘贴板');
};
close = () =>
this.setState({
open: false
});
constructor(props) {
super(props);
const __ = makeTranslator(props.locale);
const {history} = props;
this.env = {
updateLocation: (location, replace) => {
history[replace ? 'replace' : 'push'](normalizeLink(location));
},
jumpTo: (to, action) => {
if (to === 'goBack') {
return history.location.goBack();
}
to = normalizeLink(to);
if (action && action.actionType === 'url') {
action.blank === false
? (window.location.href = to)
: window.open(to);
return;
}
if (action && to && action.target) {
window.open(to, action.target);
return;
}
if (/^https?:\/\//.test(to)) {
window.location.replace(to);
} else {
history.push(to);
}
},
isCurrentUrl: to => {
const history = this.props.history;
const link = normalizeLink(to);
const location = history.location;
let pathname = link;
let search = '';
const idx = link.indexOf('?');
if (~idx) {
pathname = link.substring(0, idx);
search = link.substring(idx);
}
if (search) {
if (pathname !== location.pathname || !location.search) {
return false;
const __ = makeTranslator(props.locale);
const {history} = props;
this.env = {
updateLocation: (location, replace) => {
history[replace ? 'replace' : 'push'](normalizeLink(location));
},
jumpTo: (to, action) => {
if (to === 'goBack') {
return history.location.goBack();
}
const currentQuery = parseQuery(location);
const query = qsparse(search.substring(1));
return Object.keys(query).every(
key => query[key] === currentQuery[key]
);
} else if (pathname === location.pathname) {
return true;
}
return false;
},
fetcher: async api => {
let {url, method, data, responseType, config, headers} = api;
config = config || {};
config.url = url;
responseType && (config.responseType = responseType);
if (config.cancelExecutor) {
config.cancelToken = new axios.CancelToken(config.cancelExecutor);
}
config.headers = headers || {};
config.method = method;
config.data = data;
if (method === 'get' && data) {
config.params = data;
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data);
config.headers['Content-Type'] = 'application/json';
}
//
config.validateStatus = function () {
return true;
};
let response = await axios(config);
response = await attachmentAdpator(response, __, api);
if (response.status >= 400) {
if (response.data) {
// raw:
if (
response.status === 401 &&
response.data.location &&
response.data.location.startsWith('http')
) {
location.href = response.data.location.replace(
'{{redirect}}',
encodeURIComponent(location.href)
);
return new Promise(() => {});
} else if (response.data.msg) {
throw new Error(response.data.msg);
} else {
throw new Error(JSON.stringify(response.data, null, 2));
}
to = normalizeLink(to);
if (action && action.actionType === 'url') {
action.blank === false
? (window.location.href = to)
: window.open(to);
return;
}
if (action && to && action.target) {
window.open(to, action.target);
return;
}
if (/^https?:\/\//.test(to)) {
window.location.replace(to);
} else {
throw new Error(`${response.status}`);
history.push(to);
}
},
isCurrentUrl: to => {
const history = this.props.history;
const link = normalizeLink(to);
const location = history.location;
let pathname = link;
let search = '';
const idx = link.indexOf('?');
if (~idx) {
pathname = link.substring(0, idx);
search = link.substring(idx);
}
}
return response;
},
isCancel: value => axios.isCancel(value),
copy: (content, options) => {
copy(content, options);
toast.success('内容已复制到粘贴板');
},
blockRouting: fn => {
return history.block(fn);
},
tracker(eventTrack) {
console.debug('eventTrack', eventTrack);
},
loadTinymcePlugin: async tinymce => {
// https://www.tiny.cloud/docs/advanced/creating-a-plugin/
/*
if (search) {
if (pathname !== location.pathname || !location.search) {
return false;
}
const currentQuery = parseQuery(location);
const query = qsparse(search.substring(1));
return Object.keys(query).every(
key => query[key] === currentQuery[key]
);
} else if (pathname === location.pathname) {
return true;
}
return false;
},
fetcher: async api => {
let {url, method, data, responseType, config, headers} = api;
config = config || {};
config.url = url;
responseType && (config.responseType = responseType);
if (config.cancelExecutor) {
config.cancelToken = new axios.CancelToken(config.cancelExecutor);
}
config.headers = headers || {};
config.method = method;
config.data = data;
if (method === 'get' && data) {
config.params = data;
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data);
config.headers['Content-Type'] = 'application/json';
}
//
config.validateStatus = function () {
return true;
};
let response = await axios(config);
response = await attachmentAdpator(response, __, api);
if (response.status >= 400) {
if (response.data) {
// raw:
if (
response.status === 401 &&
response.data.location &&
response.data.location.startsWith('http')
) {
location.href = response.data.location.replace(
'{{redirect}}',
encodeURIComponent(location.href)
);
return new Promise(() => {});
} else if (response.data.msg) {
throw new Error(response.data.msg);
} else {
throw new Error(JSON.stringify(response.data, null, 2));
}
} else {
throw new Error(`${response.status}`);
}
}
return response;
},
isCancel: value => axios.isCancel(value),
copy: (content, options) => {
copy(content, options);
toast.success('内容已复制到粘贴板');
},
blockRouting: fn => {
return history.block(fn);
},
tracker(eventTrack) {
console.debug('eventTrack', eventTrack);
},
loadTinymcePlugin: async tinymce => {
// https://www.tiny.cloud/docs/advanced/creating-a-plugin/
/*
Note: We have included the plugin in the same JavaScript file as the TinyMCE
instance for display purposes only. Tiny recommends not maintaining the plugin
with the TinyMCE instance and using the `external_plugins` option.
*/
tinymce.PluginManager.add('example', function (editor, url) {
var openDialog = function () {
return editor.windowManager.open({
title: 'Example plugin',
body: {
type: 'panel',
items: [
{
type: 'input',
name: 'title',
label: 'Title'
}
]
},
buttons: [
{
type: 'cancel',
text: 'Close'
tinymce.PluginManager.add('example', function (editor, url) {
var openDialog = function () {
return editor.windowManager.open({
title: 'Example plugin',
body: {
type: 'panel',
items: [
{
type: 'input',
name: 'title',
label: 'Title'
}
]
},
{
type: 'submit',
text: 'Save',
primary: true
buttons: [
{
type: 'cancel',
text: 'Close'
},
{
type: 'submit',
text: 'Save',
primary: true
}
],
onSubmit: function (api) {
var data = api.getData();
/* Insert content when the window form is submitted */
editor.insertContent('Title: ' + data.title);
api.close();
}
],
onSubmit: function (api) {
var data = api.getData();
/* Insert content when the window form is submitted */
editor.insertContent('Title: ' + data.title);
api.close();
});
};
/* Add a button that opens a window */
editor.ui.registry.addButton('example', {
text: 'My button',
onAction: function () {
/* Open window */
openDialog();
}
});
};
/* Add a button that opens a window */
editor.ui.registry.addButton('example', {
text: 'My button',
onAction: function () {
/* Open window */
openDialog();
}
/* Adds a menu item, which can then be included in any menu via the menu/menubar configuration */
editor.ui.registry.addMenuItem('example', {
text: 'Example plugin',
onAction: function () {
/* Open window */
openDialog();
}
});
/* Return the metadata for the help plugin */
return {
getMetadata: function () {
return {
name: 'Example plugin',
url: 'http://exampleplugindocsurl.com'
};
}
};
});
/* Adds a menu item, which can then be included in any menu via the menu/menubar configuration */
editor.ui.registry.addMenuItem('example', {
text: 'Example plugin',
onAction: function () {
/* Open window */
openDialog();
}
});
/* Return the metadata for the help plugin */
return {
getMetadata: function () {
return {
name: 'Example plugin',
url: 'http://exampleplugindocsurl.com'
};
}
};
});
},
// testid
// enableTestid: true,
...envOverrides
};
this.handleEditorMount = this.handleEditorMount.bind(this);
this.iframeRef = React.createRef();
this.watchIframeReady = this.watchIframeReady.bind(this);
window.addEventListener('message', this.watchIframeReady, false);
}
handleEditorMount(editor, monaco) {
let host = `${window.location.protocol}//${window.location.host}`;
// gh-pages
if (/^\/amis/.test(window.location.pathname)) {
host += '/amis';
}
const schemaUrl = `${host}/schema.json`;
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
schemas: [
{
uri: schemaUrl,
fileMatch: ['*']
}
],
validate: true,
enableSchemaRequest: true,
allowComments: true
});
}
renderCode() {
return (
<LazyComponent
getComponent={loadEditor}
editorDidMount={this.handleEditorMount}
language="json"
value={schema}
placeholder="加载中,请稍后。。。"
disabled
/>
);
}
watchIframeReady(event) {
// iframe amis
if (event.data && event.data === 'amisReady') {
this.updateIframe();
}
}
updateIframe() {
if (this.iframeRef && this.iframeRef.current) {
this.iframeRef.current.contentWindow.postMessage(
{
schema: schema,
props: {
...(isPlainObject(schemaProps) ? schemaProps : {}),
location: this.props.location,
theme: this.props.theme,
locale: this.props.locale
}
},
'*'
);
// testid
// enableTestid: true,
...envOverrides
};
this.handleEditorMount = this.handleEditorMount.bind(this);
this.iframeRef = React.createRef();
this.watchIframeReady = this.watchIframeReady.bind(this);
window.addEventListener('message', this.watchIframeReady, false);
}
}
componentWillUnmount() {
this.props.setAsideFolded && this.props.setAsideFolded(false);
window.removeEventListener('message', this.watchIframeReady, false);
document.title = this.originalTitle;
}
handleEditorMount(editor, monaco) {
let host = `${window.location.protocol}//${window.location.host}`;
componentDidMount() {
if (schema.title) {
document.title = schema.title;
// gh-pages
if (/^\/amis/.test(window.location.pathname)) {
host += '/amis';
}
const schemaUrl = `${host}/schema.json`;
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
schemas: [
{
uri: schemaUrl,
fileMatch: ['*']
}
],
validate: true,
enableSchemaRequest: true,
allowComments: true
});
}
}
renderSchema() {
const {location, theme, locale} = this.props;
if (viewMode === 'mobile') {
renderCode() {
return (
<iframe
width="375"
height="100%"
frameBorder={0}
className="mobile-frame"
ref={this.iframeRef}
// @ts-ignore
src={__uri('../mobile.html')}
></iframe>
<LazyComponent
getComponent={loadEditor}
editorDidMount={this.handleEditorMount}
language="json"
value={schema}
placeholder="加载中,请稍后。。。"
disabled
/>
);
}
return render(
schema,
{
...(isPlainObject(schemaProps) ? schemaProps : {}),
context: {
//
amisUser: {
id: 1,
name: 'AMIS User'
}
},
location,
theme,
locale
},
this.env
);
}
watchIframeReady(event) {
// iframe amis
if (event.data && event.data === 'amisReady') {
this.updateIframe();
}
}
render() {
const ns = this.props.classPrefix;
const finalShowCode = this.props.showCode ?? showCode;
return (
<>
<div className="schema-wrapper">
updateIframe() {
if (this.iframeRef && this.iframeRef.current) {
this.iframeRef.current.contentWindow.postMessage(
{
schema: schema,
props: {
...(isPlainObject(schemaProps) ? schemaProps : {}),
location: this.props.location,
theme: this.props.theme,
locale: this.props.locale
}
},
'*'
);
}
}
componentWillUnmount() {
this.props.setAsideFolded && this.props.setAsideFolded(false);
window.removeEventListener('message', this.watchIframeReady, false);
document.title = this.originalTitle;
}
componentDidMount() {
if (schema.title) {
document.title = schema.title;
}
}
renderSchema() {
const {location, theme, locale} = this.props;
if (viewMode === 'mobile') {
return (
<iframe
width="375"
height="100%"
frameBorder={0}
className="mobile-frame"
ref={this.iframeRef}
// @ts-ignore
src={__uri('../mobile.html')}
></iframe>
);
}
return render(
schema,
{
...(isPlainObject(schemaProps) ? schemaProps : {}),
context: {
//
amisUser: {
id: 1,
name: 'AMIS User'
}
},
location,
theme,
locale
},
this.env
);
}
render() {
const ns = this.props.classPrefix;
const finalShowCode = this.props.showCode ?? showCode;
return (
<>
<div className="schema-wrapper">
{finalShowCode !== false ? (
<Drawer
classPrefix={ns}
size="lg"
onHide={this.close}
show={this.state.open}
// overlay={false}
closeOnOutside={true}
position="right"
>
{this.state.open ? this.renderCode() : null}
</Drawer>
) : null}
{this.renderSchema()}
</div>
{finalShowCode !== false ? (
<Drawer
classPrefix={ns}
size="lg"
onHide={this.close}
show={this.state.open}
// overlay={false}
closeOnOutside={true}
position="right"
// <div className="schema-toolbar-wrapper">
// <div onClick={this.toggleCode}>
// <i className="fa fa-code p-l-xs"></i>
// </div>
// <div onClick={this.copyCode}>
// <i className="fa fa-copy p-l-xs"></i>
// </div>
// </div>
<Portal
container={() => document.getElementById('Header-toolbar')}
>
{this.state.open ? this.renderCode() : null}
</Drawer>
) : null}
{this.renderSchema()}
</div>
{finalShowCode !== false ? (
// <div className="schema-toolbar-wrapper">
// <div onClick={this.toggleCode}>
// <i className="fa fa-code p-l-xs"></i>
// </div>
// <div onClick={this.copyCode}>
// <i className="fa fa-copy p-l-xs"></i>
// </div>
// </div>
<Portal container={() => document.getElementById('Header-toolbar')}>
<div className="hidden-xs hidden-sm ml-3">
<div>
<div className="Doc-headingList">
<div className="Doc-headingList-item">
<a onClick={this.toggleCode}>
查看配置 <i className="fa fa-code p-l-xs"></i>
</a>
</div>
<div className="Doc-headingList-item">
<a onClick={this.copyCode}>
复制配置 <i className="fa fa-copy p-l-xs"></i>
</a>
<div className="hidden-xs hidden-sm ml-3">
<div>
<div className="Doc-headingList">
<div className="Doc-headingList-item">
<a onClick={this.toggleCode}>
查看配置 <i className="fa fa-code p-l-xs"></i>
</a>
</div>
<div className="Doc-headingList-item">
<a onClick={this.copyCode}>
复制配置 <i className="fa fa-copy p-l-xs"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</Portal>
) : null}
</>
);
</Portal>
) : null}
</>
);
}
}
};
);
}

View File

@ -95,8 +95,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-overlays": "5.1.1",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"rimraf": "^3.0.2",
"rollup": "^2.73.0",
"rollup-plugin-auto-external": "^2.0.0",

View File

@ -81,8 +81,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-overlays": "5.1.1",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"rimraf": "^3.0.2",
"rollup": "^2.73.0",
"rollup-plugin-auto-external": "^2.0.0",

View File

@ -166,12 +166,20 @@ export default class OfficeViewer extends React.Component<
async fetchWord() {
const {env, src, data, translate: __} = this.props;
const finalSrc = src
let finalSrc;
const resolveSrc = src
? resolveVariableAndFilter(src, data, '| raw')
: undefined;
if (typeof finalSrc === 'string') {
if (typeof resolveSrc === 'string') {
finalSrc = resolveSrc;
this.fileName = finalSrc.split('/').pop();
} else if (
typeof resolveSrc === 'object' &&
typeof resolveSrc.value === 'string'
) {
finalSrc = resolveSrc.value;
this.fileName = resolveSrc.name || finalSrc.split('/').pop();
}
if (!finalSrc) {
@ -198,7 +206,6 @@ export default class OfficeViewer extends React.Component<
this.rootElement.current.innerHTML =
__('loadingFailed') + ' url:' + finalSrc;
}
} finally {
this.setState({
loading: false
});
@ -268,6 +275,9 @@ export default class OfficeViewer extends React.Component<
}
this.office = office;
this.setState({
loading: false
});
});
}
@ -275,6 +285,10 @@ export default class OfficeViewer extends React.Component<
* input-file
*/
renderFormFile() {
this.setState({
loading: true
});
const {wordOptions, name, data, display} = this.props;
const file = data[name];
if (file instanceof File) {
@ -291,6 +305,9 @@ export default class OfficeViewer extends React.Component<
this.rootElement.current.innerHTML = '';
}
this.office = office;
this.setState({
loading: false
});
});
};
reader.readAsArrayBuffer(file);

View File

@ -74,9 +74,15 @@ export default class PdfViewer extends React.Component<
componentDidUpdate(prevProps: PdfViewerProps) {
const props = this.props;
if (isApiOutdated(prevProps.src, props.src, prevProps.data, props.data)) {
if (
isApiOutdated(prevProps.src, props.src, prevProps.data, props.data) ||
resolveVariableAndFilter(props.src, props.data, '| raw') !==
resolveVariableAndFilter(prevProps.src, prevProps.data, '| raw')
) {
this.abortLoad();
this.fetchPdf();
setTimeout(() => {
this.fetchPdf();
}, 0);
}
if (getVariable(props.data, props.name)) {
@ -123,9 +129,19 @@ export default class PdfViewer extends React.Component<
@autobind
async fetchPdf() {
const {env, src, data, translate: __} = this.props;
const finalSrc = src
? resolveVariableAndFilter(src, data, '| raw')
: undefined;
let finalSrc;
if (src) {
const resolveSrc = resolveVariableAndFilter(src, data, '| raw');
if (typeof resolveSrc === 'string') {
finalSrc = resolveSrc;
} else if (
typeof resolveSrc === 'object' &&
typeof resolveSrc.value === 'string'
) {
finalSrc = resolveSrc.value;
}
}
if (!finalSrc) {
console.warn('file src is empty');
@ -134,7 +150,8 @@ export default class PdfViewer extends React.Component<
this.setState({
inited: true,
loading: true
loading: true,
error: false
});
try {