Kaynağa Gözat

主题配置功能

pipipi-pikachu 5 yıl önce
ebeveyn
işleme
c0cf96a4b5

+ 1 - 1
src/components/ColorPicker/Alpha.vue

@@ -54,7 +54,7 @@ export default defineComponent({
       else a = Math.round(left * 100 / containerWidth) / 100
 
       if(color.value.a !== a) {
-        emit('change', {
+        emit('colorChange', {
           r: color.value.r,
           g: color.value.g,
           b: color.value.b,

+ 1 - 1
src/components/ColorPicker/EditableInput.vue

@@ -30,7 +30,7 @@ export default defineComponent({
 
     const handleInput = (e: InputEvent) => {
       const value = (e.target as HTMLInputElement).value
-      if(value.length >= 6) emit('change', tinycolor(value).toRgb())
+      if(value.length >= 6) emit('colorChange', tinycolor(value).toRgb())
     }
 
     return {

+ 1 - 1
src/components/ColorPicker/Hue.vue

@@ -71,7 +71,7 @@ export default defineComponent({
         h = (360 * percent / 100)
       }
       if(color.value.h !== h) {
-        emit('change', {
+        emit('colorChange', {
           h,
           l: color.value.l,
           s: color.value.s,

+ 1 - 1
src/components/ColorPicker/Saturation.vue

@@ -48,7 +48,7 @@ export default defineComponent({
     const pointerLeft = computed(() => color.value.s * 100 + '%')
 
     const emitChangeEvent = throttle(function(param) {
-      emit('change', param)
+      emit('colorChange', param)
     }, 20, { leading: true, trailing: false })
 
     const saturationRef = ref<HTMLElement>()

+ 4 - 4
src/components/ColorPicker/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="color-picker">
     <div class="picker-saturation-wrap">
-      <Saturation :value="color" :hue="hue" @change="value => changeColor(value)" />
+      <Saturation :value="color" :hue="hue" @colorChange="value => changeColor(value)" />
     </div>
     <div class="picker-controls">
       <div class="picker-color-wrap">
@@ -10,16 +10,16 @@
       </div>
       <div class="picker-sliders">
         <div class="picker-hue-wrap">
-          <Hue :value="color" :hue="hue" @change="value => changeColor(value)" />
+          <Hue :value="color" :hue="hue" @colorChange="value => changeColor(value)" />
         </div>
         <div class="picker-alpha-wrap">
-          <Alpha :value="color" @change="value => changeColor(value)" />
+          <Alpha :value="color" @colorChange="value => changeColor(value)" />
         </div>
       </div>
     </div>
 
     <div class="picker-field">
-      <EditableInput :value="color" @change="value => changeColor(value)" />
+      <EditableInput :value="color" @colorChange="value => changeColor(value)" />
     </div>
 
     <div class="picker-presets">

+ 0 - 56
src/configs/element.ts

@@ -1,7 +1,3 @@
-import { PPTElementOutline } from '@/types/slides'
-
-const DEFAULT_COLOR = '#d14424'
-
 export const ELEMENT_TYPE = {
   'text': '文本',
   'image': '图片',
@@ -11,58 +7,6 @@ export const ELEMENT_TYPE = {
   'table': '表格',
 }
 
-export const DEFAULT_TEXT = {
-  content: '请输入内容',
-}
-
-export const DEFAULT_IMAGE = {
-  left: 0,
-  top: 0,
-  fixedRatio: true,
-}
-
-export const DEFAULT_SHAPE = {
-  fill: DEFAULT_COLOR,
-  fixedRatio: false,
-}
-
-export const DEFAULT_LINE = {
-  style: 'solid',
-  width: 4,
-  color: DEFAULT_COLOR,
-}
-
-export const DEFAULT_CHART = {
-  left: 300,
-  top: 81.25,
-  width: 400,
-  height: 400,
-  data: {
-    labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
-    series: [
-      [12, 19, 5, 2, 18],
-    ],
-  },
-}
-
-const tableOutline: PPTElementOutline = {
-  width: 2,
-  style: 'solid',
-  color: '#eeece1',
-}
-export const DEFAULT_TABLE = {
-  left: 0,
-  top: 0,
-  outline: tableOutline,
-  theme: {
-    color: DEFAULT_COLOR,
-    rowHeader: true,
-    rowFooter: false,
-    colHeader: false,
-    colFooter: false,
-  },
-}
-
 export const MIN_SIZE = {
   text: 20,
   image: 20,

+ 1 - 1
src/configs/theme.ts

@@ -1,8 +1,8 @@
 export const PRESET_THEMES = [
+  { color: '#d14424', background: '#ffffff', text: '#333' },
   { color: '#42464b', background: '#ffffff', text: '#333' },
   { color: '#5d82ba', background: '#ffffff', text: '#333' },
   { color: '#005a6f', background: '#ffffff', text: '#333' },
-  { color: '#232d05', background: '#fff244', text: '#333' },
   { color: '#d0614c', background: '#dfb044', text: '#333' },
   { color: '#86a1ad', background: '#dfdbd4', text: '#333' },
   { color: '#697586', background: '#d5c4a4', text: '#333' },

+ 40 - 18
src/hooks/useCreateElement.ts

@@ -1,19 +1,12 @@
+import { computed } from 'vue'
 import { useStore } from 'vuex'
-import { MutationTypes } from '@/store'
+import { MutationTypes, State } from '@/store'
 import { createRandomCode } from '@/utils/common'
 import { getImageSize } from '@/utils/image'
 import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
 import { ChartType, PPTElement, TableCell } from '@/types/slides'
 import { ShapePoolItem } from '@/configs/shapes'
 import { LinePoolItem } from '@/configs/lines'
-import {
-  DEFAULT_IMAGE,
-  DEFAULT_TEXT,
-  DEFAULT_SHAPE,
-  DEFAULT_LINE,
-  DEFAULT_CHART,
-  DEFAULT_TABLE,
-} from '@/configs/element'
 import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 
 interface CommonElementPosition {
@@ -31,12 +24,12 @@ interface LineElementPosition {
 }
 
 export default () => {
-  const store = useStore()
+  const store = useStore<State>()
+  const themeColor = computed(() => store.state.theme.themeColor)
+  const fontColor = computed(() => store.state.theme.fontColor)
 
   const { addHistorySnapshot } = useHistorySnapshot()
 
-  
-
   const createElement = (element: PPTElement) => {
     store.commit(MutationTypes.ADD_ELEMENT, element)
     store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [element.id])
@@ -57,22 +50,35 @@ export default () => {
       }
 
       createElement({
-        ...DEFAULT_IMAGE,
         type: 'image',
         id: createRandomCode(),
         src,
         width,
         height,
+        left: 0,
+        top: 0,
+        fixedRatio: true,
       })
     })
   }
   
   const createChartElement = (chartType: ChartType) => {
     createElement({
-      ...DEFAULT_CHART,
       type: 'chart',
       id: createRandomCode(),
       chartType,
+      left: 300,
+      top: 81.25,
+      width: 400,
+      height: 400,
+      themeColor: themeColor.value,
+      gridColor: fontColor.value,
+      data: {
+        labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
+        series: [
+          [12, 19, 5, 2, 18],
+        ],
+      },
     })
   }
   
@@ -86,33 +92,45 @@ export default () => {
     const colWidths: number[] = new Array(col).fill(1 / col)
 
     createElement({
-      ...DEFAULT_TABLE,
       type: 'table',
       id: createRandomCode(),
       width: col * DEFAULT_CELL_WIDTH,
       height: row * DEFAULT_CELL_HEIGHT,
       colWidths,
       data,
+      left: 0,
+      top: 0,
+      outline: {
+        width: 2,
+        style: 'solid',
+        color: '#eeece1',
+      },
+      theme: {
+        color: themeColor.value,
+        rowHeader: true,
+        rowFooter: false,
+        colHeader: false,
+        colFooter: false,
+      },
     })
   }
   
   const createTextElement = (position: CommonElementPosition) => {
     const { left, top, width, height } = position
     createElement({
-      ...DEFAULT_TEXT,
       type: 'text',
       id: createRandomCode(),
       left, 
       top, 
       width, 
       height,
+      content: '请输入内容',
     })
   }
   
   const createShapeElement = (position: CommonElementPosition, data: ShapePoolItem) => {
     const { left, top, width, height } = position
     createElement({
-      ...DEFAULT_SHAPE,
       type: 'shape',
       id: createRandomCode(),
       left, 
@@ -121,13 +139,14 @@ export default () => {
       height,
       viewBox: data.viewBox,
       path: data.path,
+      fill: themeColor.value,
+      fixedRatio: false,
     })
   }
   
   const createLineElement = (position: LineElementPosition, data: LinePoolItem) => {
     const { left, top, start, end } = position
     createElement({
-      ...DEFAULT_LINE,
       type: 'line',
       id: createRandomCode(),
       left, 
@@ -135,6 +154,9 @@ export default () => {
       start,
       end,
       points: data.points,
+      color: themeColor.value,
+      style: 'solid',
+      width: 2,
     })
   }
 

+ 1 - 0
src/mocks/index.ts

@@ -12,6 +12,7 @@ export const slides: Slide[] = [
         width: 300,
         height: 300,
         chartType: 'line',
+        themeColor: '#d70206',
         data: {
           labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
           series: [

+ 1 - 1
src/types/slides.ts

@@ -132,7 +132,7 @@ export interface PPTChartElement {
   data: ChartData;
   options?: ILineChartOptions & IBarChartOptions & IPieChartOptions;
   outline?: PPTElementOutline;
-  themeColors?: string[];
+  themeColor: string;
   gridColor?: string;
 }
 

+ 10 - 1
src/views/Editor/Canvas/EditableElement.vue

@@ -3,7 +3,10 @@
     class="editable-element"
     ref="elementRef"
     :id="'editable-element-' + elementInfo.id"
-    :style="{ zIndex: elementIndex }"
+    :style="{
+      zIndex: elementIndex,
+      color: themeFontColor,
+    }"
   >
     <component
       :is="currentElementComponent"
@@ -16,6 +19,8 @@
 
 <script lang="ts">
 import { computed, defineComponent, PropType } from 'vue'
+import { useStore } from 'vuex'
+import { State } from '@/store'
 import { ElementTypes, PPTElement } from '@/types/slides'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 
@@ -56,6 +61,9 @@ export default defineComponent({
     },
   },
   setup(props) {
+    const store = useStore<State>()
+    const themeFontColor = computed(() => store.state.theme.fontColor)
+
     const currentElementComponent = computed(() => {
       const elementTypeMap = {
         [ElementTypes.IMAGE]: ImageElement,
@@ -145,6 +153,7 @@ export default defineComponent({
     return {
       currentElementComponent,
       contextmenus,
+      themeFontColor,
     }
   },
 })

+ 11 - 83
src/views/Editor/Toolbar/ElementStylePanel/ChartStylePanel/index.vue

@@ -55,35 +55,14 @@
     </div>
     <div class="row">
       <div style="flex: 2;">主题配色:</div>
-      <Popover trigger="click" placement="bottom" v-model:visible="themePoolVisible">
+      <Popover trigger="click">
         <template #content>
-          <div class="theme-pool">
-            <div 
-              class="theme-item" 
-              v-for="(theme, index) in CHART_THEME_COLORS" 
-              :key="index"
-              @click="updateTheme(theme)"
-            >
-              <div 
-                class="color-block" 
-                v-for="(color, index) in theme" 
-                :key="index"
-                :style="{ backgroundColor: color }"
-              ></div>
-            </div>
-          </div>
+          <ColorPicker
+            :modelValue="themeColor"
+            @update:modelValue="value => updateTheme(value)"
+          />
         </template>
-        <Button class="theme-color-btn" style="flex: 3;">
-          <div class="theme-color-content">
-            <div 
-              class="color-block" 
-              v-for="(color, index) in themeColors" 
-              :key="index"
-              :style="{ backgroundColor: color }"
-            ></div>
-          </div>
-          <IconPlatte class="theme-color-btn-icon" />
-        </Button>
+        <ColorButton :color="themeColor" style="flex: 3;" />
       </Popover>
     </div>
     <div class="row">
@@ -144,13 +123,12 @@ export default defineComponent({
     const handleElement = computed<PPTChartElement>(() => store.getters.handleElement)
 
     const chartDataEditorVisible = ref(false)
-    const themePoolVisible = ref(false)
 
     const { addHistorySnapshot } = useHistorySnapshot()
 
     const fill = ref<string>()
 
-    const themeColors = ref<string[]>([])
+    const themeColor = ref<string>('')
     const gridColor = ref('')
 
     const lineSmooth = ref<boolean | Function>(true)
@@ -179,7 +157,7 @@ export default defineComponent({
         if(_donut !== undefined) donut.value = _donut
       }
 
-      themeColors.value = handleElement.value.themeColors || CHART_THEME_COLORS[0]
+      themeColor.value = handleElement.value.themeColor
       gridColor.value = handleElement.value.gridColor || 'rgba(0, 0, 0, 0.4)'
     }, { deep: true, immediate: true })
 
@@ -204,9 +182,8 @@ export default defineComponent({
       addHistorySnapshot()
     }
 
-    const updateTheme = (themeColors: string[]) => {
-      themePoolVisible.value = false
-      const props = { themeColors }
+    const updateTheme = (themeColor: string) => {
+      const props = { themeColor }
       store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
       addHistorySnapshot()
     }
@@ -219,7 +196,6 @@ export default defineComponent({
 
     return {
       chartDataEditorVisible,
-      themePoolVisible,
       handleElement,
       updateData,
       fill,
@@ -230,7 +206,7 @@ export default defineComponent({
       horizontalBars,
       donut,
       updateOptions,
-      themeColors,
+      themeColor,
       gridColor,
       CHART_THEME_COLORS,
       updateTheme,
@@ -253,52 +229,4 @@ export default defineComponent({
 .btn-icon {
   margin-right: 3px;
 }
-
-.theme-item {
-  border-radius: $borderRadius;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin: 0 -12px;
-  padding: 5px 32px;
-  transition: background-color .1s;
-
-  & + .theme-item {
-    margin-top: 3px;
-  }
-
-  &:hover {
-    background-color: #e1e1e1;
-    cursor: pointer;
-  }
-}
-.theme-color-btn {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 0 !important;
-}
-.theme-color-content {
-  height: 20px;
-  margin-left: 8px;
-  flex: 1;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.color-block {
-  width: 18px;
-  height: 18px;
-  border-radius: 50%;
-
-  & + .color-block {
-    margin-left: -3px;
-  }
-}
-.theme-color-btn-icon {
-  width: 30px;
-  font-size: 12px;
-  margin-top: 2px;
-  color: #bfbfbf;
-}
 </style>

+ 28 - 1
src/views/Editor/Toolbar/SlideStylePanel.vue

@@ -267,7 +267,34 @@ export default defineComponent({
     }
 
     const applyThemeAllSlide = () => {
-      console.log('applyThemeAllSlide')
+      const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value))
+      const { themeColor, backgroundColor, fontColor } = theme.value
+
+      for(const slide of newSlides) {
+        slide.background = {
+          ...slide.background,
+          type: 'solid',
+          color: backgroundColor
+        }
+
+        const elements = slide.elements
+        for(const el of elements) {
+          if(el.type === 'shape') el.fill = themeColor
+          else if(el.type === 'line') el.color = themeColor
+          else if(el.type === 'text') {
+            if(el.fill) el.fill = themeColor
+          }
+          else if(el.type === 'table') {
+            if(el.theme) el.theme.color = themeColor
+          }
+          else if(el.type === 'chart') {
+            el.themeColor = themeColor
+            el.gridColor = fontColor
+          }
+        }
+      }
+      store.commit(MutationTypes.SET_SLIDES, newSlides)
+      addHistorySnapshot()
     }
 
     return {

+ 3 - 0
src/views/Screen/ScreenElement.vue

@@ -3,6 +3,7 @@
     class="screen-element"
     :style="{
       zIndex: elementIndex,
+      color: themeFontColor,
       visibility: needWaitAnimation ? 'hidden' : 'visible',
     }"
   >
@@ -56,6 +57,7 @@ export default defineComponent({
     })
 
     const store = useStore<State>()
+    const themeFontColor = computed(() => store.state.theme.fontColor)
     const currentSlide = computed<Slide>(() => store.getters.currentSlide)
 
     const needWaitAnimation = computed(() => {
@@ -68,6 +70,7 @@ export default defineComponent({
     return {
       currentElementComponent,
       needWaitAnimation,
+      themeFontColor,
     }
   },
 })

+ 10 - 1
src/views/components/ThumbnailSlide/ThumbnailElement.vue

@@ -1,7 +1,10 @@
 <template>
   <div 
     class="base-element"
-    :style="{ zIndex: elementIndex }"
+    :style="{
+      zIndex: elementIndex,
+      color: themeFontColor,
+    }"
   >
     <component
       :is="currentElementComponent"
@@ -13,6 +16,8 @@
 
 <script lang="ts">
 import { computed, defineComponent, PropType } from 'vue'
+import { useStore } from 'vuex'
+import { State } from '@/store'
 import { ElementTypes, PPTElement } from '@/types/slides'
 
 import BaseImageElement from '@/views/components/element/ImageElement/BaseImageElement.vue'
@@ -35,6 +40,9 @@ export default defineComponent({
     },
   },
   setup(props) {
+    const store = useStore<State>()
+    const themeFontColor = computed(() => store.state.theme.fontColor)
+
     const currentElementComponent = computed(() => {
       const elementTypeMap = {
         [ElementTypes.IMAGE]: BaseImageElement,
@@ -49,6 +57,7 @@ export default defineComponent({
 
     return {
       currentElementComponent,
+      themeFontColor,
     }
   },
 })

+ 3 - 8
src/views/components/element/ChartElement/BaseChartElement.vue

@@ -18,9 +18,9 @@
         :height="elementInfo.height"
         :outline="elementInfo.outline"
       />
-      <IconChartLine :fill="color" strokeWidth="2" :size="size" v-if="elementInfo.chartType === 'line'" />
-      <IconChartHistogram :fill="color" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'bar'" />
-      <IconChartProportion :fill="color" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'pie'" />
+      <IconChartLine :fill="elementInfo.themeColor" strokeWidth="2" :size="size" v-if="elementInfo.chartType === 'line'" />
+      <IconChartHistogram :fill="elementInfo.themeColor" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'bar'" />
+      <IconChartProportion :fill="elementInfo.themeColor" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'pie'" />
     </div>
   </div>
 </template>
@@ -28,7 +28,6 @@
 <script lang="ts">
 import { computed, defineComponent, PropType } from 'vue'
 import { PPTChartElement } from '@/types/slides'
-import { CHART_THEME_COLORS } from '@/configs/chartTheme'
 
 import ElementOutline from '@/views/components/element/ElementOutline.vue'
 
@@ -45,13 +44,9 @@ export default defineComponent({
   },
   setup(props) {
     const size = computed(() => Math.min(props.elementInfo.width, props.elementInfo.height))
-    const color = computed(() => {
-      return props.elementInfo.themeColors ? props.elementInfo.themeColors[0] : CHART_THEME_COLORS[0][0]
-    })
 
     return {
       size,
-      color,
     }
   },
 })

+ 27 - 12
src/views/components/element/ChartElement/Chart.vue

@@ -15,6 +15,7 @@
 <script lang="ts">
 import { defineComponent, inject, onMounted, PropType, ref, Ref, watch } from 'vue'
 import upperFirst from 'lodash/upperFirst'
+import tinycolor from 'tinycolor2'
 import Chartist, {
   IChartistLineChart,
   IChartistBarChart,
@@ -49,8 +50,9 @@ export default defineComponent({
     options: {
       type: Object as PropType<ILineChartOptions & IBarChartOptions & IPieChartOptions>,
     },
-    themeColors: {
-      type: Array as PropType<string[]>,
+    themeColor: {
+      type: String,
+      required: true,
     },
     gridColor: {
       type: String,
@@ -102,17 +104,24 @@ export default defineComponent({
     const updateTheme = () => {
       if(!chartRef.value) return
 
-      if(props.themeColors) {
-        for(let i = 0; i < props.themeColors.length; i++) {
-          chartRef.value.style.setProperty(`--theme-color-${i + 1}`, props.themeColors[i])
+      const hsla = tinycolor(props.themeColor).toHsl()
+
+      for(let i = 0; i < 10; i++) {
+        let h = hsla.h + i * 36
+        if(h > 360) h = h - 360
+
+        const _hsla = {
+          ...hsla,
+          h,
         }
+        chartRef.value.style.setProperty(`--theme-color-${i + 1}`, tinycolor(_hsla).toRgbString())
       }
 
       if(props.gridColor) chartRef.value.style.setProperty(`--grid-color`, props.gridColor)
     }
 
     watch([
-      () => props.themeColors,
+      () => props.themeColor,
       () => props.gridColor,
     ], updateTheme)
     onMounted(updateTheme)
@@ -133,12 +142,18 @@ export default defineComponent({
 
 <style lang="scss">
 .chart-content {
-  $ct-series-names: (a, b, c, d);
-
-  --theme-color-1: #d70206;
-  --theme-color-2: #f05b4f;
-  --theme-color-3: #f4c63d;
-  --theme-color-4: #d17905;
+  $ct-series-names: (a, b, c, d, e, f, g, h, i, j);
+
+  --theme-color-1: #666;
+  --theme-color-2: #666;
+  --theme-color-3: #666;
+  --theme-color-4: #666;
+  --theme-color-5: #666;
+  --theme-color-6: #666;
+  --theme-color-7: #666;
+  --theme-color-8: #666;
+  --theme-color-9: #666;
+  --theme-color-10: #666;
 
   @for $i from 1 to length($ct-series-names) {
     $color: var(--theme-color-#{$i});

+ 1 - 1
src/views/components/element/ChartElement/ScreenChartElement.vue

@@ -24,7 +24,7 @@
         :type="elementInfo.chartType"
         :data="elementInfo.data"
         :options="elementInfo.options"
-        :themeColors="elementInfo.themeColors"
+        :themeColor="elementInfo.themeColor"
         :gridColor="elementInfo.gridColor"
       />
     </div>

+ 1 - 1
src/views/components/element/ChartElement/index.vue

@@ -27,7 +27,7 @@
         :type="elementInfo.chartType"
         :data="elementInfo.data"
         :options="elementInfo.options"
-        :themeColors="elementInfo.themeColors"
+        :themeColor="elementInfo.themeColor"
         :gridColor="elementInfo.gridColor"
       />
     </div>