fix: 修复弹窗中的弹窗编辑器中出现重复的问题 (#10073)

This commit is contained in:
liaoxuezhi 2024-04-19 15:24:04 +08:00 committed by GitHub
parent 8563dd04ea
commit c9073e9716
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 126 additions and 50 deletions

View File

@ -40,6 +40,7 @@ export interface EditorProps extends PluginEventListener {
superEditorData?: any; superEditorData?: any;
withSuperDataSchema?: boolean; withSuperDataSchema?: boolean;
/** 当前 Editor 为 SubEditor 时触发的宿主节点 */ /** 当前 Editor 为 SubEditor 时触发的宿主节点 */
hostManager?: EditorManager;
hostNode?: EditorNodeType; hostNode?: EditorNodeType;
dataBindingChange?: ( dataBindingChange?: (
value: string, value: string,
@ -154,6 +155,7 @@ export default class Editor extends Component<EditorProps> {
onChange, onChange,
showCustomRenderersPanel, showCustomRenderersPanel,
superEditorData, superEditorData,
hostManager,
...rest ...rest
} = props; } = props;
@ -179,7 +181,7 @@ export default class Editor extends Component<EditorProps> {
this.store.setShowCustomRenderersPanel(showCustomRenderersPanel); this.store.setShowCustomRenderersPanel(showCustomRenderersPanel);
} }
this.manager = new EditorManager(config, this.store); this.manager = new EditorManager(config, this.store, hostManager);
// 子编辑器不再重新设置 editorStore // 子编辑器不再重新设置 editorStore
if (!(props.isSubEditor && (window as any).editorStore)) { if (!(props.isSubEditor && (window as any).editorStore)) {

View File

@ -4,17 +4,20 @@ import React from 'react';
import {EditorStoreType} from '../../store/editor'; import {EditorStoreType} from '../../store/editor';
import {JSONGetById, modalsToDefinitions, translateSchema} from '../../util'; import {JSONGetById, modalsToDefinitions, translateSchema} from '../../util';
import {Button, Icon, ListMenu, PopOverContainer, confirm} from 'amis'; import {Button, Icon, ListMenu, PopOverContainer, confirm} from 'amis';
import {EditorManager} from '../../manager';
export interface DialogListProps { export interface DialogListProps {
classnames: ClassNamesFn; classnames: ClassNamesFn;
store: EditorStoreType; store: EditorStoreType;
manager: EditorManager;
} }
export default observer(function DialogList({ export default observer(function DialogList({
classnames: cx, classnames: cx,
store store,
manager
}: DialogListProps) { }: DialogListProps) {
const modals = store.modals; const modals = store.modals.filter(item => !item.disabled);
const handleAddDialog = React.useCallback(() => { const handleAddDialog = React.useCallback(() => {
const modal = { const modal = {
@ -29,7 +32,7 @@ export default observer(function DialogList({
] ]
}; };
store.openSubEditor({ manager.openSubEditor({
title: '编辑弹窗', title: '编辑弹窗',
value: modal, value: modal,
onChange: ({definitions, ...modal}: any, diff: any) => { onChange: ({definitions, ...modal}: any, diff: any) => {
@ -42,19 +45,12 @@ export default observer(function DialogList({
const index = parseInt(event.currentTarget.getAttribute('data-index')!, 10); const index = parseInt(event.currentTarget.getAttribute('data-index')!, 10);
const modal = store.modals[index]; const modal = store.modals[index];
const modalId = modal.$$id!; const modalId = modal.$$id!;
store.openSubEditor({ manager.openSubEditor({
title: '编辑弹窗', title: '编辑弹窗',
value: { value: {
type: 'dialog', type: 'dialog',
...(modal as any), ...(modal as any),
definitions: modalsToDefinitions( definitions: modalsToDefinitions(store.modals, {}, modal)
store.modals.filter(
(m: any) =>
// 不要把自己下发,不允许弹窗自己再弹出自己
// 不要下发自己内容里面内嵌的弹窗,否则会导致子弹窗里面的弹窗列表重复
m.$$id !== modalId && !JSONGetById(modal, m.$$id)
)
)
}, },
onChange: ({definitions, ...modal}: any, diff: any) => { onChange: ({definitions, ...modal}: any, diff: any) => {
store.updateModal(modalId, modal, definitions); store.updateModal(modalId, modal, definitions);

View File

@ -273,7 +273,7 @@ export class OutlinePanel extends React.Component<PanelProps> {
render() { render() {
const {curSearchElemKey} = this.state; const {curSearchElemKey} = this.state;
const {store} = this.props; const {store, manager} = this.props;
const outlineTabsKey = store.outlineTabsKey || 'component-outline'; const outlineTabsKey = store.outlineTabsKey || 'component-outline';
const options = store.outline; const options = store.outline;
@ -341,7 +341,7 @@ export class OutlinePanel extends React.Component<PanelProps> {
eventKey={'dialog-outline'} eventKey={'dialog-outline'}
title={'弹窗列表'} title={'弹窗列表'}
> >
<DialogList store={store} classnames={cx} /> <DialogList manager={manager} store={store} classnames={cx} />
</Tab> </Tab>
)} )}
</Tabs> </Tabs>

View File

@ -146,6 +146,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
ref={store.subEditorRef} ref={store.subEditorRef}
onChange={onChange} onChange={onChange}
data={store.subEditorContext?.data} data={store.subEditorContext?.data}
hostManager={manager}
hostNode={store.subEditorContext?.hostNode} hostNode={store.subEditorContext?.hostNode}
superEditorData={superEditorData} superEditorData={superEditorData}
schemaFilter={manager.config.schemaFilter} schemaFilter={manager.config.schemaFilter}

View File

@ -215,7 +215,8 @@ export class EditorManager {
constructor( constructor(
readonly config: EditorManagerConfig, readonly config: EditorManagerConfig,
readonly store: EditorStoreType readonly store: EditorStoreType,
readonly parent?: EditorManager
) { ) {
// 传给 amis 渲染器的默认 env // 传给 amis 渲染器的默认 env
this.env = { this.env = {
@ -1358,6 +1359,20 @@ export class EditorManager {
* @param config * @param config
*/ */
openSubEditor(config: SubEditorContext) { openSubEditor(config: SubEditorContext) {
if (
['dialog', 'drawer', 'confirmDialog'].includes(config.value.type) &&
this.parent
) {
let parent: EditorManager | undefined = this.parent;
while (parent) {
if (parent.store.schema.$$id === config.value.$$id) {
toast.warning('所选弹窗已经被打开,不能多次打开');
return;
}
parent = parent.parent;
}
}
this.store.openSubEditor(config); this.store.openSubEditor(config);
} }
@ -2212,6 +2227,7 @@ export class EditorManager {
this.trigger('dispose', { this.trigger('dispose', {
data: this data: this
}); });
delete (this as any).parent;
this.toDispose.forEach(fn => fn()); this.toDispose.forEach(fn => fn());
this.toDispose = []; this.toDispose = [];
this.plugins.forEach(p => p.dispose?.()); this.plugins.forEach(p => p.dispose?.());

View File

@ -133,6 +133,10 @@ export type EditorModalBody = (DialogSchema | DrawerSchema) & {
// 如果是公共弹窗,在 definitions 中的 key // 如果是公共弹窗,在 definitions 中的 key
$$ref?: string; $$ref?: string;
// 内嵌弹窗会转成公共弹窗下发给子弹窗,否则子弹窗里面无法选择
// 这类会在 definition 里面标记原始位置
$$originId?: string;
// 弹出方式 // 弹出方式
actionType?: string; actionType?: string;
}; };
@ -548,10 +552,14 @@ export const MainStore = types
return undefined; return undefined;
} }
const isSubEditor = self.isSubEditor;
return JSONPipeOut( return JSONPipeOut(
JSONGetById(self.schema, self.activeId), JSONGetById(self.schema, self.activeId),
getEnv(self).isHiddenProps || getEnv(self).isHiddenProps ||
((key, props) => ((key, props) =>
// 如果是子弹窗,不显示 definitions要是通过代码模式改了就麻烦了
(isSubEditor && key === 'definitions') ||
(key.substring(0, 2) === '$$' && (key.substring(0, 2) === '$$' &&
key !== '$$comments' && key !== '$$comments' &&
key !== '$$commonSchema') || key !== '$$commonSchema') ||
@ -1015,15 +1023,7 @@ export const MainStore = types
get modals(): Array<EditorModalBody> { get modals(): Array<EditorModalBody> {
const schema = self.schema; const schema = self.schema;
const modals: Array<DialogSchema | DrawerSchema> = []; const modals: Array<DialogSchema | DrawerSchema> = [];
Object.keys(schema.definitions || {}).forEach(key => {
const definition = schema.definitions[key];
if (['dialog', 'drawer'].includes(definition.type)) {
modals.push({
...definition,
$$ref: key
});
}
});
JSONTraverse(schema, (value: any, key: string, host: any) => { JSONTraverse(schema, (value: any, key: string, host: any) => {
if ( if (
key === 'actionType' && key === 'actionType' &&
@ -1031,12 +1031,7 @@ export const MainStore = types
) { ) {
const key = value === 'drawer' ? 'drawer' : 'dialog'; const key = value === 'drawer' ? 'drawer' : 'dialog';
const body = host[key] || host['args']; const body = host[key] || host['args'];
if ( if (body && !body.$ref) {
body &&
!body.$ref &&
body.$$id &&
!modals.find(m => (m as any).$$originId === body.$$id)
) {
modals.push({ modals.push({
...body, ...body,
actionType: value actionType: value
@ -1045,6 +1040,37 @@ export const MainStore = types
} }
return 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)) {
modals.unshift({
...schema,
// 如果还包含这个,子弹窗里面收集弹窗的时候会出现多份内嵌弹窗
definitions: undefined
});
}
return modals; return modals;
}, },

View File

@ -1397,7 +1397,12 @@ export const scrollToActive = debounce((selector: string) => {
} }
}, 200); }, 200);
export function addModal(schema: any, modal: any, definitions?: any) { export function addModal(
schema: any,
modal: any,
definitions?: any,
isKeyValid?: (key: string) => boolean
) {
schema = {...schema, definitions: {...schema.definitions}}; schema = {...schema, definitions: {...schema.definitions}};
// 如果有传入definitions则合并到schema中 // 如果有传入definitions则合并到schema中
@ -1407,7 +1412,10 @@ export function addModal(schema: any, modal: any, definitions?: any) {
let idx = 1; let idx = 1;
while (true) { while (true) {
if (!schema.definitions[`modal-ref-${idx}`]) { if (
!schema.definitions[`modal-ref-${idx}`] &&
(!isKeyValid || isKeyValid(`modal-ref-${idx}`))
) {
break; break;
} }
idx++; idx++;
@ -1435,16 +1443,42 @@ export function addModal(schema: any, modal: any, definitions?: any) {
*/ */
export function modalsToDefinitions( export function modalsToDefinitions(
modals: Array<EditorModalBody>, modals: Array<EditorModalBody>,
definitions: any = {} definitions: any = {},
edtingModal?: EditorModalBody
) { ) {
let schema = { let schema = {
definitions definitions
}; };
modals.forEach((modal, idx) => { modals.forEach((modal, idx) => {
if (
edtingModal &&
(edtingModal.$$ref
? edtingModal.$$ref === modal.$$ref
: edtingModal.$$id === modal.$$id)
) {
// 自己不需要转成 definitions
return;
} else if (
!modal.$$ref &&
modal.$$id &&
(JSONGetById(schema.definitions, modal.$$id) ||
(edtingModal && JSONGetById(edtingModal, modal.$$id)))
) {
// 内嵌弹窗,已经包含在 definitions 里面了
// 不需要转成 definitions
return;
}
if (modal.$$ref) { if (modal.$$ref) {
schema.definitions[modal.$$ref] = JSONPipeIn(modal); schema.definitions[modal.$$ref] = JSONPipeIn(modal);
} else { } else {
[schema] = addModal(schema, {...modal, $$originId: modal.$$id}); [schema] = addModal(
schema,
{...modal, $$originId: modal.$$id},
undefined,
key => !modals.find(m => m.$$ref && m.$$ref === key)
);
} }
}); });
return schema.definitions; return schema.definitions;

View File

@ -79,7 +79,7 @@ function DialogActionPanel({
subscribeSchemaSubmit((schema: any, nodeSchema: any, id: string) => { subscribeSchemaSubmit((schema: any, nodeSchema: any, id: string) => {
const rawActions = JSONGetById(schema, id)?.onEvent[eventKey]?.actions; const rawActions = JSONGetById(schema, id)?.onEvent[eventKey]?.actions;
if (!rawActions || !Array.isArray(rawActions)) { if (!rawActions || !Array.isArray(rawActions)) {
throw new Error('动作配置错误'); return;
} }
const actionSchema = const actionSchema =
@ -88,8 +88,9 @@ function DialogActionPanel({
? rawActions.length - 1 ? rawActions.length - 1
: actionIndex : actionIndex
]; ];
const modals: Array<LocalModal> = actionSchema.__actionModals; const modals: Array<LocalModal> = actionSchema?.__actionModals;
if (!Array.isArray(modals)) { if (!Array.isArray(modals)) {
// 不是编辑确定触发的,直接返回
return schema; return schema;
} }
@ -288,6 +289,7 @@ function DialogActionPanel({
actionSchema.actionType === 'drawer' ? 'drawer' : 'dialog' actionSchema.actionType === 'drawer' ? 'drawer' : 'dialog'
] || actionSchema.args; ] || actionSchema.args;
const schema = store.schema;
const modals: Array<LocalModal> = store.modals.map(modal => { const modals: Array<LocalModal> = store.modals.map(modal => {
const isCurrentActionModal = modal.$$id === dialogBody?.$$id; const isCurrentActionModal = modal.$$id === dialogBody?.$$id;
@ -310,7 +312,11 @@ function DialogActionPanel({
value: modal.$$id, value: modal.$$id,
modal: modal, modal: modal,
isCurrentActionModal, isCurrentActionModal,
data: modal.data data: modal.data,
// 当前编辑的弹窗不让再里面再次弹出
disabled: modal.$$ref
? modal.$$ref === schema.$$ref
: modal.$$id === schema.$$id
}; };
}); });
@ -458,7 +464,7 @@ function DialogActionPanel({
skipForm?: boolean, skipForm?: boolean,
closePopOver?: () => void closePopOver?: () => void
) => { ) => {
store.openSubEditor({ manager.openSubEditor({
title: '新建弹窗', title: '新建弹窗',
value: { value: {
type: 'dialog', type: 'dialog',
@ -513,7 +519,7 @@ function DialogActionPanel({
if (!currentModal) { if (!currentModal) {
return; return;
} }
store.openSubEditor({ manager.openSubEditor({
title: '编辑弹窗', title: '编辑弹窗',
value: { value: {
type: 'dialog', type: 'dialog',
@ -526,7 +532,9 @@ function DialogActionPanel({
], ],
...(currentModal.modal as any), ...(currentModal.modal as any),
definitions: modalsToDefinitions( definitions: modalsToDefinitions(
modals.filter(item => !item.isActive).map(item => item.modal) modals.map(item => item.modal),
{},
currentModal.modal
) )
}, },
onChange: ({definitions, ...modal}: any, diff: any) => { onChange: ({definitions, ...modal}: any, diff: any) => {

View File

@ -321,19 +321,12 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
e.stopPropagation(); e.stopPropagation();
const modalId = modal.$$id; const modalId = modal.$$id;
store.openSubEditor({ manager.openSubEditor({
title: '编辑弹窗', title: '编辑弹窗',
value: { value: {
type: 'dialog', type: 'dialog',
...modal, ...modal,
definitions: modalsToDefinitions( definitions: modalsToDefinitions(store.modals, {}, modal)
store.modals.filter(
(m: any) =>
// 不要把自己下发,不允许弹窗自己再弹出自己
// 不要下发自己内容里面内嵌的弹窗,否则会导致子弹窗里面的弹窗列表重复
m.$$id !== modalId && !JSONGetById(modal, m.$$id)
)
)
}, },
onChange: ({definitions, ...modal}: any, diff: any) => { onChange: ({definitions, ...modal}: any, diff: any) => {
store.updateModal(modalId, modal, definitions); store.updateModal(modalId, modal, definitions);