@@ -61,11 +65,12 @@ import { Id, type MPage, type MPageFragment, NodeType } from '@tmagic/schema';
import { isPage, isPageFragment } from '@tmagic/utils';
import ToolButton from '@editor/components/ToolButton.vue';
-import type { Services } from '@editor/type';
+import type { PageBarSortOptions, Services } from '@editor/type';
import { getPageFragmentList, getPageList } from '@editor/utils';
import AddButton from './AddButton.vue';
import PageBarScrollContainer from './PageBarScrollContainer.vue';
+import PageList from './PageList.vue';
import SwitchTypeButton from './SwitchTypeButton.vue';
defineOptions({
@@ -74,6 +79,7 @@ defineOptions({
defineProps<{
disabledPageFragment: boolean;
+ pageBarSortOptions?: PageBarSortOptions;
}>();
const active = ref
(NodeType.PAGE);
diff --git a/packages/editor/src/layouts/page-bar/PageBarScrollContainer.vue b/packages/editor/src/layouts/page-bar/PageBarScrollContainer.vue
index a25e61ec..18042930 100644
--- a/packages/editor/src/layouts/page-bar/PageBarScrollContainer.vue
+++ b/packages/editor/src/layouts/page-bar/PageBarScrollContainer.vue
@@ -34,11 +34,12 @@ import {
type WatchStopHandle,
} from 'vue';
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
+import Sortable, { SortableEvent } from 'sortablejs';
-import { NodeType } from '@tmagic/schema';
+import { Id, NodeType } from '@tmagic/schema';
import Icon from '@editor/components/Icon.vue';
-import type { Services } from '@editor/type';
+import type { PageBarSortOptions, Services } from '@editor/type';
defineOptions({
name: 'MEditorPageBarScrollContainer',
@@ -46,23 +47,29 @@ defineOptions({
const props = defineProps<{
type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
+ pageBarSortOptions?: PageBarSortOptions;
}>();
const services = inject('services');
const editorService = services?.editorService;
const uiService = services?.uiService;
-const itemsContainer = ref();
+const itemsContainer = ref();
const canScroll = ref(false);
const showAddPageButton = computed(() => uiService?.get('showAddPageButton'));
+const showPageListButton = computed(() => uiService?.get('showPageListButton'));
const itemsContainerWidth = ref(0);
const setCanScroll = () => {
// 减去新增、左移、右移三个按钮的宽度
// 37 = icon width 16 + padding 10 * 2 + border-right 1
- itemsContainerWidth.value = (pageBar.value?.clientWidth || 0) - 37 * 2 - (showAddPageButton.value ? 37 : 21);
+ itemsContainerWidth.value =
+ (pageBar.value?.clientWidth || 0) -
+ 37 * 2 -
+ (showAddPageButton.value ? 37 : 21) -
+ (showPageListButton.value ? 37 : 0);
nextTick(() => {
if (itemsContainer.value) {
@@ -126,6 +133,35 @@ const crateWatchLength = (length: ComputedRef) =>
} else {
scroll('end');
}
+ if (length > 1) {
+ const el = document.querySelector('.m-editor-page-bar-items') as HTMLElement;
+ let beforeDragList: Id[] = [];
+ const options = {
+ ...{
+ dataIdAttr: 'page-id', // 获取排序后的数据
+ onStart: async (event: SortableEvent) => {
+ if (typeof props.pageBarSortOptions?.beforeStart === 'function') {
+ await props.pageBarSortOptions.beforeStart(event, sortable);
+ }
+ beforeDragList = sortable.toArray();
+ },
+ onUpdate: async (event: SortableEvent) => {
+ await editorService?.sort(
+ beforeDragList[event.oldIndex as number],
+ beforeDragList[event.newIndex as number],
+ );
+ if (typeof props.pageBarSortOptions?.afterUpdate === 'function') {
+ await props.pageBarSortOptions.afterUpdate(event, sortable);
+ }
+ },
+ },
+ ...{
+ ...(props.pageBarSortOptions ? props.pageBarSortOptions : {}),
+ },
+ };
+ if (!el) return;
+ const sortable = new Sortable(el, options);
+ }
});
},
{
diff --git a/packages/editor/src/layouts/page-bar/PageList.vue b/packages/editor/src/layouts/page-bar/PageList.vue
new file mode 100644
index 00000000..546142f2
--- /dev/null
+++ b/packages/editor/src/layouts/page-bar/PageList.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/packages/editor/src/services/ui.ts b/packages/editor/src/services/ui.ts
index 5bbffbed..5088b97c 100644
--- a/packages/editor/src/services/ui.ts
+++ b/packages/editor/src/services/ui.ts
@@ -47,6 +47,7 @@ const state = reactive({
showRule: true,
propsPanelSize: 'small',
showAddPageButton: true,
+ showPageListButton: true,
hideSlideBar: false,
sideBarItems: [],
navMenuRect: {
diff --git a/packages/editor/src/theme/page-bar.scss b/packages/editor/src/theme/page-bar.scss
index 0586c0cb..590eeba1 100644
--- a/packages/editor/src/theme/page-bar.scss
+++ b/packages/editor/src/theme/page-bar.scss
@@ -18,6 +18,19 @@
}
}
+.m-editor-page-list-item {
+ display: flex;
+ width: 100%;
+ height: $--page-bar-height;
+ line-height: $--page-bar-height;
+ color: $--font-color;
+ z-index: 2;
+ overflow: hidden;
+ &:hover {
+ background-color: $--hover-color;
+ }
+}
+
.m-editor-page-bar {
display: flex;
width: 100%;
diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts
index af33dcbe..b3c03b79 100644
--- a/packages/editor/src/type.ts
+++ b/packages/editor/src/type.ts
@@ -18,6 +18,7 @@
import type { Component } from 'vue';
import type EventEmitter from 'events';
+import Sortable, { Options, SortableEvent } from 'sortablejs';
import type { PascalCasedProperties } from 'type-fest';
import type { ChildConfig, ColumnConfig, FilterFunction, FormConfig, FormItem, FormState, Input } from '@tmagic/form';
@@ -56,7 +57,6 @@ import type { StageOverlayService } from './services/stageOverlay';
import type { StorageService } from './services/storage';
import type { UiService } from './services/ui';
import type { UndoRedo } from './utils/undo-redo';
-
export interface FrameworkSlots {
header(props: {}): any;
nav(props: {}): any;
@@ -71,6 +71,7 @@ export interface FrameworkSlots {
'page-bar'(props: {}): any;
'page-bar-title'(props: { page: MPage | MPageFragment }): any;
'page-bar-popover'(props: { page: MPage | MPageFragment }): any;
+ 'page-list-popover'(props: { list: MPage[] | MPageFragment[] }): any;
}
export interface WorkspaceSlots {
@@ -239,6 +240,8 @@ export interface UiState {
propsPanelSize: 'large' | 'default' | 'small';
/** 是否显示新增页面按钮 */
showAddPageButton: boolean;
+ /** 是否在页面工具栏显示呼起页面列表按钮 */
+ showPageListButton: boolean;
/** 是否隐藏侧边栏 */
hideSlideBar: boolean;
/** 侧边栏面板配置 */
@@ -762,3 +765,11 @@ export interface EventBus extends EventEmitter {
export type PropsFormConfigFunction = (data: { editorService: EditorService }) => FormConfig;
export type PropsFormValueFunction = (data: { editorService: EditorService }) => Partial;
+
+export type PartSortableOptions = Omit;
+export interface PageBarSortOptions extends PartSortableOptions {
+ /** 在onUpdate之后调用 */
+ afterUpdate: (event: SortableEvent, sortable: Sortable) => void;
+ /** 在onStart之前调用 */
+ beforeStart: (event: SortableEvent, sortable: Sortable) => void;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4e16a28f..e3d8692b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -346,6 +346,9 @@ importers:
serialize-javascript:
specifier: ^6.0.0
version: 6.0.2
+ sortablejs:
+ specifier: ^1.15.2
+ version: 1.15.2
typescript:
specifier: '*'
version: 5.4.5
@@ -365,6 +368,9 @@ importers:
'@types/serialize-javascript':
specifier: ^5.0.1
version: 5.0.4
+ '@types/sortablejs':
+ specifier: ^1.15.8
+ version: 1.15.8
'@vitejs/plugin-vue':
specifier: ^5.0.4
version: 5.0.5(vite@5.3.1(@types/node@18.19.34)(sass@1.77.5)(terser@5.31.1))(vue@3.4.29(typescript@5.4.5))