feat: convert right-click-menu component to vue3

This commit is contained in:
IchliebedichZhu 2024-03-04 21:05:29 +00:00
parent 4cb236473c
commit 12d27d7f41
8 changed files with 167 additions and 235 deletions

View File

@ -0,0 +1,13 @@
import { ComputedRef, computed } from 'vue'
import { useStore } from 'vuex'
export function useSetupMapGetters<T extends string>(strList: T[]) {
const mapData: Partial<{[x in T]: ComputedRef}> = {}
const getters = useStore().getters
strList.forEach(val => {
mapData[val] = computed(() => getters[val])
})
return mapData as {[x in T]: ComputedRef}
}

View File

@ -8,7 +8,7 @@
// TODO: Group类型比较特殊所以需要全量循环并判断是否为group // TODO: Group类型比较特殊所以需要全量循环并判断是否为group
const arr = ['w-text', 'w-image', 'w-svg', 'w-group', 'w-qrcode'] const arr = ['w-text', 'w-image', 'w-svg', 'w-group', 'w-qrcode']
export function getTarget(currentTarget: HTMLElement) { export function getTarget(currentTarget: HTMLElement): Promise<HTMLElement | null> {
let collector: string[] = [] let collector: string[] = []
let groupTarger: HTMLElement | null = null let groupTarger: HTMLElement | null = null
let saveTarger: HTMLElement | null = null let saveTarger: HTMLElement | null = null

View File

@ -1,107 +0,0 @@
<!--
* Old Component File
* @Author: ShawnPhang
* @Date: 2023-10-08 14:15:17
* @Description: 手动抠图 - 修补擦除
* @LastEditors: ShawnPhang <https://m.palxp.cn>
* @LastEditTime: 2023-10-09 01:28:11
-->
<!-- <template>
<div>
<el-dialog v-model="show" align-center width="90%" @close="showMatting = false">
<template #header>
<div class="tool-wrap">
<el-button type="primary" plain @click="done">确认应用</el-button>
<el-radio-group v-model="isErasing" style="margin-left: 35px">
<el-radio :label="false" size="large"> <b>修补画笔</b> <i class="icon sd-xiubu" /></el-radio>
<el-radio :label="true" size="large"> <b>擦除画笔</b> <i class="icon sd-cachu" /></el-radio>
</el-radio-group>
<number-slider v-model="radius" class="slider-wrap" label="画笔尺寸" :showInput="false" labelWidth="90px" :maxValue="constants.RADIUS_SLIDER_MAX" :minValue="constants.RADIUS_SLIDER_MIN" :step="constants.RADIUS_SLIDER_STEP" />
<number-slider v-model="hardness" class="slider-wrap" label="画笔硬度" :showInput="false" labelWidth="90px" :maxValue="constants.HARDNESS_SLIDER_MAX" :minValue="constants.HARDNESS_SLIDER_MIN" :step="constants.HARDNESS_SLIDER_STEP" />
</div>
</template>
<matting v-if="showMatting" :hasHeader="false" @register="mattingStart" />
</el-dialog>
</div>
</template> -->
<script lang="ts">
// import { defineComponent, reactive, toRefs, nextTick } from 'vue'
// import matting, { MattingType } from '@palxp/image-extraction'
// import { ElRadioGroup, ElRadio } from 'element-plus'
// import numberSlider from '@/components/modules/settings/numberSlider.vue'
// export default defineComponent({
// components: { matting, ElRadioGroup, ElRadio, numberSlider },
// setup() {
// const state: any = reactive({
// show: false,
// showMatting: false,
// isErasing: false,
// radius: 0, //
// brushSize: '', //
// hardness: 0, //
// hardnessText: '', //
// constants: {},
// })
// const params: any = { raw: '', result: '' }
// let matting: MattingType | any = {}
// let callback: any = null //
// const mattingStart: any = (mattingOptions: MattingType) => {
// mattingOptions.initLoadImages(params.raw, params.result)
// state.isErasing = mattingOptions.isErasing
// state.radius = mattingOptions.radius
// state.hardness = mattingOptions.hardness
// state.constants = mattingOptions.constants
// matting = mattingOptions
// }
// const open = async (raw: any, result: any, cb: any) => {
// state.show = true
// params.raw = raw
// params.result = result
// await nextTick()
// setTimeout(() => {
// state.showMatting = true
// }, 300)
// callback = cb
// }
// const done = () => {
// state.show = false
// callback(matting.getResult())
// }
// return {
// ...toRefs(state),
// open,
// done,
// mattingStart,
// }
// },
// })
</script>
<style lang="less" scoped>
:deep(.el-dialog__body) {
padding: 0 !important;
}
:deep(.el-dialog__header) {
padding: 10px 35px;
// var(--el-dialog-padding-primary)
}
.tool-wrap {
display: flex;
align-items: center;
}
// .tool-left {
// display: inline-flex;
// flex: 1;
// }
.slider-wrap {
margin-left: 35px;
width: 240px;
}
</style>

View File

@ -2,8 +2,8 @@
* @Author: ShawnPhang * @Author: ShawnPhang
* @Date: 2022-10-08 10:07:19 * @Date: 2022-10-08 10:07:19
* @Description: * @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn> * @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2023-10-05 00:04:51 * @Date: 2024-03-04 18:10:00
--> -->
<template> <template>
<el-dialog v-model="state.dialogVisible" title="选择图片" @close="close"> <el-dialog v-model="state.dialogVisible" title="选择图片" @close="close">

View File

@ -3,7 +3,7 @@
* @Date: 2022-03-16 09:15:52 * @Date: 2022-03-16 09:15:52
* @Description: * @Description:
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn> * @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
* @Date: 2024-03-04 09:50:00 * @Date: 2024-03-04 18:50:00
--> -->
<template> <template>
<div ref="qrCodeDom" class="qrcode__wrap"></div> <div ref="qrCodeDom" class="qrcode__wrap"></div>

View File

@ -1,4 +1,11 @@
import { CornerDotType,Options } from "qr-code-styling" /*
* @Author: Jeremy Yu
* @Date: 2024-03-04 18:10:00
* @Description:
* @LastEditors: Jeremy Yu <https://github.com/JeremyYu-cn>
* @Date: 2024-03-04 18:10:00
*/
import { CornerDotType, Options } from "qr-code-styling"
import { TQrcodeProps } from "./index.vue" import { TQrcodeProps } from "./index.vue"
/** 生成二维码数据 */ /** 生成二维码数据 */

View File

@ -1,136 +1,142 @@
<template> <template>
<div v-show="showMenuBg" id="menu-bg" class="menu-bg" @click="closeMenu"> <div v-show="showMenuBg" id="menu-bg" class="menu-bg" @click="closeMenu">
<ul ref="menuList" class="menu-list" :style="styleObj"> <ul ref="menuList" class="menu-list" :style="styleObj">
<li v-for="(item, index) in menuList.list" :key="index" :class="{ 'menu-item': true, 'disable-menu': dCopyElement.length === 0 && item.type === 'paste' }" @click.stop="selectMenu(item.type)"> <li v-for="(item, index) in menuListData.list" :key="index" :class="{ 'menu-item': true, 'disable-menu': dCopyElement.length === 0 && item.type === 'paste' }" @click.stop="selectMenu(item.type)">
{{ item.text }} {{ item.text }}
</li> </li>
</ul> </ul>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue' import { computed, onMounted, ref } from 'vue'
import { mapGetters, mapActions } from 'vuex' import { useStore } from 'vuex'
import { widgetMenu, pageMenu, menuList } from './rcMenuData' import {
widgetMenu as widget,
pageMenu as page,
menuList as menu,
TMenuItemData, TWidgetItemData,
} from './rcMenuData'
import { getTarget } from '@/common/methods/target' import { getTarget } from '@/common/methods/target'
import { useSetupMapGetters } from '@/common/hooks/mapGetters';
export default defineComponent({ const store = useStore()
setup() {}, const menuListData = ref<TMenuItemData>({...menu})
data() { const showMenuBg = ref<boolean>(false)
return { const widgetMenu = ref<TWidgetItemData[]>({...widget})
menuList, const pageMenu = ref<TWidgetItemData[]>({...page})
showMenuBg: false,
widgetMenu, const {dActiveElement, dAltDown, dWidgets, dCopyElement} = useSetupMapGetters(['dActiveElement', 'dAltDown', 'dWidgets', 'dCopyElement'])
pageMenu,
} const styleObj = computed(() => {
}, return {
computed: { left: menuListData.value.left + 'px',
...mapGetters(['dActiveElement', 'dAltDown', 'dWidgets', 'dCopyElement']), top: menuListData.value.top + 'px',
styleObj() { }
return { })
left: this.menuList.left + 'px',
top: this.menuList.top + 'px', onMounted(() => {
document.oncontextmenu = mouseRightClick
})
async function mouseRightClick(e: MouseEvent) {
e.stopPropagation()
e.preventDefault()
if (showMenuBg.value) {
showMenuBg.value = false
return
}
if (!e.target) return
let target = await getTarget(e.target as HTMLElement)
if (!target) return
let type = target.getAttribute('data-type')
if (type) {
let uuid = target.getAttribute('data-uuid') //
if (uuid !== '-1' && !dAltDown) {
let widget = dWidgets.value.find((item: any) => item.uuid === uuid)
if (
widget.parent !== '-1' &&
widget.parent !== dActiveElement.value.uuid &&
widget.parent !== dActiveElement.value.parent
) {
uuid = widget.parent
} }
}, }
}, store.dispatch('selectWidget', {
mounted() { uuid: uuid ?? '-1',
document.oncontextmenu = this.mouseRightClick })
}, showMenu(e)
methods: { }
...mapActions(['selectWidget', 'copyWidget', 'pasteWidget', 'updateLayerIndex', 'deleteWidget', 'ungroup']), }
async mouseRightClick(e: any) {
e.stopPropagation() function showMenu(e: MouseEvent) {
e.preventDefault() let isPage = dActiveElement.value.uuid === '-1'
if (this.showMenuBg) { menuListData.value.list = isPage ? pageMenu.value : widgetMenu.value
this.showMenuBg = false if (dActiveElement.value.isContainer) {
let ungroup: TWidgetItemData[] = [
{
type: 'ungroup',
text: '取消组合',
},
]
menuListData.value.list = ungroup.concat(menuListData.value.list)
}
showMenuBg.value = true
// document.getElementById('menu-bg').addEventListener('click', this.closeMenu, false)
let mx = e.pageX
let my = e.pageY
let listWidth = 120
if (mx + listWidth > window.innerWidth) {
mx -= listWidth
}
let listHeight = (14 + 10) * menuListData.value.list.length + 10
if (my + listHeight > window.innerHeight) {
my -= listHeight
}
menuListData.value.left = mx
menuListData.value.top = my
}
function closeMenu() {
showMenuBg.value = false
}
/** 点击菜单触发事件 */
function selectMenu(type: TWidgetItemData['type']) {
switch (type) {
case 'copy':
store.dispatch('copyWidget')
break
case 'paste':
if (dCopyElement.value.length === 0) {
return return
} }
// let target = e.target store.dispatch('pasteWidget')
let target = await getTarget(e.target) break
case 'index-up':
store.dispatch('updateLayerIndex', {
uuid: dActiveElement.value.uuid,
value: 1,
isGroup: dActiveElement.value.isContainer,
})
break
case 'index-down':
store.dispatch('updateLayerIndex', {
uuid: dActiveElement.value.uuid,
value: -1,
isGroup: dActiveElement.value.isContainer,
})
break
case 'del':
store.dispatch('deleteWidget')
break
case 'ungroup':
store.dispatch('ungroup', dActiveElement.value.uuid)
break
}
closeMenu()
}
let type = target.getAttribute('data-type')
if (type) {
let uuid = target.getAttribute('data-uuid') //
if (uuid !== '-1' && !this.dAltDown) {
let widget = this.dWidgets.find((item: any) => item.uuid === uuid)
if (widget.parent !== '-1' && widget.parent !== this.dActiveElement.uuid && widget.parent !== this.dActiveElement.parent) {
uuid = widget.parent
}
}
this.selectWidget({
uuid: uuid || '-1',
})
this.showMenu(e)
}
},
showMenu(e: any) {
let isPage = this.dActiveElement.uuid === '-1'
this.menuList.list = isPage ? this.pageMenu : this.widgetMenu
if (this.dActiveElement.isContainer) {
let ungroup = [
{
type: 'ungroup',
text: '取消组合',
},
]
this.menuList.list = ungroup.concat(this.menuList.list)
}
this.showMenuBg = true
// document.getElementById('menu-bg').addEventListener('click', this.closeMenu, false)
let mx = e.pageX
let my = e.pageY
let listWidth = 120
if (mx + listWidth > window.innerWidth) {
mx -= listWidth
}
let listHeight = (14 + 10) * this.menuList.list.length + 10
if (my + listHeight > window.innerHeight) {
my -= listHeight
}
this.menuList.left = mx
this.menuList.top = my
},
closeMenu() {
this.showMenuBg = false
// document.getElementById('menu-bg').removeEventListener('click', this.closeMenu, false)
},
selectMenu(type) {
switch (type) {
case 'copy':
this.copyWidget()
break
case 'paste':
if (this.dCopyElement.length === 0) {
return
}
this.pasteWidget()
break
case 'index-up':
this.updateLayerIndex({
uuid: this.dActiveElement.uuid,
value: 1,
isGroup: this.dActiveElement.isContainer,
})
break
case 'index-down':
this.updateLayerIndex({
uuid: this.dActiveElement.uuid,
value: -1,
isGroup: this.dActiveElement.isContainer,
})
break
case 'del':
this.deleteWidget()
break
case 'ungroup':
this.ungroup(this.dActiveElement.uuid)
break
}
this.closeMenu()
},
},
})
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -2,15 +2,28 @@
* @Author: ShawnPhang * @Author: ShawnPhang
* @Date: 2021-07-30 17:38:50 * @Date: 2021-07-30 17:38:50
* @Description: * @Description:
* @LastEditors: ShawnPhang * @LastEditors: ShawnPhang, Jeremy Yu <https://github.com/JeremyYu-cn>
* @LastEditTime: 2021-07-30 18:15:22 * @Date: 2024-03-04 18:50:00
*/ */
export const menuList: any = {
export type TMenuItemData = {
left: number
top: number
list: TWidgetItemData[]
}
export const menuList: TMenuItemData = {
left: 0, left: 0,
top: 0, top: 0,
list: [], list: [],
} }
export const widgetMenu = [
export type TWidgetItemData = {
type: 'copy' | 'paste' | 'index-up' | 'index-down' | 'del' | 'ungroup'
text: string
}
export const widgetMenu: TWidgetItemData[] = [
{ {
type: 'copy', type: 'copy',
text: '复制', text: '复制',
@ -33,7 +46,7 @@ export const widgetMenu = [
}, },
] ]
export const pageMenu = [ export const pageMenu: TWidgetItemData[] = [
{ {
type: 'paste', type: 'paste',
text: '粘贴', text: '粘贴',