feat: 添加裁剪功能

This commit is contained in:
zackxizi 2024-04-04 11:57:17 +08:00
parent 3419879ebf
commit 6aa77b1a3d
5 changed files with 84 additions and 34 deletions

View File

@ -2,7 +2,7 @@
* @Description: widget move
* @Author: xi_zi
* @Date: 2024-04-03 10:25:49
* @LastEditTime: 2024-04-04 00:28:22
* @LastEditTime: 2024-04-04 11:52:22
* @LastEditors: xi_zi
-->
<template>
@ -73,6 +73,9 @@ import type {
OnResizeGroupEnd,
OnRotateGroup,
OnRotateGroupEnd,
ClippableProps,
OnClip,
OnClipEnd,
} from 'vue3-moveable'
import { TUpdateWidgetMultiplePayload } from '@/store/design/widget/actions/widget'
@ -81,9 +84,8 @@ type TModifierStyle = { transform?: string; transformOrigin?: string; width?: nu
const widgetStore = useWidgetStore()
const controlStore = useControlStore()
const forceStore = useForceStore()
const historyStore = useHistoryStore()
const { guidelines } = storeToRefs(useCanvasStore())
const { showMoveable, showRotatable, dAltDown, resizable, scalable, warpable, dSpaceDown } = storeToRefs(controlStore)
const { showMoveable, showRotatable, dAltDown, resizable, scalable, warpable, clippable, dSpaceDown } = storeToRefs(controlStore)
const { dSelectWidgets, dActiveElement, activeMouseEvent, dWidgets } = storeToRefs(widgetStore)
const { updateRect, updateSelect } = storeToRefs(forceStore)
@ -103,7 +105,7 @@ const modifierStyle = (target: HTMLElement | SVGElement, { transform, transformO
const updateStoreModifierStyle = (target: HTMLElement | SVGElement, { transform, transformOrigin, width, height }: TModifierStyle) => {
const uuid = target.getAttribute('id') || ''
const updateData: TUpdateWidgetMultiplePayload['data'] = []
const updateData: TUpdateWidgetMultiplePayload['data'] = []
if (transform) updateData.push({ key: 'transform', value: transform })
if (transformOrigin) updateData.push({ key: 'transformOrigin', value: transformOrigin })
if (width) updateData.push({ key: 'width', value: width })
@ -112,6 +114,7 @@ const updateStoreModifierStyle = (target: HTMLElement | SVGElement, { transform,
widgetStore.updateWidgetMultiple({
uuid,
data: updateData,
pushHistory: true,
})
}
@ -251,12 +254,33 @@ const rotateOptions = reactive<RotatableProps>({
onRotateEnd,
})
const onClip = (e: OnClip) => {
e.target.style.clipPath = e.clipStyle
}
const onClipEnd = (e: OnClipEnd) => {
const uuid = dActiveElement.value?.uuid || ''
widgetStore.updateWidgetData({
uuid,
key: 'clipPath',
value: e.target.style.clipPath,
})
}
const clipOptions = reactive<ClippableProps>({
clippable: true,
clipRelative: false,
clipArea: false,
clipTargetBounds: false,
onClip,
onClipEnd,
})
const options = computed(() => {
const opt = { ...dragOptions }
if (showRotatable.value) Object.assign(opt, rotateOptions)
if (warpable.value) Object.assign(opt, warpOptions)
if (scalable.value) Object.assign(opt, scaleOptions)
if (resizable.value) Object.assign(opt, resizeOptions)
if (clippable.value) Object.assign(opt, clipOptions)
return opt
})

View File

@ -412,8 +412,8 @@ function getChilds(uuid: string) {
// },
const widgetStyle = (widgetData: TdWidgetData): CSSProperties => {
const { transform, transformOrigin, width, height } = widgetData
const style: CSSProperties = { position: 'absolute' }
const { transform, transformOrigin, width, height, opacity, clipPath } = widgetData
const style: CSSProperties = { position: 'absolute', opacity, clipPath }
if (transform) style.transform = transform
if (transformOrigin) style.transformOrigin = transformOrigin
if (width !== undefined) style.width = `${width}px`
@ -422,11 +422,11 @@ const widgetStyle = (widgetData: TdWidgetData): CSSProperties => {
}
const groupWidgetChildrenStyle = (groupWidgetData: TdWidgetData, widgetData: TdWidgetData): CSSProperties => {
const { transform, transformOrigin, width, height } = widgetData
const { transform, transformOrigin, width, height, opacity, clipPath } = widgetData
const { transform: gTransform } = groupWidgetData
const { x: gX, y: gY } = getOffsetFromTransform(gTransform || '')
const { x, y } = getOffsetFromTransform(transform || '')
const style: CSSProperties = { position: 'absolute' }
const style: CSSProperties = { position: 'absolute', opacity, clipPath }
if (transform) style.transform = removeTranslate(transform)
if (transformOrigin) style.transformOrigin = transformOrigin
if (width !== undefined) style.width = `${width}px`

View File

@ -3,7 +3,7 @@
* @Date: 2021-08-09 11:41:53
* @Description:
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-04 11:13:38
* @LastEditTime: 2024-04-04 11:47:25
-->
<template>
<div id="w-image-style">
@ -21,8 +21,10 @@
<!-- <el-button size="mini" style="width: 100%; margin-top: 0.5rem" plain @click="openCropper">替换图片</el-button> -->
<el-button style="width: 100%; margin-bottom: 12px" plain @click="openPicBox">替换图片</el-button>
<div class="options">
<el-button v-if="state.innerElement.cropEdit" plain type="primary" @click="imgCrop(false)">完成</el-button>
<el-button v-if="clippable" plain type="primary" @click="imgCrop(false)">完成</el-button>
<el-button v-else plain type="primary" @click="imgCrop(true)"><i class="icon sd-caijian" /> 裁剪</el-button>
<!-- <el-button v-if="state.innerElement.cropEdit" plain type="primary" @click="imgCrop(false)">完成</el-button>
<el-button v-else plain type="primary" @click="imgCrop(true)"><i class="icon sd-caijian" /> 裁剪</el-button> -->
<el-button plain @click="openImageCutout"><i class="icon sd-AIkoutu" /> 抠图</el-button>
<!-- <uploader class="options__upload" @done="uploadImgDone">
<el-button size="small" plain>替换图片</el-button>
@ -137,7 +139,7 @@ const canvasStore = useCanvasStore()
// dActiveElement, dWidgets
// } = useSetupMapGetters(['dActiveElement', 'dWidgets'])
const controlStore = useControlStore()
const { dMoving } = storeToRefs(controlStore)
const { dMoving, clippable } = storeToRefs(controlStore)
const { dActiveElement, dWidgets } = storeToRefs(widgetStore)
let lastUuid: string | undefined = undefined
@ -317,14 +319,15 @@ async function selectDone(img: TGetImageListResult) {
}
async function imgCrop(val: boolean) {
// TODO:
/* // TODO: 画布内图像裁
const el = document.getElementById(state.innerElement.uuid || '')
if (!el) return
const { left, top } = el.getBoundingClientRect()
toolBarStyle = { left: left + 'px', top: top + 'px' }
state.innerElement.cropEdit = val
await nextTick()
controlStore.setShowRotatable(!val)
controlStore.setShowRotatable(!val) */
controlStore.setClippable(val)
}
function cropHandle() {

View File

@ -1,18 +1,17 @@
/*
* @Author: Jeremy Yu
* @Date: 2024-03-18 21:00:00
* @Description:
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 10:26:38
* @LastEditTime: 2024-04-04 11:50:51
*/
import { useHistoryStore } from "@/store";
import { Store, defineStore } from "pinia";
import { useHistoryStore } from '@/store'
import { Store, defineStore } from 'pinia'
type TControlState = {
/** 是否正在移动组件 */
dMoving: boolean
dMoving: boolean
/** 是否正在抓取组件 */
dDraging: boolean
/** 是否正在调整组件宽高 */
@ -29,9 +28,10 @@ type TControlState = {
dSpaceDown: boolean
/** 正在编辑or裁剪的组件id **/
dCropUuid: string
warpable: boolean, // 是否开启斜切
scalable: boolean, // 是否开启缩放
resizable: boolean, // 是否开启拖拽
warpable: boolean // 是否开启斜切
scalable: boolean // 是否开启缩放
resizable: boolean // 是否开启可调整大小
clippable: boolean // 是否裁剪
}
type TControlAction = {
@ -50,27 +50,38 @@ type TControlAction = {
setCropUuid: (uuid: string) => void
/**
*
* @param able
* @returns
* @param able
* @returns
*/
setWarpable: (able: boolean) => void
/**
*
* @param able
* @returns
* @param able
* @returns
*/
setResizable: (able: boolean) => void
/**
*
* @param able
* @returns
* @param able
* @returns
*/
setScalable: (able: boolean) => void
/**
*
* @param able
* @returns
*/
setClippable: (able: boolean) => void
/**
* moveable控制
* @returns
*/
initMoveableControl: () => void
setSpaceDown: (uuid: boolean) => void // 设置是否按下空格键
}
/** 全局控制配置 */
const ControlStore = defineStore<"controlStore", TControlState, {}, TControlAction>("controlStore", {
const ControlStore = defineStore<'controlStore', TControlState, {}, TControlAction>('controlStore', {
state: () => ({
dMoving: false, // 是否正在移动组件
dDraging: false, // 是否正在抓取组件
@ -82,7 +93,8 @@ const ControlStore = defineStore<"controlStore", TControlState, {}, TControlActi
dCropUuid: '-1', // 正在编辑or裁剪的组件id
warpable: false, // 是否开启斜切
scalable: false, // 是否开启缩放
resizable: true, // 是否开启拖拽
resizable: true, // 是否开启可调整大小
clippable: false, // 是否开启裁剪
dSpaceDown: false, // 记录是否按下空格键
}),
getters: {},
@ -127,7 +139,7 @@ const ControlStore = defineStore<"controlStore", TControlState, {}, TControlActi
stopDMove() {
if (this.dMoving) {
const historyStore = useHistoryStore()
historyStore.pushHistory("stopDMove")
historyStore.pushHistory('stopDMove')
// store.dispatch('pushHistory', 'stopDMove')
}
this.dMoving = false
@ -161,12 +173,22 @@ const ControlStore = defineStore<"controlStore", TControlState, {}, TControlActi
this.resizable = true
}
},
setClippable(able: boolean) {
this.clippable = able
},
setSpaceDown(val: boolean) {
this.dSpaceDown = val
}
}
},
initMoveableControl() {
this.showRotatable = true
this.resizable = true
this.warpable = false
this.scalable = false
this.clippable = false
},
},
})
export type TControlStore = Store<"controlStore", TControlState, {}, TControlAction>
export type TControlStore = Store<'controlStore', TControlState, {}, TControlAction>
export default ControlStore

View File

@ -3,7 +3,7 @@
* @Date: 2024-03-18 21:00:00
* @Description: Store方法export
* @LastEditors: xi_zi
* @LastEditTime: 2024-04-03 15:26:46
* @LastEditTime: 2024-04-04 11:32:47
*/
import { Store, defineStore } from "pinia";
@ -29,6 +29,7 @@ export type TdWidgetData = TPageState & Partial<TCommonItemData> & {
rotate?: string
transform?: string
transformOrigin?: string
clipPath?: string
sliceData?: Record<string, any>
flip?: boolean
cropEdit?: boolean