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

View File

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

View File

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

View File

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

View File

@ -215,7 +215,8 @@ export class EditorManager {
constructor(
readonly config: EditorManagerConfig,
readonly store: EditorStoreType
readonly store: EditorStoreType,
readonly parent?: EditorManager
) {
// 传给 amis 渲染器的默认 env
this.env = {
@ -1358,6 +1359,20 @@ export class EditorManager {
* @param config
*/
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);
}
@ -2212,6 +2227,7 @@ export class EditorManager {
this.trigger('dispose', {
data: this
});
delete (this as any).parent;
this.toDispose.forEach(fn => fn());
this.toDispose = [];
this.plugins.forEach(p => p.dispose?.());

View File

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

View File

@ -1397,7 +1397,12 @@ export const scrollToActive = debounce((selector: string) => {
}
}, 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}};
// 如果有传入definitions则合并到schema中
@ -1407,7 +1412,10 @@ export function addModal(schema: any, modal: any, definitions?: any) {
let idx = 1;
while (true) {
if (!schema.definitions[`modal-ref-${idx}`]) {
if (
!schema.definitions[`modal-ref-${idx}`] &&
(!isKeyValid || isKeyValid(`modal-ref-${idx}`))
) {
break;
}
idx++;
@ -1435,16 +1443,42 @@ export function addModal(schema: any, modal: any, definitions?: any) {
*/
export function modalsToDefinitions(
modals: Array<EditorModalBody>,
definitions: any = {}
definitions: any = {},
edtingModal?: EditorModalBody
) {
let schema = {
definitions
};
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) {
schema.definitions[modal.$$ref] = JSONPipeIn(modal);
} 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;

View File

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

View File

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