diff --git a/README.md b/README.md index e305eeb7..6492e3a6 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ npm run dev - Vertical text #### Images - Crop (custom, shape, aspect ratio) +- Rounding - Filters - Tint (mask) - Flip diff --git a/README_zh.md b/README_zh.md index d2b86860..ed341430 100644 --- a/README_zh.md +++ b/README_zh.md @@ -87,6 +87,7 @@ npm run dev - 竖向文本 #### 图片 - 裁剪(自定义、按形状、按纵横比) +- 圆角 - 滤镜 - 着色(蒙版) - 翻转 diff --git a/src/configs/imageClip.ts b/src/configs/imageClip.ts index da409d57..b3f41a46 100644 --- a/src/configs/imageClip.ts +++ b/src/configs/imageClip.ts @@ -51,7 +51,7 @@ export const CLIPPATHS: ClipPath = { name: '圆角矩形', type: ClipPathTypes.RECT, radius: '10px', - style: 'inset(0 0 0 0 round 10px 10px 10px 10px)', + style: 'inset(0 round 10px)', }, ellipse: { name: '圆形', diff --git a/src/types/slides.ts b/src/types/slides.ts index 71140d70..24cf08ed 100644 --- a/src/types/slides.ts +++ b/src/types/slides.ts @@ -258,6 +258,10 @@ export interface ImageElementClip { * flipV?: 垂直翻转 * * shadow?: 阴影 + * + * radius?: 圆角半径 + * + * colorMask?: 颜色蒙版 */ export interface PPTImageElement extends PPTBaseElement { type: 'image' @@ -269,6 +273,7 @@ export interface PPTImageElement extends PPTBaseElement { flipH?: boolean flipV?: boolean shadow?: PPTElementShadow + radius?: number colorMask?: string } diff --git a/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue b/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue index 71154449..f9f7afe3 100644 --- a/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue +++ b/src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue @@ -40,6 +40,15 @@ + +
+
圆角半径:
+ +
@@ -78,6 +87,7 @@ import Divider from '@/components/Divider.vue' import Button from '@/components/Button.vue' import ButtonGroup from '@/components/ButtonGroup.vue' import Popover from '@/components/Popover.vue' +import NumberInput from '@/components/NumberInput.vue' const shapeClipPathOptions = CLIPPATHS const ratioClipOptions = [ @@ -155,6 +165,12 @@ const getImageElementDataBeforeClip = () => { } } +const updateImage = (props: Partial) => { + if (!handleElement.value) return + slidesStore.updateElement({ id: handleElementId.value, props }) + addHistorySnapshot() +} + // 预设裁剪 const presetImageClip = (shape: string, ratio = 0) => { const _handleElement = handleElement.value as PPTImageElement @@ -183,28 +199,22 @@ const presetImageClip = (shape: string, ratio = 0) => { const distance = ((1 - imageRatio / ratio) / 2) * 100 range = [[distance, min], [max - distance, max]] } - slidesStore.updateElement({ - id: handleElementId.value, - props: { - clip: { ..._handleElement.clip, shape, range }, - left: originLeft + originWidth * (range[0][0] / 100), - top: originTop + originHeight * (range[0][1] / 100), - width: originWidth * (range[1][0] - range[0][0]) / 100, - height: originHeight * (range[1][1] - range[0][1]) / 100, - }, + updateImage({ + clip: { ..._handleElement.clip, shape, range }, + left: originLeft + originWidth * (range[0][0] / 100), + top: originTop + originHeight * (range[0][1] / 100), + width: originWidth * (range[1][0] - range[0][0]) / 100, + height: originHeight * (range[1][1] - range[0][1]) / 100, }) } // 形状裁剪(保持当前裁剪范围) else { - slidesStore.updateElement({ - id: handleElementId.value, - props: { - clip: { ..._handleElement.clip, shape, range: originClipRange } - }, - }) + const clipData = { ..._handleElement.clip, shape, range: originClipRange } + let props: Partial = { clip: clipData } + if (shape === 'rect') props = { clip: clipData, radius: 0 } + updateImage(props) } clipImage() - addHistorySnapshot() } // 替换图片(保持当前的样式) @@ -213,9 +223,8 @@ const replaceImage = (files: FileList) => { if (!imageFile) return getImageDataURL(imageFile).then(dataURL => { const props = { src: dataURL } - slidesStore.updateElement({ id: handleElementId.value, props }) + updateImage(props) }) - addHistorySnapshot() } // 重置图片:清除全部样式 @@ -230,14 +239,11 @@ const resetImage = () => { originTop, } = getImageElementDataBeforeClip() - slidesStore.updateElement({ - id: handleElementId.value, - props: { - left: originLeft, - top: originTop, - width: originWidth, - height: originHeight, - }, + updateImage({ + left: originLeft, + top: originTop, + width: originWidth, + height: originHeight, }) } diff --git a/src/views/components/element/ImageElement/BaseImageElement.vue b/src/views/components/element/ImageElement/BaseImageElement.vue index c7bce8f4..2ffb35e5 100644 --- a/src/views/components/element/ImageElement/BaseImageElement.vue +++ b/src/views/components/element/ImageElement/BaseImageElement.vue @@ -67,8 +67,8 @@ const flipH = computed(() => props.elementInfo.flipH) const flipV = computed(() => props.elementInfo.flipV) const { flipStyle } = useElementFlip(flipH, flipV) -const clip = computed(() => props.elementInfo.clip) -const { clipShape, imgPosition } = useClipImage(clip) +const imageElement = computed(() => props.elementInfo) +const { clipShape, imgPosition } = useClipImage(imageElement) const filters = computed(() => props.elementInfo.filters) const { filter } = useFilter(filters) diff --git a/src/views/components/element/ImageElement/ImageOutline/index.vue b/src/views/components/element/ImageElement/ImageOutline/index.vue index 09035920..7c41e8d5 100644 --- a/src/views/components/element/ImageElement/ImageOutline/index.vue +++ b/src/views/components/element/ImageElement/ImageOutline/index.vue @@ -36,6 +36,6 @@ const props = defineProps<{ elementInfo: PPTImageElement }>() -const clip = computed(() => props.elementInfo.clip) -const { clipShape } = useClipImage(clip) +const imageElement = computed(() => props.elementInfo) +const { clipShape } = useClipImage(imageElement) \ No newline at end of file diff --git a/src/views/components/element/ImageElement/index.vue b/src/views/components/element/ImageElement/index.vue index 972743a3..a4355158 100644 --- a/src/views/components/element/ImageElement/index.vue +++ b/src/views/components/element/ImageElement/index.vue @@ -101,8 +101,8 @@ const flipH = computed(() => props.elementInfo.flipH) const flipV = computed(() => props.elementInfo.flipV) const { flipStyle } = useElementFlip(flipH, flipV) -const clip = computed(() => props.elementInfo.clip) -const { clipShape, imgPosition } = useClipImage(clip) +const imageElement = computed(() => props.elementInfo) +const { clipShape, imgPosition } = useClipImage(imageElement) const filters = computed(() => props.elementInfo.filters) const { filter } = useFilter(filters) diff --git a/src/views/components/element/ImageElement/useClipImage.ts b/src/views/components/element/ImageElement/useClipImage.ts index 02901223..b1136788 100644 --- a/src/views/components/element/ImageElement/useClipImage.ts +++ b/src/views/components/element/ImageElement/useClipImage.ts @@ -1,17 +1,28 @@ import { computed, type Ref } from 'vue' import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip' -import type { ImageElementClip } from '@/types/slides' +import type { PPTImageElement } from '@/types/slides' -export default (clip: Ref) => { +export default (element: Ref) => { const clipShape = computed(() => { - if (!clip.value) return CLIPPATHS.rect - const shape = clip.value.shape || ClipPathTypes.RECT + let _clipShape = CLIPPATHS.rect + + if (element.value.clip) { + const shape = element.value.clip.shape || ClipPathTypes.RECT + _clipShape = CLIPPATHS[shape] + } + if (_clipShape.radius !== undefined && element.value.radius) { + _clipShape = { + ..._clipShape, + radius: `${element.value.radius}px`, + style: `inset(0 round ${element.value.radius}px)`, + } + } - return CLIPPATHS[shape] + return _clipShape }) const imgPosition = computed(() => { - if (!clip.value) { + if (!element.value.clip) { return { top: '0', left: '0', @@ -20,7 +31,7 @@ export default (clip: Ref) => { } } - const [start, end] = clip.value.range + const [start, end] = element.value.clip.range const widthScale = (end[0] - start[0]) / 100 const heightScale = (end[1] - start[1]) / 100