feat: 形状支持复杂渐变填充

This commit is contained in:
zxc 2024-08-10 15:51:18 +08:00
parent 6a54726af2
commit 2e4a5b6999
7 changed files with 63 additions and 53 deletions

View File

@ -30,13 +30,14 @@ const emit = defineEmits<{
const points = ref<GradientColor[]>([]) const points = ref<GradientColor[]>([])
watchEffect(() => {
points.value = props.value
})
const barRef = ref<HTMLElement>() const barRef = ref<HTMLElement>()
const activeIndex = ref(0) const activeIndex = ref(0)
watchEffect(() => {
points.value = props.value
if (activeIndex.value > props.value.length - 1) activeIndex.value = 0
})
watch(activeIndex, () => { watch(activeIndex, () => {
emit('update:index', activeIndex.value) emit('update:index', activeIndex.value)
}) })
@ -52,6 +53,9 @@ const removePoint = (index: number) => {
if (index === activeIndex.value) { if (index === activeIndex.value) {
activeIndex.value = (index - 1 < 0) ? 0 : index - 1 activeIndex.value = (index - 1 < 0) ? 0 : index - 1
} }
else if (activeIndex.value === props.value.length - 1) {
activeIndex.value = props.value.length - 2
}
const values = props.value.filter((item, _index) => _index !== index) const values = props.value.filter((item, _index) => _index !== index)
emit('update:value', values) emit('update:value', values)

View File

@ -517,7 +517,14 @@ export default () => {
} }
const points = formatPoints(toPoints(el.path), scale) const points = formatPoints(toPoints(el.path), scale)
const fillColor = formatColor(el.fill) let fillColor = formatColor(el.fill)
if (el.gradient) {
const colors = el.gradient.colors
const color1 = colors[0].color
const color2 = colors[colors.length - 1].color
const color = tinycolor.mix(color1, color2).toHexString()
fillColor = formatColor(color)
}
const opacity = el.opacity === undefined ? 1 : el.opacity const opacity = el.opacity === undefined ? 1 : el.opacity
const options: pptxgen.ShapeProps = { const options: pptxgen.ShapeProps = {

View File

@ -34,6 +34,15 @@ export const enum ElementTypes {
AUDIO = 'audio', AUDIO = 'audio',
} }
/**
*
*
* type: 线
*
* colors: 渐变颜色列表pos: 百分比位置color: 颜色
*
* rotate: 渐变角度线
*/
export type GradientType = 'linear' | 'radial' export type GradientType = 'linear' | 'radial'
export type GradientColor = { export type GradientColor = {
pos: number pos: number
@ -261,22 +270,6 @@ export interface PPTImageElement extends PPTBaseElement {
colorMask?: string colorMask?: string
} }
/**
*
*
* type: 线
*
* color: 渐变颜色
*
* rotate: 渐变角度线
*/
export interface ShapeGradient {
type: GradientType
color: [string, string]
rotate: number
}
export type ShapeTextAlign = 'top' | 'middle' | 'bottom' export type ShapeTextAlign = 'top' | 'middle' | 'bottom'
/** /**
@ -338,7 +331,7 @@ export interface PPTShapeElement extends PPTBaseElement {
path: string path: string
fixedRatio: boolean fixedRatio: boolean
fill: string fill: string
gradient?: ShapeGradient gradient?: Gradient
outline?: PPTElementOutline outline?: PPTElementOutline
opacity?: number opacity?: number
flipH?: boolean flipH?: boolean

View File

@ -52,27 +52,22 @@
<template v-if="fillType === 'gradient'"> <template v-if="fillType === 'gradient'">
<div class="row"> <div class="row">
<div style="width: 40%;">起点颜色</div> <GradientBar
<Popover trigger="click" style="width: 60%;"> :value="gradient.colors"
<template #content> @update:value="value => updateGradient({ colors: value })"
<ColorPicker @update:index="index => currentGradientIndex = index"
:modelValue="gradient.color[0]" />
@update:modelValue="value => updateGradient({ color: [value, gradient.color[1]] })"
/>
</template>
<ColorButton :color="gradient.color[0]" />
</Popover>
</div> </div>
<div class="row"> <div class="row">
<div style="width: 40%;">终点颜色</div> <div style="width: 40%;">当前色块</div>
<Popover trigger="click" style="width: 60%;"> <Popover trigger="click" style="width: 60%;">
<template #content> <template #content>
<ColorPicker <ColorPicker
:modelValue="gradient.color[1]" :modelValue="gradient.colors[currentGradientIndex].color"
@update:modelValue="value => updateGradient({ color: [gradient.color[0], value] })" @update:modelValue="value => updateGradientColors(value)"
/> />
</template> </template>
<ColorButton :color="gradient.color[1]" /> <ColorButton :color="gradient.colors[currentGradientIndex].color" />
</Popover> </Popover>
</div> </div>
<div class="row" v-if="gradient.type === 'linear'"> <div class="row" v-if="gradient.type === 'linear'">
@ -133,7 +128,7 @@
import { type Ref, ref, watch } from 'vue' import { type Ref, ref, watch } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store' import { useMainStore, useSlidesStore } from '@/store'
import type { GradientType, PPTShapeElement, ShapeGradient, ShapeText } from '@/types/slides' import type { GradientType, PPTShapeElement, Gradient, ShapeText } from '@/types/slides'
import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes' import { type ShapePoolItem, SHAPE_LIST, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import useShapeFormatPainter from '@/hooks/useShapeFormatPainter' import useShapeFormatPainter from '@/hooks/useShapeFormatPainter'
@ -153,6 +148,7 @@ import RadioButton from '@/components/RadioButton.vue'
import RadioGroup from '@/components/RadioGroup.vue' import RadioGroup from '@/components/RadioGroup.vue'
import Select from '@/components/Select.vue' import Select from '@/components/Select.vue'
import Popover from '@/components/Popover.vue' import Popover from '@/components/Popover.vue'
import GradientBar from '@/components/GradientBar.vue'
const mainStore = useMainStore() const mainStore = useMainStore()
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
@ -161,19 +157,27 @@ const { handleElement, handleElementId, shapeFormatPainter } = storeToRefs(mainS
const handleShapeElement = handleElement as Ref<PPTShapeElement> const handleShapeElement = handleElement as Ref<PPTShapeElement>
const fill = ref<string>('#000') const fill = ref<string>('#000')
const gradient = ref<ShapeGradient>({ const gradient = ref<Gradient>({
type: 'linear', type: 'linear',
rotate: 0, rotate: 0,
color: ['#fff', '#fff'], colors: [
{ pos: 0, color: '#fff' },
{ pos: 100, color: '#fff' },
],
}) })
const fillType = ref('fill') const fillType = ref('fill')
const textAlign = ref('middle') const textAlign = ref('middle')
const currentGradientIndex = ref(0)
watch(handleElement, () => { watch(handleElement, () => {
if (!handleElement.value || handleElement.value.type !== 'shape') return if (!handleElement.value || handleElement.value.type !== 'shape') return
fill.value = handleElement.value.fill || '#fff' fill.value = handleElement.value.fill || '#fff'
gradient.value = handleElement.value.gradient || { type: 'linear', rotate: 0, color: [fill.value, '#fff'] } const defaultGradientColor = [
{ pos: 0, color: fill.value },
{ pos: 100, color: '#fff' },
]
gradient.value = handleElement.value.gradient || { type: 'linear', rotate: 0, colors: defaultGradientColor }
fillType.value = handleElement.value.gradient ? 'gradient' : 'fill' fillType.value = handleElement.value.gradient ? 'gradient' : 'fill'
textAlign.value = handleElement.value?.text?.align || 'middle' textAlign.value = handleElement.value?.text?.align || 'middle'
}, { deep: true, immediate: true }) }, { deep: true, immediate: true })
@ -196,11 +200,18 @@ const updateFillType = (type: 'gradient' | 'fill') => {
} }
// //
const updateGradient = (gradientProps: Partial<ShapeGradient>) => { const updateGradient = (gradientProps: Partial<Gradient>) => {
if (!gradient.value) return if (!gradient.value) return
const _gradient: ShapeGradient = { ...gradient.value, ...gradientProps } const _gradient = { ...gradient.value, ...gradientProps }
updateElement({ gradient: _gradient }) updateElement({ gradient: _gradient })
} }
const updateGradientColors = (color: string) => {
const colors = gradient.value.colors.map((item, index) => {
if (index === currentGradientIndex.value) return { ...item, color }
return item
})
updateGradient({ colors })
}
// //
const updateFill = (value: string) => { const updateFill = (value: string) => {

View File

@ -31,8 +31,7 @@
<GradientDefs <GradientDefs
:id="`base-gradient-${elementInfo.id}`" :id="`base-gradient-${elementInfo.id}`"
:type="elementInfo.gradient.type" :type="elementInfo.gradient.type"
:color1="elementInfo.gradient.color[0]" :colors="elementInfo.gradient.colors"
:color2="elementInfo.gradient.color[1]"
:rotate="elementInfo.gradient.rotate" :rotate="elementInfo.gradient.rotate"
/> />
</defs> </defs>

View File

@ -8,24 +8,21 @@
y2="0%" y2="0%"
:gradientTransform="`rotate(${rotate},0.5,0.5)`" :gradientTransform="`rotate(${rotate},0.5,0.5)`"
> >
<stop offset="0%" :stop-color="color1" /> <stop v-for="(item, index) in colors" :key="index" :offset="`${item.pos}%`" :stop-color="item.color" />
<stop offset="100%" :stop-color="color2" />
</linearGradient> </linearGradient>
<radialGradient :id="id" v-else> <radialGradient :id="id" v-else>
<stop offset="0%" :stop-color="color1" /> <stop v-for="(item, index) in colors" :key="index" :offset="`${item.pos}%`" :stop-color="item.color" />
<stop offset="100%" :stop-color="color2" />
</radialGradient> </radialGradient>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { GradientType } from '@/types/slides' import type { GradientColor, GradientType } from '@/types/slides'
withDefaults(defineProps<{ withDefaults(defineProps<{
id: string id: string
type: GradientType type: GradientType
color1: string colors: GradientColor[]
color2: string
rotate?: number rotate?: number
}>(), { }>(), {
rotate: 0, rotate: 0,

View File

@ -40,8 +40,7 @@
<GradientDefs <GradientDefs
:id="`editabel-gradient-${elementInfo.id}`" :id="`editabel-gradient-${elementInfo.id}`"
:type="elementInfo.gradient.type" :type="elementInfo.gradient.type"
:color1="elementInfo.gradient.color[0]" :colors="elementInfo.gradient.colors"
:color2="elementInfo.gradient.color[1]"
:rotate="elementInfo.gradient.rotate" :rotate="elementInfo.gradient.rotate"
/> />
</defs> </defs>