Browse Source

添加形状元素的渲染

pipipi-pikachu 5 years atrás
parent
commit
8fe7b266f3

+ 3 - 2
src/hooks/useCreateElement.ts

@@ -109,7 +109,7 @@ export default () => {
     })
   }
   
-  const createShapeElement = (position: CommonElementPosition, svgCode: string) => {
+  const createShapeElement = (position: CommonElementPosition, path: string, viewBox: number) => {
     const { left, top, width, height } = position
     createElement({
       ...DEFAULT_SHAPE,
@@ -119,7 +119,8 @@ export default () => {
       top, 
       width, 
       height,
-      svgCode,
+      viewBox,
+      path,
     })
   }
   

File diff suppressed because it is too large
+ 15 - 0
src/mocks/index.ts


+ 2 - 1
src/types/slides.ts

@@ -70,7 +70,8 @@ export interface PPTShapeElement extends PPTElementBaseProps {
   type: 'shape';
   width: number;
   height: number;
-  svgCode: string;
+  viewBox: number;
+  path: string;
   fixedRatio: boolean;
   fill: string;
   rotate?: number;

+ 2 - 0
src/views/Editor/Canvas/EditableElement.vue

@@ -30,6 +30,7 @@ import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'
 
 import ImageElement from '@/views/components/element/ImageElement/index.vue'
 import TextElement from '@/views/components/element/TextElement/index.vue'
+import ShapeElement from '@/views/components/element/ShapeElement/index.vue'
 
 export default defineComponent({
   name: 'editable-element',
@@ -56,6 +57,7 @@ export default defineComponent({
       const elementTypeMap = {
         'image': ImageElement,
         'text': TextElement,
+        'shape': ShapeElement,
       }
       return elementTypeMap[props.elementInfo.type] || null
     })

+ 2 - 40
src/views/Editor/Canvas/Operate/ImageElementOperate.vue

@@ -11,15 +11,7 @@
     :clipPath="clipShape.style"
     @clip="range => clip(range)"
   />
-  <div 
-    class="image-element-operate" 
-    v-else
-    :class="{
-      'selected': isSelected,
-      'multi-select': isMultiSelect && isSelected,
-      'active': isActive,
-    }"
-  >
+  <div class="image-element-operate" v-else>
     <BorderLine 
       class="operate-border-line"
       v-for="line in borderLines" 
@@ -72,14 +64,6 @@ export default defineComponent({
       type: Object as PropType<PPTImageElement>,
       required: true,
     },
-    isSelected: {
-      type: Boolean,
-      required: true,
-    },
-    isActive: {
-      type: Boolean,
-      required: true,
-    },
     isActiveGroupElement: {
       type: Boolean,
       required: true,
@@ -136,26 +120,4 @@ export default defineComponent({
     }
   },
 })
-</script>
-
-<style lang="scss" scoped>
-.image-element-operate {
-  &.selected {
-    .operate-border-line,
-    .operate-resize-handler,
-    .operate-rotate-handler {
-      display: block;
-    }
-  }
-
-  &.multi-select:not(.active) .operate-border-line {
-    border-color: rgba($color: $themeColor, $alpha: .3);
-  }
-
-  .operate-border-line,
-  .operate-resize-handler,
-  .operate-rotate-handler {
-    display: none;
-  }
-}
-</style>
+</script>

+ 85 - 0
src/views/Editor/Canvas/Operate/ShapeElementOperate.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="text-element-operate">
+    <BorderLine 
+      class="operate-border-line"
+      v-for="line in borderLines" 
+      :key="line.type" 
+      :type="line.type" 
+      :style="line.style"
+    />
+    <template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
+      <ResizeHandler
+        class="operate-resize-handler" 
+        v-for="point in resizeHandlers"
+        :key="point.direction"
+        :type="point.direction"
+        :style="point.style"
+        @mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
+      />
+      <RotateHandler
+        class="operate-rotate-handler" 
+        :style="{ left: scaleWidth / 2 + 'px' }"
+        @mousedown.stop="rotateElement(elementInfo)"
+      />
+    </template>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, PropType } from 'vue'
+import { useStore } from 'vuex'
+import { State } from '@/store'
+
+import { PPTShapeElement } from '@/types/slides'
+import { OperateResizeHandler } from '@/types/edit'
+import useCommonOperate from '../hooks/useCommonOperate'
+
+import RotateHandler from './RotateHandler.vue'
+import ResizeHandler from './ResizeHandler.vue'
+import BorderLine from './BorderLine.vue'
+
+export default defineComponent({
+  name: 'text-element-operate',
+  components: {
+    RotateHandler,
+    ResizeHandler,
+    BorderLine,
+  },
+  props: {
+    elementInfo: {
+      type: Object as PropType<PPTShapeElement>,
+      required: true,
+    },
+    isActiveGroupElement: {
+      type: Boolean,
+      required: true,
+    },
+    isMultiSelect: {
+      type: Boolean,
+      required: true,
+    },
+    rotateElement: {
+      type: Function as PropType<(element: PPTShapeElement) => void>,
+      required: true,
+    },
+    scaleElement: {
+      type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandler) => void>,
+      required: true,
+    },
+  },
+  setup(props) {
+    const store = useStore<State>()
+    const canvasScale = computed(() => store.state.canvasScale)
+
+    const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
+    const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
+    const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
+
+    return {
+      scaleWidth,
+      resizeHandlers,
+      borderLines,
+    }
+  },
+})
+</script>

+ 2 - 39
src/views/Editor/Canvas/Operate/TextElementOperate.vue

@@ -1,12 +1,5 @@
 <template>
-  <div 
-    class="text-element-operate" 
-    :class="{
-      'selected': isSelected,
-      'multi-select': isMultiSelect && isSelected,
-      'active': isActive,
-    }"
-  >
+  <div class="text-element-operate">
     <BorderLine 
       class="operate-border-line"
       v-for="line in borderLines" 
@@ -57,14 +50,6 @@ export default defineComponent({
       type: Object as PropType<PPTTextElement>,
       required: true,
     },
-    isSelected: {
-      type: Boolean,
-      required: true,
-    },
-    isActive: {
-      type: Boolean,
-      required: true,
-    },
     isActiveGroupElement: {
       type: Boolean,
       required: true,
@@ -98,26 +83,4 @@ export default defineComponent({
     }
   },
 })
-</script>
-
-<style lang="scss" scoped>
-.text-element-operate {
-  &.selected {
-    .operate-border-line,
-    .operate-resize-handler,
-    .operate-rotate-handler {
-      display: block;
-    }
-  }
-
-  &.multi-select:not(.active) .operate-border-line {
-    border-color: rgba($color: $themeColor, $alpha: .3);
-  }
-
-  .operate-border-line,
-  .operate-resize-handler,
-  .operate-rotate-handler {
-    display: none;
-  }
-}
-</style>
+</script>

+ 9 - 4
src/views/Editor/Canvas/Operate/index.vue

@@ -1,21 +1,20 @@
 <template>
   <div
     class="operate"
+    :class="{ 'multi-select': isMultiSelect && !isActive }"
+    v-if="isSelected"
     :style="{
       top: elementInfo.top * canvasScale + 'px',
       left: elementInfo.left * canvasScale + 'px',
       transform: `rotate(${elementInfo.rotate}deg)`,
-      'transform-origin': `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
+      transformOrigin: `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
     }"
   >
     <component
       :is="currentOperateComponent"
       :elementInfo="elementInfo"
