@tmagic/editor 1.1.6 → 1.2.0-beta.10

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 (82) hide show
  1. package/dist/style.css +191 -8
  2. package/dist/tmagic-editor.mjs +2104 -1027
  3. package/dist/tmagic-editor.mjs.map +1 -1
  4. package/dist/tmagic-editor.umd.js +2136 -1053
  5. package/dist/tmagic-editor.umd.js.map +1 -1
  6. package/package.json +11 -10
  7. package/src/Editor.vue +27 -12
  8. package/src/components/ContentMenu.vue +4 -4
  9. package/src/components/Icon.vue +7 -5
  10. package/src/{layouts → components}/Layout.vue +15 -5
  11. package/src/components/ScrollBar.vue +1 -1
  12. package/src/components/ScrollViewer.vue +10 -1
  13. package/src/components/ToolButton.vue +30 -15
  14. package/src/fields/Code.vue +5 -2
  15. package/src/fields/CodeLink.vue +20 -12
  16. package/src/fields/CodeSelect.vue +131 -0
  17. package/src/fields/UISelect.vue +9 -8
  18. package/src/index.ts +7 -2
  19. package/src/layouts/AddPageBox.vue +13 -20
  20. package/src/layouts/CodeEditor.vue +106 -120
  21. package/src/layouts/Framework.vue +62 -40
  22. package/src/layouts/NavMenu.vue +1 -1
  23. package/src/layouts/PropsPanel.vue +5 -10
  24. package/src/layouts/Resizer.vue +1 -1
  25. package/src/layouts/sidebar/ComponentListPanel.vue +15 -13
  26. package/src/layouts/sidebar/LayerMenu.vue +89 -92
  27. package/src/layouts/sidebar/LayerPanel.vue +273 -178
  28. package/src/layouts/sidebar/Sidebar.vue +129 -37
  29. package/src/layouts/sidebar/code-block/CodeBlockEditor.vue +169 -0
  30. package/src/layouts/sidebar/code-block/CodeBlockList.vue +267 -0
  31. package/src/layouts/workspace/Breadcrumb.vue +32 -0
  32. package/src/layouts/workspace/PageBar.vue +12 -11
  33. package/src/layouts/workspace/PageBarScrollContainer.vue +1 -1
  34. package/src/layouts/workspace/Stage.vue +15 -7
  35. package/src/layouts/workspace/ViewerMenu.vue +8 -7
  36. package/src/layouts/workspace/Workspace.vue +10 -3
  37. package/src/services/codeBlock.ts +415 -0
  38. package/src/services/editor.ts +35 -5
  39. package/src/theme/breadcrumb.scss +6 -0
  40. package/src/theme/code-block.scss +184 -0
  41. package/src/theme/component-list-panel.scss +3 -7
  42. package/src/theme/index.scss +1 -13
  43. package/src/theme/layer-panel.scss +7 -2
  44. package/src/theme/layout.scss +5 -0
  45. package/src/theme/theme.scss +16 -0
  46. package/src/type.ts +88 -1
  47. package/src/utils/props.ts +9 -3
  48. package/src/utils/scroll-viewer.ts +18 -2
  49. package/src/utils/stage.ts +7 -0
  50. package/types/Editor.vue.d.ts +2 -2
  51. package/types/components/ContentMenu.vue.d.ts +1 -3
  52. package/types/components/Icon.vue.d.ts +1 -3
  53. package/types/{layouts → components}/Layout.vue.d.ts +0 -0
  54. package/types/components/ScrollBar.vue.d.ts +1 -3
  55. package/types/components/ScrollViewer.vue.d.ts +45 -1
  56. package/types/components/ToolButton.vue.d.ts +1 -3
  57. package/types/fields/Code.vue.d.ts +9 -3
  58. package/types/fields/CodeLink.vue.d.ts +5 -3
  59. package/types/fields/CodeSelect.vue.d.ts +94 -0
  60. package/types/fields/UISelect.vue.d.ts +1 -3
  61. package/types/index.d.ts +4 -0
  62. package/types/layouts/AddPageBox.vue.d.ts +43 -3
  63. package/types/layouts/CodeEditor.vue.d.ts +158 -45
  64. package/types/layouts/NavMenu.vue.d.ts +1 -3
  65. package/types/layouts/sidebar/LayerMenu.vue.d.ts +31 -63
  66. package/types/layouts/sidebar/LayerPanel.vue.d.ts +38 -1074
  67. package/types/layouts/sidebar/Sidebar.vue.d.ts +119 -17
  68. package/types/layouts/sidebar/code-block/CodeBlockEditor.vue.d.ts +50 -0
  69. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +73 -0
  70. package/types/layouts/workspace/Breadcrumb.vue.d.ts +44 -0
  71. package/types/layouts/workspace/Stage.vue.d.ts +23 -7
  72. package/types/layouts/workspace/ViewerMenu.vue.d.ts +6 -3
  73. package/types/layouts/workspace/Workspace.vue.d.ts +23 -5
  74. package/types/services/codeBlock.d.ts +155 -0
  75. package/types/services/editor.d.ts +12 -1
  76. package/types/services/props.d.ts +12 -0
  77. package/types/services/ui.d.ts +1 -1
  78. package/types/type.d.ts +78 -1
  79. package/types/utils/props.d.ts +12 -0
  80. package/types/utils/scroll-viewer.d.ts +5 -0
  81. package/src/layouts/sidebar/TabPane.vue +0 -99
  82. package/types/layouts/sidebar/TabPane.vue.d.ts +0 -14
