pipipi-pikachu 5 năm trước cách đây
mục cha
commit
045f361e14

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

@@ -17,6 +17,9 @@
 .ant-radio-group {
   font-size: 13px !important;
 }
+.ant-select-selection-item {
+  font-size: 13px;
+}
 .ant-select-item-option-content {
   font-size: 13px !important;
 }

+ 2 - 0
src/hooks/useSlideBackgroundStyle.ts

@@ -12,10 +12,12 @@ export default (background: Ref<SlideBackground | undefined>) => {
         return {
           backgroundImage: `url(${value}`,
           backgroundRepeat: 'repeat',
+          backgroundSize: 'initial',
         }
       }
       return {
         backgroundImage: `url(${value}`,
+        backgroundRepeat: 'no-repeat',
         backgroundSize: size,
       }
     }

+ 1 - 1
src/types/slides.ts

@@ -153,7 +153,7 @@ export interface PPTAnimation {
 export interface SlideBackground {
   type: 'solid' | 'image';
   value: string;
-  size?: 'cover' | 'contain' | 'repeat';
+  size?: 'cover' | 'contain' | 'repeat' | 'initial';
 }
 
 export interface Slide {

+ 0 - 1
src/views/Editor/Canvas/SlideBackground.vue

@@ -43,7 +43,6 @@ export default defineComponent({
   width: 100%;
   height: 100%;
   background-position: center;
-  background-size: cover;
   position: absolute;
 }
 </style>

+ 24 - 8
src/views/Editor/CanvasTool/index.vue

@@ -1,29 +1,45 @@
 <template>
   <div class="canvas-tool">
     <div class="left-handler">
-      <IconFont type="icon-undo" class="handler-item" :class="{ 'disable': !canUndo }" @click="undo()" />
-      <IconFont type="icon-redo" class="handler-item" :class="{ 'disable': !canRedo }" @click="redo()" />
+      <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="撤销">
+        <IconFont type="icon-undo" class="handler-item" :class="{ 'disable': !canUndo }" @click="undo()" />
+      </Tooltip>
+      <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="重做">
+        <IconFont type="icon-redo" class="handler-item" :class="{ 'disable': !canRedo }" @click="redo()" />
+      </Tooltip>
     </div>
 
     <div class="add-element-handler">
-      <IconFont type="icon-font-size" class="handler-item" @click="drawText()" />
+      <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="插入文字">
+        <IconFont type="icon-font-size" class="handler-item" @click="drawText()" />
+      </Tooltip>
       <FileInput @change="files => insertImageElement(files)">
-        <IconFont type="icon-image" class="handler-item" />
+        <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="插入图片">
+          <IconFont type="icon-image" class="handler-item" />
+        </Tooltip>
       </FileInput>
       <Popover trigger="click" v-model:visible="isOpenShapePool">
         <template #content>
           <ShapePool @select="shape => drawShape(shape)" />
         </template>
-        <IconFont type="icon-star" class="handler-item" />
+        <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="插入形状">
+          <IconFont type="icon-star" class="handler-item" />
+        </Tooltip>
       </Popover>
       <Popover trigger="click" v-model:visible="isOpenLinePool">
         <template #content>
           <LinePool @select="line => drawLine(line)" />
         </template>
-        <IconFont type="icon-line" class="handler-item" />
+        <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="插入线条">
+          <IconFont type="icon-line" class="handler-item" />
+        </Tooltip>
       </Popover>
-      <IconFont type="icon-table" class="handler-item" />
-      <IconFont type="icon-piechart" class="handler-item" />
+      <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="插入表格">
+        <IconFont type="icon-table" class="handler-item" />
+      </Tooltip>
+      <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="插入图表">
+        <IconFont type="icon-piechart" class="handler-item" />
+      </Tooltip>
     </div>
 
     <div class="right-handler">

+ 0 - 1
src/views/Editor/EditorHeader/index.vue

@@ -4,7 +4,6 @@
       <div class="menu-item">文件</div>
       <div class="menu-item">编辑</div>
       <div class="menu-item">设置</div>
-      <div class="menu-item">素材</div>
       <div class="menu-item">演示</div>
       <div class="menu-item">帮助</div>
     </div>

+ 2 - 2
src/views/Editor/Toolbar/MultiPositionPanel.vue

@@ -26,8 +26,8 @@
     <Divider />
 
     <ButtonGroup class="row">
-      <Button :disabled="!canCombine" @click="combineElements()" style="flex: 1;">组合</Button>
-      <Button :disabled="canCombine" @click="uncombineElements()" style="flex: 1;">取消组合</Button>
+      <Button :disabled="!canCombine" @click="combineElements()" style="flex: 1;"><IconFont type="icon-group" />组合</Button>
+      <Button :disabled="canCombine" @click="uncombineElements()" style="flex: 1;"><IconFont type="icon-ungroup" />取消组合</Button>
     </ButtonGroup>
   </div>
 </template>

+ 168 - 3
src/views/Editor/Toolbar/SlideStylePanel.vue

@@ -1,13 +1,178 @@
 <template>
   <div class="slide-style-panel">
-    <div>背景填充</div>
+    <div class="title">背景填充</div>
+    <div class="row">
+      <Select 
+        style="flex: 10;" 
+        :value="background.type" 
+        @change="value => updateBackgroundType(value)"
+      >
+        <SelectOption value="solid">纯色填充</SelectOption>
+        <SelectOption value="image">图片填充</SelectOption>
+      </Select>
+      <div style="flex: 1;"></div>
+      <Popover trigger="click" v-if="background.type === 'solid'">
+        <template #content>
+          <ColorPicker
+            :modelValue="background.value"
+            @update:modelValue="value => updateBackground({ value })"
+          />
+        </template>
+        <ColorButton :color="background.value" style="flex: 10;" />
+      </Popover>
+      <Select 
+        style="flex: 10;" 
+        :value="background.size || 'cover'" 
+        @change="value => updateBackground({ size: value })"
+        v-else
+      >
+        <SelectOption value="initial">原始大小</SelectOption>
+        <SelectOption value="contain">缩放</SelectOption>
+        <SelectOption value="repeat">拼贴</SelectOption>
+        <SelectOption value="cover">缩放铺满</SelectOption>
+      </Select>
+    </div>
+    <div class="background-image-wrapper" v-if="background.type === 'image'">
+      <FileInput @change="files => uploadBackgroundImage(files)">
+        <div class="background-image">
+          <div 
+            class="content"
+            :style="backgroundStyle"
+          >
+            <IconFont type="icon-plus" />
+          </div>
+        </div>
+      </FileInput>
+    </div>
+    <div class="row"><Button style="flex: 1;" @click="applyAllSlide()">应用到全部</Button></div>
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue'
+import { computed, defineComponent, Ref } from 'vue'
+import { useStore } from 'vuex'
+import { MutationTypes, State } from '@/store'
+import { Slide, SlideBackground } from '@/types/slides'
+import useHistorySnapshot from '@/hooks/useHistorySnapshot'
+
+import ColorButton from './common/ColorButton.vue'
+import { getImageDataURL } from '@/utils/image'
+import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
 
 export default defineComponent({
   name: 'slide-style-panel',
+  components: {
+    ColorButton,
+  },
+  setup() {
+    const store = useStore<State>()
+    const slides = computed(() => store.state.slides)
+    const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
+
+    const background = computed(() => {
+      if(!currentSlide.value.background) {
+        return {
+          type: 'solid',
+          value: '#fff',
+        } as SlideBackground
+      }
+      return currentSlide.value.background
+    })
+
+    const { backgroundStyle } = useSlideBackgroundStyle(background)
+
+    const { addHistorySnapshot } = useHistorySnapshot()
+
+    const updateBackgroundType = (type: 'solid' | 'image') => {
+      if(type === 'solid') {
+        const background: SlideBackground = {
+          type: 'solid',
+          value: '#fff',
+        }
+        store.commit(MutationTypes.UPDATE_SLIDE, { background })
+      }
+      else {
+        const background: SlideBackground = {
+          type: 'image',
+          value: '',
+          size: 'cover',
+        }
+        store.commit(MutationTypes.UPDATE_SLIDE, { background })
+      }
+      addHistorySnapshot()
+    }
+
+    const updateBackground = (props: Partial<SlideBackground>) => {
+      store.commit(MutationTypes.UPDATE_SLIDE, { background: { ...background.value, ...props } })
+      addHistorySnapshot()
+    }
+
+    const uploadBackgroundImage = (files: File[]) => {
+      const imageFile = files[0]
+      if(!imageFile) return
+      getImageDataURL(imageFile).then(dataURL => updateBackground({ value: dataURL }))
+    }
+
+    const applyAllSlide = () => {
+      const newSlides = slides.value.map(slide => {
+        return {
+          ...slide,
+          background: currentSlide.value.background,
+        }
+      })
+      store.commit(MutationTypes.SET_SLIDES, newSlides)
+      addHistorySnapshot()
+    }
+
+    return {
+      background,
+      backgroundStyle,
+      updateBackgroundType,
+      updateBackground,
+      uploadBackgroundImage,
+      applyAllSlide,
+    }
+  },
 })
-</script>
+</script>
+
+<style lang="scss" scoped>
+.row {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+}
+.title {
+  margin-bottom: 10px;
+}
+.background-image-wrapper {
+  margin-bottom: 10px;
+}
+.background-image {
+  height: 0;
+  padding-bottom: 56.25%;
+  border: 1px dashed $borderColor;
+  border-radius: $borderRadius;
+  position: relative;
+  transition: all .2s;
+
+  &:hover {
+    border-color: $themeColor;
+    color: $themeColor;
+  }
+
+  .content {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-position: center;
+    cursor: pointer;
+  }
+}
+</style>

+ 38 - 0
src/views/Editor/Toolbar/common/ColorButton.vue

@@ -0,0 +1,38 @@
+<template>
+  <Button class="color-btn">
+    <div class="color-block" :style="{ backgroundColor: color }"></div>
+    <IconFont type="icon-down" class="color-btn-icon" />
+  </Button>
+</template>
+
+<script lang="ts">
+export default {
+  name: 'color-button',
+  props: {
+    color: {
+      type: String,
+      required: true,
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.color-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 !important;
+}
+.color-block {
+  height: 20px;
+  margin-left: 8px;
+  flex: 1;
+}
+.color-btn-icon {
+  width: 30px;
+  font-size: 12px;
+  margin-top: 2px;
+  color: #bfbfbf;
+}
+</style>

+ 6 - 20
src/views/Editor/Toolbar/common/ElementOutline.vue

@@ -30,10 +30,7 @@
               @update:modelValue="value => updateOutline({ color: value })"
             />
           </template>
-          <Button class="color-btn" style="flex: 3;">
-            <div class="color-block" :style="{ backgroundColor: outline.color }"></div>
-            <IconFont type="icon-down" class="color-btn-icon" />
-          </Button>
+          <ColorButton :color="outline.color" style="flex: 3;" />
         </Popover>
       </div>
       <div class="row">
@@ -55,8 +52,13 @@ import { MutationTypes, State } from '@/store'
 import { PPTElement, PPTElementOutline } from '@/types/slides'
 import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 
+import ColorButton from './ColorButton.vue'
+
 export default defineComponent({
   name: 'element-outline',
+  components: {
+    ColorButton,
+  },
   setup() {
     const store = useStore<State>()
     const handleElement: Ref<PPTElement> = computed(() => store.getters.handleElement)
@@ -104,22 +106,6 @@ export default defineComponent({
   align-items: center;
   margin-bottom: 10px;
 }
-.color-btn {
-  display: flex;
-  align-items: center;
-  padding: 0 !important;
-}
-.color-block {
-  width: 100px;
-  height: 20px;
-  background-color: #777;
-  margin: 0 8px;
-}
-.color-btn-icon {
-  font-size: 12px;
-  margin-top: 2px;
-  color: #bfbfbf;
-}
 .switch-wrapper {
   text-align: right;
 }

+ 6 - 20
src/views/Editor/Toolbar/common/ElementShadow.vue

@@ -49,10 +49,7 @@
               @update:modelValue="value => updateShadow({ color: value })"
             />
           </template>
-          <Button class="color-btn" style="flex: 3;">
-            <div class="color-block"></div>
-            <IconFont type="icon-down" class="color-btn-icon" />
-          </Button>
+          <ColorButton :color="shadow.color" style="flex: 3;" />
         </Popover>
       </div>
     </template>
@@ -66,8 +63,13 @@ import { MutationTypes, State } from '@/store'
 import { PPTElement, PPTElementShadow } from '@/types/slides'
 import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 
+import ColorButton from './ColorButton.vue'
+
 export default defineComponent({
   name: 'element-shadow',
+  components: {
+    ColorButton,
+  },
   setup() {
     const store = useStore<State>()
     const handleElement: Ref<PPTElement> = computed(() => store.getters.handleElement)
@@ -115,22 +117,6 @@ export default defineComponent({
   align-items: center;
   margin-bottom: 10px;
 }
-.color-btn {
-  display: flex;
-  align-items: center;
-  padding: 0 !important;
-}
-.color-block {
-  width: 100px;
-  height: 20px;
-  background-color: #777;
-  margin: 0 8px;
-}
-.color-btn-icon {
-  font-size: 12px;
-  margin-top: 2px;
-  color: #bfbfbf;
-}
 .switch-wrapper {
   text-align: right;
 }

+ 2 - 1
src/views/Screen/ScreenSlide.vue

@@ -67,8 +67,9 @@ export default defineComponent({
   transform-origin: 0 0;
 }
 .background {
+  width: 100%;
+  height: 100%;
   background-position: center;
-  background-size: cover;
   position: absolute;
 }
 </style>

+ 2 - 1
src/views/components/ThumbnailSlide/index.vue

@@ -69,8 +69,9 @@ export default defineComponent({
   transform-origin: 0 0;
 }
 .background {
+  width: 100%;
+  height: 100%;
   background-position: center;
-  background-size: cover;
   position: absolute;
 }
 </style>