mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-05 13:28:49 +08:00
fdb09001e1
* publish beta * chore: sdk embed 方法添加 callback 在 callback 中可确保 scoped 方法是可用的
302 lines
8.1 KiB
TypeScript
302 lines
8.1 KiB
TypeScript
import './polyfills/index';
|
|
import React from 'react';
|
|
import {createRoot} from 'react-dom/client';
|
|
import axios from 'axios';
|
|
import {match} from 'path-to-regexp';
|
|
import copy from 'copy-to-clipboard';
|
|
import {normalizeLink} from 'amis-core';
|
|
|
|
import qs from 'qs';
|
|
import {
|
|
toast,
|
|
alert,
|
|
confirm,
|
|
ToastComponent,
|
|
AlertComponent,
|
|
render as renderAmis,
|
|
makeTranslator
|
|
} from 'amis';
|
|
|
|
import 'amis-ui/lib/locale/en-US';
|
|
import 'history';
|
|
import {attachmentAdpator} from 'amis-core';
|
|
|
|
import type {ToastLevel, ToastConf} from 'amis-ui/lib/components/Toast';
|
|
|
|
export function embed(
|
|
container: string | HTMLElement,
|
|
schema: any,
|
|
props: any = {},
|
|
env?: any,
|
|
callback?: () => void
|
|
) {
|
|
const __ = makeTranslator(env?.locale || props?.locale);
|
|
|
|
// app 模式自动加 affixOffsetTop
|
|
if (!('affixOffsetTop' in props) && schema.type === 'app') {
|
|
props.affixOffsetTop = 50;
|
|
}
|
|
|
|
if (typeof container === 'string') {
|
|
container = document.querySelector(container) as HTMLElement;
|
|
}
|
|
if (!container) {
|
|
console.error(__('Embed.invalidRoot'));
|
|
return;
|
|
} else if (container.tagName === 'BODY') {
|
|
let div = document.createElement('div');
|
|
container.appendChild(div);
|
|
container = div;
|
|
}
|
|
container.classList.add('amis-scope');
|
|
let scoped = {};
|
|
|
|
const requestAdaptor = (config: any) => {
|
|
const fn =
|
|
env && typeof env.requestAdaptor === 'function'
|
|
? env.requestAdaptor.bind()
|
|
: (config: any) => config;
|
|
const request = fn(config) || config;
|
|
|
|
return request;
|
|
};
|
|
|
|
const responseAdaptor = (api: any) => (response: any) => {
|
|
let payload = response.data || {}; // blob 下可能会返回内容为空?
|
|
// 之前拼写错了,需要兼容
|
|
if (env && env.responseAdpater) {
|
|
env.responseAdaptor = env.responseAdpater;
|
|
}
|
|
if (env && env.responseAdaptor) {
|
|
const url = api.url;
|
|
const idx = url.indexOf('?');
|
|
const query = ~idx ? qs.parse(url.substring(idx)) : {};
|
|
const request = {
|
|
...api,
|
|
query: query,
|
|
body: api.data
|
|
};
|
|
payload = env.responseAdaptor(api, payload, query, request, response);
|
|
} else {
|
|
if (payload.hasOwnProperty('errno')) {
|
|
payload.status = payload.errno;
|
|
payload.msg = payload.errmsg;
|
|
} else if (payload.hasOwnProperty('no')) {
|
|
payload.status = payload.no;
|
|
payload.msg = payload.error;
|
|
}
|
|
}
|
|
|
|
const result = {
|
|
...response,
|
|
data: payload
|
|
};
|
|
return result;
|
|
};
|
|
|
|
const amisEnv = {
|
|
getModalContainer: () =>
|
|
env?.getModalContainer?.() || document.querySelector('.amis-scope'),
|
|
notify: (type: ToastLevel, msg: string, conf?: ToastConf) =>
|
|
toast[type]
|
|
? toast[type](msg, conf)
|
|
: console.warn('[Notify]', type, msg),
|
|
alert,
|
|
confirm,
|
|
updateLocation: (to: any, replace: boolean) => {
|
|
if (to === 'goBack') {
|
|
return window.history.back();
|
|
}
|
|
|
|
if (replace && window.history.replaceState) {
|
|
window.history.replaceState('', document.title, to);
|
|
return;
|
|
}
|
|
|
|
location.href = normalizeLink(to);
|
|
},
|
|
isCurrentUrl: (to: string, ctx?: any) => {
|
|
const link = normalizeLink(to);
|
|
const location = window.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 query = qs.parse(search.substring(1));
|
|
const currentQuery = qs.parse(location.search.substring(1));
|
|
|
|
return Object.keys(query).every(
|
|
key => query[key] === currentQuery[key]
|
|
);
|
|
} else if (pathname === location.pathname) {
|
|
return true;
|
|
} else if (!~pathname.indexOf('http') && ~pathname.indexOf(':')) {
|
|
return match(link, {
|
|
decode: decodeURIComponent,
|
|
strict: ctx?.strict ?? true
|
|
})(location.pathname);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
jumpTo: (to: string, action?: any) => {
|
|
if (to === 'goBack') {
|
|
return window.history.back();
|
|
}
|
|
|
|
to = normalizeLink(to);
|
|
|
|
if (action && action.actionType === 'url') {
|
|
action.blank === false ? (window.location.href = to) : window.open(to);
|
|
return;
|
|
}
|
|
|
|
// 主要是支持 nav 中的跳转
|
|
if (action && to && action.target) {
|
|
window.open(to, action.target);
|
|
return;
|
|
}
|
|
|
|
if (/^https?:\/\//.test(to)) {
|
|
window.location.replace(to);
|
|
} else {
|
|
location.href = to;
|
|
}
|
|
},
|
|
fetcher: async (api: any) => {
|
|
let {url, method, data, responseType, config, headers} = api;
|
|
config = config || {};
|
|
config.url = url;
|
|
config.withCredentials = true;
|
|
responseType && (config.responseType = responseType);
|
|
|
|
if (config.cancelExecutor) {
|
|
config.cancelToken = new (axios as any).CancelToken(
|
|
config.cancelExecutor
|
|
);
|
|
}
|
|
|
|
config.headers = headers || {};
|
|
config.method = method;
|
|
config.data = data;
|
|
|
|
config = requestAdaptor(config);
|
|
|
|
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, __);
|
|
response = responseAdaptor(api)(response);
|
|
|
|
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(
|
|
__('System.requestError') + JSON.stringify(response.data, null, 2)
|
|
);
|
|
}
|
|
} else {
|
|
throw new Error(
|
|
`${__('System.requestErrorStatus')} ${response.status}`
|
|
);
|
|
}
|
|
}
|
|
|
|
return response;
|
|
},
|
|
isCancel: (value: any) => (axios as any).isCancel(value),
|
|
copy: (contents: string, options: any = {}) => {
|
|
const ret = copy(contents);
|
|
ret && options.silent !== true && toast.info(__('System.copy'));
|
|
return ret;
|
|
},
|
|
richTextToken: '',
|
|
affixOffsetBottom: 0,
|
|
...env
|
|
};
|
|
|
|
let amisProps: any = {};
|
|
function createElements(props: any): any {
|
|
amisProps = {
|
|
...amisProps,
|
|
...props,
|
|
scopeRef: (ref: any) => {
|
|
if (ref) {
|
|
Object.assign(scoped, ref);
|
|
callback?.();
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="amis-routes-wrapper">
|
|
<ToastComponent
|
|
position={(env && env.toastPosition) || 'top-center'}
|
|
closeButton={false}
|
|
timeout={5000}
|
|
locale={props?.locale}
|
|
theme={env?.theme}
|
|
/>
|
|
<AlertComponent
|
|
locale={props?.locale}
|
|
theme={env?.theme}
|
|
container={() => env?.getModalContainer?.() || container}
|
|
/>
|
|
|
|
{renderAmis(schema, amisProps, amisEnv)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const root = createRoot(container);
|
|
root.render(createElements(props));
|
|
|
|
return Object.assign(scoped, {
|
|
updateProps: (props: any, callback?: () => void) => {
|
|
root.render(createElements(props));
|
|
},
|
|
unmount: () => {
|
|
root.unmount();
|
|
}
|
|
});
|
|
}
|