diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index d35d5a13..c44f2c92 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -201,7 +201,16 @@ export default defineConfig({ }, { text: 'uiService', - link: '/api/editor/uiServiceMethods.md', + items: [ + { + text: '方法', + link: '/api/editor/uiServiceMethods.md', + }, + { + text: '事件', + link: '/api/editor/uiServiceEvents.md', + }, + ], }, { text: 'codeBlockService', diff --git a/docs/api/editor/uiServiceEvents.md b/docs/api/editor/uiServiceEvents.md new file mode 100644 index 00000000..3b634028 --- /dev/null +++ b/docs/api/editor/uiServiceEvents.md @@ -0,0 +1,28 @@ +# uiService事件 + +## state-change + +- **详情:** UI 状态发生变化时触发,[uiService.set()](./uiServiceMethods.md#set) 在写入的新值与旧值不同时触发 + +- **事件回调函数:** `(name: keyof UiState, value: UiState[typeof name], preValue: UiState[typeof name]) => void` + + ::: details 查看 UiState 类型定义 + <<< @/../packages/editor/src/type.ts#UiState{ts} + ::: + +- **示例:** + +```js +import { uiService } from '@tmagic/editor'; + +uiService.on('state-change', (name, value, preValue) => { + console.log(`${name} 从`, preValue, '变为', value); +}); + +uiService.set('zoom', 1.5); +``` + +:::tip +- 新值与旧值相同时不会触发该事件 +- 通过 `set('stageRect', value)` 修改画布尺寸时,内部会走 `setStageRect` 逻辑并可能联动更新 `zoom`,但不会触发 `state-change` 事件 +::: diff --git a/docs/api/editor/uiServiceMethods.md b/docs/api/editor/uiServiceMethods.md index 3b3ca6bd..f1e2d121 100644 --- a/docs/api/editor/uiServiceMethods.md +++ b/docs/api/editor/uiServiceMethods.md @@ -13,7 +13,7 @@ - **详情:** - 设置UI服务的状态 + 设置UI服务的状态。新值与旧值不同时会触发 [`state-change`](./uiServiceEvents.md#state-change) 事件 可用的状态键: - `uiSelectMode`: UI选择模式 @@ -31,6 +31,7 @@ - `showPageListButton`: 是否显示页面列表按钮 - `hideSlideBar`: 是否隐藏侧边栏 - `sideBarItems`: 侧边栏项目 + - `sideBarActiveTabName`: 当前激活的侧边栏面板 - `navMenuRect`: 导航菜单尺寸 - `frameworkRect`: 框架尺寸 diff --git a/packages/editor/src/layouts/sidebar/Sidebar.vue b/packages/editor/src/layouts/sidebar/Sidebar.vue index fc2378f1..ffdb8288 100644 --- a/packages/editor/src/layouts/sidebar/Sidebar.vue +++ b/packages/editor/src/layouts/sidebar/Sidebar.vue @@ -239,7 +239,12 @@ const unWatchEditorContentHeight = watch( }, ); -const activeTabName = ref(props.data?.status); +const activeTabName = computed({ + get: () => uiService.get('sideBarActiveTabName'), + set: (value) => uiService.set('sideBarActiveTabName', value), +}); + +uiService.set('sideBarActiveTabName', props.data?.status || ''); const getItemConfig = (data: SideItem): SideComponent => { const map: Record = { diff --git a/packages/editor/src/services/ui.ts b/packages/editor/src/services/ui.ts index f651b7c2..bc5fa497 100644 --- a/packages/editor/src/services/ui.ts +++ b/packages/editor/src/services/ui.ts @@ -62,6 +62,7 @@ const state = shallowReactive({ showPageListButton: true, hideSlideBar: false, sideBarItems: [], + sideBarActiveTabName: '', navMenuRect: { left: 0, top: 0, @@ -104,7 +105,13 @@ class Ui extends BaseService { mask?.showRule(value as unknown as boolean); } + const preValue = state[name]; + state[name] = value; + + if (preValue !== value) { + this.emit('state-change', name, value, preValue); + } } public get(name: K) { diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index 088be110..01cad151 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -312,6 +312,8 @@ export interface UiState { hideSlideBar: boolean; /** 侧边栏面板配置 */ sideBarItems: SideComponent[]; + /** 当前激活的侧边栏面板 */ + sideBarActiveTabName: string; // navMenu 的宽高 navMenuRect: { diff --git a/packages/editor/tests/unit/layouts/sidebar/Sidebar.spec.ts b/packages/editor/tests/unit/layouts/sidebar/Sidebar.spec.ts index a6f331c5..f22d3c34 100644 --- a/packages/editor/tests/unit/layouts/sidebar/Sidebar.spec.ts +++ b/packages/editor/tests/unit/layouts/sidebar/Sidebar.spec.ts @@ -4,15 +4,18 @@ * Copyright (C) 2025 Tencent. */ import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { defineComponent, h, nextTick } from 'vue'; +import { defineComponent, h, nextTick, reactive } from 'vue'; import { mount } from '@vue/test-utils'; import Sidebar from '@editor/layouts/sidebar/Sidebar.vue'; const depService = { get: vi.fn(() => false) }; +const uiState: Record = reactive({}); const uiService = { - get: vi.fn(() => ({ left: 200 })), - set: vi.fn(), + get: vi.fn((name: string) => (name === 'sideBarActiveTabName' ? uiState.sideBarActiveTabName : { left: 200 })), + set: vi.fn((name: string, value: any) => { + uiState[name] = value; + }), }; const propsService = { getDisabledDataSource: vi.fn(() => false), @@ -91,7 +94,10 @@ beforeEach(() => { vi.clearAllMocks(); propsService.getDisabledDataSource.mockReturnValue(false); propsService.getDisabledCodeBlock.mockReturnValue(false); - uiService.get.mockReturnValue({ left: 200 }); + Object.keys(uiState).forEach((key) => delete uiState[key]); + uiService.get.mockImplementation((name: string) => + name === 'sideBarActiveTabName' ? uiState.sideBarActiveTabName : { left: 200 }, + ); }); const baseProps = (extra: any = {}) => ({