From 20b2ddb82eeddab7138ac5dcfb40853faa5967a3 Mon Sep 17 00:00:00 2001 From: liaoxuezhi <2betop.cn@gmail.com> Date: Thu, 17 Jun 2021 01:06:34 +0800 Subject: [PATCH] =?UTF-8?q?jssdk=20=E6=94=AF=E6=8C=81=20hash=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh-CN/start/getting-started.md | 50 ++-- examples/embed.tsx | 368 +++++++++++++++------------- src/store/app.ts | 5 + 3 files changed, 224 insertions(+), 199 deletions(-) diff --git a/docs/zh-CN/start/getting-started.md b/docs/zh-CN/start/getting-started.md index 7157396db..12061391e 100644 --- a/docs/zh-CN/start/getting-started.md +++ b/docs/zh-CN/start/getting-started.md @@ -83,6 +83,18 @@ SDK 版本适合对前端或 React 不了解的开发者,它不依赖 npm 及 ``` +### 更新属性 + +可以通过 amisScoped 对象的 updateProps 方法来更新下发到 amis 的属性。 + +```ts +amisScoped.updateProps( + { + // 新的属性对象 + } /*, () => {} 更新回调 */ +); +``` + ### 切换主题 jssdk 版本默认使用 `sdk.css` 即默认主题,如果你想用使用云舍,请改成引用 `cxd.css`。同时 js 渲染地方第四个参数传入 `theme` 属性。如: @@ -100,6 +112,11 @@ amis.embed( theme: 'cxd' } ); + +// 或者 +amisScoped.updateProps({ + theme: 'cxd' +}); ``` 暗黑主题同理,改成引用 'dark.css' 同时主题设置成 `dark`。 @@ -179,27 +196,6 @@ let amisScoped = amis.embed( 还可以通过 `amisScoped.getComponentByName('page1.form1').setValues({'name1': 'othername'})` 来修改表单中的值。 -### 切换主题 - -jssdk 版本默认使用 `sdk.css` 即默认主题,如果你想用使用云舍,请改成引用 `cxd.css`。同时 js 渲染地方第四个参数传入 `theme` 属性。如: - -```js -amis.embed( - '#root', - { - // amis schema - }, - { - // 默认数据 - }, - { - theme: 'cxd' - } -); -``` - -暗黑主题同理,改成引用 'dark.css' 同时主题设置成 `dark`。 - ### 多页模式 默认 amis 渲染是单页模式,如果想实现多页应用,请使用 [app 渲染器](../../components/app)。 @@ -210,6 +206,14 @@ amis.embed( 参考:https://github.com/baidu/amis/blob/master/examples/components/Example.tsx#L551-L575 +### 销毁 + +如果是单页应用,在离开当前页面的时候通常需要销毁实例,可以通过 unmount 方法来完成。 + +```ts +amisScoped.unmount(); +``` + ## react 初始项目请参考 。 @@ -290,8 +294,8 @@ import {toast} from 'amis/lib/components/Toast'; class MyComponent extends React.Component { render() { - let amisScoped; - let theme = 'default'; + let amisScoped; + let theme = 'default'; return (

通过 amis 渲染页面

diff --git a/examples/embed.tsx b/examples/embed.tsx index e3d43ebc2..73ae0915b 100644 --- a/examples/embed.tsx +++ b/examples/embed.tsx @@ -1,6 +1,6 @@ import './polyfills/index'; import React from 'react'; -import {render as renderReact} from 'react-dom'; +import {render as renderReact, unmountComponentAtNode} from 'react-dom'; import axios from 'axios'; import {match} from 'path-to-regexp'; import copy from 'copy-to-clipboard'; @@ -46,11 +46,15 @@ export function embed( ) { const disposition = response.headers['content-disposition']; let filename = ''; + if (disposition && disposition.indexOf('attachment') !== -1) { - let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i; - let matches = filenameRegex.exec(disposition); - if (matches != null && matches[1]) { - filename = matches[1].replace(/['"]/g, ''); + // disposition 有可能是 attachment; filename="??.xlsx"; filename*=UTF-8''%E4%B8%AD%E6%96%87.xlsx + // 这种情况下最后一个才是正确的文件名 + let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)$/; + + let matches = disposition.match(filenameRegex); + if (matches && matches.length) { + filename = matches[1].replace(`UTF-8''`, '').replace(/['"]/g, ''); } // 很可能是中文被 url-encode 了 @@ -123,7 +127,7 @@ export function embed( }; const responseAdaptor = (api: any) => (value: any) => { - let response = value.data; + let response = value.data || {}; // blob 下可能会返回内容为空? // 之前拼写错了,需要兼容 if (env && env.responseAdpater) { env.responseAdaptor = env.responseAdpater; @@ -155,178 +159,190 @@ export function embed( return result; }; - renderReact( -
- - env?.getModalContainer?.() || container} - /> + const amisEnv = { + getModalContainer: () => + env?.getModalContainer?.() || document.querySelector('.amis-scope'), + notify: (type: string, msg: string) => + toast[type] + ? toast[type](msg, type === 'error' ? '系统错误' : '系统消息') + : console.warn('[Notify]', type, msg), + alert, + confirm, + updateLocation: (to: any, replace: boolean) => { + if (to === 'goBack') { + return window.history.back(); + } - {renderAmis( - schema, - { - ...props, - scopeRef: (ref: any) => (scoped = ref) - }, - { - getModalContainer: () => - env?.getModalContainer?.() || document.querySelector('.amis-scope'), - notify: (type: string, msg: string) => - toast[type] - ? toast[type](msg, type === 'error' ? '系统错误' : '系统消息') - : 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; + } - 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); + } - 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; - } - - 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.withCredentials = true; - responseType && (config.responseType = responseType); - - if (config.cancelExecutor) { - config.cancelToken = new (axios as any).CancelToken( - config.cancelExecutor - ); - } - - config.headers = headers || {}; - config.method = method; - - 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 (status) { - return true; - }; - - data && (config.data = data); - let response = await axios(url, config); - response = 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( - '接口报错:' + JSON.stringify(response.data, null, 2) - ); - } - } else { - throw new Error(`接口出错,状态码是 ${response.status}`); - } - } - - return response; - }, - isCancel: (value: any) => (axios as any).isCancel(value), - copy: (contents: string, options: any = {}) => { - const ret = copy(contents, options); - ret && options.shutup !== true && toast.info('内容已拷贝到剪切板'); - return ret; - }, - richTextToken: '', - affixOffsetBottom: 0, - ...env + if (search) { + if (pathname !== location.pathname || !location.search) { + return false; } - )} -
, - container - ); - return scoped; + + 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; + } + + 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.withCredentials = true; + responseType && (config.responseType = responseType); + + if (config.cancelExecutor) { + config.cancelToken = new (axios as any).CancelToken( + config.cancelExecutor + ); + } + + config.headers = headers || {}; + config.method = method; + + 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 (status) { + return true; + }; + + data && (config.data = data); + let response = await axios(url, 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( + '接口报错:' + JSON.stringify(response.data, null, 2) + ); + } + } else { + throw new Error(`接口出错,状态码是 ${response.status}`); + } + } + + return response; + }, + isCancel: (value: any) => (axios as any).isCancel(value), + copy: (contents: string, options: any = {}) => { + const ret = copy(contents, options); + ret && options.shutup !== true && toast.info('内容已拷贝到剪切板'); + return ret; + }, + richTextToken: '', + affixOffsetBottom: 0, + ...env + }; + + let amisProps: any = {}; + function createElements(props: any): any { + amisProps = { + ...amisProps, + ...props, + scopeRef: (ref: any) => (scoped = ref) + }; + + return ( +
+ + env?.getModalContainer?.() || container} + /> + + {renderAmis(schema, amisProps, amisEnv)} +
+ ); + } + + renderReact(createElements(props), container); + + return { + ...scoped, + updateProps: (props: any, callback?: () => void) => { + renderReact(createElements(props), container as HTMLElement, callback); + }, + unmount: () => { + unmountComponentAtNode(container as HTMLElement); + } + }; } diff --git a/src/store/app.ts b/src/store/app.ts index 115cea4c0..bb287d508 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -134,6 +134,11 @@ export const AppStore = ServiceStore.named('AppStore') }, setActivePage(page: any, env: RendererEnv, params?: any) { + // 同一个页面直接返回。 + if (self.activePage?.id === page.id) { + return; + } + let bcn: Array = []; findTree(self.pages, (item, index, level, paths) => {