mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
372 lines
11 KiB
Vue
372 lines
11 KiB
Vue
<template>
|
|
<div class="chart-style-panel">
|
|
<Button class="full-width-btn" @click="chartDataEditorVisible = true">
|
|
<IconEdit class="btn-icon" /> 编辑图表数据
|
|
</Button>
|
|
|
|
<Divider />
|
|
|
|
<template v-if="handleChartElement.chartType === 'line'">
|
|
<div class="row">
|
|
<Checkbox
|
|
@change="e => updateOptions({ showArea: e.target.checked })"
|
|
:checked="showArea"
|
|
style="flex: 1;"
|
|
>面积图样式</Checkbox>
|
|
<Checkbox
|
|
@change="e => updateOptions({ showLine: !e.target.checked })"
|
|
:checked="!showLine"
|
|
style="flex: 1;"
|
|
>散点图样式</Checkbox>
|
|
</div>
|
|
<div class="row">
|
|
<Checkbox
|
|
@change="e => updateOptions({ lineSmooth: e.target.checked })"
|
|
:checked="lineSmooth"
|
|
>使用平滑曲线</Checkbox>
|
|
</div>
|
|
</template>
|
|
<div class="row" v-if="handleChartElement.chartType === 'bar'">
|
|
<Checkbox
|
|
@change="e => updateOptions({ horizontalBars: e.target.checked })"
|
|
:checked="horizontalBars"
|
|
>条形图样式</Checkbox>
|
|
<Checkbox
|
|
@change="e => updateOptions({ stackBars: e.target.checked })"
|
|
:checked="stackBars"
|
|
>堆叠样式</Checkbox>
|
|
</div>
|
|
<div class="row" v-if="handleChartElement.chartType === 'pie'">
|
|
<Checkbox
|
|
@change="e => updateOptions({ donut: e.target.checked })"
|
|
:checked="donut"
|
|
>环形图样式</Checkbox>
|
|
</div>
|
|
|
|
<Divider />
|
|
|
|
<div class="row">
|
|
<div style="flex: 2;">图例:</div>
|
|
<Select style="flex: 3;" :value="legend" @change="value => updateLegend(value as '' | 'top' | 'bottom')">
|
|
<SelectOption value="">不显示</SelectOption>
|
|
<SelectOption value="top">显示在上方</SelectOption>
|
|
<SelectOption value="bottom">显示在下方</SelectOption>
|
|
</Select>
|
|
</div>
|
|
|
|
<Divider />
|
|
|
|
<div class="row">
|
|
<div style="flex: 2;">背景填充:</div>
|
|
<Popover trigger="click">
|
|
<template #content>
|
|
<ColorPicker
|
|
:modelValue="fill"
|
|
@update:modelValue="value => updateFill(value)"
|
|
/>
|
|
</template>
|
|
<ColorButton :color="fill" style="flex: 3;" />
|
|
</Popover>
|
|
</div>
|
|
<div class="row">
|
|
<div style="flex: 2;">网格颜色:</div>
|
|
<Popover trigger="click">
|
|
<template #content>
|
|
<ColorPicker
|
|
:modelValue="gridColor"
|
|
@update:modelValue="value => updateGridColor(value)"
|
|
/>
|
|
</template>
|
|
<ColorButton :color="gridColor" style="flex: 3;" />
|
|
</Popover>
|
|
</div>
|
|
|
|
<Divider />
|
|
|
|
<div class="row" v-for="(color, index) in themeColor" :key="index">
|
|
<div style="flex: 2;">{{index === 0 ? '主题配色:' : ''}}</div>
|
|
<Popover trigger="click">
|
|
<template #content>
|
|
<ColorPicker
|
|
:modelValue="color"
|
|
@update:modelValue="value => updateTheme(value, index)"
|
|
/>
|
|
</template>
|
|
<div class="color-btn-wrap" style="flex: 3;">
|
|
<ColorButton :color="color" style="width: 100%;" />
|
|
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="删除">
|
|
<div class="delete-color-btn" @click.stop="deleteThemeColor(index)" v-if="index !== 0"><IconCloseSmall /></div>
|
|
</Tooltip>
|
|
</div>
|
|
</Popover>
|
|
</div>
|
|
<ButtonGroup class="row">
|
|
<Popover trigger="click" v-model:open="presetThemesVisible">
|
|
<template #content>
|
|
<div class="preset-themes">
|
|
<div class="preset-theme" v-for="(item, index) in presetChartThemes" :key="index">
|
|
<div
|
|
class="preset-theme-color"
|
|
:class="{ 'select': presetThemeColorHoverIndex[0] === index && itemIndex <= presetThemeColorHoverIndex[1] }"
|
|
v-for="(color, itemIndex) in item"
|
|
:key="color"
|
|
:style="{ backgroundColor: color }"
|
|
@click="applyPresetTheme(item, itemIndex)"
|
|
@mouseenter="presetThemeColorHoverIndex = [index, itemIndex]"
|
|
@mouseleave="presetThemeColorHoverIndex = [-1, -1]"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<Button class="no-padding" style="flex: 2;">推荐主题</Button>
|
|
</Popover>
|
|
<Button
|
|
class="no-padding"
|
|
:disabled="themeColor.length >= 10"
|
|
style="flex: 3;"
|
|
@click="addThemeColor()"
|
|
>
|
|
<IconPlus class="btn-icon" /> 添加主题色
|
|
</Button>
|
|
</ButtonGroup>
|
|
|
|
<Divider />
|
|
|
|
<ElementOutline />
|
|
|
|
<Modal
|
|
v-model:open="chartDataEditorVisible"
|
|
:footer="null"
|
|
centered
|
|
:closable="false"
|
|
:width="648"
|
|
destroyOnClose
|
|
>
|
|
<ChartDataEditor
|
|
:data="handleChartElement.data"
|
|
@close="chartDataEditorVisible = false"
|
|
@save="value => updateData(value)"
|
|
/>
|
|
</Modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { onUnmounted, ref, watch, type Ref } from 'vue'
|
|
import { storeToRefs } from 'pinia'
|
|
import { useMainStore, useSlidesStore } from '@/store'
|
|
import type { ChartData, ChartOptions, PPTChartElement } from '@/types/slides'
|
|
import emitter, { EmitterEvents } from '@/utils/emitter'
|
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|
|
|
import ElementOutline from '../../common/ElementOutline.vue'
|
|
import ColorButton from '../../common/ColorButton.vue'
|
|
import ChartDataEditor from './ChartDataEditor.vue'
|
|
import ColorPicker from '@/components/ColorPicker/index.vue'
|
|
import {
|
|
Divider,
|
|
Button,
|
|
Tooltip,
|
|
Popover,
|
|
Select,
|
|
Modal,
|
|
Checkbox,
|
|
} from 'ant-design-vue'
|
|
const ButtonGroup = Button.Group
|
|
const SelectOption = Select.Option
|
|
|
|
const presetChartThemes = [
|
|
['#d87c7c', '#919e8b', '#d7ab82', '#6e7074', '#61a0a8', '#efa18d'],
|
|
['#dd6b66', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53', '#eedd78'],
|
|
['#516b91', '#59c4e6', '#edafda', '#93b7e3', '#a5e7f0', '#cbb0e3'],
|
|
['#893448', '#d95850', '#eb8146', '#ffb248', '#f2d643', '#ebdba4'],
|
|
['#4ea397', '#22c3aa', '#7bd9a5', '#d0648a', '#f58db2', '#f2b3c9'],
|
|
['#3fb1e3', '#6be6c1', '#626c91', '#a0a7e6', '#c4ebad', '#96dee8'],
|
|
['#fc97af', '#87f7cf', '#f7f494', '#72ccff', '#f7c5a0', '#d4a4eb'],
|
|
['#c1232b', '#27727b', '#fcce10', '#e87c25', '#b5c334', '#fe8463'],
|
|
['#2ec7c9', '#b6a2de', '#5ab1ef', '#ffb980', '#d87a80', '#8d98b3'],
|
|
['#e01f54', '#001852', '#f5e8c8', '#b8d2c7', '#c6b38e', '#a4d8c2'],
|
|
['#c12e34', '#e6b600', '#0098d9', '#2b821d', '#005eaa', '#339ca8'],
|
|
['#8a7ca8', '#e098c7', '#8fd3e8', '#71669e', '#cc70af', '#7cb4cc'],
|
|
]
|
|
|
|
const mainStore = useMainStore()
|
|
const slidesStore = useSlidesStore()
|
|
const { handleElement, handleElementId } = storeToRefs(mainStore)
|
|
const { theme } = storeToRefs(slidesStore)
|
|
|
|
const handleChartElement = handleElement as Ref<PPTChartElement>
|
|
|
|
const chartDataEditorVisible = ref(false)
|
|
const presetThemesVisible = ref(false)
|
|
const presetThemeColorHoverIndex = ref<[number, number]>([-1, -1])
|
|
|
|
const { addHistorySnapshot } = useHistorySnapshot()
|
|
|
|
const fill = ref<string>('#000')
|
|
|
|
const themeColor = ref<string[]>([])
|
|
const gridColor = ref('')
|
|
const legend = ref('')
|
|
|
|
const lineSmooth = ref(true)
|
|
const showLine = ref(true)
|
|
const showArea = ref(false)
|
|
const horizontalBars = ref(false)
|
|
const donut = ref(false)
|
|
const stackBars = ref(false)
|
|
|
|
watch(handleElement, () => {
|
|
if (!handleElement.value || handleElement.value.type !== 'chart') return
|
|
fill.value = handleElement.value.fill || '#fff'
|
|
|
|
if (handleElement.value.options) {
|
|
const {
|
|
lineSmooth: _lineSmooth,
|
|
showLine: _showLine,
|
|
showArea: _showArea,
|
|
horizontalBars: _horizontalBars,
|
|
donut: _donut,
|
|
stackBars: _stackBars,
|
|
} = handleElement.value.options
|
|
|
|
lineSmooth.value = !!_lineSmooth
|
|
showLine.value = !!_showLine
|
|
showArea.value = !!_showArea
|
|
horizontalBars.value = !!_horizontalBars
|
|
donut.value = !!_donut
|
|
stackBars.value = !!_stackBars
|
|
}
|
|
|
|
themeColor.value = handleElement.value.themeColor
|
|
gridColor.value = handleElement.value.gridColor || '#333'
|
|
legend.value = handleElement.value.legend || ''
|
|
}, { deep: true, immediate: true })
|
|
|
|
const updateElement = (props: Partial<PPTChartElement>) => {
|
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
|
addHistorySnapshot()
|
|
}
|
|
|
|
// 设置图表数据
|
|
const updateData = (data: ChartData) => {
|
|
chartDataEditorVisible.value = false
|
|
updateElement({ data })
|
|
}
|
|
|
|
// 设置填充色
|
|
const updateFill = (value: string) => {
|
|
updateElement({ fill: value })
|
|
}
|
|
|
|
// 设置其他选项:柱状图转条形图、折线图转面积图、折线图转散点图、饼图转环形图、折线图开关平滑曲线
|
|
const updateOptions = (optionProps: ChartOptions) => {
|
|
const _handleElement = handleElement.value as PPTChartElement
|
|
|
|
const newOptions = { ..._handleElement.options, ...optionProps }
|
|
updateElement({ options: newOptions })
|
|
}
|
|
|
|
// 设置主题色
|
|
const updateTheme = (color: string, index: number) => {
|
|
const props = {
|
|
themeColor: themeColor.value.map((c, i) => i === index ? color : c),
|
|
}
|
|
updateElement(props)
|
|
}
|
|
|
|
// 添加主题色
|
|
const addThemeColor = () => {
|
|
const props = {
|
|
themeColor: [...themeColor.value, theme.value.themeColor],
|
|
}
|
|
updateElement(props)
|
|
}
|
|
|
|
// 使用预置主题配色
|
|
const applyPresetTheme = (colors: string[], index: number) => {
|
|
const themeColor = colors.slice(0, index + 1)
|
|
updateElement({ themeColor })
|
|
presetThemesVisible.value = false
|
|
}
|
|
|
|
// 删除主题色
|
|
const deleteThemeColor = (index: number) => {
|
|
const props = {
|
|
themeColor: themeColor.value.filter((c, i) => i !== index),
|
|
}
|
|
updateElement(props)
|
|
}
|
|
|
|
// 设置网格颜色
|
|
const updateGridColor = (gridColor: string) => {
|
|
updateElement({ gridColor })
|
|
}
|
|
|
|
// 设置图例位置/不显示
|
|
const updateLegend = (legend: '' | 'top' | 'bottom') => {
|
|
updateElement({ legend })
|
|
}
|
|
|
|
const openDataEditor = () => chartDataEditorVisible.value = true
|
|
|
|
emitter.on(EmitterEvents.OPEN_CHART_DATA_EDITOR, openDataEditor)
|
|
onUnmounted(() => {
|
|
emitter.off(EmitterEvents.OPEN_CHART_DATA_EDITOR, openDataEditor)
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.chart-style-panel {
|
|
user-select: none;
|
|
}
|
|
.row {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
}
|
|
.full-width-btn {
|
|
width: 100%;
|
|
}
|
|
.btn-icon {
|
|
margin-right: 3px;
|
|
}
|
|
.color-btn-wrap {
|
|
position: relative;
|
|
}
|
|
.delete-color-btn {
|
|
position: absolute;
|
|
width: 30px;
|
|
right: 2px;
|
|
top: 2px;
|
|
bottom: 2px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
background-color: #fff;
|
|
cursor: pointer;
|
|
}
|
|
.preset-themes {
|
|
width: 250px;
|
|
display: flex;
|
|
margin-bottom: -10px;
|
|
|
|
@include flex-grid-layout();
|
|
}
|
|
.preset-theme {
|
|
display: flex;
|
|
cursor: pointer;
|
|
|
|
@include flex-grid-layout-children(2, 48%);
|
|
}
|
|
.preset-theme-color {
|
|
width: 20px;
|
|
height: 20px;
|
|
|
|
&.select {
|
|
transform: scale(1.2);
|
|
transition: transform $transitionDelayFast;
|
|
}
|
|
}
|
|
</style> |