From 13f1890db7dff1ed74d80e5b910401ef10830ab8 Mon Sep 17 00:00:00 2001 From: qkiroc <30946345+qkiroc@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:54:51 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=20=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8F=AA=E8=AF=BB=E6=A8=A1=E5=BC=8F=20(#1085?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 扩展编辑器功能 * 弹框支持readonly * bugfix * bugfix --------- Co-authored-by: qinhaoyan <30946345+qinhaoyan@users.noreply.github.com> Co-authored-by: hsm-lv <80095014+hsm-lv@users.noreply.github.com> --- packages/amis-editor-core/scss/editor.scss | 13 +++ .../amis-editor-core/src/component/Editor.tsx | 13 ++- .../src/component/HighlightBox.tsx | 6 +- .../src/component/Panel/RightPanels.tsx | 30 +++++- .../src/component/Preview.tsx | 4 + .../src/component/SubEditor.tsx | 13 ++- .../src/component/base/SchemaForm.tsx | 6 +- packages/amis-editor-core/src/plugin.ts | 10 ++ packages/amis-editor-core/src/store/editor.ts | 101 +++++++----------- packages/amis-editor-core/src/store/node.ts | 22 ++++ packages/amis-editor-core/src/util.ts | 58 ++++++++++ 11 files changed, 204 insertions(+), 72 deletions(-) diff --git a/packages/amis-editor-core/scss/editor.scss b/packages/amis-editor-core/scss/editor.scss index e3a3af6bf..952939812 100644 --- a/packages/amis-editor-core/scss/editor.scss +++ b/packages/amis-editor-core/scss/editor.scss @@ -83,6 +83,7 @@ min-width: 980px; overflow: hidden; user-select: none; + position: relative; // 覆盖amis默认top值,避免导致未垂直居中 .ae-Editor-toolbar svg.icon { @@ -125,6 +126,18 @@ border: 1px dashed rgb(206, 208, 211); } +.subEditor-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + & .subEditor-dialog { + height: 100%; + margin: 0; + } +} + // 弹窗编辑器面板 .subEditor-dialog { overflow: hidden; diff --git a/packages/amis-editor-core/src/component/Editor.tsx b/packages/amis-editor-core/src/component/Editor.tsx index f69c051b4..28c436ccb 100644 --- a/packages/amis-editor-core/src/component/Editor.tsx +++ b/packages/amis-editor-core/src/component/Editor.tsx @@ -135,6 +135,7 @@ export interface EditorProps extends PluginEventListener { getHostNodeDataSchema?: () => Promise; getAvaiableContextFields?: (node: EditorNodeType) => Promise; + readonly?: boolean; } export default class Editor extends Component { @@ -275,6 +276,10 @@ export default class Editor extends Component { return; } + if (this.props.readonly) { + return; + } + const manager = this.manager; const store = manager.store; @@ -573,7 +578,8 @@ export default class Editor extends Component { previewProps, autoFocus, isSubEditor, - amisEnv + amisEnv, + readonly } = this.props; return ( @@ -588,7 +594,7 @@ export default class Editor extends Component { )} >
- {!preview && ( + {!preview && !readonly && ( { amisEnv={amisEnv} autoFocus={autoFocus} toolbarContainer={this.getToolbarContainer} + readonly={readonly} >
@@ -628,6 +635,7 @@ export default class Editor extends Component { theme={theme} appLocale={appLocale} amisEnv={amisEnv} + readonly={readonly} /> )} @@ -639,6 +647,7 @@ export default class Editor extends Component { manager={this.manager} theme={theme} amisEnv={amisEnv} + readonly={readonly} /> void; manager: EditorManager; children?: React.ReactNode; + readonly?: boolean; } export default observer(function ({ @@ -30,7 +31,8 @@ export default observer(function ({ node, toolbarContainer, onSwitch, - manager + manager, + readonly }: HighlightBoxProps) { const handleWResizerMouseDown = React.useCallback( (e: MouseEvent) => startResize(e, 'horizontal'), @@ -250,7 +252,7 @@ export default observer(function ({ draggable={!!curFreeContainerId || isDraggableContainer} onDragStart={handleDragStart} > - {isActive ? ( + {isActive && !readonly ? (
+ ) { + const {manager, readonly} = this.props; + + if (readonly) { + const diff = arg[1]; + if ( + !diff?.find((item: any) => + item.path.find( + (p: string) => !p.startsWith('__') && p !== 'pullRefresh' + ) + ) + ) { + return; + } + toast.error('不支持编辑'); + } else { + manager.panelChangeValue(...arg); + } + } + render() { const {store, manager, theme} = this.props; const {isOpenStatus, isFixedStatus} = this.state; @@ -77,7 +101,7 @@ export class RightPanels extends React.Component< path: node?.path, node: node, value: store.value, - onChange: manager.panelChangeValue, + onChange: this.handlePanelChangeValue, store: store, manager: manager, popOverContainer: this.getPopOverContainer @@ -90,7 +114,7 @@ export class RightPanels extends React.Component< info={node?.info} path={node?.path} value={store.value} - onChange={manager.panelChangeValue} + onChange={this.handlePanelChangeValue} store={store} manager={manager} popOverContainer={this.getPopOverContainer} diff --git a/packages/amis-editor-core/src/component/Preview.tsx b/packages/amis-editor-core/src/component/Preview.tsx index b066865af..4bfb4ea79 100644 --- a/packages/amis-editor-core/src/component/Preview.tsx +++ b/packages/amis-editor-core/src/component/Preview.tsx @@ -39,6 +39,8 @@ export interface PreviewProps { autoFocus?: boolean; toolbarContainer?: () => any; + + readonly?: boolean; } export interface PreviewState { @@ -604,9 +606,11 @@ export default class Preview extends Component { toolbarContainer={toolbarContainer} onSwitch={this.handleNavSwitch} manager={manager} + readonly={this.props.readonly} > {node.childRegions.map(region => !node.memberImmutable(region.region) && + !this.props.readonly && store.isRegionActive(region.id, region.region) ? ( { } buildSchema() { - const {store, manager, amisEnv} = this.props; + const {store, manager, amisEnv, readonly} = this.props; const subEditorContext = store.subEditorContext; const config = manager.config; let superEditorData: any = store.superEditorData; @@ -118,6 +119,7 @@ export class SubEditor extends React.Component { ? { type: 'form', mode: 'normal', + wrapWithPanel: false, wrapperComponent: 'div', onValidate: async (value: any) => { const result = await store.subEditorContext?.validate?.(value); @@ -190,6 +192,7 @@ export class SubEditor extends React.Component { getAvaiableContextFields={node => manager.getAvailableContextFields(node) } + readonly={readonly} /> ) } @@ -244,10 +247,14 @@ export class SubEditor extends React.Component { } render() { - const {store, theme, manager} = this.props; + const {store, theme, manager, readonly} = this.props; + if (!store.subEditorContext) { + return null; + } return render( { - type: 'dialog', + type: readonly ? 'container' : 'dialog', + className: readonly ? 'subEditor-container' : 'subEditor-dialog', ...this.buildSchema() }, diff --git a/packages/amis-editor-core/src/component/base/SchemaForm.tsx b/packages/amis-editor-core/src/component/base/SchemaForm.tsx index f5a6a467d..856e659b7 100644 --- a/packages/amis-editor-core/src/component/base/SchemaForm.tsx +++ b/packages/amis-editor-core/src/component/base/SchemaForm.tsx @@ -101,6 +101,8 @@ export function SchemaFrom({ return schema; }, [body, controls, submitOnChange]); + const [init, setInit] = React.useState(true); + const themeConfig = React.useMemo(() => getThemeConfig(), []); const submitSubscribers = React.useRef>([]); const subscribeSubmit = React.useCallback( @@ -147,10 +149,10 @@ export function SchemaFrom({ newValue = pipeOut ? await pipeOut(newValue, value) : newValue; const diffValue = diff(value, newValue); // 没有变化时不触发onChange - if (!diffValue) { + if (!diffValue || init) { + setInit(false); return; } - onChange(newValue, diffValue, (schema, value, id, diff) => { return submitSubscribers.current.reduce((schema, fn) => { return fn(schema, value, id, diff); diff --git a/packages/amis-editor-core/src/plugin.ts b/packages/amis-editor-core/src/plugin.ts index a609c76fd..4bea29a08 100644 --- a/packages/amis-editor-core/src/plugin.ts +++ b/packages/amis-editor-core/src/plugin.ts @@ -1029,6 +1029,9 @@ export abstract class BasePlugin implements PluginInterface { static scene = ['global']; + name?: string; + rendererName?: string; + /** * 如果配置里面有 rendererName 自动返回渲染器信息。 * @param renderer @@ -1279,6 +1282,13 @@ export abstract class BasePlugin implements PluginInterface { originalValue: node.schema.value // 记录原始值,循环引用检测需要 } as any; } + + getKeyAndName() { + return { + key: this.rendererName, + name: this.name + }; + } } /** diff --git a/packages/amis-editor-core/src/store/editor.ts b/packages/amis-editor-core/src/store/editor.ts index dfbba1be9..2573b8ddb 100644 --- a/packages/amis-editor-core/src/store/editor.ts +++ b/packages/amis-editor-core/src/store/editor.ts @@ -23,7 +23,8 @@ import { appTranslate, JSONGetByPath, addModal, - mergeDefinitions + mergeDefinitions, + getModals } from '../../src/util'; import { InsertEventContext, @@ -404,6 +405,9 @@ export const MainStore = types ): EditorNodeType | undefined { return self.root.getNodeById(id, regionOrType); }, + getNodeByComponentId(id: string): EditorNodeType | undefined { + return self.root.getNodeByComponentId(id); + }, get activeNodeInfo(): RendererInfo | null | undefined { return this.getNodeById(self.activeId)?.info; @@ -1039,64 +1043,7 @@ export const MainStore = types // 获取弹窗大纲列表 get modals(): Array { const schema = self.schema; - const modals: Array = []; - - JSONTraverse(schema, (value: any, key: string, host: any) => { - if ( - key === 'actionType' && - ['dialog', 'drawer', 'confirmDialog'].includes(value) - ) { - const key = value === 'drawer' ? 'drawer' : 'dialog'; - const body = host[key] || host['args']; - if ( - body && - !body.$ref && - !modals.find(item => item.$$id === body.$$id) - ) { - modals.push({ - ...body, - type: key, - actionType: value - }); - } - } - return value; - }); - - // 公共组件排在前面 - Object.keys(schema.definitions || {}) - .reverse() - .forEach(key => { - const definition = schema.definitions[key]; - if (['dialog', 'drawer'].includes(definition.type)) { - // 不要把已经内嵌弹窗中的弹窗再放到外面 - if ( - definition.$$originId && - modals.find(item => item.$$id === definition.$$originId) - ) { - return; - } - - modals.unshift({ - ...definition, - $$ref: key - }); - } - }); - - // 子弹窗时,自己就是个弹窗 - if (['dialog', 'drawer', 'confirmDialog'].includes(schema.type)) { - const idx = modals.findIndex(item => item.$$id === schema.$$id); - if (~idx) { - modals.splice(idx, 1); - } - - modals.unshift({ - ...schema, - // 如果还包含这个,子弹窗里面收集弹窗的时候会出现多份内嵌弹窗 - definitions: undefined - }); - } + const modals: Array = getModals(schema); return modals; }, @@ -1367,7 +1314,8 @@ export const MainStore = types setActiveId( id: string, region: string = '', - selections: Array = [] + selections: Array = [], + onEditorActive: boolean = true ) { const node = id ? self.getNodeById(id) : undefined; @@ -1381,6 +1329,39 @@ export const MainStore = types // if (!self.panelKey && id) { // self.panelKey = 'config'; // } + const schema = self.getSchema(id); + + onEditorActive && (window as any).onEditorActive?.(schema); + }, + + setActiveIdByComponentId(id: string) { + const node = self.getNodeByComponentId(id); + if (node) { + this.setActiveId(node.id, node.region, [], false); + this.closeSubEditor(); + } else { + const modals = self.modals; + const modalSchema = find(modals, modal => modal.id === id); + if (modalSchema) { + this.openSubEditor({ + value: modalSchema, + title: '弹窗预览', + onChange: (value: any) => {} + }); + } else { + const subEditorRef = this.getSubEditorRef(); + if (subEditorRef) { + subEditorRef.store.setActiveIdByComponentId(id); + const $$id = subEditorRef.props.value.$$id; + const modalSchema = find(modals, modal => modal.$$id === $$id); + this.openSubEditor({ + value: modalSchema, + title: '弹窗预览', + onChange: (value: any) => {} + }); + } + } + } }, setSelections(ids: Array) { diff --git a/packages/amis-editor-core/src/store/node.ts b/packages/amis-editor-core/src/store/node.ts index 1d3bcf624..74323cdfe 100644 --- a/packages/amis-editor-core/src/store/node.ts +++ b/packages/amis-editor-core/src/store/node.ts @@ -104,6 +104,28 @@ export const EditorNode = types return resolved; }, + getNodeByComponentId(id: string) { + let pool = self.children.concat(); + let resolved: any = undefined; + + while (pool.length) { + const item = pool.shift(); + const schema = item.schema; + + if (schema && schema.id === id) { + resolved = item; + break; + } + + // 将当前节点的子节点全部放置到 pool中 + if (item.children.length) { + pool.push.apply(pool, item.uniqueChildren); + } + } + + return resolved; + }, + setInfo(value: RendererInfo) { info = value; }, diff --git a/packages/amis-editor-core/src/util.ts b/packages/amis-editor-core/src/util.ts index 53cb53b56..22e4af8ff 100644 --- a/packages/amis-editor-core/src/util.ts +++ b/packages/amis-editor-core/src/util.ts @@ -23,6 +23,8 @@ import merge from 'lodash/merge'; import {EditorModalBody} from './store/editor'; import {filter} from 'lodash'; import type {SchemaType} from 'amis/lib/Schema'; +import type {DialogSchema} from '../../amis/src/renderers/Dialog'; +import type {DrawerSchema} from '../../amis/src/renderers/Drawer'; const { guid, @@ -1852,6 +1854,62 @@ export function setDefaultColSize( return tempList; } +export function getModals(schema: any) { + const modals: Array = []; + JSONTraverse(schema, (value: any, key: string, host: any) => { + if ( + key === 'actionType' && + ['dialog', 'drawer', 'confirmDialog'].includes(value) + ) { + const key = value === 'drawer' ? 'drawer' : 'dialog'; + const body = host[key] || host['args']; + if (body && !body.$ref && !modals.find(item => item.$$id === body.$$id)) { + modals.push({ + ...body, + type: key, + actionType: value + }); + } + } + return value; + }); + + // 公共组件排在前面 + Object.keys(schema.definitions || {}) + .reverse() + .forEach(key => { + const definition = schema.definitions[key]; + if (['dialog', 'drawer'].includes(definition.type)) { + // 不要把已经内嵌弹窗中的弹窗再放到外面 + if ( + definition.$$originId && + modals.find(item => item.$$id === definition.$$originId) + ) { + return; + } + + modals.unshift({ + ...definition, + $$ref: key + }); + } + }); + + // 子弹窗时,自己就是个弹窗 + if (['dialog', 'drawer', 'confirmDialog'].includes(schema.type)) { + const idx = modals.findIndex(item => item.$$id === schema.$$id); + if (~idx) { + modals.splice(idx, 1); + } + + modals.unshift({ + ...schema, + // 如果还包含这个,子弹窗里面收集弹窗的时候会出现多份内嵌弹窗 + definitions: undefined + }); + } + return modals; +} export const RAW_TYPE_MAP: { [k in SchemaType | 'user-select' | 'department-select']?: | 'string'