diff --git a/src/configs/theme.ts b/src/configs/theme.ts index 08bc717c..a2938ed8 100644 --- a/src/configs/theme.ts +++ b/src/configs/theme.ts @@ -1,30 +1,93 @@ -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: '#d0614c', background: '#dfb044', text: '#333' }, - { color: '#86a1ad', background: '#dfdbd4', text: '#333' }, - { color: '#697586', background: '#d5c4a4', text: '#333' }, - { color: '#333333', background: '#7acfa6', text: '#333' }, - { color: '#42464b', background: '#415065', text: '#fff' }, - { color: '#0c5999', background: '#35a2cd', text: '#fff' }, - { color: '#c49a41', background: '#8c4357', text: '#fff' }, - { color: '#dfaa00', background: '#2e4e7d', text: '#fff' }, - { color: '#d1ad88', background: '#f99070', text: '#fff' }, - { color: '#464d52', background: '#657984', text: '#fff' }, - { color: '#ffcfb6', background: '#1e4c6f', text: '#fff' }, - { color: '#c3a043', background: '#43292a', text: '#fff' }, - { color: '#464d52', background: '#60546f', text: '#fff' }, - { color: '#df9636', background: '#5b89a0', text: '#fff' }, - { color: '#b898a4', background: '#93716b', text: '#fff' }, - { color: '#c47a11', background: '#187db1', text: '#fff' }, - { color: '#333333', background: '#759564', text: '#fff' }, - { color: '#355b5e', background: '#424b50', text: '#fff' }, - { color: '#d29090', background: '#942a32', text: '#fff' }, - { color: '#00cfdf', background: '#3b434d', text: '#fff' }, - { color: '#424246', background: '#c70042', text: '#fff' }, - { color: '#2e4155', background: '#b35d44', text: '#fff' }, - { color: '#11bfce', background: '#8f98aa', text: '#fff' }, - { color: '#333333', background: '#549688', text: '#fff' }, +export interface PresetTheme { + background: string + fontColor: string + fontname: string + colors: string[] +} + +export const PRESET_THEMES: PresetTheme[] = [ + { + background: '#ffffff', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#5b9bd5', '#ed7d31', '#a5a5a5', '#ffc000', '#4472c4', '#70ad47'], + }, + { + background: '#ffffff', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#83992a', '#3c9670', '#44709d', '#a23b32', '#d87728', '#deb340'], + }, + { + background: '#ffffff', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#e48312', '#bd582c', '#865640', '#9b8357', '#c2bc80', '#94a088'], + }, + { + background: '#ffffff', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#bdc8df', '#003fa9', '#f5ba00', '#ff7567', '#7676d9', '#923ffc'], + }, + { + background: '#ffffff', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#90c225', '#54a121', '#e6b91e', '#e86618', '#c42f19', '#918756'], + }, + { + background: '#ffffff', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#1cade4', '#2683c6', '#27ced7', '#42ba97', '#3e8853', '#62a39f'], + }, + { + background: '#e9efd6', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#a5300f', '#de7e18', '#9f8351', '#728653', '#92aa4c', '#6aac91'], + }, + { + background: '#17444e', + fontColor: '#ffffff', + fontname: 'Microsoft Yahei', + colors: ['#b01513', '#ea6312', '#e6b729', '#6bab90', '#55839a', '#9e5d9d'], + }, + { + background: '#36234d', + fontColor: '#ffffff', + fontname: 'Microsoft Yahei', + colors: ['#b31166', '#e33d6f', '#e45f3c', '#e9943a', '#9b6bf2', '#d63cd0'], + }, + { + background: '#247fad', + fontColor: '#ffffff', + fontname: 'Microsoft Yahei', + colors: ['#052f61', '#a50e82', '#14967c', '#6a9e1f', '#e87d37', '#c62324'], + }, + { + background: '#103f55', + fontColor: '#ffffff', + fontname: 'Microsoft Yahei', + colors: ['#40aebd', '#97e8d5', '#a1cf49', '#628f3e', '#f2df3a', '#fcb01c'], + }, + { + background: '#242367', + fontColor: '#ffffff', + fontname: 'Microsoft Yahei', + colors: ['#ac3ec1', '#477bd1', '#46b298', '#90ba4c', '#dd9d31', '#e25345'], + }, + { + background: '#e4b75e', + fontColor: '#333333', + fontname: 'Microsoft Yahei', + colors: ['#f0a22e', '#a5644e', '#b58b80', '#c3986d', '#a19574', '#c17529'], + }, + { + background: '#333333', + fontColor: '#ffffff', + fontname: 'Microsoft Yahei', + colors: ['#bdc8df', '#003fa9', '#f5ba00', '#ff7567', '#7676d9', '#923ffc'], + }, ] \ No newline at end of file diff --git a/src/hooks/useSlideTheme.ts b/src/hooks/useSlideTheme.ts new file mode 100644 index 00000000..706525bd --- /dev/null +++ b/src/hooks/useSlideTheme.ts @@ -0,0 +1,180 @@ +import tinycolor from 'tinycolor2' +import { storeToRefs } from 'pinia' +import { useSlidesStore } from '@/store' +import { Slide } from '@/types/slides' +import { PresetTheme } from '@/configs/theme' +import useHistorySnapshot from '@/hooks/useHistorySnapshot' + +export default () => { + const slidesStore = useSlidesStore() + const { slides, currentSlide, theme } = storeToRefs(slidesStore) + + const { addHistorySnapshot } = useHistorySnapshot() + + // 获取指定幻灯片内所有颜色(主要的) + const getSlideAllColors = (slide: Slide) => { + const colors: string[] = [] + for (const el of slide.elements) { + if (el.type === 'shape' && tinycolor(el.fill).getAlpha() !== 0) { + const color = tinycolor(el.fill).toRgbString() + if (!colors.includes(color)) colors.push(color) + } + if (el.type === 'text' && el.fill && tinycolor(el.fill).getAlpha() !== 0) { + const color = tinycolor(el.fill).toRgbString() + if (!colors.includes(color)) colors.push(color) + } + if (el.type === 'table' && el.theme && tinycolor(el.theme.color).getAlpha() !== 0) { + const color = tinycolor(el.theme.color).toRgbString() + if (!colors.includes(color)) colors.push(color) + } + if (el.type === 'chart' && el.fill && tinycolor(el.fill).getAlpha() !== 0) { + const color = tinycolor(el.fill).toRgbString() + if (!colors.includes(color)) colors.push(color) + } + if (el.type === 'line' && tinycolor(el.color).getAlpha() !== 0) { + const color = tinycolor(el.color).toRgbString() + if (!colors.includes(color)) colors.push(color) + } + if (el.type === 'audio' && tinycolor(el.color).getAlpha() !== 0) { + const color = tinycolor(el.color).toRgbString() + if (!colors.includes(color)) colors.push(color) + } + } + return colors + } + + // 创建原颜色与新颜色的对应关系表 + const createSlideThemeColorMap = (slide: Slide, newColors: string[]): { [key: string]: string } => { + const oldColors = getSlideAllColors(slide) + const themeColorMap = {} + + if (oldColors.length > newColors.length) { + const analogous = tinycolor(newColors[0]).analogous(oldColors.length - newColors.length + 10) + const otherColors = analogous.map(item => item.toHexString()).slice(1) + newColors.push(...otherColors) + } + for (let i = 0; i < oldColors.length; i++) { + themeColorMap[oldColors[i]] = newColors[i] + } + + return themeColorMap + } + + // 设置幻灯片主题 + const setSlideTheme = (slide: Slide, theme: PresetTheme) => { + const colorMap = createSlideThemeColorMap(slide, theme.colors) + + if (!slide.background || slide.background.type !== 'image') { + slide.background = { + type: 'solid', + color: theme.background, + } + } + for (const el of slide.elements) { + if (el.type === 'shape') { + el.fill = colorMap[tinycolor(el.fill).toRgbString()] || el.fill + if (el.gradient) delete el.gradient + } + if (el.type === 'text') { + if (el.fill) el.fill = colorMap[tinycolor(el.fill).toRgbString()] || el.fill + el.defaultColor = theme.fontColor + el.defaultFontName = theme.fontname + } + if (el.type === 'table') { + if (el.theme) el.theme.color = colorMap[tinycolor(el.theme.color).toRgbString()] || el.theme.color + for (const rowCells of el.data) { + for (const cell of rowCells) { + if (cell.style) { + cell.style.color = theme.fontColor + cell.style.fontname = theme.fontname + } + } + } + } + if (el.type === 'chart') { + el.themeColor = [colorMap[tinycolor(el.themeColor[0]).toRgbString()]] || el.themeColor + el.gridColor = theme.fontColor + } + if (el.type === 'line') el.color = colorMap[tinycolor(el.color).toRgbString()] || el.color + if (el.type === 'audio') el.color = colorMap[tinycolor(el.color).toRgbString()] || el.color + if (el.type === 'latex') el.color = theme.fontColor + } + } + + // 应用预置主题(单页) + const applyPresetThemeToSingleSlide = (theme: PresetTheme) => { + const newSlide: Slide = JSON.parse(JSON.stringify(currentSlide.value)) + setSlideTheme(newSlide, theme) + slidesStore.updateSlide({ + background: newSlide.background, + elements: newSlide.elements, + }) + addHistorySnapshot() + } + + // 应用预置主题(全部) + const applyPresetThemeToAllSlides = (theme: PresetTheme) => { + const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value)) + for (const slide of newSlides) { + setSlideTheme(slide, theme) + } + slidesStore.setTheme({ + backgroundColor: theme.background, + themeColor: theme.colors[0], + fontColor: theme.fontColor, + fontName: theme.fontname, + }) + slidesStore.setSlides(newSlides) + addHistorySnapshot() + } + + // 将当前主题配置应用到全部页面 + const applyThemeToAllSlides = () => { + const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value)) + const { themeColor, backgroundColor, fontColor, fontName } = theme.value + + for (const slide of newSlides) { + if (!slide.background || slide.background.type !== 'image') { + slide.background = { + type: 'solid', + color: backgroundColor + } + } + + for (const el of slide.elements) { + if (el.type === 'shape') el.fill = themeColor + else if (el.type === 'line') el.color = themeColor + else if (el.type === 'text') { + el.defaultColor = fontColor + el.defaultFontName = fontName + if (el.fill) el.fill = themeColor + } + else if (el.type === 'table') { + if (el.theme) el.theme.color = themeColor + for (const rowCells of el.data) { + for (const cell of rowCells) { + if (cell.style) { + cell.style.color = fontColor + cell.style.fontname = fontName + } + } + } + } + else if (el.type === 'chart') { + el.themeColor = [themeColor] + el.gridColor = fontColor + } + else if (el.type === 'latex') el.color = fontColor + else if (el.type === 'audio') el.color = themeColor + } + } + slidesStore.setSlides(newSlides) + addHistorySnapshot() + } + + return { + applyPresetThemeToSingleSlide, + applyPresetThemeToAllSlides, + applyThemeToAllSlides, + } +} \ No newline at end of file diff --git a/src/views/Editor/Toolbar/SlideDesignPanel.vue b/src/views/Editor/Toolbar/SlideDesignPanel.vue index 43da7c2d..f52f7cef 100644 --- a/src/views/Editor/Toolbar/SlideDesignPanel.vue +++ b/src/views/Editor/Toolbar/SlideDesignPanel.vue @@ -165,29 +165,34 @@ - -
+
+ + + +
预置主题
+
-
Aa
-
+
文字 Aa
+
+
+
+ +
+
应用
+
应用全局
+
- -
@@ -195,10 +200,11 @@ import { computed, ref } from 'vue' import { storeToRefs } from 'pinia' import { useMainStore, useSlidesStore } from '@/store' -import { Slide, SlideBackground, SlideTheme } from '@/types/slides' +import { SlideBackground, SlideTheme } from '@/types/slides' import { PRESET_THEMES } from '@/configs/theme' import { WEB_FONTS } from '@/configs/font' import useHistorySnapshot from '@/hooks/useHistorySnapshot' +import useSlideTheme from '@/hooks/useSlideTheme' import ColorButton from './common/ColorButton.vue' import { getImageDataURL } from '@/utils/image' @@ -218,6 +224,11 @@ const background = computed(() => { }) const { addHistorySnapshot } = useHistorySnapshot() +const { + applyPresetThemeToSingleSlide, + applyPresetThemeToAllSlides, + applyThemeToAllSlides, +} = useSlideTheme() // 设置背景模式:纯色、图片、渐变色 const updateBackgroundType = (type: 'solid' | 'image' | 'gradient') => { @@ -281,58 +292,6 @@ const updateTheme = (themeProps: Partial) => { slidesStore.setTheme(themeProps) } -// 将当前主题应用到全部页面 -const applyThemeAllSlide = () => { - const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value)) - const { themeColor, backgroundColor, fontColor, fontName } = theme.value - - for (const slide of newSlides) { - if (!slide.background || slide.background.type !== 'image') { - 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') { - el.defaultColor = fontColor - el.defaultFontName = fontName - if (el.fill) el.fill = themeColor - } - else if (el.type === 'table') { - if (el.theme) el.theme.color = themeColor - for (const rowCells of el.data) { - for (const cell of rowCells) { - if (cell.style) { - cell.style.color = fontColor - cell.style.fontname = fontName - } - } - } - } - else if (el.type === 'chart') { - el.themeColor = [themeColor] - el.gridColor = fontColor - } - else if (el.type === 'latex') el.color = fontColor - else if (el.type === 'audio') el.color = themeColor - } - } - slidesStore.setSlides(newSlides) - addHistorySnapshot() -} - -// 是否显示预设主题 -const showPresetThemes = ref(true) -const togglePresetThemesVisible = () => { - showPresetThemes.value = !showPresetThemes.value -} - // 设置画布尺寸(宽高比例) const updateViewportRatio = (value: number) => { slidesStore.setViewportRatio(value) @@ -351,21 +310,6 @@ const updateViewportRatio = (value: number) => { } .title { margin-bottom: 10px; - - &.dropdown { - display: flex; - align-items: center; - cursor: pointer; - - .icon { - margin-left: 5px; - transition: transform $transitionDelayFast; - } - - &:not(.active) .icon { - transform: rotate(-90deg); - } - } } .background-image-wrapper { margin-bottom: 10px; @@ -400,9 +344,9 @@ const updateViewportRatio = (value: number) => { @include flex-grid-layout(); } .theme-item { - @include flex-grid-layout-children(4, 22%); + @include flex-grid-layout-children(2, 48%); - padding-bottom: 22%; + padding-bottom: 30%; border-radius: $borderRadius; position: relative; cursor: pointer; @@ -413,22 +357,52 @@ const updateViewportRatio = (value: number) => { display: flex; flex-direction: column; justify-content: center; - align-items: center; - transition: box-shadow $transitionDelay; - - &:hover { - box-shadow: 0 0 4px #888; - } + padding: 8px; + border: 1px solid $borderColor; } .text { font-size: 16px; } - + .colors { + display: flex; + } .color-block { - width: 28px; - height: 10px; - margin-top: 5px; + margin-top: 8px; + width: 12px; + height: 12px; + margin-right: 2px; + } + + &:hover .btns { + display: flex; + } + + .btns { + @include absolute-0(); + + flex-direction: column; + justify-content: center; + align-items: center; + display: none; + background-color: rgba($color: #000, $alpha: .25); + } + .btn { + width: 72px; + padding: 5px 0; + text-align: center; + background-color: $themeColor; + color: #fff; + font-size: 12px; + border-radius: $borderRadius; + + &:hover { + background-color: #c42f19; + } + + & + .btn { + margin-top: 5px; + } } } .slider {