pipipi-pikachu 5 лет назад
Родитель
Сommit
99171297df

+ 6 - 0
src/types/slides.ts

@@ -72,6 +72,11 @@ export interface PPTImageElement {
   shadow?: PPTElementShadow;
 }
 
+export interface ShapeGradient {
+  type: 'line' | 'radial';
+  color: [string, string];
+  rotate: number;
+}
 export interface PPTShapeElement {
   type: 'shape';
   id: string;
@@ -85,6 +90,7 @@ export interface PPTShapeElement {
   path: string;
   fixedRatio: boolean;
   fill: string;
+  gradient?: ShapeGradient;
   rotate?: number;
   outline?: PPTElementOutline;
   opacity?: number;

+ 4 - 3
src/views/Editor/Toolbar/ElementStylePanel/ChartStylePanel/index.vue

@@ -53,7 +53,7 @@
     </div>
     <div class="row">
       <div style="flex: 2;">主题配色:</div>
-      <Popover trigger="click" v-model:visible="themePoolVisible">
+      <Popover trigger="click" placement="bottom" v-model:visible="themePoolVisible">
         <template #content>
           <div class="theme-pool">
             <div 
@@ -257,11 +257,12 @@ export default defineComponent({
   display: flex;
   align-items: center;
   justify-content: center;
-  padding: 5px 20px;
+  margin: 0 -12px;
+  padding: 5px 32px;
   transition: background-color .1s;
 
   & + .theme-item {
-    margin-top: 8px;
+    margin-top: 3px;
   }
 
   &:hover {

+ 89 - 4
src/views/Editor/Toolbar/ElementStylePanel/ShapeStylePanel.vue

@@ -1,17 +1,72 @@
 <template>
   <div class="shape-style-panel">
     <div class="row">
-      <div style="flex: 2;">填充颜色:</div>
-      <Popover trigger="click">
+      <Select 
+        style="flex: 10;" 
+        :value="fillType" 
+        @change="value => updateFillType(value)"
+      >
+        <SelectOption value="fill">纯色填充</SelectOption>
+        <SelectOption value="gradient">渐变填充</SelectOption>
+      </Select>
+      <div style="flex: 1;"></div>
+      <Popover trigger="click" v-if="fillType === 'fill'">
         <template #content>
           <ColorPicker
             :modelValue="fill"
             @update:modelValue="value => updateFill(value)"
           />
         </template>
-        <ColorButton :color="fill" style="flex: 3;" />
+        <ColorButton :color="fill" style="flex: 10;" />
       </Popover>
+      <Select 
+        style="flex: 10;" 
+        :value="gradient.type" 
+        @change="value => updateGradient({ type: value })"
+        v-else
+      >
+        <SelectOption value="line">线性渐变</SelectOption>
+        <SelectOption value="radial">径向渐变</SelectOption>
+      </Select>
     </div>
+    
+    <template v-if="fillType === 'gradient'">
+      <div class="row">
+        <div style="flex: 2;">起点颜色:</div>
+        <Popover trigger="click">
+          <template #content>
+            <ColorPicker
+              :modelValue="gradient.color[0]"
+              @update:modelValue="value => updateGradient({ color: [value, gradient.color[1]] })"
+            />
+          </template>
+          <ColorButton :color="gradient.color[0]" style="flex: 3;" />
+        </Popover>
+      </div>
+      <div class="row">
+        <div style="flex: 2;">终点颜色:</div>
+        <Popover trigger="click">
+          <template #content>
+            <ColorPicker
+              :modelValue="gradient.color[1]"
+              @update:modelValue="value => updateGradient({ color: [gradient.color[0], value] })"
+            />
+          </template>
+          <ColorButton :color="gradient.color[1]" style="flex: 3;" />
+        </Popover>
+      </div>
+      <div class="row" v-if="gradient.type === 'line'">
+        <div style="flex: 2;">渐变角度:</div>
+        <Slider
+          :min="0"
+          :max="360"
+          :step="15"
+          :value="gradient.rotate"
+          style="flex: 3;"
+          @change="value => updateGradient({ rotate: value })" 
+        />
+      </div>
+    </template>
 
     <Divider />
     <ElementOutline />
@@ -26,7 +81,7 @@
 import { computed, defineComponent, ref, Ref, watch } from 'vue'
 import { useStore } from 'vuex'
 import { MutationTypes, State } from '@/store'
-import { PPTShapeElement } from '@/types/slides'
+import { PPTShapeElement, ShapeGradient } from '@/types/slides'
 import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 
 import ElementOpacity from '../common/ElementOpacity.vue'
@@ -47,14 +102,40 @@ export default defineComponent({
     const handleElement: Ref<PPTShapeElement> = computed(() => store.getters.handleElement)
 
     const fill = ref<string>()
+    const gradient = ref<ShapeGradient>()
+    const fillType = ref('fill')
 
     watch(handleElement, () => {
       if(!handleElement.value) return
       fill.value = handleElement.value.fill || '#000'
+
+      gradient.value = handleElement.value.gradient || { type: 'line', rotate: 0, color: [fill.value, '#fff'] }
+
+      fillType.value = handleElement.value.gradient ? 'gradient' : 'fill'
     }, { deep: true, immediate: true })
 
     const { addHistorySnapshot } = useHistorySnapshot()
 
+    const updateFillType = (type: 'gradient' | 'fill') => {
+      if(type === 'fill') {
+        store.commit(MutationTypes.REMOVE_ELEMENT_PROPS, {
+          id: handleElement.value.id,
+          propName: 'gradient',
+        })
+      }
+      else {
+        const props = { gradient: gradient.value }
+        store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
+      }
+      addHistorySnapshot()
+    }
+
+    const updateGradient = (gradientProps: Partial<ShapeGradient>) => {
+      const props = { gradient: { ...gradient.value, ...gradientProps } }
+      store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
+      addHistorySnapshot()
+    }
+
     const updateFill = (value: string) => {
       const props = { fill: value }
       store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
@@ -63,7 +144,11 @@ export default defineComponent({
 
     return {
       fill,
+      gradient,
+      fillType,
+      updateFillType,
       updateFill,
+      updateGradient,
     }
   },
 })

+ 15 - 1
src/views/components/element/ShapeElement/BaseShapeElement.vue

@@ -20,6 +20,15 @@
         :width="elementInfo.width"
         :height="elementInfo.height"
       >
+        <defs v-if="elementInfo.gradient">
+          <GradientDefs
+            :id="`base-gradient-${elementInfo.id}`" 
+            :type="elementInfo.gradient.type"
+            :color1="elementInfo.gradient.color[0]"
+            :color2="elementInfo.gradient.color[1]"
+            :rotate="elementInfo.gradient.rotate"
+          />
+        </defs>
         <g 
           :transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
         >
@@ -29,7 +38,7 @@
             stroke-miterlimit="8"
             stroke-linejoin="" 
             :d="elementInfo.path" 
-            :fill="elementInfo.fill"
+            :fill="elementInfo.gradient ? `url(#base-gradient-${elementInfo.id})` : elementInfo.fill"
             :stroke="outlineColor"
             :stroke-width="outlineWidth" 
             :stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'" 
@@ -46,8 +55,13 @@ import { PPTShapeElement } from '@/types/slides'
 import useElementOutline from '@/views/components/element/hooks/useElementOutline'
 import useElementShadow from '@/views/components/element/hooks/useElementShadow'
 
+import GradientDefs from './GradientDefs.vue'
+
 export default defineComponent({
   name: 'base-element-shape',
+  components: {
+    GradientDefs,
+  },
   props: {
     elementInfo: {
       type: Object as PropType<PPTShapeElement>,

+ 48 - 0
src/views/components/element/ShapeElement/GradientDefs.vue

@@ -0,0 +1,48 @@
+<template>
+  <linearGradient 
+    v-if="type === 'line'"
+    :id="id" 
+    x1="0%" 
+    y1="0%" 
+    x2="100%" 
+    y2="0%" 
+    :gradientTransform="`rotate(${rotate},0.5,0.5)`"
+  >
+    <stop offset="0%" :stop-color="color1" />
+    <stop offset="100%" :stop-color="color2" />
+  </linearGradient>
+
+  <radialGradient :id="id" v-else>
+    <stop offset="0%" :stop-color="color1" />
+    <stop offset="100%" :stop-color="color2" />
+  </radialGradient>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType } from 'vue'
+
+export default defineComponent({
+  name: 'gradient-defs',
+  props: {
+    id: {
+      type: String,
+      required: true,
+    },
+    type: {
+      type: String as PropType<'line' | 'radial'>,
+    },
+    color1: {
+      type: String,
+      required: true,
+    },
+    color2: {
+      type: String,
+      required: true,
+    },
+    rotate: {
+      type: Number,
+      default: 0,
+    },
+  },
+})
+</script>

+ 15 - 1
src/views/components/element/ShapeElement/index.vue

@@ -23,6 +23,15 @@
         :width="elementInfo.width"
         :height="elementInfo.height"
       >
+        <defs v-if="elementInfo.gradient">
+          <GradientDefs
+            :id="`editabel-gradient-${elementInfo.id}`" 
+            :type="elementInfo.gradient.type"
+            :color1="elementInfo.gradient.color[0]"
+            :color2="elementInfo.gradient.color[1]"
+            :rotate="elementInfo.gradient.rotate"
+          />
+        </defs>
         <g 
           :transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
         >
@@ -32,7 +41,7 @@
             stroke-miterlimit="8"
             stroke-linejoin="" 
             :d="elementInfo.path" 
-            :fill="elementInfo.fill"
+            :fill="elementInfo.gradient ? `url(#editabel-gradient-${elementInfo.id})` : elementInfo.fill"
             :stroke="outlineColor"
             :stroke-width="outlineWidth" 
             :stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'" 
@@ -50,8 +59,13 @@ import { ContextmenuItem } from '@/components/Contextmenu/types'
 import useElementOutline from '@/views/components/element/hooks/useElementOutline'
 import useElementShadow from '@/views/components/element/hooks/useElementShadow'
 
+import GradientDefs from './GradientDefs.vue'
+
 export default defineComponent({
   name: 'editable-element-shape',
+  components: {
+    GradientDefs,
+  },
   props: {
     elementInfo: {
       type: Object as PropType<PPTShapeElement>,