Преглед на файлове

基本完成图片属性面板

pipipi-pikachu преди 5 години
родител
ревизия
eb33459d52

+ 4 - 0
src/assets/styles/antd.scss

@@ -13,6 +13,10 @@
 }
 .ant-btn {
   font-size: 13px !important;
+
+  &.no-padding {
+    padding: 0;
+  }
 }
 .ant-radio-group {
   font-size: 13px !important;

+ 1 - 1
src/store/constants.ts

@@ -22,7 +22,7 @@ export enum MutationTypes {
   UPDATE_SLIDE_INDEX = 'updateSlideIndex',
   ADD_ELEMENT = 'addElement',
   UPDATE_ELEMENT = 'updateElement',
-  REMOVE_ELEMENT_PROP = 'removeElementProp',
+  REMOVE_ELEMENT_PROPS = 'removeElementProps',
 
   // snapshot
   SET_SNAPSHOT_CURSOR = 'setSnapshotCursor',

+ 5 - 4
src/store/mutations.ts

@@ -1,4 +1,5 @@
 import { MutationTree } from 'vuex'
+import omit from 'lodash/omit'
 import { MutationTypes } from './constants'
 import { State } from './index'
 import { Slide, PPTElement } from '@/types/slides'
@@ -8,7 +9,7 @@ import { isSupportFontFamily } from '@/utils/fontFamily'
 
 interface RemoveElementPropData {
   id: string;
-  propName: string;
+  propName: string | string[];
 }
 
 interface UpdateElementData {
@@ -120,14 +121,14 @@ export const mutations: MutationTree<State> = {
     state.slides[slideIndex].elements = (elements as PPTElement[])
   },
 
-  [MutationTypes.REMOVE_ELEMENT_PROP](state, data: RemoveElementPropData) {
+  [MutationTypes.REMOVE_ELEMENT_PROPS](state, data: RemoveElementPropData) {
     const { id, propName } = data
+    const propsNames = typeof propName === 'string' ? [propName] : propName
 
     const slideIndex = state.slideIndex
     const slide = state.slides[slideIndex]
     const elements = slide.elements.map(el => {
-      if(el.id === id) delete el[propName]
-      return el
+      return el.id === id ? omit(el, propsNames) : el
     })
     state.slides[slideIndex].elements = (elements as PPTElement[])
   },

+ 96 - 54
src/views/Editor/Toolbar/ElementStylePanel/ImageStylePanel.vue

@@ -5,38 +5,39 @@
       :style="{ backgroundImage: `url(${handleElement.src})` }"
     ></div>
 
-    <Popover trigger="click" v-model:visible="clipPanelVisible">
-      <template #content>
-        <div class="clip">
-          <Button class="full-width-btn" @click="clipImage()">裁剪</Button>
-
-          <div class="title">按形状裁剪:</div>
-          <div class="shape-clip">
-            <div 
-              class="shape-clip-item" 
-              v-for="(item, index) in shapeClipPathOptions" 
-              :key="index"
-              @click="presetImageClip(index)"
-            >
-              <div class="shape" :style="{ clipPath: item.style }"></div>
+    <ButtonGroup class="row">
+      <Button style="flex: 5;" @click="clipImage()">裁剪图片</Button>
+      <Popover trigger="click" v-model:visible="clipPanelVisible">
+        <template #content>
+          <div class="clip">
+            <div class="title">按形状裁剪:</div>
+            <div class="shape-clip">
+              <div 
+                class="shape-clip-item" 
+                v-for="(item, index) in shapeClipPathOptions" 
+                :key="index"
+                @click="presetImageClip(index)"
+              >
+                <div class="shape" :style="{ clipPath: item.style }"></div>
+              </div>
             </div>
-          </div>
 
-          <template v-for="type in ratioClipOptions" :key="type.label">
-            <div class="title" v-if="type.label">{{type.label}}:</div>
-            <ButtonGroup class="row">
-              <Button 
-                style="flex: 1;"
-                v-for="item in type.children"
-                :key="item.key"
-                @click="presetImageClip('rect', item.ratio)"
-              >{{item.key}}</Button>
-            </ButtonGroup>
-          </template>
-        </div>
-      </template>
-      <Button class="full-width-btn">裁剪图片</Button>
-    </Popover>
+            <template v-for="type in ratioClipOptions" :key="type.label">
+              <div class="title" v-if="type.label">{{type.label}}:</div>
+              <ButtonGroup class="row">
+                <Button 
+                  style="flex: 1;"
+                  v-for="item in type.children"
+                  :key="item.key"
+                  @click="presetImageClip('rect', item.ratio)"
+                >{{item.key}}</Button>
+              </ButtonGroup>
+            </template>
+          </div>
+        </template>
+        <Button class="no-padding" style="flex: 1;"><IconFont type="icon-down" /></Button>
+      </Popover>
+    </ButtonGroup>
 
     <Popover trigger="click">
       <template #content>
@@ -83,8 +84,10 @@
     <ElementShadow />
     <Divider />
     
-    <Button class="full-width-btn">替换图片</Button>
-    <Button class="full-width-btn">重置样式</Button>
+    <FileInput @change="files => replaceImage(files)">
+      <Button class="full-width-btn">替换图片</Button>
+    </FileInput>
+    <Button class="full-width-btn" @click="resetImage()">重置样式</Button>
   </div>
 </template>
 
@@ -94,6 +97,7 @@ import { useStore } from 'vuex'
 import { MutationTypes, State } from '@/store'
 import { PPTImageElement } from '@/types/slides'
 import { CLIPPATHS } from '@/configs/imageClip'
+import { getImageDataURL } from '@/utils/image'
 import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 
 import ElementOutline from '../common/ElementOutline.vue'
@@ -213,7 +217,7 @@ export default defineComponent({
       clipPanelVisible.value = false
     }
 
-    const presetImageClip = (shape: string, ratio = 0) => {
+    const getImageElementDataBeforeClip = () => {
       // 图片当前宽高位置、裁剪范围
       const imgWidth = handleElement.value.width
       const imgHeight = handleElement.value.height
@@ -226,27 +230,27 @@ export default defineComponent({
       const originHeight = imgHeight / ((originClipRange[1][1] - originClipRange[0][1]) / 100)
       const originLeft = imgLeft - originWidth * (originClipRange[0][0] / 100)
       const originTop = imgTop - originHeight * (originClipRange[0][1] / 100)
-      
-      // 取消裁剪(移除裁剪属性,并将宽高位置设置为原本未裁剪过时的状态)
-      if(shape === 'none') {
-        store.commit(MutationTypes.UPDATE_ELEMENT, {
-          id: handleElement.value.id,
-          props: {
-            left: originLeft,
-            top: originTop,
-            width: originWidth,
-            height: originHeight,
-          },
-        })
-        store.commit(MutationTypes.REMOVE_ELEMENT_PROP, {
-          id: handleElement.value.id,
-          propName: 'clip',
-        })
-        clipPanelVisible.value = false
+
+      return {
+        originClipRange,
+        originWidth,
+        originHeight,
+        originLeft,
+        originTop,
       }
+    }
 
+    const presetImageClip = (shape: string, ratio = 0) => {
+      const {
+        originClipRange,
+        originWidth,
+        originHeight,
+        originLeft,
+        originTop,
+      } = getImageElementDataBeforeClip()
+      
       // 设置形状和纵横比
-      else if(ratio) {
+      if(ratio) {
         const imageRatio = originHeight / originWidth
 
         const min = 0
@@ -271,9 +275,7 @@ export default defineComponent({
             height: originHeight * (range[1][1] - range[0][1]) / 100,
           },
         })
-        clipImage()
       }
-
       // 仅设置形状(维持目前的裁剪范围)
       else {
         store.commit(MutationTypes.UPDATE_ELEMENT, {
@@ -282,8 +284,46 @@ export default defineComponent({
             clip: { ...handleElement.value.clip, shape, range: originClipRange }
           },
         })
-        clipImage()
       }
+      clipImage()
+      addHistorySnapshot()
+    }
+
+    const replaceImage = (files: File[]) => {
+      const imageFile = files[0]
+      if(!imageFile) return
+      getImageDataURL(imageFile).then(dataURL => {
+        const props = { src: dataURL }
+        store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
+      })
+      addHistorySnapshot()
+    }
+
+    const resetImage = () => {
+      if(handleElement.value.clip) {
+        const {
+          originWidth,
+          originHeight,
+          originLeft,
+          originTop,
+        } = getImageElementDataBeforeClip()
+
+        store.commit(MutationTypes.UPDATE_ELEMENT, {
+          id: handleElement.value.id,
+          props: {
+            left: originLeft,
+            top: originTop,
+            width: originWidth,
+            height: originHeight,
+          },
+        })
+      }
+
+      store.commit(MutationTypes.REMOVE_ELEMENT_PROPS, {
+        id: handleElement.value.id,
+        propName: ['clip', 'outline', 'flip', 'shadow', 'filter'],
+      })
+      addHistorySnapshot()
     }
 
     return {
@@ -297,6 +337,8 @@ export default defineComponent({
       updateFilter,
       clipImage,
       presetImageClip,
+      replaceImage,
+      resetImage,
     }
   },
 })

+ 2 - 2
src/views/Editor/Toolbar/common/ElementShadow.vue

@@ -10,7 +10,7 @@
       <div class="row">
         <div style="flex: 2;">水平阴影:</div>
         <Slider 
-          :min="1" 
+          :min="0" 
           :max="10" 
           :step="1" 
           :value="shadow.h" 
@@ -21,7 +21,7 @@
       <div class="row">
         <div style="flex: 2;">垂直阴影:</div>
         <Slider
-          :min="1"
+          :min="0"
           :max="10"
           :step="1"
           :value="shadow.v"