@tmagic/editor 1.0.0-beta.1 → 1.0.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +5432 -0
  2. package/README.md +1 -0
  3. package/dist/style.css +53 -24
  4. package/dist/tmagic-editor.es.js +901 -412
  5. package/dist/tmagic-editor.es.js.map +1 -1
  6. package/dist/tmagic-editor.umd.js +918 -412
  7. package/dist/tmagic-editor.umd.js.map +1 -1
  8. package/dist/types/src/Editor.vue.d.ts +5 -5
  9. package/dist/types/src/components/ScrollViewer.vue.d.ts +23 -0
  10. package/dist/types/src/layouts/CodeEditor.vue.d.ts +5 -4
  11. package/dist/types/src/layouts/NavMenu.vue.d.ts +2 -1
  12. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +1 -1
  13. package/dist/types/src/layouts/sidebar/LayerPanel.vue.d.ts +10 -3
  14. package/dist/types/src/layouts/workspace/PageBar.vue.d.ts +8 -3
  15. package/dist/types/src/layouts/workspace/Stage.vue.d.ts +77 -42
  16. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +4 -10
  17. package/dist/types/src/services/BaseService.d.ts +4 -1
  18. package/dist/types/src/services/editor.d.ts +11 -4
  19. package/dist/types/src/services/props.d.ts +1 -1
  20. package/dist/types/src/services/ui.d.ts +10 -3
  21. package/dist/types/src/type.d.ts +23 -7
  22. package/dist/types/src/utils/editor.d.ts +3 -3
  23. package/dist/types/src/utils/scroll-viewer.d.ts +35 -0
  24. package/package.json +25 -12
  25. package/src/Editor.vue +5 -5
  26. package/src/components/ScrollViewer.vue +66 -0
  27. package/src/components/ToolButton.vue +5 -3
  28. package/src/layouts/AddPageBox.vue +3 -1
  29. package/src/layouts/CodeEditor.vue +35 -59
  30. package/src/layouts/NavMenu.vue +7 -3
  31. package/src/layouts/PropsPanel.vue +6 -10
  32. package/src/layouts/sidebar/ComponentListPanel.vue +2 -2
  33. package/src/layouts/sidebar/LayerMenu.vue +15 -39
  34. package/src/layouts/sidebar/LayerPanel.vue +63 -16
  35. package/src/layouts/workspace/PageBar.vue +113 -14
  36. package/src/layouts/workspace/Stage.vue +67 -61
  37. package/src/layouts/workspace/ViewerMenu.vue +5 -3
  38. package/src/layouts/workspace/Workspace.vue +45 -34
  39. package/src/services/BaseService.ts +70 -22
  40. package/src/services/editor.ts +111 -55
  41. package/src/services/props.ts +2 -1
  42. package/src/services/ui.ts +42 -5
  43. package/src/theme/common/var.scss +2 -2
  44. package/src/theme/layer-panel.scss +9 -0
  45. package/src/theme/nav-menu.scss +3 -3
  46. package/src/theme/page-bar.scss +21 -2
  47. package/src/theme/props-panel.scss +8 -0
  48. package/src/theme/stage.scss +8 -7
  49. package/src/type.ts +26 -7
  50. package/src/utils/editor.ts +30 -27
  51. package/src/utils/props.ts +1 -1
  52. package/src/utils/scroll-viewer.ts +216 -0
  53. package/dist/types/src/layouts/sidebar/LayerMenu.vue.d.ts +0 -31
@@ -1,9 +1,12 @@
1
1
  <template>
2
- <div class="m-editor-page-bar">
3
- <div class="m-editor-page-bar-item" @click="addPage">
4
- <el-icon class="m-editor-page-bar-menu-add-icon"><plus></plus></el-icon>
2
+ <div class="m-editor-page-bar" ref="pageBar">
3
+ <div id="m-editor-page-bar-add-icon" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="addPage">
4
+ <el-icon><plus></plus></el-icon>
5
5
  </div>
6
- <template v-if="root">
6
+ <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('left')">
7
+ <el-icon><arrow-left-bold></arrow-left-bold></el-icon>
8
+ </div>
9
+ <div v-if="root" class="m-editor-page-bar-items" ref="itemsContainer" :style="`width: ${itemsContainerWidth}px`">
7
10
  <div
