2024-05-18 01:56:09 +08:00

398 lines
11 KiB
Vue

<template>
<div
:id="params.uuid"
ref="widgetRef"
:class="['w-image', { 'layer-lock': params.lock }]"
:style="{
position: state.position,
left: params.left - parent.left + 'px',
top: params.top - parent.top + 'px',
width: params.width + 'px',
height: params.height + 'px',
opacity: params.opacity,
}"
>
<div
v-if="cropEdit"
:id="params.uuid + '_ebox'"
:ref="params.uuid + '_ebox'"
:style="state.editBoxStyle"
class="svg__edit__wrap"
>
<img class="edit__model" :src="params.imgUrl" />
</div>
<div :style="{ transform: params.flip ? `rotate${params.flip}(180deg)` : undefined, borderRadius: params.radius + 'px', '-webkit-mask-image': `${params.mask ? `url('${params.mask}')` : 'initial'}` }" :class="['img__box', { mask: params.mask }]">
<div v-if="params.isNinePatch" ref="targetRef" class="target" :style="{ border: `${(params.height * params.sliceData.ratio) / 2}px solid transparent`, borderImage: `url('${params.imgUrl}') ${params.sliceData.left} round` }"></div>
<img v-else ref="targetRef" class="target" style="transform-origin: center" :src="params.imgUrl" />
</div>
<div v-if="isMask" class="drop__mask">
<div putIn="true" :style="{ fontSize: params.width / 12 + 'px' }" class="drop__btn">拖入</div>
</div>
</div>
</template>
<script lang="ts" setup>
// 图片组件
// const NAME = 'w-image'
import { CSSProperties, StyleValue, computed, nextTick, onBeforeUnmount, onMounted, onUpdated, reactive, ref, watch } from 'vue'
// import { getMatrix } from '@/common/methods/handleTransform'
import setting from "./wImageSetting"
// import { useSetupMapGetters } from '@/common/hooks/mapGetters'
import { useRoute } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useCanvasStore, useControlStore, useForceStore, useWidgetStore } from '@/store'
type TProps = {
params: typeof setting
parent: {
left: number
top: number
}
}
type TState = {
position: 'absolute' | 'relative', // 'absolute'relative
editBoxStyle: CSSProperties,
cropWidgetXY: {
x: number
y: number
}
holdPosition: {
left: number
top: number
}
}
const props = defineProps<TProps>()
const state = reactive<TState>({
position: 'absolute', // 'absolute'relative
editBoxStyle: {
transformOrigin: 'center',
transform: '',
},
cropWidgetXY: {
x: 0,
y: 0,
},
holdPosition: {
left: 0,
top: 0,
}
})
const route = useRoute()
const controlStore = useControlStore()
const widgetStore = useWidgetStore()
const forceStore = useForceStore()
const widgetRef = ref<HTMLElement | null>(null)
const targetRef = ref<HTMLImageElement | null>(null)
let rotateTemp: number | null = null
let flipTemp: string | null = null
// const {
// dActiveElement, dWidgets, dMouseXY, dDropOverUuid, dCropUuid
// } = useSetupMapGetters(['dActiveElement', 'dWidgets', 'dMouseXY', 'dDropOverUuid', 'dCropUuid'])
const { dZoom } = storeToRefs(useCanvasStore())
const {
dActiveElement, dWidgets, dMouseXY, dDropOverUuid
} = storeToRefs(widgetStore)
const { dCropUuid } = storeToRefs(controlStore)
// ...mapGetters(['dActiveElement', 'dWidgets', 'dZoom', 'dMouseXY', 'dDropOverUuid', 'dCropUuid']),
const cropEdit = computed(() => {
return props.params.uuid === dCropUuid.value
})
const tZoom = computed(() => {
return props.params.zoom
})
const isMask = computed(() => {
return props.params.mask && dDropOverUuid.value === props.params.uuid
})
const isDraw = computed(() => {
return route.name === 'Draw'
})
watch(
() => cropEdit.value,
(val) => {
// TODO 移动事件绑定
const el = document.getElementById(`${props.params.uuid}`)
if (val) {
el?.addEventListener('mousedown', touchstart, false)
} else {
el?.removeEventListener('mousedown', touchstart, false)
}
fixRotate()
lockOthers(val)
}
)
watch(
() => tZoom.value,
async () => {
await nextTick()
updateRecord()
}
)
onUpdated(() => {
updateRecord()
forceStore.setUpdateRect()
// store.commit('updateRect')
})
onMounted(async () => {
updateRecord()
await nextTick()
document.addEventListener('mouseup', touchend, false)
// if (this.params.transformData) {
// // this.$refs.widget.style.transform += `scale(${this.params.transformData.a}, ${this.params.transformData.d})`
// this.$refs.widget.style.transform += `matrix(${String(getMatrix(this.params.transformData))})`
// } else {
// // this.$refs.widget.style.transform += `scale(${this.params.transformData.a}, ${this.params.transformData.d})`
// this.params.rotate && (this.$refs.widget.style.transform += `rotate(${this.params.rotate})`)
// }
// console.log(this.params, this.params.filter)
// this.$refs.widget.style.filter = 'hue-rotate(100deg) blur(50px)'
if (widgetRef.value) {
widgetRef.value.style.transform += props.params.rotate && (widgetRef.value.style.transform += `rotate(${props.params.rotate})`)
}
})
onBeforeUnmount(() => {
document.removeEventListener('mouseup', touchend, false)
})
// ...mapActions(['updateWidgetData']),
function touchstart(e: MouseEvent) {
// const editBox = this.$refs[props.params.uuid + '_ebox']
const editBox = document.getElementById(props.params.uuid + '_ebox')
state.cropWidgetXY = {
x: Number(editBox?.style.left.replace('px', '')) || 0,
y: Number(editBox?.style.top.replace('px', '')) || 0,
}
// 绑定鼠标移动事件
document.addEventListener('mousemove', handlemousemove, true)
}
/** 取消鼠标移动事件 */
function touchend() {
document.removeEventListener('mousemove', handlemousemove, true)
// const left = Number(this.editBoxStyle.left.replace('px', ''))
// const flow = (this.params.width * (1 - this.tZoom)) / 2
// if (left + flow < 0) {
// }
}
function handlemousemove(e?: MouseEvent) {
if (!move(e)) {
return
}
e && e.stopPropagation()
e && e.preventDefault()
const { left, top } = move(e)
// TODO 触发位置刷新
state.holdPosition = { left, top }
state.editBoxStyle.left = left + 'px'
state.editBoxStyle.top = top + 'px'
changeFinish(left / props.params.zoom, top / props.params.zoom)
}
function changeFinish(x: number, y: number) {
setTransform('translate', `${x}px, ${y}px`)
}
function move(payload?: MouseEvent) {
if (payload) {
const widgetXY = { x: state.cropWidgetXY.x, y: state.cropWidgetXY.y }
const dx = Number(payload.pageX) - dMouseXY.value.x
const dy = Number(payload.pageY) - dMouseXY.value.y
let left = Number(widgetXY.x) + Math.floor((dx * 100) / dZoom.value)
let top = Number(widgetXY.y) + Math.floor((dy * 100) / dZoom.value)
// TODO: 旋转后计算坐标
// const rotate = Number(this.params.rotate.replace('deg', ''))
// console.log(Math.sin(rotate), Math.cos(rotate))
return { left, top }
} else {
return state.holdPosition
}
}
function updateRecord() {
if (dActiveElement.value?.uuid === props.params.uuid) {
let record = dActiveElement.value?.record
if (widgetRef.value) {
record.width = widgetRef.value.offsetWidth
record.height = widgetRef.value.offsetHeight
}
}
updateZoom()
}
function setTransform(attrName: string, value: string | number) {
const iof = props.params.transform.indexOf(attrName)
let setValue = ''
if (iof != -1) {
const index = iof + attrName.length
const tf = props.params.transform
const FRONT = tf.slice(0, index + 1)
const half = tf.substring(index + 1)
const END = half.substring(half.indexOf(')'))
setValue = FRONT + value + END
} else {
setValue = props.params.transform + ` ${attrName}(${value})`
}
widgetStore.updateWidgetData({
uuid: props.params.uuid,
key: 'transform',
value: setValue,
pushHistory: false,
})
// store.dispatch("updateWidgetData", {
// uuid: props.params.uuid,
// key: 'transform',
// value: setValue,
// pushHistory: false,
// })
if (props.params.transform && targetRef.value) {
targetRef.value.style.transform = props.params.transform
}
}
function setEditBox(attrName: string, value: string | number) {
const iof = state.editBoxStyle.transform?.indexOf(attrName)
let setValue = ''
if (iof != -1 && iof != undefined) {
const index = iof + attrName.length
const tf = state.editBoxStyle.transform ?? ''
const FRONT = tf.slice(0, index + 1)
const half = tf.substring(index + 1)
const END = half.substring(half.indexOf(')'))
setValue = FRONT + value + END
} else {
setValue = state.editBoxStyle.transform + ` ${attrName}(${value})`
}
state.editBoxStyle.transform = setValue
}
function updateZoom() {
setEditBox('scale', props.params.zoom)
setTransform('scale', props.params.zoom)
// this.$refs.target.style.transform = this.params.transform
handlemousemove()
}
function fixRotate() {
// 裁切时修正角度
if (rotateTemp) {
widgetRef.value && (widgetRef.value.style.transform = `rotate(${rotateTemp})`)
props.params.flip = flipTemp
rotateTemp = null
} else {
rotateTemp = props.params.rotate
widgetRef.value && (widgetRef.value.style.transform = `rotate(0deg)`)
flipTemp = props.params.flip
props.params.flip = null
}
// store.commit('setShowMoveable', false)
controlStore.setShowMoveable(false)
setTimeout(() => {
// store.commit('setShowMoveable', true)
controlStore.setShowMoveable(true)
}, 100)
}
function lockOthers(isCrop) {
// 裁剪时锁定其他图层
widgetStore.lockWidgets()
if (!isCrop) return
for (const widget of dWidgets.value) {
if (widget.uuid === props.params.uuid) {
widget.lock = false
break;
}
}
}
// cropDone(e) {
// e.stopPropagation()
// e.preventDefault()
// this.updateWidgetData({
// uuid: this.params.uuid,
// key: 'cropEdit',
// value: false,
// pushHistory: false,
// })
// },
</script>
<style lang="less" scoped>
// .w-image {
// // cursor: pointer;
// // outline: none;
// }
.mask {
-webkit-mask-size: 100% 100%;
-webkit-mask-position: center;
}
.img__box {
width: 100%;
height: 100%;
overflow: hidden;
img {
display: block;
}
}
.target {
display: block;
width: 100%;
height: 100%;
}
.svg__edit__wrap {
width: 100%;
height: 100%;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
}
.edit__model {
opacity: 0.2;
width: 100%;
height: 100%;
// object-fit: contain;
}
.drop {
&__mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
&__btn {
color: #ffffff;
background: rgba(0, 0, 0, 0.5);
border: 2px dashed #ffffff;
border-radius: 12px;
padding: 2rem;
}
}
.slider__bar {
position: absolute;
z-index: 999;
right: 0;
top: -80px;
transform: scale(1.5);
}
</style>