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[]>([])
watchEffect(() => {
points.value = props.value
})
const barRef = ref<HTMLElement>()
const activeIndex = ref(0)
watchEffect(() => {
points.value = props.value
if (activeIndex.value > props.value.length - 1) activeIndex.value = 0
})
watch(activeIndex, () => {
emit('update:index', activeIndex.value)
})
@ -52,6 +53,9 @@ const removePoint = (index: number) => {
if (index === activeIndex.value) {
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)
emit('update:value', values)

View File

@ -517,7 +517,14 @@ export default () => {
}
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 options: pptxgen.ShapeProps = {

View File

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

View File

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

View File

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

View File

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

View File

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