mirror of
				https://github.com/Tencent/tmagic-editor.git
				synced 2025-10-31 12:32:17 +08:00 
			
		
		
		
	chore(editor): 重构tool-button,将功能逻辑移到nav-menu中
This commit is contained in:
		
							parent
							
								
									ca5e4d2702
								
							
						
					
					
						commit
						4872e5352b
					
				| @ -8,7 +8,7 @@ | ||||
|     "build": "npm run clean:top && vuepress build src -d dist" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@element-plus/icons-vue": "^2.0.6", | ||||
|     "@element-plus/icons-vue": "^2.0.9", | ||||
|     "@tmagic/form": "1.1.0-beta.12", | ||||
|     "@tmagic/schema": "1.1.0-beta.12", | ||||
|     "@tmagic/utils": "1.1.0-beta.12", | ||||
|  | ||||
| @ -44,7 +44,7 @@ | ||||
|   ], | ||||
|   "dependencies": { | ||||
|     "@babel/core": "^7.18.0", | ||||
|     "@element-plus/icons-vue": "^2.0.6", | ||||
|     "@element-plus/icons-vue": "^2.0.9", | ||||
|     "@tmagic/core": "1.1.0-beta.12", | ||||
|     "@tmagic/form": "1.1.0-beta.12", | ||||
|     "@tmagic/schema": "1.1.0-beta.12", | ||||
|  | ||||
| @ -64,7 +64,7 @@ import historyService from './services/history'; | ||||
| import propsService from './services/props'; | ||||
| import storageService from './services/storage'; | ||||
| import uiService from './services/ui'; | ||||
| import type { ComponentGroup, MenuBarData, MenuItem, Services, SideBarData, StageRect } from './type'; | ||||
| import type { ComponentGroup, MenuBarData, MenuButton, MenuComponent, Services, SideBarData, StageRect } from './type'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'm-editor', | ||||
| @ -97,12 +97,12 @@ export default defineComponent({ | ||||
|     }, | ||||
| 
 | ||||
|     layerContentMenu: { | ||||
|       type: Array as PropType<MenuItem[]>, | ||||
|       type: Array as PropType<(MenuButton | MenuComponent)[]>, | ||||
|       default: () => [], | ||||
|     }, | ||||
| 
 | ||||
|     stageContentMenu: { | ||||
|       type: Array as PropType<MenuItem[]>, | ||||
|       type: Array as PropType<(MenuButton | MenuComponent)[]>, | ||||
|       default: () => [], | ||||
|     }, | ||||
| 
 | ||||
|  | ||||
| @ -25,13 +25,13 @@ | ||||
| <script lang="ts" setup> | ||||
| import { nextTick, onMounted, onUnmounted, ref } from 'vue'; | ||||
| 
 | ||||
| import { MenuButton, MenuItem } from '../type'; | ||||
| import { MenuButton, MenuComponent } from '../type'; | ||||
| 
 | ||||
| import ToolButton from './ToolButton.vue'; | ||||
| 
 | ||||
| const props = withDefaults( | ||||
|   defineProps<{ | ||||
|     menuData?: MenuItem[]; | ||||
|     menuData?: (MenuButton | MenuComponent)[]; | ||||
|     isSubMenu?: boolean; | ||||
|   }>(), | ||||
|   { | ||||
| @ -45,7 +45,7 @@ const emit = defineEmits(['hide', 'show']); | ||||
| const menu = ref<HTMLDivElement>(); | ||||
| const subMenu = ref<any>(); | ||||
| const visible = ref(false); | ||||
| const subMenuData = ref<MenuItem[]>([]); | ||||
| const subMenuData = ref<(MenuButton | MenuComponent)[]>([]); | ||||
| const menuStyle = ref({ | ||||
|   left: '0', | ||||
|   top: '0', | ||||
| @ -94,7 +94,7 @@ const show = (e: MouseEvent) => { | ||||
|   }, 300); | ||||
| }; | ||||
| 
 | ||||
| const showSubMenu = (item: MenuItem) => { | ||||
| const showSubMenu = (item: MenuButton | MenuComponent) => { | ||||
|   const menuItem = item as MenuButton; | ||||
|   if (typeof item !== 'object' || !menuItem.items?.length) { | ||||
|     return; | ||||
|  | ||||
| @ -1,29 +1,15 @@ | ||||
| <template> | ||||
|   <el-icon v-if="!icon"><edit></edit></el-icon> | ||||
|   <el-icon v-if="!icon"><Edit></Edit></el-icon> | ||||
|   <el-icon v-else-if="typeof icon === 'string' && icon.startsWith('http')"><img :src="icon" /></el-icon> | ||||
|   <i v-else-if="typeof icon === 'string'" :class="icon"></i> | ||||
|   <el-icon v-else><component :is="toRaw(icon)"></component></el-icon> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { Component, defineComponent, PropType, toRaw } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { toRaw } from 'vue'; | ||||
| import { Edit } from '@element-plus/icons-vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'm-icon', | ||||
| 
 | ||||
|   components: { Edit }, | ||||
| 
 | ||||
|   props: { | ||||
|     icon: { | ||||
|       type: [String, Object] as PropType<string | Component>, | ||||
|     }, | ||||
|   }, | ||||
| 
 | ||||
|   setup() { | ||||
|     return { | ||||
|       toRaw, | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| defineProps<{ | ||||
|   icon?: any; | ||||
| }>(); | ||||
| </script> | ||||
|  | ||||
| @ -2,169 +2,80 @@ | ||||
|   <div | ||||
|     v-if="display" | ||||
|     class="menu-item" | ||||
|     :class="item.type" | ||||
|     @click="clickHandler(item, $event)" | ||||
|     @mousedown="mousedownHandler(item, $event)" | ||||
|     @mouseup="mouseupHandler(item, $event)" | ||||
|     :class="`${data.type} ${data.className}`" | ||||
|     @click="clickHandler(data, $event)" | ||||
|     @mousedown="mousedownHandler(data, $event)" | ||||
|     @mouseup="mouseupHandler(data, $event)" | ||||
|   > | ||||
|     <el-divider v-if="item.type === 'divider'" :direction="item.direction || 'vertical'"></el-divider> | ||||
|     <div v-else-if="item.type === 'text'" class="menu-item-text">{{ item.text }}</div> | ||||
|     <el-divider v-if="data.type === 'divider'" :direction="data.direction || 'vertical'"></el-divider> | ||||
|     <div v-else-if="data.type === 'text'" class="menu-item-text">{{ data.text }}</div> | ||||
| 
 | ||||
|     <template v-else-if="item.type === 'zoom'"> | ||||
|       <tool-button | ||||
|         :data="{ type: 'button', icon: ZoomOut, handler: zoomOutHandler, tooltip: '缩小' }" | ||||
|         :event-type="eventType" | ||||
|       ></tool-button> | ||||
|       <span class="menu-item-text" style="margin: 0 5px">{{ parseInt(`${zoom * 100}`, 10) }}%</span> | ||||
|       <tool-button | ||||
|         :data="{ type: 'button', icon: ZoomIn, handler: zoomInHandler, tooltip: '放大' }" | ||||
|         :event-type="eventType" | ||||
|       ></tool-button> | ||||
|     </template> | ||||
| 
 | ||||
|     <template v-else-if="item.type === 'button'"> | ||||
|       <el-tooltip v-if="item.tooltip" effect="dark" placement="bottom-start" :content="item.tooltip"> | ||||
|     <template v-else-if="data.type === 'button'"> | ||||
|       <el-tooltip v-if="data.tooltip" effect="dark" placement="bottom-start" :content="data.tooltip"> | ||||
|         <el-button size="small" text :disabled="disabled" | ||||
|           ><m-icon v-if="item.icon" :icon="item.icon"></m-icon><span>{{ item.text }}</span></el-button | ||||
|           ><MIcon v-if="data.icon" :icon="data.icon"></MIcon><span>{{ data.text }}</span></el-button | ||||
|         > | ||||
|       </el-tooltip> | ||||
|       <el-button v-else size="small" text :disabled="disabled" | ||||
|         ><m-icon v-if="item.icon" :icon="item.icon"></m-icon><span>{{ item.text }}</span></el-button | ||||
|         ><MIcon v-if="data.icon" :icon="data.icon"></MIcon><span>{{ data.text }}</span></el-button | ||||
|       > | ||||
|     </template> | ||||
| 
 | ||||
|     <el-dropdown v-else-if="item.type === 'dropdown'" trigger="click" :disabled="disabled" @command="dropdownHandler"> | ||||
|     <el-dropdown v-else-if="data.type === 'dropdown'" trigger="click" :disabled="disabled" @command="dropdownHandler"> | ||||
|       <span class="el-dropdown-link menubar-menu-button"> | ||||
|         {{ item.text }}<el-icon class="el-icon--right"><arrow-down></arrow-down></el-icon> | ||||
|         {{ data.text }}<el-icon class="el-icon--right"><ArrowDown></ArrowDown></el-icon> | ||||
|       </span> | ||||
|       <template #dropdown> | ||||
|         <el-dropdown-menu v-if="item.items && item.items.length"> | ||||
|           <el-dropdown-item v-for="(subItem, index) in item.items" :key="index" :command="{ item, subItem }">{{ | ||||
|         <el-dropdown-menu v-if="data.items && data.items.length"> | ||||
|           <el-dropdown-item v-for="(subItem, index) in data.items" :key="index" :command="{ data, subItem }">{{ | ||||
|             subItem.text | ||||
|           }}</el-dropdown-item> | ||||
|         </el-dropdown-menu> | ||||
|       </template> | ||||
|     </el-dropdown> | ||||
| 
 | ||||
|     <component v-else-if="item.type === 'component'" v-bind="item.props || {}" :is="item.component"></component> | ||||
|     <component v-else-if="data.type === 'component'" v-bind="data.props || {}" :is="data.component"></component> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { computed, inject, markRaw, PropType } from 'vue'; | ||||
| import { ArrowDown, Back, Delete, Grid, Right, ScaleToOriginal, ZoomIn, ZoomOut } from '@element-plus/icons-vue'; | ||||
| 
 | ||||
| import { NodeType } from '@tmagic/schema'; | ||||
| import { computed, inject } from 'vue'; | ||||
| import { ArrowDown } from '@element-plus/icons-vue'; | ||||
| 
 | ||||
| import MIcon from '../components/Icon.vue'; | ||||
| import type { MenuButton, MenuComponent, MenuItem, Services } from '../type'; | ||||
| import type { MenuButton, MenuComponent, Services } from '../type'; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   data: { | ||||
|     type: [Object, String] as PropType<MenuItem | string>, | ||||
|     require: true, | ||||
|     default: () => ({ | ||||
| const props = withDefaults( | ||||
|   defineProps<{ | ||||
|     data?: MenuButton | MenuComponent; | ||||
|     eventType?: 'mousedown' | 'mouseup' | 'click'; | ||||
|   }>(), | ||||
|   { | ||||
|     data: () => ({ | ||||
|       type: 'text', | ||||
|       display: false, | ||||
|     }), | ||||
|     eventType: 'click', | ||||
|   }, | ||||
| 
 | ||||
|   eventType: { | ||||
|     type: String as PropType<'mousedown' | 'mouseup' | 'click'>, | ||||
|     default: 'click', | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| ); | ||||
| const services = inject<Services>('services'); | ||||
| const uiService = services?.uiService; | ||||
| 
 | ||||
| const zoom = computed((): number => uiService?.get<number>('zoom') ?? 1); | ||||
| const showGuides = computed((): boolean => uiService?.get<boolean>('showGuides') ?? true); | ||||
| const showRule = computed((): boolean => uiService?.get<boolean>('showRule') ?? true); | ||||
| 
 | ||||
| const zoomInHandler = () => uiService?.zoom(0.1); | ||||
| const zoomOutHandler = () => uiService?.zoom(-0.1); | ||||
| 
 | ||||
| const item = computed((): MenuButton | MenuComponent => { | ||||
|   if (typeof props.data !== 'string') { | ||||
|     return props.data; | ||||
|   } | ||||
|   switch (props.data) { | ||||
|     case '/': | ||||
|       return { | ||||
|         type: 'divider', | ||||
|       }; | ||||
|     case 'zoom': | ||||
|       return { | ||||
|         type: 'zoom', | ||||
|       }; | ||||
|     case 'delete': | ||||
|       return { | ||||
|         type: 'button', | ||||
|         icon: markRaw(Delete), | ||||
|         tooltip: '刪除', | ||||
|         disabled: () => services?.editorService.get('node')?.type === NodeType.PAGE, | ||||
|         handler: () => services?.editorService.remove(services?.editorService.get('node')), | ||||
|       }; | ||||
|     case 'undo': | ||||
|       return { | ||||
|         type: 'button', | ||||
|         icon: markRaw(Back), | ||||
|         tooltip: '后退', | ||||
|         disabled: () => !services?.historyService.state.canUndo, | ||||
|         handler: () => services?.editorService.undo(), | ||||
|       }; | ||||
|     case 'redo': | ||||
|       return { | ||||
|         type: 'button', | ||||
|         icon: markRaw(Right), | ||||
|         tooltip: '前进', | ||||
|         disabled: () => !services?.historyService.state.canRedo, | ||||
|         handler: () => services?.editorService.redo(), | ||||
|       }; | ||||
|     case 'zoom-in': | ||||
|       return { | ||||
|         type: 'button', | ||||
|         icon: markRaw(ZoomIn), | ||||
|         tooltip: '放大', | ||||
|         handler: zoomInHandler, | ||||
|       }; | ||||
|     case 'zoom-out': | ||||
|       return { | ||||
|         type: 'button', | ||||
|         icon: markRaw(ZoomOut), | ||||
|         tooltip: '縮小', | ||||
|         handler: zoomOutHandler, | ||||
|       }; | ||||
|     case 'rule': | ||||
|       return { | ||||
|         type: 'button', | ||||
|         icon: markRaw(ScaleToOriginal), | ||||
|         tooltip: showRule.value ? '隐藏标尺' : '显示标尺', | ||||
|         handler: () => uiService?.set('showRule', !showRule.value), | ||||
|       }; | ||||
|     case 'guides': | ||||
|       return { | ||||
|         type: 'button', | ||||
|         icon: markRaw(Grid), | ||||
|         tooltip: showGuides.value ? '隐藏参考线' : '显示参考线', | ||||
|         handler: () => uiService?.set('showGuides', !showGuides.value), | ||||
|       }; | ||||
|     default: | ||||
|       return { | ||||
|         type: 'text', | ||||
|         text: props.data, | ||||
|       }; | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| const disabled = computed(() => { | ||||
|   if (typeof item.value === 'string') return false; | ||||
|   if (item.value.type === 'component') return false; | ||||
|   if (typeof item.value.disabled === 'function') { | ||||
|     return item.value.disabled(services); | ||||
|   if (typeof props.data === 'string') return false; | ||||
|   if (props.data.type === 'component') return false; | ||||
|   if (typeof props.data.disabled === 'function') { | ||||
|     return props.data.disabled(services); | ||||
|   } | ||||
|   return item.value.disabled; | ||||
|   return props.data.disabled; | ||||
| }); | ||||
| 
 | ||||
| const display = computed(() => { | ||||
|   if (!props.data) return false; | ||||
|   if (typeof props.data === 'string') return true; | ||||
|   if (typeof props.data.display === 'function') { | ||||
|     return props.data.display(services); | ||||
|   } | ||||
|   return props.data.display ?? true; | ||||
| }); | ||||
| 
 | ||||
| const buttonHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => { | ||||
| @ -174,15 +85,6 @@ const buttonHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => { | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const display = computed(() => { | ||||
|   if (!item.value) return false; | ||||
|   if (typeof item.value === 'string') return true; | ||||
|   if (typeof item.value.display === 'function') { | ||||
|     return item.value.display(services); | ||||
|   } | ||||
|   return item.value.display ?? true; | ||||
| }); | ||||
| 
 | ||||
| const dropdownHandler = (command: any) => { | ||||
|   if (command.item.handler) { | ||||
|     command.item.handler(services); | ||||
|  | ||||
| @ -45,6 +45,7 @@ export { default as LayerPanel } from './layouts/sidebar/LayerPanel.vue'; | ||||
| export { default as PropsPanel } from './layouts/PropsPanel.vue'; | ||||
| export { default as ToolButton } from './components/ToolButton.vue'; | ||||
| export { default as ContentMenu } from './components/ContentMenu.vue'; | ||||
| export { default as Icon } from './components/Icon.vue'; | ||||
| 
 | ||||
| const defaultInstallOpt: InstallOptions = { | ||||
|   // @todo, 自定义图片上传方法等编辑器依赖的外部选项
 | ||||
|  | ||||
| @ -1,41 +1,151 @@ | ||||
| <template> | ||||
|   <div class="m-editor-nav-menu" :style="{ height: `${height}px` }"> | ||||
|     <div v-for="key in keys" :class="`menu-${key}`" :key="key" :style="`width: ${columnWidth?.[key]}px`"> | ||||
|       <tool-button :data="item" v-for="(item, index) in data[key]" :key="index"></tool-button> | ||||
|       <tool-button :data="item" v-for="(item, index) in buttons[key]" :key="index"></tool-button> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { computed, defineComponent, inject, PropType } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { computed, inject, markRaw } from 'vue'; | ||||
| import { Back, Delete, Grid, Memo, Right, ZoomIn, ZoomOut } from '@element-plus/icons-vue'; | ||||
| 
 | ||||
| import { NodeType } from '@tmagic/schema'; | ||||
| 
 | ||||
| import ToolButton from '../components/ToolButton.vue'; | ||||
| import { GetColumnWidth, MenuBarData, Services } from '../type'; | ||||
| import { ColumnLayout, GetColumnWidth, MenuBarData, MenuButton, MenuComponent, MenuItem, Services } from '../type'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'nav-menu', | ||||
| 
 | ||||
|   components: { ToolButton }, | ||||
| 
 | ||||
|   props: { | ||||
|     data: { | ||||
|       type: Object as PropType<MenuBarData>, | ||||
|       default: () => ({}), | ||||
|     }, | ||||
| 
 | ||||
|     height: { | ||||
|       type: Number, | ||||
|     }, | ||||
| const props = withDefaults( | ||||
|   defineProps<{ | ||||
|     data?: MenuBarData; | ||||
|     height?: number; | ||||
|   }>(), | ||||
|   { | ||||
|     data: () => ({}), | ||||
|     height: 35, | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
|   setup(props) { | ||||
|     const services = inject<Services>('services'); | ||||
| const services = inject<Services>('services'); | ||||
| const uiService = services?.uiService; | ||||
| 
 | ||||
|     return { | ||||
|       keys: computed(() => Object.keys(props.data) as Array<keyof MenuBarData>), | ||||
| const columnWidth = computed(() => services?.uiService.get<GetColumnWidth>('columnWidth')); | ||||
| const keys = Object.values(ColumnLayout); | ||||
| 
 | ||||
|       columnWidth: computed(() => services?.uiService.get<GetColumnWidth>('columnWidth')), | ||||
|     }; | ||||
|   }, | ||||
| const showGuides = computed((): boolean => uiService?.get<boolean>('showGuides') ?? true); | ||||
| const showRule = computed((): boolean => uiService?.get<boolean>('showRule') ?? true); | ||||
| const zoom = computed((): number => uiService?.get<number>('zoom') ?? 1); | ||||
| 
 | ||||
| const getConfig = (item: MenuItem): (MenuButton | MenuComponent)[] => { | ||||
|   if (typeof item !== 'string') { | ||||
|     return [item]; | ||||
|   } | ||||
|   const config: (MenuButton | MenuComponent)[] = []; | ||||
|   switch (item) { | ||||
|     case '/': | ||||
|       config.push({ | ||||
|         type: 'divider', | ||||
|         className: 'divider', | ||||
|       }); | ||||
|       break; | ||||
|     case 'zoom': | ||||
|       config.push( | ||||
|         ...getConfig('zoom-out'), | ||||
|         ...getConfig(`${parseInt(`${zoom.value * 100}`, 10)}%`), | ||||
|         ...getConfig('zoom-in'), | ||||
|       ); | ||||
|       break; | ||||
|     case 'delete': | ||||
|       config.push({ | ||||
|         type: 'button', | ||||
|         className: 'delete', | ||||
|         icon: markRaw(Delete), | ||||
|         tooltip: '刪除', | ||||
|         disabled: () => services?.editorService.get('node')?.type === NodeType.PAGE, | ||||
|         handler: () => services?.editorService.remove(services?.editorService.get('node')), | ||||
|       }); | ||||
|       break; | ||||
|     case 'undo': | ||||
|       config.push({ | ||||
|         type: 'button', | ||||
|         className: 'undo', | ||||
|         icon: markRaw(Back), | ||||
|         tooltip: '后退', | ||||
|         disabled: () => !services?.historyService.state.canUndo, | ||||
|         handler: () => services?.editorService.undo(), | ||||
|       }); | ||||
|       break; | ||||
|     case 'redo': | ||||
|       config.push({ | ||||
|         type: 'button', | ||||
|         className: 'redo', | ||||
|         icon: markRaw(Right), | ||||
|         tooltip: '前进', | ||||
|         disabled: () => !services?.historyService.state.canRedo, | ||||
|         handler: () => services?.editorService.redo(), | ||||
|       }); | ||||
|       break; | ||||
|     case 'zoom-in': | ||||
|       config.push({ | ||||
|         type: 'button', | ||||
|         className: 'zoom-in', | ||||
|         icon: markRaw(ZoomIn), | ||||
|         tooltip: '放大', | ||||
|         handler: () => uiService?.zoom(0.1), | ||||
|       }); | ||||
|       break; | ||||
|     case 'zoom-out': | ||||
|       config.push({ | ||||
|         type: 'button', | ||||
|         className: 'zoom-out', | ||||
|         icon: markRaw(ZoomOut), | ||||
|         tooltip: '縮小', | ||||
|         handler: () => uiService?.zoom(-0.1), | ||||
|       }); | ||||
|       break; | ||||
|     case 'rule': | ||||
|       config.push({ | ||||
|         type: 'button', | ||||
|         className: 'rule', | ||||
|         icon: markRaw(Memo), | ||||
|         tooltip: showRule.value ? '隐藏标尺' : '显示标尺', | ||||
|         handler: () => uiService?.set('showRule', !showRule.value), | ||||
|       }); | ||||
|       break; | ||||
|     case 'guides': | ||||
|       config.push({ | ||||
|         type: 'button', | ||||
|         className: 'guides', | ||||
|         icon: markRaw(Grid), | ||||
|         tooltip: showGuides.value ? '隐藏参考线' : '显示参考线', | ||||
|         handler: () => uiService?.set('showGuides', !showGuides.value), | ||||
|       }); | ||||
|       break; | ||||
|     default: | ||||
|       config.push({ | ||||
|         type: 'text', | ||||
|         text: item, | ||||
|       }); | ||||
|   } | ||||
|   return config; | ||||
| }; | ||||
| 
 | ||||
| const buttons = computed(() => { | ||||
|   const data: { | ||||
|     [ColumnLayout.LEFT]: (MenuButton | MenuComponent)[]; | ||||
|     [ColumnLayout.CENTER]: (MenuButton | MenuComponent)[]; | ||||
|     [ColumnLayout.RIGHT]: (MenuButton | MenuComponent)[]; | ||||
|   } = { | ||||
|     [ColumnLayout.LEFT]: [], | ||||
|     [ColumnLayout.CENTER]: [], | ||||
|     [ColumnLayout.RIGHT]: [], | ||||
|   }; | ||||
|   keys.forEach((key) => { | ||||
|     const items = props.data[key] || []; | ||||
|     items.forEach((item) => { | ||||
|       data[key].push(...getConfig(item)); | ||||
|     }); | ||||
|   }); | ||||
|   return data; | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| @ -9,7 +9,7 @@ import { Delete, DocumentCopy, Files, Plus } from '@element-plus/icons-vue'; | ||||
| import { NodeType } from '@tmagic/schema'; | ||||
| 
 | ||||
| import ContentMenu from '../../components/ContentMenu.vue'; | ||||
| import type { ComponentGroup, MenuButton, MenuItem, Services } from '../../type'; | ||||
| import type { ComponentGroup, MenuButton, MenuComponent, Services } from '../../type'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   components: { ContentMenu }, | ||||
| @ -22,7 +22,7 @@ export default defineComponent({ | ||||
|     const isPage = computed(() => node.value?.type === NodeType.PAGE); | ||||
|     const componentList = computed(() => services?.componentListService.getList() || []); | ||||
| 
 | ||||
|     const layerContentMenu = inject<MenuItem[]>('layerContentMenu', []); | ||||
|     const layerContentMenu = inject<(MenuComponent | MenuButton)[]>('layerContentMenu', []); | ||||
| 
 | ||||
|     const createMenuItems = (group: ComponentGroup): MenuButton[] => | ||||
|       group.items.map((component) => ({ | ||||
| @ -77,7 +77,7 @@ export default defineComponent({ | ||||
| 
 | ||||
|     return { | ||||
|       menu, | ||||
|       menuData: computed<MenuItem[]>(() => [ | ||||
|       menuData: computed<(MenuButton | MenuComponent)[]>(() => [ | ||||
|         { | ||||
|           type: 'button', | ||||
|           text: '新增', | ||||
|  | ||||
| @ -12,7 +12,7 @@ import { isPage } from '@tmagic/utils'; | ||||
| 
 | ||||
| import ContentMenu from '../../components/ContentMenu.vue'; | ||||
| import storageService from '../../services/storage'; | ||||
| import { LayerOffset, Layout, MenuItem, Services } from '../../type'; | ||||
| import { LayerOffset, Layout, MenuButton, MenuComponent, Services } from '../../type'; | ||||
| import { COPY_STORAGE_KEY } from '../../utils/editor'; | ||||
| 
 | ||||
| const props = withDefaults(defineProps<{ isMultiSelect?: boolean }>(), { isMultiSelect: false }); | ||||
| @ -28,9 +28,9 @@ const nodes = computed(() => editorService?.get<MNode[]>('nodes')); | ||||
| const parent = computed(() => editorService?.get('parent')); | ||||
| const stage = computed(() => editorService?.get<StageCore>('stage')); | ||||
| 
 | ||||
| const stageContentMenu = inject<MenuItem[]>('stageContentMenu', []); | ||||
| const stageContentMenu = inject<(MenuButton | MenuComponent)[]>('stageContentMenu', []); | ||||
| 
 | ||||
| const menuData = reactive<MenuItem[]>([ | ||||
| const menuData = reactive<(MenuButton | MenuComponent)[]>([ | ||||
|   { | ||||
|     type: 'button', | ||||
|     text: '水平居中', | ||||
|  | ||||
| @ -69,5 +69,11 @@ | ||||
|     .menu-item-text { | ||||
|       color: $--nav-color; | ||||
|     } | ||||
| 
 | ||||
|     &.rule { | ||||
|       .el-icon { | ||||
|         transform: rotate(-90deg); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -82,16 +82,22 @@ export interface ComponentGroupState { | ||||
|   list: ComponentGroup[]; | ||||
| } | ||||
| 
 | ||||
| export enum ColumnLayout { | ||||
|   LEFT = 'left', | ||||
|   CENTER = 'center', | ||||
|   RIGHT = 'right', | ||||
| } | ||||
| 
 | ||||
| export interface SetColumnWidth { | ||||
|   left?: number; | ||||
|   center?: number | 'auto'; | ||||
|   right?: number; | ||||
|   [ColumnLayout.LEFT]?: number; | ||||
|   [ColumnLayout.CENTER]?: number | 'auto'; | ||||
|   [ColumnLayout.RIGHT]?: number; | ||||
| } | ||||
| 
 | ||||
| export interface GetColumnWidth { | ||||
|   left: number; | ||||
|   center: number; | ||||
|   right: number; | ||||
|   [ColumnLayout.LEFT]: number; | ||||
|   [ColumnLayout.CENTER]: number; | ||||
|   [ColumnLayout.RIGHT]: number; | ||||
| } | ||||
| 
 | ||||
| export interface StageRect { | ||||
| @ -174,6 +180,7 @@ export interface MenuButton { | ||||
|   /** type为button/dropdown时点击运行的方法 */ | ||||
|   handler?: (data: Services, event: MouseEvent) => Promise<any> | any; | ||||
|   /** type为dropdown时,下拉的菜单列表, 或者有子菜单时 */ | ||||
|   className?: string; | ||||
|   items?: MenuButton[]; | ||||
| } | ||||
| 
 | ||||
| @ -187,6 +194,7 @@ export interface MenuComponent { | ||||
|   listeners?: Record<string, Function>; | ||||
|   slots?: Record<string, any>; | ||||
|   /** 是否显示,默认为true */ | ||||
|   className?: string; | ||||
|   display?: boolean | ((data?: Services) => Promise<boolean> | boolean); | ||||
| } | ||||
| 
 | ||||
| @ -209,16 +217,17 @@ export type MenuItem = | ||||
|   | 'guides' | ||||
|   | 'rule' | ||||
|   | MenuButton | ||||
|   | MenuComponent; | ||||
|   | MenuComponent | ||||
|   | string; | ||||
| 
 | ||||
| /** 工具栏 */ | ||||
| export interface MenuBarData { | ||||
|   /** 顶部工具栏左边项 */ | ||||
|   left?: MenuItem[]; | ||||
|   [ColumnLayout.LEFT]?: MenuItem[]; | ||||
|   /** 顶部工具栏中间项 */ | ||||
|   center?: MenuItem[]; | ||||
|   [ColumnLayout.CENTER]?: MenuItem[]; | ||||
|   /** 顶部工具栏右边项 */ | ||||
|   right?: MenuItem[]; | ||||
|   [ColumnLayout.RIGHT]?: MenuItem[]; | ||||
| } | ||||
| 
 | ||||
| export interface SideComponent extends MenuComponent { | ||||
|  | ||||
| @ -34,7 +34,7 @@ | ||||
|     "url": "https://github.com/Tencent/tmagic-editor.git" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@element-plus/icons-vue": "^2.0.6", | ||||
|     "@element-plus/icons-vue": "^2.0.9", | ||||
|     "@tmagic/utils": "1.1.0-beta.12", | ||||
|     "element-plus": "^2.2.6", | ||||
|     "lodash-es": "^4.17.21", | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
|     "serve": "vite preview" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@element-plus/icons-vue": "^2.0.6", | ||||
|     "@element-plus/icons-vue": "^2.0.9", | ||||
|     "@tmagic/editor": "1.1.0-beta.12", | ||||
|     "@tmagic/form": "1.1.0-beta.12", | ||||
|     "@tmagic/schema": "1.1.0-beta.12", | ||||
|  | ||||
							
								
								
									
										22
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										22
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @ -74,7 +74,7 @@ importers: | ||||
| 
 | ||||
|   docs: | ||||
|     specifiers: | ||||
|       '@element-plus/icons-vue': ^2.0.6 | ||||
|       '@element-plus/icons-vue': ^2.0.9 | ||||
|       '@tmagic/form': 1.1.0-beta.12 | ||||
|       '@tmagic/schema': 1.1.0-beta.12 | ||||
|       '@tmagic/utils': 1.1.0-beta.12 | ||||
| @ -92,7 +92,7 @@ importers: | ||||
|       vue: ^3.2.37 | ||||
|       vuepress: ^2.0.0-beta.49 | ||||
|     dependencies: | ||||
|       '@element-plus/icons-vue': 2.0.6_vue@3.2.37 | ||||
|       '@element-plus/icons-vue': 2.0.9_vue@3.2.37 | ||||
|       '@tmagic/form': link:../packages/form | ||||
|       '@tmagic/schema': link:../packages/schema | ||||
|       '@tmagic/utils': link:../packages/utils | ||||
| @ -154,7 +154,7 @@ importers: | ||||
|   packages/editor: | ||||
|     specifiers: | ||||
|       '@babel/core': ^7.18.0 | ||||
|       '@element-plus/icons-vue': ^2.0.6 | ||||
|       '@element-plus/icons-vue': ^2.0.9 | ||||
|       '@tmagic/core': 1.1.0-beta.12 | ||||
|       '@tmagic/form': 1.1.0-beta.12 | ||||
|       '@tmagic/schema': 1.1.0-beta.12 | ||||
| @ -184,7 +184,7 @@ importers: | ||||
|       vue-tsc: ^0.39.4 | ||||
|     dependencies: | ||||
|       '@babel/core': 7.18.2 | ||||
|       '@element-plus/icons-vue': 2.0.6_vue@3.2.37 | ||||
|       '@element-plus/icons-vue': 2.0.9_vue@3.2.37 | ||||
|       '@tmagic/core': link:../core | ||||
|       '@tmagic/form': link:../form | ||||
|       '@tmagic/schema': link:../schema | ||||
| @ -217,7 +217,7 @@ importers: | ||||
|   packages/form: | ||||
|     specifiers: | ||||
|       '@babel/core': ^7.18.0 | ||||
|       '@element-plus/icons-vue': ^2.0.6 | ||||
|       '@element-plus/icons-vue': ^2.0.9 | ||||
|       '@tmagic/utils': 1.1.0-beta.12 | ||||
|       '@types/lodash-es': ^4.17.4 | ||||
|       '@types/node': ^15.12.4 | ||||
| @ -235,7 +235,7 @@ importers: | ||||
|       vue: ^3.2.37 | ||||
|       vue-tsc: ^0.39.4 | ||||
|     dependencies: | ||||
|       '@element-plus/icons-vue': 2.0.6_vue@3.2.37 | ||||
|       '@element-plus/icons-vue': 2.0.9_vue@3.2.37 | ||||
|       '@tmagic/utils': link:../utils | ||||
|       element-plus: 2.2.6_vue@3.2.37 | ||||
|       lodash-es: 4.17.21 | ||||
| @ -427,7 +427,7 @@ importers: | ||||
| 
 | ||||
|   playground: | ||||
|     specifiers: | ||||
|       '@element-plus/icons-vue': ^2.0.6 | ||||
|       '@element-plus/icons-vue': ^2.0.9 | ||||
|       '@tmagic/editor': 1.1.0-beta.12 | ||||
|       '@tmagic/form': 1.1.0-beta.12 | ||||
|       '@tmagic/schema': 1.1.0-beta.12 | ||||
| @ -450,7 +450,7 @@ importers: | ||||
|       vue-router: ^4.0.10 | ||||
|       vue-tsc: ^0.39.4 | ||||
|     dependencies: | ||||
|       '@element-plus/icons-vue': 2.0.6_vue@3.2.37 | ||||
|       '@element-plus/icons-vue': 2.0.9_vue@3.2.37 | ||||
|       '@tmagic/editor': link:../packages/editor | ||||
|       '@tmagic/form': link:../packages/form | ||||
|       '@tmagic/schema': link:../packages/schema | ||||
| @ -1175,8 +1175,8 @@ packages: | ||||
|     resolution: {integrity: sha512-HsbMKc0ZAQH+EUeCmI/2PvTYSybmkaWwakU8QGDYYgMVIg9BQ5sM0A0Nnombjxo2+JzXHxmH+jw//yGX+y6GYw==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /@element-plus/icons-vue/2.0.6_vue@3.2.37: | ||||
|     resolution: {integrity: sha512-lPpG8hYkjL/Z97DH5Ei6w6o22Z4YdNglWCNYOPcB33JCF2A4wye6HFgSI7hEt9zdLyxlSpiqtgf9XcYU+m5mew==} | ||||
|   /@element-plus/icons-vue/2.0.9_vue@3.2.37: | ||||
|     resolution: {integrity: sha512-okdrwiVeKBmW41Hkl0eMrXDjzJwhQMuKiBOu17rOszqM+LS/yBYpNQNV5Jvoh06Wc+89fMmb/uhzf8NZuDuUaQ==} | ||||
|     peerDependencies: | ||||
|       vue: ^3.2.0 | ||||
|     dependencies: | ||||
| @ -3324,7 +3324,7 @@ packages: | ||||
|       vue: ^3.2.0 | ||||
|     dependencies: | ||||
|       '@ctrl/tinycolor': 3.4.1 | ||||
|       '@element-plus/icons-vue': 2.0.6_vue@3.2.37 | ||||
|       '@element-plus/icons-vue': 2.0.9_vue@3.2.37 | ||||
|       '@floating-ui/dom': 0.5.4 | ||||
|       '@popperjs/core': /@sxzz/popperjs-es/2.11.7 | ||||
|       '@types/lodash': 4.14.182 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user