mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 形状支持复杂渐变填充
This commit is contained in:
parent
6a54726af2
commit
2e4a5b6999
@ -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)
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user