@@ -1,63 +1,155 @@
1
1
  <template>
2
- <el-tabs
2
+ <TMagicTabs
3
3
  v-if="data.type === 'tabs' && data.items.length"
4
4
  class="m-editor-sidebar"
5
5
  v-model="activeTabName"
6
6
  type="card"
7
7
  tab-position="left"
8
8
  >
9
- <tab-pane v-for="(item, index) in data.items" :key="index" :data="item">
10
- <template #layer-panel-header v-if="item === 'layer'">
11
- <slot name="layer-panel-header"></slot>
9
+ <component :is="uiComponent.component" v-for="(config, index) in sideBarItems" :key="index" :name="config.text">
10
+ <template #label>
11
+ <div :key="config.text">
12
+ <MIcon v-if="config.icon" :icon="config.icon"></MIcon>
13
+ <div v-if="config.text" class="magic-editor-tab-panel-title">{{ config.text }}</div>
14
+ </div>
12
15
  </template>
13
16
 
14
- <template #layer-node-content="{ node, data }" v-if="item === 'layer'">
15
- <slot name="layer-node-content" :data="data" :node="node"></slot>
16
- </template>
17
+ <component v-if="config" :is="config.component" v-bind="config.props || {}" v-on="config?.listeners || {}">
18
+ <template
19
+ #component-list-panel-header
20
+ v-if="config.$key === 'component-list' || config.slots?.componentListPanelHeader"
21
+ >
22
+ <slot v-if="config.$key === 'component-list'" name="component-list-panel-header"></slot>
23
+ <component v-else-if="config.slots?.componentListPanelHeader" :is="config.slots.componentListPanelHeader" />
24
+ </template>
17
25
 
18
- <template #component-list-panel-header v-if="item === 'component-list'">
19
- <slot name="component-list-panel-header"></slot>
20
- </template>
26
+ <template
27
+ #component-list-item="{ component }"
28
+ v-if="config.$key === 'component-list' || config.slots?.componentListItem"
29
+ >
30
+ <slot v-if="config.$key === 'component-list'" name="component-list-item" :component="component"></slot>
31
+ <component
32
+ v-else-if="config.slots?.componentListItem"
33
+ :is="config.slots.componentListItem"
34
+ :component="component"
35
+ />
36
+ </template>
21
37
 