8
11
  v-for="item in root.items"
9
12
  :key="item.key"
@@ -11,9 +14,13 @@
11
14
  :class="{ active: page?.id === item.id }"
12
15
  @click="switchPage(item)"
13
16
  >
14
- <slot name="page-bar-title" :page="item">
15
- <span>{{ item.name }}</span>
16
- </slot>
17
+ <div class="m-editor-page-bar-title">
18
+ <slot name="page-bar-title" :page="item">
19
+ <el-tooltip effect="dark" placement="top-start" :content="item.name">
20
+ <span>{{ item.name }}</span>
21
+ </el-tooltip>
22
+ </slot>
23
+ </div>
17
24
 
18
25
  <el-popover placement="top" :width="160" trigger="hover">
19
26
  <div>
@@ -29,28 +36,120 @@
29
36
  </template>
30
37
  </el-popover>
31
38
  </div>
32
- </template>
39
+ </div>
40
+ <div v-if="canScroll" class="m-editor-page-bar-item m-editor-page-bar-item-icon" @click="scroll('right')">
41
+ <el-icon><arrow-right-bold></arrow-right-bold></el-icon>
42
+ </div>
33
43
  </div>
34
44
  </template>
35
45
 
36
46
  <script lang="ts">
37
- import { computed, defineComponent, inject, toRaw } from 'vue';
38
- import { CaretBottom, Plus } from '@element-plus/icons';
47
+ import { computed, ComputedRef, defineComponent, inject, onMounted, onUnmounted, ref, toRaw, watch } from 'vue';
48
+ import { ArrowLeftBold, ArrowRightBold, CaretBottom, Plus } from '@element-plus/icons';
39
49
 
40
- import type { MPage } from '@tmagic/schema';
50
+ import type { MApp, MPage } from '@tmagic/schema';
51
+ import { NodeType } from '@tmagic/schema';
41
52
 
42
53
  import type { Services } from '@editor/type';
43
54
  import { generatePageNameByApp } from '@editor/utils/editor';
44
55
 
