mirror of
https://github.com/palxiao/poster-design.git
synced 2025-07-15 16:02:19 +08:00
Merge pull request #65 from JeremyYu-cn/feat-upgrade-vue3
Feat: Convert right-click-menu component and save-download component to vue3
This commit is contained in:
commit
65b724ac0e
@ -113,11 +113,14 @@ export default class DragHelper {
|
|||||||
private moveFlutter(x: number, y: number, d = 0, lazy = 0) {
|
private moveFlutter(x: number, y: number, d = 0, lazy = 0) {
|
||||||
const { width, height, finallySize } = this.initial as TInitial
|
const { width, height, finallySize } = this.initial as TInitial
|
||||||
let scale: string | null = null
|
let scale: string | null = null
|
||||||
if (!d) {
|
if (d) {
|
||||||
if (width > finallySize) {
|
if (width > finallySize) {
|
||||||
scale = width - d >= finallySize ? `transform: scale(${(width - d) / width});` : null
|
scale = width - d >= finallySize ? `transform: scale(${(width - d) / width});` : null
|
||||||
} else scale = width + d <= finallySize ? `transform: scale(${(width + d) / width})` : null
|
} else {
|
||||||
|
scale = width + d <= finallySize ? `transform: scale(${(width + d) / width})` : null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = [`left: ${x}px`, `top: ${y}px`, `width: ${width}px`, `height: ${height}px`]
|
const options = [`left: ${x}px`, `top: ${y}px`, `width: ${width}px`, `height: ${height}px`]
|
||||||
scale && options.push(scale)
|
scale && options.push(scale)
|
||||||
options.push(`transition: all ${lazy}s`)
|
options.push(`transition: all ${lazy}s`)
|
||||||
@ -137,9 +140,7 @@ export default class DragHelper {
|
|||||||
this.dragging = false
|
this.dragging = false
|
||||||
store.commit('setDraging', false)
|
store.commit('setDraging', false)
|
||||||
store.commit('selectItem', {})
|
store.commit('selectItem', {})
|
||||||
if (!this.cloneEl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
const { pageX, offsetX, pageY, offsetY } = this.initial as TInitial
|
const { pageX, offsetX, pageY, offsetY } = this.initial as TInitial
|
||||||
this.changeStyle([`left: ${pageX - offsetX}px`, `top: ${pageY - offsetY}px`, 'transform: scale(1)', 'transition: all 0.3s'])
|
this.changeStyle([`left: ${pageX - offsetX}px`, `top: ${pageY - offsetY}px`, 'transform: scale(1)', 'transition: all 0.3s'])
|
||||||
|
13
src/common/hooks/mapGetters.ts
Normal file
13
src/common/hooks/mapGetters.ts
Normal 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}
|
||||||
|
}
|
@ -15,7 +15,7 @@ interface Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
upload: async (file: File, options: Options, cb?: IQiniuSubscribeCb) => {
|
upload: async (file: File | Blob, options: Options, cb?: IQiniuSubscribeCb) => {
|
||||||
const win = window
|
const win = window
|
||||||
let name = ''
|
let name = ''
|
||||||
const suffix = file.type.split('/')[1] || 'png' // 文件后缀
|
const suffix = file.type.split('/')[1] || 'png' // 文件后缀
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
|
@ -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">
|
||||||
|
@ -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>
|
||||||
@ -11,10 +11,11 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, watch, nextTick, defineProps } from 'vue'
|
import { onMounted, ref, watch, nextTick, defineProps } from 'vue'
|
||||||
import QRCodeStyling, { DrawType, TypeNumber, Mode, ErrorCorrectionLevel, DotType, CornerSquareType, CornerDotType, } from 'qr-code-styling'
|
import QRCodeStyling, {DotType, Options } from 'qr-code-styling'
|
||||||
import { debounce } from 'throttle-debounce'
|
import { debounce } from 'throttle-debounce'
|
||||||
|
import { generateOption } from './method'
|
||||||
|
|
||||||
type TProps = {
|
export type TQrcodeProps = {
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
image?: string
|
image?: string
|
||||||
@ -25,7 +26,7 @@ type TProps = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TProps>(), {
|
const props = withDefaults(defineProps<TQrcodeProps>(), {
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 300,
|
height: 300,
|
||||||
dotsOptions: () => ({
|
dotsOptions: () => ({
|
||||||
@ -34,7 +35,7 @@ const props = withDefaults(defineProps<TProps>(), {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let options = {}
|
let options: Options = {}
|
||||||
watch(
|
watch(
|
||||||
() => [props.width, props.height, props.dotsOptions],
|
() => [props.width, props.height, props.dotsOptions],
|
||||||
() => {
|
() => {
|
||||||
@ -43,56 +44,11 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const render = debounce(300, false, async () => {
|
const render = debounce(300, false, async () => {
|
||||||
options = {
|
options = generateOption(props)
|
||||||
width: props.width,
|
|
||||||
height: props.height,
|
|
||||||
type: 'canvas' as DrawType, // canvas svg
|
|
||||||
data: props.value,
|
|
||||||
image: props.image, // /favicon.svg
|
|
||||||
margin: 0,
|
|
||||||
qrOptions: {
|
|
||||||
typeNumber: 3 as TypeNumber,
|
|
||||||
mode: 'Byte' as Mode,
|
|
||||||
errorCorrectionLevel: 'M' as ErrorCorrectionLevel,
|
|
||||||
},
|
|
||||||
imageOptions: {
|
|
||||||
hideBackgroundDots: true,
|
|
||||||
imageSize: 0.4,
|
|
||||||
margin: 6,
|
|
||||||
crossOrigin: 'anonymous',
|
|
||||||
},
|
|
||||||
backgroundOptions: {
|
|
||||||
color: '#ffffff',
|
|
||||||
},
|
|
||||||
dotsOptions: {
|
|
||||||
// color: '#41b583',
|
|
||||||
// type: 'rounded' as DotType,
|
|
||||||
...props.dotsOptions,
|
|
||||||
},
|
|
||||||
cornersSquareOptions: {
|
|
||||||
color: props.dotsOptions.color,
|
|
||||||
type: '',
|
|
||||||
// type: 'extra-rounded' as CornerSquareType,
|
|
||||||
// gradient: {
|
|
||||||
// type: 'linear', // 'radial'
|
|
||||||
// rotation: 180,
|
|
||||||
// colorStops: [{ offset: 0, color: '#25456e' }, { offset: 1, color: '#4267b2' }]
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
cornersDotOptions: {
|
|
||||||
color: props.dotsOptions.color,
|
|
||||||
type: 'square' as CornerDotType,
|
|
||||||
// gradient: {
|
|
||||||
// type: 'linear', // 'radial'
|
|
||||||
// rotation: 180,
|
|
||||||
// colorStops: [{ offset: 0, color: '#00266e' }, { offset: 1, color: '#4060b3' }]
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if (props.value) {
|
if (props.value) {
|
||||||
qrCode.update(options)
|
options && qrCode.update(options)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
if (!qrCodeDom.value || !qrCodeDom.value.firstChild) return
|
if (!qrCodeDom?.value?.firstChild) return
|
||||||
(qrCodeDom.value.firstChild as HTMLElement).setAttribute('style', "width: 100%;") // 强制其适应缩放
|
(qrCodeDom.value.firstChild as HTMLElement).setAttribute('style', "width: 100%;") // 强制其适应缩放
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
60
src/components/business/qrcode/method.ts
Normal file
60
src/components/business/qrcode/method.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* @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"
|
||||||
|
|
||||||
|
/** 生成二维码数据 */
|
||||||
|
export function generateOption(props: TQrcodeProps): Options {
|
||||||
|
return {
|
||||||
|
width: props.width,
|
||||||
|
height: props.height,
|
||||||
|
type: 'canvas', // canvas svg
|
||||||
|
data: props.value,
|
||||||
|
image: props.image, // /favicon.svg
|
||||||
|
margin: 0,
|
||||||
|
qrOptions: {
|
||||||
|
typeNumber: 3,
|
||||||
|
mode: 'Byte',
|
||||||
|
errorCorrectionLevel: 'M',
|
||||||
|
},
|
||||||
|
imageOptions: {
|
||||||
|
hideBackgroundDots: true,
|
||||||
|
imageSize: 0.4,
|
||||||
|
margin: 6,
|
||||||
|
crossOrigin: 'anonymous',
|
||||||
|
},
|
||||||
|
backgroundOptions: {
|
||||||
|
color: '#ffffff',
|
||||||
|
},
|
||||||
|
dotsOptions: {
|
||||||
|
// color: '#41b583',
|
||||||
|
// type: 'rounded' as DotType,
|
||||||
|
...props.dotsOptions,
|
||||||
|
},
|
||||||
|
cornersSquareOptions: {
|
||||||
|
color: props.dotsOptions.color,
|
||||||
|
// type: '',
|
||||||
|
// type: 'extra-rounded' as CornerSquareType,
|
||||||
|
// gradient: {
|
||||||
|
// type: 'linear', // 'radial'
|
||||||
|
// rotation: 180,
|
||||||
|
// colorStops: [{ offset: 0, color: '#25456e' }, { offset: 1, color: '#4267b2' }]
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
cornersDotOptions: {
|
||||||
|
color: props.dotsOptions.color,
|
||||||
|
type: 'square' as CornerDotType,
|
||||||
|
// gradient: {
|
||||||
|
// type: 'linear', // 'radial'
|
||||||
|
// rotation: 180,
|
||||||
|
// colorStops: [{ offset: 0, color: '#00266e' }, { offset: 1, color: '#4060b3' }]
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
@ -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: '粘贴',
|
||||||
|
@ -2,66 +2,63 @@
|
|||||||
* @Author: ShawnPhang
|
* @Author: ShawnPhang
|
||||||
* @Date: 2021-08-01 11:12:17
|
* @Date: 2021-08-01 11:12:17
|
||||||
* @Description: 前端出图 - 用于封面
|
* @Description: 前端出图 - 用于封面
|
||||||
* @LastEditors: ShawnPhang <https://m.palxp.cn>
|
* @LastEditors: ShawnPhang <https://m.palxp.cn>, Jeremy Yu <https://github.com/JeremyYu-cn>
|
||||||
* @LastEditTime: 2023-09-13 17:36:36
|
* @Date: 2024-03-04 18:50:00
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div id="cover-wrap"></div>
|
<div id="cover-wrap"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, reactive, toRefs, watch, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
import { useStore } from 'vuex'
|
||||||
import { mapGetters, mapActions } from 'vuex'
|
|
||||||
import html2canvas from 'html2canvas'
|
import html2canvas from 'html2canvas'
|
||||||
import api from '@/api'
|
|
||||||
import Qiniu from '@/common/methods/QiNiu'
|
import Qiniu from '@/common/methods/QiNiu'
|
||||||
|
import { useSetupMapGetters } from '@/common/hooks/mapGetters'
|
||||||
|
|
||||||
export default defineComponent({
|
const store = useStore();
|
||||||
props: ['modelValue'],
|
|
||||||
emits: ['update:modelValue'],
|
|
||||||
setup(props, context) {
|
|
||||||
const { proxy }: any = getCurrentInstance() as ComponentInternalInstance
|
|
||||||
|
|
||||||
async function createCover(cb: any) {
|
const { dZoom } = useSetupMapGetters(['dZoom'])
|
||||||
const nowZoom = proxy?.dZoom
|
|
||||||
// 取消选中元素
|
|
||||||
proxy?.selectWidget({
|
|
||||||
uuid: '-1',
|
|
||||||
})
|
|
||||||
proxy?.updateZoom(100)
|
|
||||||
const opts = {
|
|
||||||
useCORS: true, // 跨域图片
|
|
||||||
scale: 0.2,
|
|
||||||
}
|
|
||||||
setTimeout(async () => {
|
|
||||||
const clonePage: HTMLElement = document.getElementById('page-design-canvas').cloneNode(true)
|
|
||||||
clonePage.setAttribute('id', 'clone-page')
|
|
||||||
document.body.appendChild(clonePage)
|
|
||||||
html2canvas(document.getElementById('clone-page'), opts).then((canvas: any) => {
|
|
||||||
canvas.toBlob(
|
|
||||||
async (blobObj: Blob) => {
|
|
||||||
const result: any = await Qiniu.upload(blobObj, { bucket: 'xp-design', prePath: 'cover/user' })
|
|
||||||
cb(result)
|
|
||||||
},
|
|
||||||
'image/jpeg',
|
|
||||||
0.15,
|
|
||||||
)
|
|
||||||
proxy?.updateZoom(nowZoom)
|
|
||||||
clonePage.remove()
|
|
||||||
})
|
|
||||||
}, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
createCover,
|
// props: ['modelValue'],
|
||||||
}
|
// emits: ['update:modelValue'],
|
||||||
},
|
|
||||||
computed: {
|
async function createCover(cb: any) {
|
||||||
...mapGetters(['dZoom']),
|
const nowZoom = dZoom.value
|
||||||
},
|
// 取消选中元素
|
||||||
methods: {
|
store.dispatch('selectWidget', {
|
||||||
...mapActions(['selectWidget', 'updateZoom']),
|
uuid: '-1',
|
||||||
},
|
})
|
||||||
|
store.dispatch('updateZoom', 100)
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
useCORS: true, // 跨域图片
|
||||||
|
scale: 0.2,
|
||||||
|
}
|
||||||
|
setTimeout(async () => {
|
||||||
|
const clonePage = document.getElementById('page-design-canvas')?.cloneNode(true) as HTMLElement
|
||||||
|
if (!clonePage) return
|
||||||
|
clonePage.setAttribute('id', 'clone-page')
|
||||||
|
document.body.appendChild(clonePage)
|
||||||
|
html2canvas(clonePage, opts).then((canvas) => {
|
||||||
|
canvas.toBlob(
|
||||||
|
async (blobObj) => {
|
||||||
|
if (blobObj) {
|
||||||
|
const result = await Qiniu.upload(blobObj, { bucket: 'xp-design', prePath: 'cover/user' })
|
||||||
|
cb(result)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'image/jpeg',
|
||||||
|
0.15,
|
||||||
|
)
|
||||||
|
store.dispatch('updateZoom', nowZoom)
|
||||||
|
clonePage.remove()
|
||||||
|
})
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
createCover
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@ -40,7 +40,7 @@ interface IQiniuSubscribeCb {
|
|||||||
interface Window {
|
interface Window {
|
||||||
qiniu: {
|
qiniu: {
|
||||||
upload: (
|
upload: (
|
||||||
file: File,
|
file: File | Blob,
|
||||||
name: string,
|
name: string,
|
||||||
token: string,
|
token: string,
|
||||||
exObj: Record<string, any>,
|
exObj: Record<string, any>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user