22
- <template #component-list-item="{ component }" v-if="item === 'component-list'">
23
- <slot name="component-list-item" :component="component"></slot>
24
- </template>
25
- </tab-pane>
26
- </el-tabs>
38
+ <template #layer-panel-header v-if="config.$key === 'layer' || config.slots?.layerPanelHeader">
39
+ <slot v-if="config.$key === 'layer'" name="layer-panel-header"></slot>
40
+ <component v-else-if="config.slots?.layerPanelHeader" :is="config.slots.layerPanelHeader" />
41
+ </template>
42
+
43
+ <template #code-block-panel-header v-if="config.$key === 'code-block' || config.slots?.codeBlockPanelHeader">
44
+ <slot v-if="config.$key === 'code-block'" name="code-block-panel-header"></slot>
45
+ <component v-else-if="config.slots?.codeBlockPanelHeader" :is="config.slots.codeBlockPanelHeader" />
46
+ </template>
47
+
48
+ <template
49
+ #code-block-panel-tool="{ id, data }"
50
+ v-if="config.$key === 'code-block' || config.slots?.codeBlockPanelTool"
51
+ >
52
+ <slot v-if="config.$key === 'code-block'" name="code-block-panel-tool" :id="id" :data="data"></slot>
53
+ <component v-else-if="config.slots?.codeBlockPanelTool" :is="config.slots.codeBlockPanelTool" />
54
+ </template>
55
+
56
+ <template
57
+ #code-block-edit-panel-header="{ id }"
58
+ v-if="config.$key === 'code-block' || config.slots?.codeBlockEditPanelHeader"
59
+ >
60
+ <slot v-if="config.$key === 'code-block'" name="code-block-edit-panel-header" :id="id"></slot>
61
+ <component v-else-if="config.slots?.codeBlockEditPanelHeader" :is="config.slots.codeBlockEditPanelHeader" />
62
+ </template>
63
+
64
+ <template
65
+ #layer-node-content="{ data: nodeData, node }"
66
+ v-if="config.$key === 'layer' || config.slots?.layerNodeContent"
67
+ >
68
+ <slot v-if="config.$key === 'layer'" name="layer-node-content" :data="nodeData" :node="node"></slot>
69
+ <component
70
+ v-else-if="config.slots?.layerNodeContent"
71
+ :is="config.slots.layerNodeContent"
72
+ :data="nodeData"
73
+ :node="node"
74
+ />
75
+ </template>
76
+ </component>
77
+ </component>
78
+ </TMagicTabs>
27
79
  </template>
28
80
 
29
- <script lang="ts">
30
- import { defineComponent, PropType, ref, watch } from 'vue';
81
+ <script lang="ts" setup name="MEditorSidebar">
82
+ import { computed, ref, watch } from 'vue';
83
+ import { Coin, EditPen, Files } from '@element-plus/icons-vue';
84
+
85
+ import { getConfig, TMagicTabs } from '@tmagic/design';
31
86
 
87
+ import MIcon from '../../components/Icon.vue';
88
+ import type { MenuButton, MenuComponent, SideComponent, SideItem } from '../../type';
32
89
  import { SideBarData } from '../../type';
33
90
 
34
- import TabPane from './TabPane.vue';
91
+ import CodeBlockList from './code-block/CodeBlockList.vue';
92
+ import ComponentListPanel from './ComponentListPanel.vue';
93
+ import LayerPanel from './LayerPanel.vue';
35
94
 
