Skip to content

Commit 0c5485b

Browse files
parismaroymondchen
parisma
authored and
roymondchen
committed
feat(editor): 支持拖拽调整页面顺序
1 parent 0ffc223 commit 0c5485b

File tree

11 files changed

+149
-13
lines changed

11 files changed

+149
-13
lines changed

packages/editor/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,15 @@
6161
"keycon": "^1.4.0",
6262
"lodash-es": "^4.17.21",
6363
"moveable": "^0.53.0",
64-
"serialize-javascript": "^6.0.0"
64+
"serialize-javascript": "^6.0.0",
65+
"sortablejs": "^1.15.2"
6566
},
6667
"devDependencies": {
6768
"@types/events": "^3.0.0",
6869
"@types/lodash-es": "^4.17.4",
6970
"@types/node": "^18.19.0",
7071
"@types/serialize-javascript": "^5.0.1",
72+
"@types/sortablejs": "^1.15.8",
7173
"@vitejs/plugin-vue": "^5.0.4",
7274
"@vue/compiler-sfc": "^3.4.27",
7375
"@vue/test-utils": "^2.4.6",
@@ -86,8 +88,8 @@
8688
"@tmagic/stage": "workspace:*",
8789
"@tmagic/utils": "workspace:*",
8890
"monaco-editor": "^0.48.0",
89-
"vue": "^3.4.27",
90-
"typescript": "*"
91+
"typescript": "*",
92+
"vue": "^3.4.27"
9193
},
9294
"peerDependenciesMeta": {
9395
"typescript": {

packages/editor/src/Editor.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<Framework :disabled-page-fragment="disabledPageFragment">
2+
<Framework :disabled-page-fragment="disabledPageFragment" :page-bar-sort-options="pageBarSortOptions">
33
<template #header>
44
<slot name="header"></slot>
55
</template>
@@ -106,6 +106,7 @@
106106
<template #page-bar><slot name="page-bar"></slot></template>
107107
<template #page-bar-title="{ page }"><slot name="page-bar-title" :page="page"></slot></template>
108108
<template #page-bar-popover="{ page }"><slot name="page-bar-popover" :page="page"></slot></template>
109+
<template #page-list-popover="{ list }"><slot name="page-list-popover" :list="list"></slot></template>
109110
</Framework>
110111
</template>
111112

packages/editor/src/editorProps.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
MenuBarData,
1818
MenuButton,
1919
MenuComponent,
20+
PageBarSortOptions,
2021
SideBarData,
2122
StageRect,
2223
} from './type';
@@ -90,6 +91,8 @@ export interface EditorProps {
9091
/** 用于自定义组件树与画布的右键菜单 */
9192
customContentMenu?: (menus: (MenuButton | MenuComponent)[], type: string) => (MenuButton | MenuComponent)[];
9293
extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
94+
/** 页面顺序拖拽配置参数 */
95+
pageBarSortOptions?: PageBarSortOptions;
9396
}
9497

9598
export const defaultEditorProps = {

packages/editor/src/layouts/Framework.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@
3838
</slot>
3939

4040
<slot name="page-bar">
41-
<PageBar :disabled-page-fragment="disabledPageFragment">
41+
<PageBar :disabled-page-fragment="disabledPageFragment" :page-bar-sort-options="pageBarSortOptions">
4242
<template #page-bar-title="{ page }"><slot name="page-bar-title" :page="page"></slot></template>
4343
<template #page-bar-popover="{ page }"><slot name="page-bar-popover" :page="page"></slot></template>
44+
<template #page-list-popover="{ list }"><slot name="page-list-popover" :list="list"></slot></template>
4445
</PageBar>
4546
</slot>
4647
</template>
@@ -63,7 +64,7 @@ import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue';
6364
import { TMagicScrollbar } from '@tmagic/design';
6465
6566
import SplitView from '@editor/components/SplitView.vue';
66-
import type { FrameworkSlots, GetColumnWidth, Services } from '@editor/type';
67+
import type { FrameworkSlots, GetColumnWidth, PageBarSortOptions, Services } from '@editor/type';
6768
import { getConfig } from '@editor/utils/config';
6869
6970
import PageBar from './page-bar/PageBar.vue';
@@ -78,6 +79,7 @@ defineOptions({
7879
7980
defineProps<{
8081
disabledPageFragment: boolean;
82+
pageBarSortOptions?: PageBarSortOptions;
8183
}>();
8284
8385
const DEFAULT_LEFT_COLUMN_WIDTH = 310;

packages/editor/src/layouts/page-bar/PageBar.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
<div class="m-editor-page-bar-tabs">
33
<SwitchTypeButton v-if="!disabledPageFragment" v-model="active" />
44

5-
<PageBarScrollContainer :type="active">
5+
<PageBarScrollContainer :type="active" :page-bar-sort-options="pageBarSortOptions">
66
<template #prepend>
77
<AddButton :type="active"></AddButton>
8+
<PageList :list="list">
9+
<template #page-list-popover="{ list }"><slot name="page-list-popover" :list="list"></slot></template>
10+
</PageList>
811
</template>
912

1013
<div
1114
v-for="item in list"
1215
class="m-editor-page-bar-item"
1316
:key="item.id"
17+
:page-id="item.id"
1418
:class="{ active: page?.id === item.id }"
1519
@click="switchPage(item.id)"
1620
>
@@ -61,11 +65,12 @@ import { Id, type MPage, type MPageFragment, NodeType } from '@tmagic/schema';
6165
import { isPage, isPageFragment } from '@tmagic/utils';
6266
6367
import ToolButton from '@editor/components/ToolButton.vue';
64-
import type { Services } from '@editor/type';
68+
import type { PageBarSortOptions, Services } from '@editor/type';
6569
import { getPageFragmentList, getPageList } from '@editor/utils';
6670
6771
import AddButton from './AddButton.vue';
6872
import PageBarScrollContainer from './PageBarScrollContainer.vue';
73+
import PageList from './PageList.vue';
6974
import SwitchTypeButton from './SwitchTypeButton.vue';
7075
7176
defineOptions({
@@ -74,6 +79,7 @@ defineOptions({
7479
7580
defineProps<{
7681
disabledPageFragment: boolean;
82+
pageBarSortOptions?: PageBarSortOptions;
7783
}>();
7884
7985
const active = ref<NodeType.PAGE | NodeType.PAGE_FRAGMENT>(NodeType.PAGE);

packages/editor/src/layouts/page-bar/PageBarScrollContainer.vue

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,35 +34,42 @@ import {
3434
type WatchStopHandle,
3535
} from 'vue';
3636
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
37+
import Sortable, { SortableEvent } from 'sortablejs';
3738
38-
import { NodeType } from '@tmagic/schema';
39+
import { Id, NodeType } from '@tmagic/schema';
3940
4041
import Icon from '@editor/components/Icon.vue';
41-
import type { Services } from '@editor/type';
42+
import type { PageBarSortOptions, Services } from '@editor/type';
4243
4344
defineOptions({
4445
name: 'MEditorPageBarScrollContainer',
4546
});
4647
4748
const props = defineProps<{
4849
type: NodeType.PAGE | NodeType.PAGE_FRAGMENT;
50+
pageBarSortOptions?: PageBarSortOptions;
4951
}>();
5052
5153
const services = inject<Services>('services');
5254
const editorService = services?.editorService;
5355
const uiService = services?.uiService;
5456
55-
const itemsContainer = ref<HTMLDivElement>();
57+
const itemsContainer = ref<HTMLElement>();
5658
const canScroll = ref(false);
5759
5860
const showAddPageButton = computed(() => uiService?.get('showAddPageButton'));
61+
const showPageListButton = computed(() => uiService?.get('showPageListButton'));
5962
6063
const itemsContainerWidth = ref(0);
6164
6265
const setCanScroll = () => {
6366
// 减去新增、左移、右移三个按钮的宽度
6467
// 37 = icon width 16 + padding 10 * 2 + border-right 1
65-
itemsContainerWidth.value = (pageBar.value?.clientWidth || 0) - 37 * 2 - (showAddPageButton.value ? 37 : 21);
68+
itemsContainerWidth.value =
69+
(pageBar.value?.clientWidth || 0) -
70+
37 * 2 -
71+
(showAddPageButton.value ? 37 : 21) -
72+
(showPageListButton.value ? 37 : 0);
6673
6774
nextTick(() => {
6875
if (itemsContainer.value) {
@@ -126,6 +133,35 @@ const crateWatchLength = (length: ComputedRef<number>) =>
126133
} else {
127134
scroll('end');
128135
}
136+
if (length > 1) {
137+
const el = document.querySelector('.m-editor-page-bar-items') as HTMLElement;
138+
let beforeDragList: Id[] = [];
139+
const options = {
140+
...{
141+
dataIdAttr: 'page-id', // 获取排序后的数据
142+
onStart: async (event: SortableEvent) => {
143+
if (typeof props.pageBarSortOptions?.beforeStart === 'function') {
144+
await props.pageBarSortOptions.beforeStart(event, sortable);
145+
}
146+
beforeDragList = sortable.toArray();
147+
},
148+
onUpdate: async (event: SortableEvent) => {
149+
await editorService?.sort(
150+
beforeDragList[event.oldIndex as number],
151+
beforeDragList[event.newIndex as number],
152+
);
153+
if (typeof props.pageBarSortOptions?.afterUpdate === 'function') {
154+
await props.pageBarSortOptions.afterUpdate(event, sortable);
155+
}
156+
},
157+
},
158+
...{
159+
...(props.pageBarSortOptions ? props.pageBarSortOptions : {}),
160+
},
161+
};
162+
if (!el) return;
163+
const sortable = new Sortable(el, options);
164+
}
129165
});
130166
},
131167
{
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<template>
2+
<div
3+
v-if="showPageListButton"
4+
id="m-editor-page-bar-list-icon"
5+
class="m-editor-page-bar-item m-editor-page-bar-item-icon"
6+
>
7+
<TMagicPopover popper-class="page-bar-popover" placement="top" :width="160" trigger="hover">
8+
<div>
9+
<slot name="page-list-popover" :list="list">
10+
<ToolButton
11+
v-for="(item, index) in list"
12+
:data="{
13+
type: 'button',
14+
text: item.devconfig?.tabName || item.name || item.id,
15+
handler: () => switchPage(item.id),
16+
}"
17+
:key="index"
18+
></ToolButton>
19+
</slot>
20+
</div>
21+
<template #reference>
22+
<TMagicIcon class="m-editor-page-list-menu-icon">
23+
<Files></Files>
24+
</TMagicIcon>
25+
</template>
26+
</TMagicPopover>
27+
</div>
28+
</template>
29+
30+
<script setup lang="ts">
31+
import { computed, inject } from 'vue';
32+
import { Files } from '@element-plus/icons-vue';
33+
34+
import { TMagicIcon, TMagicPopover } from '@tmagic/design';
35+
import { Id, MPage, MPageFragment } from '@tmagic/schema';
36+
37+
import ToolButton from '@editor/components/ToolButton.vue';
38+
import type { Services } from '@editor/type';
39+
defineOptions({
40+
name: 'MEditorPageList',
41+
});
42+
43+
defineProps<{
44+
list: MPage[] | MPageFragment[];
45+
}>();
46+
47+
const services = inject<Services>('services');
48+
const uiService = services?.uiService;
49+
const editorService = services?.editorService;
50+
51+
const showPageListButton = computed(() => uiService?.get('showPageListButton'));
52+
const switchPage = (id: Id) => {
53+
editorService?.select(id);
54+
};
55+
</script>

packages/editor/src/services/ui.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const state = reactive<UiState>({
4747
showRule: true,
4848
propsPanelSize: 'small',
4949
showAddPageButton: true,
50+
showPageListButton: true,
5051
hideSlideBar: false,
5152
sideBarItems: [],
5253
navMenuRect: {

packages/editor/src/theme/page-bar.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
}
1919
}
2020

21+
.m-editor-page-list-item {
22+
display: flex;
23+
width: 100%;
24+
height: $--page-bar-height;
25+
line-height: $--page-bar-height;
26+
color: $--font-color;
27+
z-index: 2;
28+
overflow: hidden;
29+
&:hover {
30+
background-color: $--hover-color;
31+
}
32+
}
33+
2134
.m-editor-page-bar {
2235
display: flex;
2336
width: 100%;

packages/editor/src/type.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import type { Component } from 'vue';
2020
import type EventEmitter from 'events';
21+
import Sortable, { Options, SortableEvent } from 'sortablejs';
2122
import type { PascalCasedProperties } from 'type-fest';
2223

2324
import type { ChildConfig, ColumnConfig, FilterFunction, FormConfig, FormItem, FormState, Input } from '@tmagic/form';
@@ -56,7 +57,6 @@ import type { StageOverlayService } from './services/stageOverlay';
5657
import type { StorageService } from './services/storage';
5758
import type { UiService } from './services/ui';
5859
import type { UndoRedo } from './utils/undo-redo';
59-
6060
export interface FrameworkSlots {
6161
header(props: {}): any;
6262
nav(props: {}): any;
@@ -71,6 +71,7 @@ export interface FrameworkSlots {
7171
'page-bar'(props: {}): any;
7272
'page-bar-title'(props: { page: MPage | MPageFragment }): any;
7373
'page-bar-popover'(props: { page: MPage | MPageFragment }): any;
74+
'page-list-popover'(props: { list: MPage[] | MPageFragment[] }): any;
7475
}
7576

7677
export interface WorkspaceSlots {
@@ -239,6 +240,8 @@ export interface UiState {
239240
propsPanelSize: 'large' | 'default' | 'small';
240241
/** 是否显示新增页面按钮 */
241242
showAddPageButton: boolean;
243+
/** 是否在页面工具栏显示呼起页面列表按钮 */
244+
showPageListButton: boolean;
242245
/** 是否隐藏侧边栏 */
243246
hideSlideBar: boolean;
244247
/** 侧边栏面板配置 */
@@ -762,3 +765,11 @@ export interface EventBus extends EventEmitter {
762765

763766
export type PropsFormConfigFunction = (data: { editorService: EditorService }) => FormConfig;
764767
export type PropsFormValueFunction = (data: { editorService: EditorService }) => Partial<MNode>;
768+
769+
export type PartSortableOptions = Omit<Options, 'onStart' | 'onUpdate'>;
770+
export interface PageBarSortOptions extends PartSortableOptions {
771+
/** 在onUpdate之后调用 */
772+
afterUpdate: (event: SortableEvent, sortable: Sortable) => void;
773+
/** 在onStart之前调用 */
774+
beforeStart: (event: SortableEvent, sortable: Sortable) => void;
775+
}

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)