diff --git a/package.json b/package.json index 344e4712..507b8d5e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "lint": "vue-cli-service lint" }, "dependencies": { - "@ant-design/icons-vue": "^5.1.7", "ant-design-vue": "^2.0.0-rc.3", "clipboard": "^2.0.6", "core-js": "^3.6.5", diff --git a/public/index.html b/public/index.html index 5e98cfd2..f1ced8e1 100644 --- a/public/index.html +++ b/public/index.html @@ -14,5 +14,7 @@
+ + diff --git a/src/components/Contextmenu/ContextmenuContent.vue b/src/components/Contextmenu/ContextmenuContent.vue new file mode 100644 index 00000000..b2835d92 --- /dev/null +++ b/src/components/Contextmenu/ContextmenuContent.vue @@ -0,0 +1,179 @@ + + + + + \ No newline at end of file diff --git a/src/components/Contextmenu/index.vue b/src/components/Contextmenu/index.vue index e69de29b..1b6f4029 100644 --- a/src/components/Contextmenu/index.vue +++ b/src/components/Contextmenu/index.vue @@ -0,0 +1,124 @@ + + + + + \ No newline at end of file diff --git a/src/components/Contextmenu/types.ts b/src/components/Contextmenu/types.ts new file mode 100644 index 00000000..4bc0e77d --- /dev/null +++ b/src/components/Contextmenu/types.ts @@ -0,0 +1,16 @@ +export interface ContextmenuItem { + text?: string; + subText?: string; + icon?: string; + divider?: boolean; + disable?: boolean; + hide?: boolean; + iconPlacehoder?: boolean; + children?: ContextmenuItem[]; + action?: (el: HTMLElement) => void; +} + +export interface Axis { + x: number; + y: number; +} \ No newline at end of file diff --git a/src/components/IconFont.ts b/src/components/IconFont.ts deleted file mode 100644 index 599524d1..00000000 --- a/src/components/IconFont.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createFromIconfontCN } from '@ant-design/icons-vue' - -export default createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js', -}) \ No newline at end of file diff --git a/src/components/IconFont.vue b/src/components/IconFont.vue new file mode 100644 index 00000000..2352a755 --- /dev/null +++ b/src/components/IconFont.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 1bae6314..ef3dea84 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,14 @@ import store from './store' import '@/assets/styles/global.scss' +import IconFont from '@/components/IconFont.vue' +import contextmenu from './plugins/contextmenu' +import clickOutside from './plugins/clickOutside' + const app = createApp(App) +app.component('IconFont', IconFont) +app.use(contextmenu) +app.use(clickOutside) app.use(store) app.use(router) app.mount('#app') diff --git a/src/plugins/clickOutside.ts b/src/plugins/clickOutside.ts new file mode 100644 index 00000000..59500248 --- /dev/null +++ b/src/plugins/clickOutside.ts @@ -0,0 +1,34 @@ +import { Directive, App, DirectiveBinding } from 'vue' + +const CTX_CLICK_OUTSIDE_HANDLER = 'CTX_CLICK_OUTSIDE_HANDLER' + +const clickListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => { + const handler = binding.value + + const path = event.composedPath() + const isClickOutside = path ? path.indexOf(el) < 0 : !el.contains(event.target as HTMLElement) + + if(!isClickOutside) return + handler(event) +} + +const ClickOutsideDirective: Directive = { + mounted(el: HTMLElement, binding) { + el[CTX_CLICK_OUTSIDE_HANDLER] = (event: MouseEvent) => clickListener(el, event, binding) + document.addEventListener('mousedown', el[CTX_CLICK_OUTSIDE_HANDLER]) + }, + + unmounted(el: HTMLElement) { + if(el && el[CTX_CLICK_OUTSIDE_HANDLER]) { + document.removeEventListener('mousedown', el[CTX_CLICK_OUTSIDE_HANDLER]) + delete el[CTX_CLICK_OUTSIDE_HANDLER] + } + }, +} + +export default { + install(app: App) { + app.directive('click-outside', ClickOutsideDirective) + }, + directive: ClickOutsideDirective, +} \ No newline at end of file diff --git a/src/plugins/contextmenu.ts b/src/plugins/contextmenu.ts new file mode 100644 index 00000000..a9a2121d --- /dev/null +++ b/src/plugins/contextmenu.ts @@ -0,0 +1,62 @@ +import { Directive, App, createVNode, render, DirectiveBinding } from 'vue' +import ContextmenuComponent from '@/components/Contextmenu/index.vue' + +const CTX_CONTEXTMENU_HANDLER = 'CTX_CONTEXTMENU_HANDLER' + +const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => { + event.stopPropagation() + event.preventDefault() + + const menus = binding.value(el) + if(!menus) return + const isDark = binding.modifiers.dark + + let container: HTMLDivElement | null = null + + const removeContextMenu = () => { + if(container) { + document.body.removeChild(container) + container = null + } + el.classList.remove('contextmenu-active') + document.body.removeEventListener('scroll', removeContextMenu) + window.removeEventListener('resize', removeContextMenu) + } + + const options = { + axis: { x: event.x, y: event.y }, + el, + menus, + isDark, + removeContextMenu, + } + container = document.createElement('div') + const vm = createVNode(ContextmenuComponent, options, null) + render(vm, container) + document.body.appendChild(container) + + el.classList.add('contextmenu-active') + + document.body.addEventListener('scroll', removeContextMenu) + window.addEventListener('resize', removeContextMenu) +} + +const ContextmenuDirective: Directive = { + mounted(el: HTMLElement, binding) { + el[CTX_CONTEXTMENU_HANDLER] = (event: MouseEvent) => contextmenuListener(el, event, binding) + el.addEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]) + }, + + unmounted(el: HTMLElement) { + if(el && el[CTX_CONTEXTMENU_HANDLER]) { + el.removeEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]) + delete el[CTX_CONTEXTMENU_HANDLER] + } + }, +} + +export default { + install(app: App) { + app.directive('contextmenu', ContextmenuDirective) + } +} \ No newline at end of file diff --git a/src/views/Editor/Canvas/index.vue b/src/views/Editor/Canvas/index.vue index 1c78f01a..778d01ef 100644 --- a/src/views/Editor/Canvas/index.vue +++ b/src/views/Editor/Canvas/index.vue @@ -3,6 +3,7 @@ class="canvas" ref="canvasRef" @mousedown="$event => handleClickBlankArea($event)" + v-contextmenu="contextmenus" >
{ + return [ + { + text: '全选', + subText: 'Ctrl + A', + }, + { + text: '粘贴', + subText: 'Ctrl + V', + }, + { divider: true }, + { + text: '参考线', + children: [ + { + text: '打开', + }, + { + text: '关闭', + }, + ], + }, + { + text: '背景设置', + }, + { divider: true }, + { + text: '清空页面', + }, + ] + } + return { canvasRef, viewportRef, viewportStyles, mouseSelectionState, handleClickBlankArea, + contextmenus, } }, })