56
+ const useScroll = (root: ComputedRef<MApp | undefined>) => {
57
+ const pageBar = ref<HTMLDivElement>();
58
+ const itemsContainer = ref<HTMLDivElement>();
59
+
60
+ const pageBarWidth = ref(0);
61
+ const canScroll = ref(false);
62
+
63
+ const itemsContainerWidth = computed(() => pageBarWidth.value - 105);
64
+
65
+ let translateLeft = 0;
66
+ const resizeObserver = new ResizeObserver((entries) => {
67
+ for (const { contentRect } of entries) {
68
+ const { width } = contentRect;
69
+ pageBarWidth.value = width || 0;
70
+
71
+ setCanScroll();
72
+ }
73
+ });
74
+
75
+ const setCanScroll = () => {
76
+ if (itemsContainer.value) {
77
+ canScroll.value = itemsContainer.value.scrollWidth > pageBarWidth.value - 105;
78
+ }
79
+ };
80
+
81
+ const scroll = (type: 'left' | 'right' | 'start' | 'end') => {
82
+ if (!itemsContainer.value) return;
83
+
84
+ const maxScrollLeft = itemsContainer.value.scrollWidth - itemsContainerWidth.value;
85
+
86
+ if (type === 'left') {
87
+ translateLeft += 100;
88
+
89
+ if (translateLeft > 0) {
90
+ translateLeft = 0;
91
+ }
92
+ } else if (type === 'right') {
93
+ translateLeft -= 100;
94
+
95
+ if (-translateLeft > maxScrollLeft) {
96
+ translateLeft = -maxScrollLeft;
97
+ }
98
+ } else if (type === 'start') {
99
+ translateLeft = 0;
100
+ } else if (type === 'end') {
101
+ translateLeft = -maxScrollLeft;
102
+ }
103
+
104
+ itemsContainer.value.style.transform = `translate(${translateLeft}px, 0px)`;
105
+ };
106
+
107
+ onMounted(() => {
108
+ pageBar.value && resizeObserver.observe(pageBar.value);
109
+ });
110
+
111
+ onUnmounted(() => {
112
+ resizeObserver.disconnect();
113
+ });
114
+
115
+ watch(
116
+ () => root.value?.items.length,
117
+ (length = 0, preLength = 0) => {
118
+ setTimeout(() => {
119
+ setCanScroll();
120
+ if (length < preLength) {
121
+ scroll('start');
122
+ } else {
123
+ scroll('end');
124
+ }
125
+ });
126
+ },
127
+ );
128
+
129
+ return {
130
+ pageBar,
131
+ itemsContainer,
132
+ canScroll,
133
+
134
+ itemsContainerWidth,
135
+
136
+ scroll,
137
+ };
138
+ };
139
+
45
140
  export default defineComponent({
46
- components: { CaretBottom, Plus },
141
+ components: { ArrowLeftBold, ArrowRightBold, CaretBottom, Plus },
47
142
 
48
143
  setup() {
49
144
  const services = inject<Services>('services');
50
145
  const editorService = services?.editorService;
51
146
 
147
+ const root = computed(() => editorService?.get<MApp>('root'));
148
+
52
149
  return {
53
- root: computed(() => editorService?.get('root')),
150
+ ...useScroll(root),
151
+
152
+ root,
54
153
  page: computed(() => editorService?.get('page')),
55
154
 
56
155
  switchPage(page: MPage) {
@@ -60,7 +159,7 @@ export default defineComponent({
60
159
  addPage() {
61
160
  if (!editorService) return;
62
161
  const pageConfig = {
63
- type: 'page',
162
+ type: NodeType.PAGE,
64
163
  name: generatePageNameByApp(toRaw(editorService.get('root'))),
65
164
  };
66
165
  editorService.add(pageConfig);
@@ -1,15 +1,21 @@
1
1
  <template>
2
- <div class="m-editor-stage">
2
+ <scroll-viewer
3
+ class="m-editor-stage"
4
+ ref="stageWrap"
5
+ :width="stageRect?.width"
6
+ :height="stageRect?.height"
7
+ :zoom="zoom"
8
+ >
3
9
  <div
4
10
  class="m-editor-stage-container"
5
11
  ref="stageContainer"
6
- :style="stageStyle"
7
12
  @contextmenu="contextmenuHandler"
13
+ :style="`transform: scale(${zoom})`"
8
14
  ></div>
9
15
  <teleport to="body">
10
16
  <viewer-menu ref="menu" :style="menuStyle"></viewer-menu>
11
17
  </teleport>
12
- </div>
18
+ </scroll-viewer>
13
19
  </template>
14
20
 
15
21
  <script lang="ts">
@@ -17,6 +23,7 @@ import {
17
23
  computed,
18
24
  defineComponent,
19
25
  inject,
26
+ markRaw,
20
27
  onMounted,
21
28
  onUnmounted,
22
29
  PropType,
@@ -31,7 +38,8 @@ import type { MApp, MNode, MPage } from '@tmagic/schema';
31
38
  import type { MoveableOptions, Runtime, SortEventData, UpdateEventData } from '@tmagic/stage';
32
39
  import StageCore from '@tmagic/stage';
33
40
 
34
- import type { Services } from '@editor/type';
41
+ import ScrollViewer from '@editor/components/ScrollViewer.vue';
42
+ import type { Services, StageRect } from '@editor/type';
35
43
 
36
44
  import ViewerMenu from './ViewerMenu.vue';
37
45
 
@@ -79,6 +87,7 @@ export default defineComponent({
79
87
 
80
88
  components: {
81
89
  ViewerMenu,
90
+ ScrollViewer,
82
91
  },
83
92
 
84
93
  props: {
@@ -88,26 +97,6 @@ export default defineComponent({
88
97
 
89
98
  runtimeUrl: String,
90
99
 
91
- root: {
92
- type: Object as PropType<MApp>,
93
- },
94
-
95
- page: {
96
- type: Object as PropType<MPage>,
97
- },
98
-
99
- node: {
100
- type: Object as PropType<MNode>,
101
- },
102
-
103
- uiSelectMode: {
104
- type: Boolean,
105
- },
106
-
107
- zoom: {
108
- type: Number,
109
- },
110
-
111
100
  canSelect: {
112
101
  type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
113
102
  default: (el: HTMLElement) => Boolean(el.id),
@@ -121,16 +110,18 @@ export default defineComponent({
121
110
  },
122
111
  },
123
112
 
124
- emits: ['select', 'update', 'sort'],
125
-
126
- setup(props, { emit }) {
113
+ setup(props) {
127
114
  const services = inject<Services>('services');
115
+
116
+ const stageWrap = ref<InstanceType<typeof ScrollViewer>>();
128
117
  const stageContainer = ref<HTMLDivElement>();
129
118
 
130
- const stageStyle = computed(() => ({
131
- ...services?.uiService.get<Record<string, string | number>>('stageStyle'),
132
- transform: `scale(${props.zoom}) translate3d(0, -50%, 0)`,
133
- }));
119
+ const stageRect = computed(() => services?.uiService.get<StageRect>('stageRect'));
120
+ const uiSelectMode = computed(() => services?.uiService.get<boolean>('uiSelectMode'));
121
+ const root = computed(() => services?.editorService.get<MApp>('root'));
122
+ const page = computed(() => services?.editorService.get<MPage>('page'));
123
+ const zoom = computed(() => services?.uiService.get<number>('zoom'));
124
+ const node = computed(() => services?.editorService.get<MNode>('node'));
134
125
 
135
126
  let stage: StageCore | null = null;
136
127
  let runtime: Runtime | null = null;
@@ -139,16 +130,16 @@ export default defineComponent({
139
130
  if (stage) return;
140
131
 
141
132
  if (!stageContainer.value) return;
142
- if (!(props.runtimeUrl || props.render) || !props.root) return;
133
+ if (!(props.runtimeUrl || props.render) || !root.value) return;
143
134
 
144
135
  stage = new StageCore({
145
136
  render: props.render,
146
137
  runtimeUrl: props.runtimeUrl,
147
- zoom: props.zoom,
148
- canSelect: (el, stop) => {
138
+ zoom: zoom.value,
139
+ canSelect: (el, event, stop) => {
149
140
  const elCanSelect = props.canSelect(el);
150
141
  // 在组件联动过程中不能再往下选择,返回并触发 ui-select
151
- if (props.uiSelectMode && elCanSelect) {
142
+ if (uiSelectMode.value && elCanSelect && event.type === 'mousedown') {
152
143
  document.dispatchEvent(new CustomEvent('ui-select', { detail: el }));
153
144
  return stop();
154
145
  }
@@ -158,63 +149,78 @@ export default defineComponent({
158
149
  moveableOptions: props.moveableOptions,
159
150
  });
160
151
 
161
- services?.editorService.set('stage', stage);
152
+ services?.editorService.set('stage', markRaw(stage));
162
153
 
163
154
  stage?.mount(stageContainer.value);
164
155
 
165
- stage?.on('select', (el: HTMLElement) => emit('select', el));
156
+ stage?.on('select', (el: HTMLElement) => {
157
+ services?.editorService.select(el.id);
158
+ });
159
+
160
+ stage?.on('highlight', (el: HTMLElement) => {
161
+ services?.editorService.highlight(el.id);
162
+ });
166
163
 
167
164
  stage?.on('update', (ev: UpdateEventData) => {
168
- emit('update', { id: ev.el.id, style: ev.style });
165
+ services?.editorService.update({ id: ev.el.id, style: ev.style });
169
166
  });
170
167
 
171
168
  stage?.on('sort', (ev: SortEventData) => {
172
- emit('sort', ev);
169
+ services?.editorService.sort(ev.src, ev.dist);
173
170
  });
174
171
 
175
172
  stage?.on('changeGuides', () => {
176
173
  services?.uiService.set('showGuides', true);
177
174
  });
178
175
 
179
- if (!props.node?.id) return;
176
+ if (!node.value?.id) return;
180
177
  stage?.on('runtime-ready', (rt) => {
181
178
  runtime = rt;
182
179
  // toRaw返回的值是一个引用而非快照,需要cloneDeep
183
- props.root && runtime?.updateRootConfig(cloneDeep(toRaw(props.root)));
184
- props.page?.id && runtime?.updatePageId?.(props.page.id);
180
+ root.value && runtime?.updateRootConfig(cloneDeep(toRaw(root.value)));
181
+ page.value?.id && runtime?.updatePageId?.(page.value.id);
185
182
  setTimeout(() => {
186
- props.node && stage?.select(toRaw(props.node.id));
183
+ node.value && stage?.select(toRaw(node.value.id));
187
184
  });
188
185
  });
189
186
  });
190
187
 
191
- watch(
192
- () => props.zoom,
193
- (zoom) => {
194
- if (!stage || !zoom) return;
195
- stage?.setZoom(zoom);
196
- },
197
- );
188
+ watch(zoom, (zoom) => {
189
+ if (!stage || !zoom) return;
190
+ stage.setZoom(zoom);
191
+ });
198
192
 
199
- watch(
200
- () => props.root,
201
- (root) => {
202
- if (runtime && root) {
203
- runtime.updateRootConfig(cloneDeep(toRaw(root)));
204
- }
205
- },
206
- );
193
+ watch(root, (root) => {
194
+ if (runtime && root) {
195
+ runtime.updateRootConfig(cloneDeep(toRaw(root)));
196
+ }
197
+ });
198
+
199
+ const resizeObserver = new ResizeObserver((entries) => {
200
+ for (const { contentRect } of entries) {
201
+ services?.uiService.set('stageContainerRect', {
202
+ width: contentRect.width,
203
+ height: contentRect.height,
204
+ });
205
+ }
206
+ });
207
+
208
+ onMounted(() => {
209
+ stageWrap.value?.container && resizeObserver.observe(stageWrap.value.container);
210
+ });
207
211
 
208
212
  onUnmounted(() => {
209
213
  stage?.destroy();
214
+ resizeObserver.disconnect();
210
215
  services?.editorService.set('stage', null);
211
216
  });
212
217
 
213
218
  return {
214
- stageStyle,
215
- ...useMenu(),
216
-
219
+ stageWrap,
217
220
  stageContainer,
221
+ stageRect,
222
+ zoom,
223
+ ...useMenu(),
218
224
  };
219
225
  },
220
226
  });
@@ -24,6 +24,7 @@
24
24
  <script lang="ts">
25
25
  import { computed, defineComponent, inject, onMounted, ref, watch } from 'vue';
26
26
 
27
+ import { NodeType } from '@tmagic/schema';
27
28
  import type StageCore from '@tmagic/stage';
28
29
 
29
30
  import { LayerOffset, Layout, Services } from '@editor/type';
@@ -53,7 +54,8 @@ export default defineComponent({
53
54
  if (!parent.value || !editorService) return (canCenter.value = false);
54
55
  const layout = await editorService.getLayout(parent.value);
55
56
  canCenter.value =
56
- [Layout.ABSOLUTE, Layout.FIXED].includes(layout) && !['app', 'page', 'pop'].includes(`${node.value?.type}`);
57
+ [Layout.ABSOLUTE, Layout.FIXED].includes(layout) &&
58
+ ![NodeType.ROOT, NodeType.PAGE, 'pop'].includes(`${node.value?.type}`);
57
59
  },
58
60
  { immediate: true },
59
61
  );
@@ -62,8 +64,8 @@ export default defineComponent({
62
64
  menu,
63
65
  canPaste,
64
66
 
65
- canDelete: computed(() => node.value?.type !== 'page'),
66
- canMoveZPos: computed(() => node.value?.type !== 'page'),
67
+ canDelete: computed(() => node.value?.type !== NodeType.PAGE),
68
+ canMoveZPos: computed(() => node.value?.type !== NodeType.PAGE),
67
69
  canCenter,
68
70
 
69
71
  center() {
@@ -1,19 +1,11 @@
1
1
  <template>
2
- <div class="m-editor-workspace">
2
+ <div class="m-editor-workspace" tabindex="1" ref="workspace">
3
3
  <magic-stage
4
4
  :key="page?.id"
5
5
  :runtime-url="runtimeUrl"
6
6
  :render="render"
7
- :ui-select-mode="uiSelectMode"
8
- :root="root"
9
- :page="page"
10
- :node="node"
11
- :zoom="zoom"
12
7
  :moveable-options="moveableOptions"
13
8
  :can-select="canSelect"
14
- @select="selectHandler"
15
- @update="updateNodeHandler"
16
- @sort="sortNodeHandler"
17
9
  ></magic-stage>
18
10
 
19
11
  <slot name="workspace-content"></slot>
@@ -26,10 +18,11 @@
26
18
  </template>
27
19
 
28
20
  <script lang="ts">
29
- import { computed, defineComponent, inject, nextTick, PropType, watch } from 'vue';
21
+ import { computed, defineComponent, inject, onMounted, onUnmounted, PropType, ref } from 'vue';
22
+ import KeyController from 'keycon';
30
23
 
31
- import type { MApp, MComponent, MContainer, MNode, MPage } from '@tmagic/schema';
32
- import type { MoveableOptions, SortEventData } from '@tmagic/stage';
24
+ import type { MNode, MPage } from '@tmagic/schema';
25
+ import type { MoveableOptions } from '@tmagic/stage';
33
26
  import StageCore from '@tmagic/stage';
34
27
 
35
28
  import type { Services } from '@editor/type';
@@ -63,35 +56,53 @@ export default defineComponent({
63
56
 
64
57
  setup() {
65
58
  const services = inject<Services>('services');
59
+ const workspace = ref<HTMLDivElement>();
66
60
  const node = computed(() => services?.editorService.get<MNode>('node'));
67
- const stage = computed(() => services?.editorService.get<StageCore>('stage'));
61
+ let keycon: KeyController;
68
62
 
69
- watch([() => node.value?.id, stage], ([id, stage]) => {
70
- nextTick(() => {
71
- // 等待相关dom变更完成后,再select,适用大多数场景
72
- id && stage?.select(id);
73
- });
74
- });
63
+ const mouseenterHandler = () => {
64
+ workspace.value?.focus();
65
+ };
75
66
 
76
- return {
77
- uiSelectMode: computed(() => services?.uiService.get<boolean>('uiSelectMode')),
78
- root: computed(() => services?.editorService.get<MApp>('root')),
79
- page: computed(() => services?.editorService.get<MPage>('page')),
80
- zoom: computed(() => services?.uiService.get<number>('zoom')),
67
+ const mouseleaveHandler = () => {
68
+ workspace.value?.blur();
69
+ };
81
70
 
82
- node,
71
+ onMounted(() => {
72
+ workspace.value?.addEventListener('mouseenter', mouseenterHandler);
73
+
74
+ workspace.value?.addEventListener('mouseleave', mouseleaveHandler);
75
+
76
+ keycon = new KeyController(workspace.value);
77
+
78
+ keycon
79
+ .keyup('delete', () => {
80
+ node.value && services?.editorService.remove(node.value);
81
+ })
82
+ .keyup(['ctrl', 'c'], () => {
83
+ node.value && services?.editorService.copy(node.value);
84
+ })
85
+ .keyup(['ctrl', 'v'], () => {
86
+ node.value && services?.editorService.paste();
87
+ })
88
+ .keyup(['ctrl', 'x'], () => {
89
+ if (node.value && services) {
90
+ services.editorService.copy(node.value);
91
+ services.editorService.remove(node.value);
92
+ }
93
+ });
94
+ });
83
95
 
84
- selectHandler(el: HTMLElement) {
85
- services?.editorService.select(el.id);
86
- },
96
+ onUnmounted(() => {
97
+ workspace.value?.removeEventListener('mouseenter', mouseenterHandler);
98
+ workspace.value?.removeEventListener('mouseleave', mouseleaveHandler);
99
+ keycon.destroy();
100
+ });
87
101
 
88
- updateNodeHandler(node: MComponent | MContainer | MPage) {
89
- services?.editorService.update(node);
90
- },
102
+ return {
103
+ workspace,
91
104
 
92
- sortNodeHandler(ev: SortEventData) {
93
- services?.editorService.sort(ev.src, ev.dist);
94
- },
105
+ page: computed(() => services?.editorService.get<MPage>('page')),
95
106
  };
96
107
  },
97
108
  });
@@ -24,6 +24,46 @@ const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpper
24
24
 
25
25
  const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
26
26
 
27
+ const doAction = async (
28
+ args: any[],
29
+ scope: any,
30
+ sourceMethod: any,
31
+ beforeMethodName: string,
32
+ afterMethodName: string,
33
+ fn: (args: any[], next?: Function | undefined) => Promise<void>,
34
+ ) => {
35
+ try {
36
+ let beforeArgs = args;
37
+
38
+ for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
39
+ let beforeReturnValue = (await beforeMethod(...beforeArgs)) || [];
40
+
41
+ if (isError(beforeReturnValue)) throw beforeReturnValue;
42
+
43
+ if (!Array.isArray(beforeReturnValue)) {
44
+ beforeReturnValue = [beforeReturnValue];
45
+ }
46
+
47
+ beforeArgs = beforeArgs.map((v: any, index: number) => {
48
+ if (typeof beforeReturnValue[index] === 'undefined') return v;
49
+ return beforeReturnValue[index];
50
+ });
51
+ }
52
+
53
+ let returnValue: any = await fn(beforeArgs, sourceMethod.bind(scope));
54
+
55
+ for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
56
+ returnValue = await afterMethod(...beforeArgs, returnValue);
57
+
58
+ if (isError(returnValue)) throw returnValue;
59
+ }
60
+
61
+ return returnValue;
62
+ } catch (error) {
63
+ throw error;
64
+ }
65
+ };
66
+
27
67
  /**
28
68
  * 提供两种方式对Class进行扩展
29
69
  * 方法1:
@@ -74,8 +114,10 @@ const isError = (error: any): boolean => Object.prototype.toString.call(error) =
74
114
  export default class extends EventEmitter {
75
115
  private pluginOptionsList: Record<string, Function[]> = {};
76
116
  private middleware: Record<string, Function[]> = {};
117
+ private taskList: (() => Promise<void>)[] = [];
118
+ private doingTask = false;
77
119
 
78
- constructor(methods: string[]) {
120
+ constructor(methods: string[] = [], serialMethods: string[] = []) {
79
121
  super();
80
122
 
81
123
  methods.forEach((propertyName: string) => {
@@ -93,32 +135,28 @@ export default class extends EventEmitter {
93
135
  const fn = compose(this.middleware[propertyName]);
94
136
  Object.defineProperty(scope, propertyName, {
95
137
  value: async (...args: any[]) => {
96
- let beforeArgs = args;
97
-
98
- for (const beforeMethod of this.pluginOptionsList[beforeMethodName]) {
99
- let beforeReturnValue = (await beforeMethod(...beforeArgs)) || [];
100
-
101
- if (isError(beforeReturnValue)) throw beforeReturnValue;
102
-
103
- if (!Array.isArray(beforeReturnValue)) {
104
- beforeReturnValue = [beforeReturnValue];
105
- }
106
-
107
- beforeArgs = beforeArgs.map((v: any, index: number) => {
108
- if (typeof beforeReturnValue[index] === 'undefined') return v;
109
- return beforeReturnValue[index];
110
- });
138
+ if (!serialMethods.includes(propertyName)) {
139
+ return doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
111
140
  }
112
141
 
113
- let returnValue = await fn(beforeArgs, sourceMethod.bind(scope));
114
-
115
- for (const afterMethod of this.pluginOptionsList[afterMethodName]) {
116
- returnValue = await afterMethod(...beforeArgs, returnValue);
142
+ // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
143
+ // 这里保证函数调用严格按顺序执行;
144
+ const promise = new Promise<any>((resolve, reject) => {
145
+ this.taskList.push(async () => {
146
+ try {
147
+ const value = await doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
148
+ resolve(value);
149
+ } catch (e) {
150
+ reject(e);
151
+ }
152
+ });
153
+ });
117
154
 
118
- if (isError(returnValue)) throw returnValue;
155
+ if (!this.doingTask) {
156
+ this.doTask();
119
157
  }
120
158
 
121
- return returnValue;
159
+ return promise;
122
160
  },
123
161
  });
124
162
  });
@@ -135,4 +173,14 @@ export default class extends EventEmitter {
135
173
  if (typeof method === 'function') this.pluginOptionsList[methodName].push(method);
136
174
  });
137
175
  }
176
+
177
+ private async doTask() {
178
+ this.doingTask = true;
179
+ let task = this.taskList.shift();
180
+ while (task) {
181
+ await task();
182
+ task = this.taskList.shift();
183
+ }
184
+ this.doingTask = false;
185
+ }
138
186
  }