-      :isSelected="isSelected"
-      :isActive="isActive"
       :isActiveGroupElement="isActiveGroupElement"
       :isMultiSelect="isMultiSelect"
-      :animationIndex="elementIndexInAnimation"
       :rotateElement="rotateElement"
       :scaleElement="scaleElement"
     ></component>
@@ -38,6 +37,7 @@ import { OperateResizeHandler } from '@/types/edit'
 
 import ImageElementOperate from './ImageElementOperate.vue'
 import TextElementOperate from './TextElementOperate.vue'
+import ShapeElementOperate from './ShapeElementOperate.vue'
 
 export default defineComponent({
   name: 'operate',
@@ -81,6 +81,7 @@ export default defineComponent({
       const elementTypeMap = {
         'image': ImageElementOperate,
         'text': TextElementOperate,
+        'shape': ShapeElementOperate,
       }
       return elementTypeMap[props.elementInfo.type] || null
     })
@@ -105,6 +106,10 @@ export default defineComponent({
   position: absolute;
   z-index: 100;
   user-select: none;
+
+  &.multi-select {
+    opacity: .3;
+  }
 }
 .animation-index {
   position: absolute;

+ 2 - 0
src/views/components/ThumbnailSlide/ThumbnailElement.vue

@@ -16,6 +16,7 @@ import { PPTElement } from '@/types/slides'
 
 import BaseImageElement from '@/views/components/element/ImageElement/BaseImageElement.vue'
 import BaseTextElement from '@/views/components/element/TextElement/BaseTextElement.vue'
+import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
 
 export default defineComponent({
   name: 'base-element',
@@ -34,6 +35,7 @@ export default defineComponent({
       const elementTypeMap = {
         'image': BaseImageElement,
         'text': BaseTextElement,
+        'shape': BaseShapeElement,
       }
       return elementTypeMap[props.elementInfo.type] || null
     })

+ 92 - 0
src/views/components/element/ShapeElement/BaseShapeElement.vue

@@ -0,0 +1,92 @@
+<template>
+  <div class="base-element-shape"
+    :style="{
+      top: elementInfo.top + 'px',
+      left: elementInfo.left + 'px',
+      width: elementInfo.width + 'px',
+      transform: `rotate(${elementInfo.rotate}deg)`,
+    }"
+  >
+    <div 
+      class="element-content"
+      :style="{
+        opacity: elementInfo.opacity,
+        filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
+      }"
+    >
+      <SvgWrapper overflow="visible" 
+        :width="elementInfo.width"
+        :height="elementInfo.height"
+      >
+        <g 
+          :transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
+        >
+          <path 
+            vector-effect="non-scaling-stroke" 
+            stroke-linecap="butt" 
+            stroke-miterlimit="8"
+            stroke-linejoin="" 
+            :d="elementInfo.path" 
+            :fill="elementInfo.fill"
+            :stroke="outlineColor"
+            :stroke-width="outlineWidth" 
+            :stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'" 
+          ></path>
+        </g>
+			</SvgWrapper>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, PropType } from 'vue'
+import { PPTShapeElement } from '@/types/slides'
+import useElementOutline from '@/views/components/element/hooks/useElementOutline'
+import useElementShadow from '@/views/components/element/hooks/useElementShadow'
+
+import SvgWrapper from '@/components/SvgWrapper.vue'
+
+export default defineComponent({
+  name: 'base-element-shape',
+  components: {
+    SvgWrapper,
+  },
+  props: {
+    elementInfo: {
+      type: Object as PropType<PPTShapeElement>,
+      required: true,
+    },
+  },
+  setup(props) {
+    const outline = computed(() => props.elementInfo.outline)
+    const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)
+    
+    const shadow = computed(() => props.elementInfo.shadow)
+    const { shadowStyle } = useElementShadow(shadow)
+
+    return {
+      shadowStyle,
+      outlineWidth,
+      outlineStyle,
+      outlineColor,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.base-element-shape {
+  position: absolute;
+}
+
+.element-content {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  svg {
+    transform-origin: 0 0;
+    overflow: visible;
+  }
+}
+</style>

+ 116 - 0
src/views/components/element/ShapeElement/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="editable-element-shape"
+    :class="{ 'lock': elementInfo.lock }"
+    :style="{
+      top: elementInfo.top + 'px',
+      left: elementInfo.left + 'px',
+      width: elementInfo.width + 'px',
+      transform: `rotate(${elementInfo.rotate}deg)`,
+    }"
+    @mousedown="$event => handleSelectElement($event)"
+  >
+    <div 
+      class="element-content" 
+      v-contextmenu="contextmenus"
+      :style="{
+        opacity: elementInfo.opacity,
+        filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
+      }"
+    >
+      <SvgWrapper overflow="visible" 
+        :width="elementInfo.width"
+        :height="elementInfo.height"
+      >
+        <g 
+          :transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
+        >
+          <path 
+            vector-effect="non-scaling-stroke" 
+            stroke-linecap="butt" 
+            stroke-miterlimit="8"
+            stroke-linejoin="" 
+            :d="elementInfo.path" 
+            :fill="elementInfo.fill"
+            :stroke="outlineColor"
+            :stroke-width="outlineWidth" 
+            :stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'" 
+          ></path>
+        </g>
+			</SvgWrapper>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, PropType } from 'vue'
+import { PPTShapeElement } from '@/types/slides'
+import { ContextmenuItem } from '@/components/Contextmenu/types'
+import useElementOutline from '@/views/components/element/hooks/useElementOutline'
+import useElementShadow from '@/views/components/element/hooks/useElementShadow'
+
+import SvgWrapper from '@/components/SvgWrapper.vue'
+
+export default defineComponent({
+  name: 'editable-element-shape',
+  components: {
+    SvgWrapper,
+  },
+  props: {
+    elementInfo: {
+      type: Object as PropType<PPTShapeElement>,
+      required: true,
+    },
+    selectElement: {
+      type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, canMove?: boolean) => void>,
+      required: true,
+    },
+    contextmenus: {
+      type: Function as PropType<() => ContextmenuItem[]>,
+    },
+  },
+  setup(props) {
+    const handleSelectElement = (e: MouseEvent) => {
+      if(props.elementInfo.lock) return
+      e.stopPropagation()
+
+      props.selectElement(e, props.elementInfo)
+    }
+
+    const outline = computed(() => props.elementInfo.outline)
+    const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)
+    
+    const shadow = computed(() => props.elementInfo.shadow)
+    const { shadowStyle } = useElementShadow(shadow)
+
+    return {
+      handleSelectElement,
+      shadowStyle,
+      outlineWidth,
+      outlineStyle,
+      outlineColor,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.editable-element-shape {
+  position: absolute;
+  cursor: move;
+
+  &.lock .element-content {
+    cursor: default;
+  }
+}
+
+.element-content {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  svg {
+    transform-origin: 0 0;
+    overflow: visible;
+  }
+}
+</style>