mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-03 14:58:47 +08:00
refactor(editor): 解耦 FloatingBox 的 uiService 依赖并改为 props 传入
- FloatingBox 不再强制依赖 uiService - frameworkWidth 默认回退到视窗宽度 - 新增 initialStyle prop 支持外部设置初始样式 - 各调用方显式传入 frameworkWidth 以保留右边界收敛行为
This commit is contained in:
parent
284be0d276
commit
3b9fb714e5
@ -6,6 +6,7 @@
|
||||
v-model:height="codeBlockEditorHeight"
|
||||
:body-style="{ padding: '0 16px' }"
|
||||
:title="content.name ? `${disabled ? '查看' : '编辑'}${content.name}` : '新增代码'"
|
||||
:framework-width="frameworkWidth"
|
||||
:position="boxPosition"
|
||||
:before-close="beforeClose"
|
||||
>
|
||||
@ -207,6 +208,7 @@ const closedHandler = () => {
|
||||
|
||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
||||
|
||||
watch(boxVisible, (visible) => {
|
||||
nextTick(() => {
|
||||
|
||||
@ -30,7 +30,6 @@ import VanillaMoveable from 'moveable';
|
||||
import { TMagicButton, useZIndex } from '@tmagic/design';
|
||||
|
||||
import MIcon from '@editor/components/Icon.vue';
|
||||
import { useServices } from '@editor/hooks/use-services';
|
||||
|
||||
interface Position {
|
||||
left: number;
|
||||
@ -46,11 +45,17 @@ const props = withDefaults(
|
||||
position?: Position;
|
||||
title?: string;
|
||||
bodyStyle?: CSSProperties;
|
||||
/** 浮窗初始样式,会与内部计算样式合并,外部传入优先 */
|
||||
initialStyle?: CSSProperties;
|
||||
/** 用于约束浮窗 left 的容器宽度,传入时按宽度收敛 left,避免超出右边界;默认取视窗宽度 */
|
||||
frameworkWidth?: number;
|
||||
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
||||
}>(),
|
||||
{
|
||||
title: '',
|
||||
position: () => ({ left: 0, top: 0 }),
|
||||
initialStyle: () => ({}),
|
||||
frameworkWidth: 0,
|
||||
},
|
||||
);
|
||||
|
||||
@ -73,12 +78,11 @@ const bodyHeight = computed(() => {
|
||||
return 'auto';
|
||||
});
|
||||
|
||||
const { uiService } = useServices();
|
||||
const frameworkWidth = computed(() => uiService.get('frameworkRect').width || 0);
|
||||
const style = computed(() => {
|
||||
let { left } = props.position;
|
||||
if (width.value) {
|
||||
left = left + width.value > frameworkWidth.value ? frameworkWidth.value - width.value : left;
|
||||
const frameworkWidth = props.frameworkWidth || globalThis.window?.innerWidth || 0;
|
||||
if (width.value && frameworkWidth) {
|
||||
left = left + width.value > frameworkWidth ? frameworkWidth - width.value : left;
|
||||
}
|
||||
|
||||
return {
|
||||
@ -86,6 +90,7 @@ const style = computed(() => {
|
||||
top: `${props.position.top}px`,
|
||||
width: width.value ? `${width.value}px` : 'auto',
|
||||
height: height.value ? `${height.value}px` : 'auto',
|
||||
...props.initialStyle,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
v-model:width="width"
|
||||
v-model:height="editorHeight"
|
||||
:title="fieldTitle"
|
||||
:framework-width="frameworkWidth"
|
||||
:position="boxPosition"
|
||||
>
|
||||
<template #body>
|
||||
@ -34,6 +35,7 @@
|
||||
v-model:width="width"
|
||||
v-model:height="editorHeight"
|
||||
title="快速添加数据定义"
|
||||
:framework-width="frameworkWidth"
|
||||
:position="boxPosition"
|
||||
>
|
||||
<template #body>
|
||||
@ -360,6 +362,7 @@ const { height: editorHeight } = useEditorContentHeight();
|
||||
|
||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
||||
|
||||
/**
|
||||
* 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
v-model:width="width"
|
||||
v-model:height="editorHeight"
|
||||
:title="drawerTitle"
|
||||
:framework-width="frameworkWidth"
|
||||
:position="boxPosition"
|
||||
>
|
||||
<template #body>
|
||||
@ -255,4 +256,5 @@ const addDialogVisible = defineModel<boolean>('visible', { default: false });
|
||||
const { height: editorHeight } = useEditorContentHeight();
|
||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
||||
</script>
|
||||
|
||||
@ -132,6 +132,7 @@
|
||||
v-model:height="columnLeftHeight"
|
||||
:width="columnLeftWidth"
|
||||
:title="config.text"
|
||||
:framework-width="frameworkWidth"
|
||||
:position="{
|
||||
left: floatBoxStates[config.$key].left,
|
||||
top: floatBoxStates[config.$key].top,
|
||||
@ -221,6 +222,7 @@ const taskLength = computed(() => depService.get('taskLength'));
|
||||
const tipsBarVisible = ref(true);
|
||||
|
||||
const columnLeftWidth = computed(() => uiService.get('columnWidth')[ColumnLayout.LEFT]);
|
||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
||||
const { height: editorContentHeight } = useEditorContentHeight();
|
||||
const columnLeftHeight = ref(0);
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
v-model:width="width"
|
||||
v-model:height="editorHeight"
|
||||
:title="title"
|
||||
:framework-width="frameworkWidth"
|
||||
:position="boxPosition"
|
||||
>
|
||||
<template #body>
|
||||
@ -66,6 +67,7 @@ const { height: editorHeight } = useEditorContentHeight();
|
||||
|
||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
||||
|
||||
/** 供「方法定义」tab 内的字段消费,用于打开数据源详情后自动打开指定方法 */
|
||||
provide(
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
ref="box"
|
||||
v-model:visible="visible"
|
||||
title="当前位置下的组件"
|
||||
:framework-width="frameworkWidth"
|
||||
:position="menuPosition"
|
||||
>
|
||||
<template #body>
|
||||
@ -36,7 +37,9 @@ import { useNodeStatus } from '@editor/layouts/sidebar/layer/use-node-status';
|
||||
import type { TreeNodeData } from '@editor/type';
|
||||
|
||||
const services = useServices();
|
||||
const { editorService } = services;
|
||||
const { editorService, uiService } = services;
|
||||
|
||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
||||
|
||||
const visible = ref(false);
|
||||
const buttonVisible = ref(false);
|
||||
|
||||
@ -64,6 +64,18 @@ const services = {
|
||||
},
|
||||
};
|
||||
|
||||
const rect200: DOMRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 200,
|
||||
height: 100,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 200,
|
||||
bottom: 100,
|
||||
toJSON: () => '',
|
||||
};
|
||||
|
||||
describe('FloatingBox.vue', () => {
|
||||
beforeEach(() => {
|
||||
moveableHandlers.clear();
|
||||
@ -157,14 +169,61 @@ describe('FloatingBox.vue', () => {
|
||||
|
||||
test('left + width 超过 frameworkWidth 时 left 被收敛', async () => {
|
||||
const wrapper = mount(FloatingBox as any, {
|
||||
...services,
|
||||
props: { visible: true, position: { left: 950, top: 0 }, width: 200 },
|
||||
props: { visible: true, position: { left: 950, top: 0 }, width: 200, frameworkWidth: 1000 },
|
||||
attachTo: document.body,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
await wrapper.vm.$nextTick();
|
||||
const box = document.querySelector('.m-editor-float-box') as HTMLElement;
|
||||
expect(box).not.toBeNull();
|
||||
// jsdom 中 getBoundingClientRect 返回 0,width 会被重置为 0,故不触发收敛,left 保持原值
|
||||
expect(box.style.left).toBe('950px');
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('当实际宽度使 left + width 超过 frameworkWidth 时 left 收敛到右边界内', async () => {
|
||||
const getBoundingClientRectSpy = vi.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue(rect200);
|
||||
const wrapper = mount(FloatingBox as any, {
|
||||
props: { visible: true, position: { left: 950, top: 0 }, frameworkWidth: 1000 },
|
||||
attachTo: document.body,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
await wrapper.vm.$nextTick();
|
||||
const box = document.querySelector('.m-editor-float-box') as HTMLElement;
|
||||
expect(box).not.toBeNull();
|
||||
expect(box.style.left).toBe('800px');
|
||||
getBoundingClientRectSpy.mockRestore();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('未传入 frameworkWidth 时默认按视窗宽度收敛 left', async () => {
|
||||
const getBoundingClientRectSpy = vi.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue(rect200);
|
||||
const innerWidthSpy = vi.spyOn(globalThis, 'window', 'get').mockReturnValue({ innerWidth: 300 } as any);
|
||||
const wrapper = mount(FloatingBox as any, {
|
||||
props: { visible: true, position: { left: 250, top: 0 } },
|
||||
attachTo: document.body,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
await wrapper.vm.$nextTick();
|
||||
const box = document.querySelector('.m-editor-float-box') as HTMLElement;
|
||||
expect(box).not.toBeNull();
|
||||
// 250 + 200 = 450 > 视窗宽度 300,left 收敛为 300 - 200 = 100
|
||||
expect(box.style.left).toBe('100px');
|
||||
getBoundingClientRectSpy.mockRestore();
|
||||
innerWidthSpy.mockRestore();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('传入 initialStyle 时合并到浮窗样式', async () => {
|
||||
const wrapper = mount(FloatingBox as any, {
|
||||
props: { visible: true, initialStyle: { backgroundColor: 'rgb(255, 0, 0)' } },
|
||||
attachTo: document.body,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
await wrapper.vm.$nextTick();
|
||||
const box = document.querySelector('.m-editor-float-box') as HTMLElement;
|
||||
expect(box).not.toBeNull();
|
||||
expect(box.style.backgroundColor).toBe('rgb(255, 0, 0)');
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
|
||||
@ -38,10 +38,14 @@ const editorService = {
|
||||
select: vi.fn(),
|
||||
};
|
||||
|
||||
const uiService = {
|
||||
get: vi.fn((k: string) => (k === 'frameworkRect' ? { width: 1000 } : undefined)),
|
||||
};
|
||||
|
||||
const nodeStatusMap = ref(new Map<string, any>([['p1', { selected: false }]]));
|
||||
|
||||
vi.mock('@editor/hooks/use-services', () => ({
|
||||
useServices: () => ({ editorService }),
|
||||
useServices: () => ({ editorService, uiService }),
|
||||
}));
|
||||
|
||||
vi.mock('@editor/layouts/sidebar/layer/use-node-status', () => ({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user