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