36
- export default defineComponent({
37
- components: { TabPane },
95
+ const props = withDefaults(
96
+ defineProps<{
97
+ data?: SideBarData;
98
+ layerContentMenu: (MenuButton | MenuComponent)[];
99
+ }>(),
100
+ {
101
+ data: () => ({ type: 'tabs', status: '组件', items: ['component-list', 'layer', 'code-block'] }),
102
+ },
103
+ );
104
+
105
+ const uiComponent = getConfig('components').tabPane;
38
106
 
39
- name: 'm-sidebar',
107
+ const activeTabName = ref(props.data?.status);
40
108
 
41
- props: {
42
- data: {
43
- type: Object as PropType<SideBarData>,
44
- default: () => ({ type: 'tabs', status: '组件', items: ['component-list', 'layer'] }),
109
+ const getItemConfig = (data: SideItem): SideComponent => {
110
+ const map: Record<string, SideComponent> = {
111
+ 'component-list': {
112
+ $key: 'component-list',
113
+ type: 'component',
114
+ icon: Coin,
115
+ text: '组件',
116
+ component: ComponentListPanel,
117
+ slots: {},
45
118
  },
46
- },
119
+ layer: {
120
+ $key: 'layer',
121
+ type: 'component',
122
+ icon: Files,
123
+ text: '已选组件',
124
+ props: {
125
+ layerContentMenu: props.layerContentMenu,
126
+ },
127
+ component: LayerPanel,
128
+ slots: {},
129
+ },
130
+ 'code-block': {
131
+ $key: 'code-block',
132
+ type: 'component',
133
+ icon: EditPen,
134
+ text: '代码编辑',
135
+ component: CodeBlockList,
136
+ slots: {},
137
+ },
138
+ };
47
139
 
48
- setup(props) {
49
- const activeTabName = ref(props.data?.status);
140
+ return typeof data === 'string' ? map[data] : data;
141
+ };
50
142
 
51
- watch(
52
- () => props.data?.status,
53
- (status) => {
54
- activeTabName.value = status || '0';
55
- },
56
- );
143
+ const sideBarItems = computed(() => props.data.items.map((item) => getItemConfig(item)));
57
144
 
58
- return {
59
- activeTabName,
60
- };
145
+ watch(
146
+ () => props.data.status,
147
+ (status) => {
148
+ activeTabName.value = status || '0';
61
149
  },
150
+ );
151
+
152
+ defineExpose({
153
+ activeTabName,
62
154
  });
63
155
  </script>
@@ -0,0 +1,169 @@
1
+ <template>
2
+ <TMagicDialog
3
+ v-model="isShowCodeBlockEditor"
4
+ class="code-editor-dialog"
5
+ :title="currentTitle"
6
+ :fullscreen="true"
7
+ :before-close="saveAndClose"
8
+ :append-to-body="true"
9
+ >
10
+ <Layout v-model:left="left" :min-left="45" class="code-editor-layout">
11
+ <!-- 左侧列表 -->
12
+ <template #left v-if="mode === CodeEditorMode.LIST">
13
+ <TMagicTree
14
+ v-if="!isEmpty(state.codeList)"
15
+ class="side-tree"
16
+ node-key="id"
17
+ empty-text="暂无代码块"
18
+ :data="state.codeList"
19
+ :highlight-current="true"
20
+ :current-node-key="state.codeList[0].id"
21
+ @node-click="selectHandler"
22
+ >
23
+ <template #default="{ data }">
24
+ <div :id="data.id" class="list-container">
25
+ <div class="list-item">
26
+ <div class="code-name">{{ data.name }}({{ data.id }})</div>
27
+ </div>
28
+ </div>
29
+ </template>
30
+ </TMagicTree>
31
+ </template>
32
+ <!-- 右侧区域 -->
33
+ <template #center>
34
+ <div
35
+ v-if="!isEmpty(codeConfig)"
36
+ :class="[
37
+ mode === CodeEditorMode.LIST
38
+ ? 'm-editor-code-block-editor-panel-list-mode'
39
+ : 'm-editor-code-block-editor-panel',
40
+ ]"
41
+ >
42
+ <slot name="code-block-edit-panel-header" :id="id"></slot>
43
+ <TMagicCard shadow="never">
44
+ <template #header>
45
+ <div class="code-name-wrapper">
46
+ <div class="code-name-label">代码块名称</div>
47
+ <TMagicInput
48
+ v-if="codeConfig"
49
+ class="code-name-input"
50
+ v-model="codeConfig.name"
51
+ :disabled="!editable"
52
+ />
53
+ </div>
54
+ </template>
55
+ <div class="m-editor-wrapper">
56
+ <MagicCodeEditor
57
+ v-if="codeConfig"
58
+ ref="codeEditor"
59
+ class="m-editor-container"
60
+ :init-values="`${codeConfig.content}`"
61
+ @save="saveCode"
62
+ :options="{
63
+ tabSize: 2,
64
+ fontSize: 16,
65
+ formatOnPaste: true,
66
+ readOnly: !editable,
67
+ }"
68
+ ></MagicCodeEditor>
69
+ <div class="m-editor-content-bottom" v-if="editable">
70
+ <TMagicButton type="primary" class="button" @click="saveCode">保存</TMagicButton>
71
+ <TMagicButton type="primary" class="button" @click="saveAndClose">关闭</TMagicButton>
72
+ </div>
73
+ <div class="m-editor-content-bottom" v-else>
74
+ <TMagicButton type="primary" class="button" @click="saveAndClose">关闭</TMagicButton>
75
+ </div>
76
+ </div>
77
+ </TMagicCard>
78
+ </div>
79
+ </template>
80
+ </Layout>
81
+ </TMagicDialog>
82
+ </template>
83
+
84
+ <script lang="ts" setup name="MEditorCodeBlockEditor">
85
+ import { computed, inject, reactive, ref, watchEffect } from 'vue';
86
+ import { forIn, isEmpty } from 'lodash-es';
87
+ import type * as monaco from 'monaco-editor';
88
+
89
+ import { TMagicButton, TMagicCard, TMagicDialog, TMagicInput, tMagicMessage, TMagicTree } from '@tmagic/design';
90
+
91
+ import Layout from '../../../components/Layout.vue';
92
+ import type { CodeBlockContent, CodeDslList, ListState, Services } from '../../../type';
93
+ import { CodeEditorMode } from '../../../type';
94
+ import MagicCodeEditor from '../../CodeEditor.vue';
95
+
96
+ const services = inject<Services>('services');
97
+
98
+ const codeEditor = ref<InstanceType<typeof MagicCodeEditor>>();
99
+ const left = ref(200);
100
+ const currentTitle = ref('');
101
+ // 编辑器当前需展示的代码块内容
102
+ const codeConfig = ref<CodeBlockContent | null>(null);
103
+ // select选择的内容(ListState)
104
+ const state = reactive<ListState>({
105
+ codeList: [],
106
+ });
107
+
108
+ // 是否展示代码编辑区
109
+ const isShowCodeBlockEditor = computed(
110
+ () => (codeConfig.value && services?.codeBlockService.getCodeEditorShowStatus()) || false,
111
+ );
112
+ const mode = computed(() => services?.codeBlockService.getMode());
113
+ const id = computed(() => services?.codeBlockService.getId() || '');
114
+ const editable = computed(() => services?.codeBlockService.getEditStatus());
115
+ // 当前选中组件绑定的代码块id数组
116
+ const selectedIds = computed(() => services?.codeBlockService.getCombineIds() || []);
117
+
118
+ watchEffect(async () => {
119
+ codeConfig.value = (await services?.codeBlockService.getCodeContentById(id.value)) || null;
120
+ });
121
+
122
+ watchEffect(async () => {
123
+ const codeDsl = (await services?.codeBlockService.getCodeDslByIds(selectedIds.value)) || null;
124
+ if (!codeDsl) return;
125
+ state.codeList = [];
126
+ forIn(codeDsl, (value: CodeBlockContent, key: string) => {
127
+ state.codeList.push({
128
+ id: key,
129
+ name: value.name,
130
+ });
131
+ });
132
+ currentTitle.value = state.codeList[0]?.name || '';
133
+ });
134
+
135
+ // 保存代码
136
+ const saveCode = async (): Promise<boolean> => {
137
+ if (!codeEditor.value || !codeConfig.value || !editable.value) return true;
138
+
139
+ try {
140
+ // 代码内容
141
+ const codeContent = (codeEditor.value.getEditor() as monaco.editor.IStandaloneCodeEditor)?.getValue();
142
+ /* eslint no-eval: "off" */
143
+ codeConfig.value.content = eval(codeContent);
144
+ } catch (e: any) {
145
+ tMagicMessage.error(e.stack);
146
+ return false;
147
+ }
148
+ // 存入dsl
149
+ await services?.codeBlockService.setCodeDslById(id.value, {
150
+ name: codeConfig.value.name,
151
+ content: codeConfig.value.content,
152
+ });
153
+ tMagicMessage.success('代码保存成功');
154
+ return true;
155
+ };
156
+
157
+ // 保存并关闭
158
+ const saveAndClose = async () => {
159
+ const saveRes = await saveCode();
160
+ if (saveRes) {
161
+ await services?.codeBlockService.setCodeEditorShowStatus(false);
162
+ }
163
+ };
164
+
165
+ const selectHandler = (data: CodeDslList) => {
166
+ services?.codeBlockService.setId(data.id);
167
+ currentTitle.value = data.name;
168
+ };
169
+ </script>
@@ -0,0 +1,267 @@
1
+ <template>
2
+ <div class="m-editor-code-block-list">
3
+ <slot name="code-block-panel-header">
4
+ <div class="code-header-wrapper">
5
+ <TMagicInput
6
+ :class="[editable ? 'code-filter-input' : 'code-filter-input-no-btn']"
7
+ size="small"
8
+ placeholder="输入关键字进行过滤"
9
+ clearable
10
+ v-model="filterText"
11
+ @input="filterTextChangeHandler"
12
+ ></TMagicInput>
13
+ <TMagicButton class="create-code-button" type="primary" size="small" @click="createCodeBlock" v-if="editable"
14
+ >新增</TMagicButton
15
+ >
16
+ </div>
17
+ </slot>
18
+
19
+ <!-- 代码块列表 -->
20
+ <TMagicTree
21
+ v-if="!isEmpty(state.codeList)"
22
+ ref="tree"
23
+ node-key="id"
24
+ empty-text="暂无代码块"
25
+ :data="state.codeList"
26
+ :highlight-current="true"
27
+ :filter-node-method="filterNode"
28
+ @node-click="toggleCombineRelation"
29
+ >
30
+ <template #default="{ data }">
31
+ <div :id="data.id" class="list-container">
32
+ <div class="list-item">
33
+ <div class="code-name">{{ data.name }}({{ data.id }})</div>
34
+ <!-- 右侧工具栏 -->
35
+ <div class="right-tool">
36
+ <TMagicTooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
37
+ <Icon :icon="editable ? Edit : View" class="edit-icon" @click.stop="editCode(`${data.id}`)"></Icon>
38
+ </TMagicTooltip>
39
+ <TMagicTooltip
40
+ effect="dark"
41
+ content="查看绑定关系"
42
+ placement="bottom"
43
+ v-if="state.bindComps[data.id] && state.bindComps[data.id].length > 0"
44
+ >
45
+ <Icon :icon="Link" class="edit-icon" @click.stop="toggleCombineRelation(data)"></Icon>
46
+ </TMagicTooltip>
47
+ <TMagicTooltip effect="dark" content="删除" placement="bottom" v-if="editable">
48
+ <Icon :icon="Close" class="edit-icon" @click.stop="deleteCode(`${data.id}`)"></Icon>
49
+ </TMagicTooltip>
50
+ <slot name="code-block-panel-tool" :id="data.id" :data="data.codeBlockContent"></slot>
51
+ </div>
52
+ </div>
53
+ <!-- 展示代码块下绑定的组件 -->
54
+ <div
55
+ class="code-comp-map-wrapper"
56
+ v-if="data.showRelation && state.bindComps[data.id] && state.bindComps[data.id].length > 0"
57
+ >
58
+ <svg class="arrow-left" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
59
+ <path
60
+ fill="currentColor"
61
+ d="M609.408 149.376 277.76 489.6a32 32 0 0 0 0 44.672l331.648 340.352a29.12 29.12 0 0 0 41.728 0 30.592 30.592 0 0 0 0-42.752L339.264 511.936l311.872-319.872a30.592 30.592 0 0 0 0-42.688 29.12 29.12 0 0 0-41.728 0z"
62
+ ></path>
63
+ </svg>
64
+ <!-- todo 功能暂时隐藏 -->
65
+ <!-- <TMagicButton
66
+ v-for="(comp, index) in state.bindComps[data.id]"
67
+ :key="index"
68
+ class="code-comp"
69
+ size="small"
70
+ :plain="true"
71
+ >{{ comp.name }}<Icon :icon="Close" class="comp-delete-icon" @click.stop="unbind(comp.id, data.id)"></Icon
72
+ ></TMagicButton> -->
73
+ <TMagicButton
74
+ v-for="(comp, index) in state.bindComps[data.id]"
75
+ :key="index"
76
+ class="code-comp"
77
+ size="small"
78
+ :plain="true"
79
+ @click.stop="selectComp(comp.id)"
80
+ >{{ comp.name }}</TMagicButton
81
+ >
82
+ </div>
83
+ </div>
84
+ </template>
85
+ </TMagicTree>
86
+
87
+ <!-- 代码块编辑区 -->
88
+ <code-block-editor>
89
+ <template #code-block-edit-panel-header="{ id }">
90
+ <slot name="code-block-edit-panel-header" :id="id"></slot>
91
+ </template>
92
+ </code-block-editor>
93
+ </div>
94
+ </template>
95
+
96
+ <script lang="ts" setup name="MEditorCodeBlockList">
97
+ import { computed, inject, reactive, ref, watch } from 'vue';
98
+ import { Close, Edit, Link, View } from '@element-plus/icons-vue';
99
+ import { forIn, isEmpty } from 'lodash-es';
100
+
101
+ import { TMagicButton, TMagicInput, tMagicMessage, TMagicTooltip, TMagicTree } from '@tmagic/design';
102
+ import { Id } from '@tmagic/schema';
103
+ import StageCore from '@tmagic/stage';
104
+
105
+ import Icon from '../../../components/Icon.vue';
106
+ import type { CodeBlockContent, CodeRelation, Services } from '../../../type';
107
+ import { CodeDeleteErrorType, CodeDslList, CodeEditorMode, ListRelationState } from '../../../type';
108
+
109
+ import codeBlockEditor from './CodeBlockEditor.vue';
110
+
111
+ const props = defineProps<{
112
+ customError?: (id: string, errorType: CodeDeleteErrorType) => any;
113
+ }>();
114
+
115
+ const services = inject<Services>('services');
116
+
117
+ // 代码块列表
118
+ const state = reactive<ListRelationState>({
119
+ codeList: [],
120
+ bindComps: {},
121
+ });
122
+
123
+ const editable = computed(() => services?.codeBlockService.getEditStatus());
124
+
125
+ // 根据代码块ID获取其绑定的组件信息
126
+ const getBindCompsByCodeId = (codeId: string, codeBlockContent: CodeBlockContent) => {
127
+ if (isEmpty(codeBlockContent) || isEmpty(codeBlockContent.comps)) {
128
+ state.bindComps[codeId] = [];
129
+ return;
130
+ }
131
+ const compsField = codeBlockContent.comps as CodeRelation;
132
+ const bindCompIds = Object.keys(compsField);
133
+ const bindCompsFiltered = bindCompIds.filter((compId) => !isEmpty(compsField[compId]));
134
+ const compsInfo = bindCompsFiltered.map((compId) => ({
135
+ id: compId,
136
+ name: getCompName(compId),
137
+ }));
138
+ state.bindComps[codeId] = compsInfo;
139
+ };
140
+
141
+ // 初始化代码块列表
142
+ const initList = async () => {
143
+ const codeDsl = (await services?.codeBlockService.getCodeDsl()) || null;
144
+ if (!codeDsl) return;
145
+ state.codeList = [];
146
+ forIn(codeDsl, (value: CodeBlockContent, codeId: string) => {
147
+ getBindCompsByCodeId(codeId, value);
148
+ state.codeList.push({
149
+ id: codeId,
150
+ name: value.name,
151
+ codeBlockContent: value,
152
+ showRelation: true,
153
+ });
154
+ });
155
+ };
156
+
157
+ watch(
158
+ () => services?.codeBlockService.getCodeDsl(),
159
+ () => {
160
+ initList();
161
+ },
162
+ {
163
+ immediate: true,
164
+ deep: true,
165
+ },
166
+ );
167
+
168
+ // 监听组件名称修改,更新到代码块列表
169
+ watch(
170
+ () => services?.editorService.get('node'),
171
+ (curNode) => {
172
+ if (!curNode?.id) return;
173
+ forIn(state.bindComps, (bindCompInfo) => {
174
+ bindCompInfo.forEach((comp) => {
175
+ if (comp.id === curNode.id) {
176
+ comp.name = curNode.name;
177
+ }
178
+ });
179
+ });
180
+ },
181
+ );
182
+
183
+ // 新增代码块
184
+ const createCodeBlock = async () => {
185
+ const { codeBlockService } = services || {};
186
+ if (!codeBlockService) {
187
+ tMagicMessage.error('新增代码块失败');
188
+ return;
189
+ }
190
+ const codeConfig: CodeBlockContent = {
191
+ name: '代码块',
192
+ content: `() => {\n // place your code here\n}`,
193
+ };
194
+ await codeBlockService.setMode(CodeEditorMode.EDITOR);
195
+ const id = await codeBlockService.getUniqueId();
196
+ await codeBlockService.setCodeDslById(id, codeConfig);
197
+ codeBlockService.setCodeEditorContent(true, id);
198
+ };
199
+
200
+ // 编辑代码块
201
+ const editCode = async (key: string) => {
202
+ await services?.codeBlockService.setMode(CodeEditorMode.EDITOR);
203
+ services?.codeBlockService.setCodeEditorContent(true, key);
204
+ };
205
+
206
+ // 删除代码块
207
+ const deleteCode = (key: string) => {
208
+ const existBinds = !!(state.bindComps[key]?.length > 0);
209
+ const undeleteableList = services?.codeBlockService.getUndeletableList() || [];
210
+ if (!existBinds && !undeleteableList.includes(key)) {
211
+ // 无绑定关系,且不在不可删除列表中
212
+ services?.codeBlockService.deleteCodeDslByIds([key]);
213
+ } else {
214
+ if (typeof props.customError === 'function') {
215
+ props.customError(key, existBinds ? CodeDeleteErrorType.BIND : CodeDeleteErrorType.UNDELETEABLE);
216
+ } else {
217
+ tMagicMessage.error('代码块删除失败');
218
+ }
219
+ }
220
+ };
221
+
222
+ const filterText = ref('');
223
+ const tree = ref();
224
+
225
+ const filterNode = (value: string, data: CodeDslList): boolean => {
226
+ if (!value) {
227
+ return true;
228
+ }
229
+ return `${data.name}${data.id}`.indexOf(value) !== -1;
230
+ };
231
+
232
+ const filterTextChangeHandler = (val: string) => {
233
+ tree.value?.filter(val);
234
+ };
235
+
236
+ // 展示/隐藏组件绑定关系
237
+ const toggleCombineRelation = (data: CodeDslList) => {
238
+ const { id } = data;
239
+ const currentCode = state.codeList.find((item) => item.id === id);
240
+ if (!currentCode) return;
241
+ currentCode.showRelation = !currentCode?.showRelation;
242
+ };
243
+
244
+ // 获取组件名称展示到tag上
245
+ const getCompName = (compId: Id): string => {
246
+ const node = services?.editorService.getNodeById(compId);
247
+ return node?.name || String(compId);
248
+ };
249
+
250
+ // todo 功能暂时隐藏
251
+ // 解除绑定
252
+ // const unbind = async (compId: Id, codeId: string) => {
253
+ // const res = await services?.codeBlockService.unbind(compId, codeId, codeHooks);
254
+ // if (res) {
255
+ // ElMessage.success('绑定关系解除成功');
256
+ // } else {
257
+ // ElMessage.error('绑定关系解除失败');
258
+ // }
259
+ // };
260
+
261
+ // 选中组件
262
+ const selectComp = (compId: Id) => {
263
+ const stage = services?.editorService.get<StageCore | null>('stage');
264
+ services?.editorService.select(compId);
265
+ stage?.select(compId);
266
+ };
267
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <div v-if="nodes.length === 1" class="m-editor-breadcrumb">
3
+ <template v-for="(item, index) in path" :key="item.id">
4
+ <TMagicButton text :disabled="item.id === node?.id" @click="select(item)">{{ item.name }}</TMagicButton
5
+ ><span v-if="index < path.length - 1">/</span>
6
+ </template>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts" name="MEditorBreadcrumb">
11
+ import { computed, inject } from 'vue';
12
+
13
+ import { TMagicButton } from '@tmagic/design';
14
+ import type { MApp, MNode } from '@tmagic/schema';
15
+ import type StageCore from '@tmagic/stage';
16
+ import { getNodePath } from '@tmagic/utils';
17
+
18
+ import type { Services } from '../../type';
19
+
20
+ const services = inject<Services>('services');
21
+ const editorService = services?.editorService;
22
+
23
+ const node = computed(() => editorService?.get<MNode>('node'));
24
+ const nodes = computed(() => editorService?.get<MNode[]>('nodes') || []);
25
+ const root = computed(() => editorService?.get<MApp>('root'));
26
+ const path = computed(() => getNodePath(node.value?.id || '', root.value?.items || []));
27
+
28
+ const select = async (node: MNode) => {
29
+ await editorService?.select(node);
30
+ editorService?.get<StageCore>('stage')?.select(node.id);
31
+ };
32
+ </script>