Merge pull request #8166 from ascend13/dialog-fix-bugs

fix:修复弹窗大纲滚动条超出无法滚动,移动端弹窗预览挂载点出错等问题
This commit is contained in:
hsm-lv 2023-09-20 11:31:52 +08:00 committed by GitHub
commit 575893efdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 166 additions and 105 deletions

View File

@ -33,9 +33,24 @@
flex-direction: column;
height: 100%;
.ae-outline-tabs {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
.ae-outline-tabs-header {
padding: 0 12px;
}
.ae-outline-tabs-content {
flex: 1;
min-height: 0;
margin-top: 12px;
}
.ae-outline-tabs-panel.is-active {
display: flex;
flex-direction: column;
height: 100%;
padding: 0;
}
}
}

View File

@ -223,6 +223,8 @@
flex: 1;
}
.amis-dialog-widget {
// 编辑态防止内容超出底部空白
position: static;
z-index: 0;
}
}
@ -373,7 +375,8 @@
.ae-IFramePreview {
&,
& > .frame-content {
& > .frame-content,
.ae-Dialog-preview-mount-node {
width: 100%;
height: 100%;
display: flex;
@ -381,7 +384,7 @@
flex: 1;
}
> .frame-content > *:first-child {
> .frame-content .ae-Dialog-preview-mount-node > *:first-child {
flex: 1;
min-width: 100%;
position: relative;

View File

@ -28,6 +28,8 @@ export interface IFramePreviewProps {
@observer
export default class IFramePreview extends React.Component<IFramePreviewProps> {
initialContent: string = '';
dialogMountRef: React.RefObject<HTMLDivElement> = React.createRef();
iframeRef: HTMLIFrameElement;
constructor(props: IFramePreviewProps) {
super(props);
@ -66,9 +68,9 @@ export default class IFramePreview extends React.Component<IFramePreviewProps> {
}
@autobind
iframeRef(iframe: any) {
iframeRefFunc(iframe: any) {
const store = this.props.store;
this.iframeRef = iframe;
isAlive(store) && store.setIframe(iframe);
}
@ -83,6 +85,16 @@ export default class IFramePreview extends React.Component<IFramePreviewProps> {
return true;
}
@autobind
getDialogMountRef() {
return this.dialogMountRef.current;
}
@autobind
iframeContentDidMount() {
this.iframeRef.contentWindow?.document.body.classList.add(`is-modalOpened`);
}
render() {
const {editable, store, appLocale, autoFocus, env, data, manager, ...rest} =
this.props;
@ -91,27 +103,31 @@ export default class IFramePreview extends React.Component<IFramePreviewProps> {
<Frame
className={`ae-PreviewIFrame`}
initialContent={this.initialContent}
ref={this.iframeRef}
ref={this.iframeRefFunc}
contentDidMount={this.iframeContentDidMount}
>
<InnerComponent store={store} editable={editable} manager={manager} />
{render(
editable ? store.filteredSchema : store.filteredSchemaForPreview,
{
...rest,
key: editable ? 'edit-mode' : 'preview-mode',
theme: env.theme,
data: data ?? store.ctx,
locale: appLocale
},
{
...env,
session: `${env.session}-iframe-preview`,
useMobileUI: true,
isMobile: this.isMobile,
getModalContainer: this.getModalContainer
}
)}
<InnerSvgSpirit />
<div ref={this.dialogMountRef} className="ae-Dialog-preview-mount-node">
{render(
editable ? store.filteredSchema : store.filteredSchemaForPreview,
{
...rest,
key: editable ? 'edit-mode' : 'preview-mode',
theme: env.theme,
data: data ?? store.ctx,
locale: appLocale,
editorDialogMountNode: this.getDialogMountRef
},
{
...env,
session: `${env.session}-iframe-preview`,
useMobileUI: true,
isMobile: this.isMobile,
getModalContainer: this.getModalContainer
}
)}
<InnerSvgSpirit />
</div>
</Frame>
);
}
@ -191,7 +207,7 @@ function InnerComponent({
doc!.addEventListener('click', handleBodyClick);
layer!.addEventListener('mouseleave', handleMouseLeave);
layer!.addEventListener('mousemove', handleMouseMove);
layer!.addEventListener('click', handleClick);
layer!.addEventListener('click', handleClick, true);
layer!.addEventListener('mouseover', handeMouseOver);
const unSensor = resizeSensor(doc!.body, () => {

View File

@ -235,7 +235,7 @@ export class OutlinePanel extends React.Component<PanelProps> {
if (option.type === 'dialog' || option.type === 'drawer') {
if (!isNode || (isNode && !option.region)) {
if (option.type === 'drawer') {
rendererTitle = `${option[title] || '抽屉'}(抽屉`;
rendererTitle = `${option[title] || '抽屉式弹窗'}(抽屉式弹窗`;
} else {
if (option.dialogType === 'confirm') {
rendererTitle = `${option[title] || '确认对话框'}(确认对话框)`;
@ -284,11 +284,13 @@ export class OutlinePanel extends React.Component<PanelProps> {
<Tabs
className="ae-outline-tabs"
linksClassName="ae-outline-tabs-header"
contentClassName="ae-outline-tabs-content"
tabsMode="line"
onSelect={this.handleTabChange}
activeKey={outlineTabsKey}
>
<Tab
className={'ae-outline-tabs-panel'}
key={'component-outline'}
eventKey={'component-outline'}
title={'组件大纲'}
@ -334,6 +336,7 @@ export class OutlinePanel extends React.Component<PanelProps> {
</div>
</Tab>
<Tab
className={'ae-outline-tabs-panel'}
key={'dialog-outline'}
eventKey={'dialog-outline'}
title={'弹窗大纲'}

View File

@ -48,6 +48,7 @@ export interface PreviewState {
@observer
export default class Preview extends Component<PreviewProps> {
currentDom: HTMLElement; // 用于记录当前dom元素
dialogReaction: any;
env: RenderOptions = {
...this.props.manager.env,
notify: (type, msg, conf) => {
@ -72,22 +73,43 @@ export default class Preview extends Component<PreviewProps> {
this.currentDom.addEventListener('mouseleave', this.handleMouseLeave);
this.currentDom.addEventListener('mousemove', this.handleMouseMove);
this.currentDom.addEventListener('click', this.handleClick);
this.currentDom.addEventListener('click', this.handleClick, true);
this.currentDom.addEventListener('mouseover', this.handeMouseOver);
this.currentDom.addEventListener('mousedown', this.handeMouseDown);
this.props.manager.on('after-update', this.handlePanelChange);
const store = this.props.store;
// 添加弹窗事件或弹窗列表进行弹窗切换后自动选中对应的弹窗
this.dialogReaction = reaction(
() =>
store.root.children?.length
? `${store.root.children[0]?.type}:${store.root.children[0]?.id}`
: '',
info => {
const type = info.split(':')[0];
if (type === 'dialog' || type === 'drawer') {
const dialogId = info.split(':')[1];
store.changeOutlineTabsKey('dialog-outline');
store.setPreviewDialogId(dialogId);
store.setActiveId(dialogId);
} else {
store.setActiveId(store.getRootId());
}
}
);
}
componentWillUnmount() {
if (this.currentDom) {
this.currentDom.removeEventListener('mouseleave', this.handleMouseLeave);
this.currentDom.removeEventListener('mousemove', this.handleMouseMove);
this.currentDom.removeEventListener('click', this.handleClick);
this.currentDom.removeEventListener('click', this.handleClick, true);
this.currentDom.removeEventListener('mouseover', this.handeMouseOver);
this.currentDom.removeEventListener('mousedown', this.handeMouseDown);
this.props.manager.off('after-update', this.handlePanelChange);
this.dialogReaction?.();
}
this.scrollLayer?.removeEventListener('scroll', this.handlePanelChange);
@ -95,6 +117,18 @@ export default class Preview extends Component<PreviewProps> {
setTimeout(() => clearStoresCache([this.env.session!]), 500);
}
componentDidUpdate() {
const store = this.props.store;
if (store.activeDialogPath) {
let activeId = store.getSchemaByPath(
store.activeDialogPath.split('/')
)?.$$id;
activeId && store.setPreviewDialogId(activeId);
store.setActiveDialogPath('');
}
}
unSensor?: () => void;
layer?: HTMLDivElement;
scrollLayer?: HTMLDivElement;
@ -593,7 +627,6 @@ export interface SmartPreviewProps {
}
@observer
class SmartPreview extends React.Component<SmartPreviewProps> {
dialogReaction: any;
dialogMountRef: React.RefObject<HTMLDivElement> = React.createRef();
componentDidMount() {
@ -615,34 +648,10 @@ class SmartPreview extends React.Component<SmartPreviewProps> {
} else {
this.props.manager.buildRenderersAndPanels();
}
// 添加弹窗事件或弹窗列表进行弹窗切换后自动选中对应的弹窗
this.dialogReaction = reaction(
() =>
store.root.children?.length
? `${store.root.children[0]?.type}:${store.root.children[0]?.id}`
: '',
info => {
const type = info.split(':')[0];
if (type === 'dialog' || type === 'drawer') {
const dialogId = info.split(':')[1];
store.changeOutlineTabsKey('dialog-outline');
store.setPreviewDialogId(dialogId);
store.setActiveId(dialogId);
} else {
store.setActiveId(store.getRootId());
}
}
);
}
componentWillUnmount() {
this.dialogReaction?.();
}
componentDidUpdate(prevProps: SmartPreviewProps) {
const props = this.props;
const store = props.store;
if (props.editable !== prevProps.editable) {
if (props.editable) {
@ -652,13 +661,6 @@ class SmartPreview extends React.Component<SmartPreviewProps> {
});
}
}
if (store.activeDialogPath) {
let activeId = store.getSchemaByPath(
store.activeDialogPath.split('/')
)?.$$id;
activeId && store.setPreviewDialogId(activeId);
store.setActiveDialogPath('');
}
}
@autobind

View File

@ -76,6 +76,7 @@ export function makeWrapper(
path: this.props.$path,
schemaPath: info.schemaPath,
dialogTitle: info.dialogTitle,
dialogType: info.dialogType,
info,
getData: () => this.props.data
});

View File

@ -1272,6 +1272,7 @@ export class EditorManager {
schema.type === 'dialog' || schema.type === 'drawer'
? schema.title
: '',
dialogType: schema.dialogType,
schemaPath
};
return true;

View File

@ -303,6 +303,7 @@ export interface RendererInfo extends RendererScaffoldInfo {
/** 共享上下文 */
sharedContext?: Record<string, any>;
dialogTitle?: string; //弹窗标题用于弹窗大纲的展示
dialogType?: string; //区分确认对话框类型
}
export type BasicRendererInfo = Omit<

View File

@ -43,7 +43,7 @@ import {
JSONPipeOut,
JSONUpdate
} from '../util';
import type {Schema, SchemaNode} from 'amis';
import type {JSONSchema, Schema} from 'amis';
import {toast, resolveVariable} from 'amis';
import find from 'lodash/find';
import {InsertSubRendererPanel} from '../component/Panel/InsertSubRendererPanel';

View File

@ -602,6 +602,7 @@ export const EditorNode = types
preferTag?: string;
schemaPath?: string;
dialogTitle?: string;
dialogType?: string;
regionInfo?: RegionConfig;
widthMutable?: boolean;
memberIndex?: number;

View File

@ -1261,11 +1261,12 @@ export const scrollToActive = debounce((selector: string) => {
*
* @param schema schema
* @param listType list或label value形式的数据源
* @param dialogActions
* @param filterId id
*/
export const getDialogActions = (
schema: Schema,
listType: 'list' | 'source'
listType: 'list' | 'source',
filterId?: string
) => {
let dialogActions: any[] = [];
JSONTraverse(schema, (value: any, key: string, object: any) => {

View File

@ -10,7 +10,8 @@ import {
noop,
defaultValue,
EditorNodeType,
isEmpty
isEmpty,
getI18nEnabled
} from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control/helper';
import omit from 'lodash/omit';
@ -35,7 +36,7 @@ export class DialogPlugin extends BasePlugin {
$schema = '/schemas/DialogSchema.json';
// 组件名称
name = '弹';
name = '弹';
isBaseComponent = true;
wrapperProps = {
@ -128,6 +129,7 @@ export class DialogPlugin extends BasePlugin {
panelTitle = '弹框';
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
const i18nEnabled = getI18nEnabled();
// 确认对话框的配置面板
if (context.schema?.dialogType === 'confirm') {
return getSchemaTpl('tabs', [
@ -224,7 +226,7 @@ export class DialogPlugin extends BasePlugin {
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
{
label: '标题',
type: 'input-text',
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
name: 'title'
},
getSchemaTpl('switch', {
@ -321,7 +323,7 @@ export class DialogPlugin extends BasePlugin {
disabled: true,
clearable: true,
unitOptions: ['px', '%', 'em', 'vh', 'vw'],
visibleOn: 'data.size !== "custom"',
visibleOn: 'this.size !== "custom"',
pipeIn: (value: any, form: any) => {
if (!form.data.size) {
return '500px';
@ -343,7 +345,7 @@ export class DialogPlugin extends BasePlugin {
name: 'style.width',
clearable: true,
unitOptions: ['px', '%', 'em', 'vh', 'vw'],
visibleOn: 'data.size === "custom"',
visibleOn: 'this.size === "custom"',
pipeOut: (value: string) => {
const curValue = parseInt(value);
if (value === 'auto' || curValue || curValue === 0) {
@ -358,7 +360,7 @@ export class DialogPlugin extends BasePlugin {
label: '高度',
name: 'style.height',
disabled: true,
visibleOn: 'data.size !== "custom"',
visibleOn: 'this.size !== "custom"',
clearable: true,
unitOptions: ['px', '%', 'em', 'vh', 'vw']
},
@ -366,7 +368,7 @@ export class DialogPlugin extends BasePlugin {
type: 'input-number',
label: '高度',
name: 'style.height',
visibleOn: 'data.size === "custom"',
visibleOn: 'this.size === "custom"',
clearable: true,
unitOptions: ['px', '%', 'em', 'vh', 'vw'],
pipeOut: (value: string) => {
@ -488,19 +490,20 @@ export class DialogPlugin extends BasePlugin {
}
}
// 数据链
const hostNodeDataSchema =
await this.manager.config.getHostNodeDataSchema?.();
hostNodeDataSchema
.filter(
(item: any) => !['system-variable', 'page-global'].includes(item.$id)
)
?.forEach((item: any) => {
dataSchema = {
...dataSchema,
...item.properties
};
});
// 弹窗改版可能会有多个按钮触发一个弹窗,无法确定按钮的上下文
// TODO 数据链
// const hostNodeDataSchema =
// await this.manager.config.getHostNodeDataSchema?.();
// hostNodeDataSchema
// ?.filter(
// (item: any) => !['system-variable', 'page-global'].includes(item.$id)
// )
// ?.forEach((item: any) => {
// dataSchema = {
// ...dataSchema,
// ...item.properties
// };
// });
}
return {

View File

@ -8,7 +8,8 @@ import {
getSchemaTpl,
noop,
EditorNodeType,
isEmpty
isEmpty,
getI18nEnabled
} from 'amis-editor-core';
import {getEventControlConfig} from '../renderer/event-control/helper';
import {tipedLabel} from 'amis-editor-core';
@ -115,6 +116,7 @@ export class DrawerPlugin extends BasePlugin {
panelTitle = '弹框';
panelJustify = true;
panelBodyCreator = (context: BaseEventContext) => {
const i18nEnabled = getI18nEnabled();
return getSchemaTpl('tabs', [
{
title: '属性',
@ -125,7 +127,7 @@ export class DrawerPlugin extends BasePlugin {
getSchemaTpl('layout:originPosition', {value: 'left-top'}),
{
label: '标题',
type: 'input-text',
type: i18nEnabled ? 'input-text-i18n' : 'input-text',
name: 'title'
},
getSchemaTpl('switch', {
@ -366,19 +368,20 @@ export class DrawerPlugin extends BasePlugin {
}
}
// 弹窗改版可能会有多个按钮触发一个弹窗,无法确定按钮的上下文
// 数据链
const hostNodeDataSchema =
await this.manager.config.getHostNodeDataSchema?.();
hostNodeDataSchema
.filter(
(item: any) => !['system-variable', 'page-global'].includes(item.$id)
)
?.forEach((item: any) => {
dataSchema = {
...dataSchema,
...item.properties
};
});
// const hostNodeDataSchema =
// await this.manager.config.getHostNodeDataSchema?.();
// hostNodeDataSchema
// ?.filter(
// (item: any) => !['system-variable', 'page-global'].includes(item.$id)
// )
// ?.forEach((item: any) => {
// dataSchema = {
// ...dataSchema,
// ...item.properties
// };
// });
}
return {

View File

@ -6,7 +6,6 @@ import {
BaseEventContext,
defaultValue,
EditorManager,
getDialogActions,
getSchemaTpl,
JsonGenerateID,
JSONPipeIn,
@ -279,9 +278,6 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
const variableOptions = variableManager?.getVariableOptions() || [];
const pageVariableOptions = variableManager?.getPageVariablesOptions() || [];
// let dialogActions: any[] = [];
let dialogActions = getDialogActions(manager.store.schema, 'source');
return [
{
actionLabel: '页面',
@ -437,7 +433,7 @@ export const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
name: '__selectDialog',
type: 'select',
label: '选择弹窗',
options: dialogActions,
source: '${__dialogActions}',
mode: 'horizontal',
size: 'lg',
visibleOn: '__dialogSource === "current"',

View File

@ -35,7 +35,8 @@ import {
PluginEvents,
RendererPluginAction,
RendererPluginEvent,
SubRendererPluginAction
SubRendererPluginAction,
getDialogActions
} from 'amis-editor-core';
export * from './helper';
import {i18n as _i18n} from 'i18n-runtime';
@ -695,6 +696,11 @@ export class EventControl extends React.Component<
return updateComponentContext(variables);
}
// 获取现有弹窗列表
getDialogList(manager: EditorManager) {
return getDialogActions(manager.store.schema, 'source');
}
// 唤起动作配置弹窗
async activeActionDialog(
data: Pick<EventControlState, 'showAcionDialog' | 'type' | 'actionData'>
@ -757,6 +763,7 @@ export class EventControl extends React.Component<
__actionSchema: actionNode!.schema, // 树节点schema
__subActions: hasSubActionNode?.actions, // 树节点子动作
__cmptTreeSource: supportComponents ?? [],
__dialogActions: this.getDialogList(manager),
__superCmptTreeSource: allComponents,
// __supersCmptTreeSource: '',
__setValueDs: setValueDs
@ -805,7 +812,8 @@ export class EventControl extends React.Component<
variables,
pluginActions,
getContextSchemas,
__superCmptTreeSource: allComponents
__superCmptTreeSource: allComponents,
__dialogActions: this.getDialogList(manager)
};
}
this.setState(data);

View File

@ -430,9 +430,16 @@ setSchemaTpl('dataMap', {
className: 'mb-0',
pipeIn: defaultValue(false),
onChange: (value: boolean, origin: boolean, item: any, form: any) => {
const data = form.data?.data || {};
form.setValues({
data: value ? {'&': '$$'} : {},
dataMap: {}
data: value
? {
...data,
'&': '$$'
}
: data && data['&'] === '$$'
? omit(data, '&')
: data
});
}
}),