添加表格单元格样式和主题
This commit is contained in:
parent
a1515e1994
commit
604494cd8e
|
@ -34,22 +34,22 @@ export const slides: Slide[] = [
|
|||
},
|
||||
data: [
|
||||
[
|
||||
{ id: '1', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '2', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '3', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '4', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '1', colspan: 1, rowspan: 1, text: '1' },
|
||||
{ id: '2', colspan: 1, rowspan: 1, text: '2' },
|
||||
{ id: '3', colspan: 1, rowspan: 1, text: '3' },
|
||||
{ id: '4', colspan: 1, rowspan: 1, text: '4' },
|
||||
],
|
||||
[
|
||||
{ id: '6', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '7', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '8', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '9', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '6', colspan: 1, rowspan: 1, text: '6' },
|
||||
{ id: '7', colspan: 1, rowspan: 1, text: '7' },
|
||||
{ id: '8', colspan: 1, rowspan: 1, text: '8' },
|
||||
{ id: '9', colspan: 1, rowspan: 1, text: '9' },
|
||||
],
|
||||
[
|
||||
{ id: '11', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '12', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '13', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '14', colspan: 1, rowspan: 1, text: '' },
|
||||
{ id: '11', colspan: 1, rowspan: 1, text: '11' },
|
||||
{ id: '12', colspan: 1, rowspan: 1, text: '12' },
|
||||
{ id: '13', colspan: 1, rowspan: 1, text: '13' },
|
||||
{ id: '14', colspan: 1, rowspan: 1, text: '14' },
|
||||
],
|
||||
],
|
||||
},
|
||||
|
|
|
@ -136,20 +136,30 @@ export interface PPTChartElement {
|
|||
gridColor?: string;
|
||||
}
|
||||
|
||||
export interface TableCellStyle {
|
||||
bold?: boolean;
|
||||
em?: boolean;
|
||||
underline?: boolean;
|
||||
strikethrough?: boolean;
|
||||
color?: string;
|
||||
backcolor?: string;
|
||||
fontsize?: string;
|
||||
fontname?: string;
|
||||
align?: string;
|
||||
}
|
||||
export interface TableCell {
|
||||
id: string;
|
||||
colspan: number;
|
||||
rowspan: number;
|
||||
text: string;
|
||||
style?: {
|
||||
color?: string;
|
||||
bgColor?: string;
|
||||
fontSize?: number;
|
||||
fontName?: string;
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
align?: string;
|
||||
};
|
||||
style?: TableCellStyle;
|
||||
}
|
||||
export interface TableTheme {
|
||||
color: string;
|
||||
rowHeader: boolean;
|
||||
rowFooter: boolean;
|
||||
colHeader: boolean;
|
||||
colFooter: boolean;
|
||||
}
|
||||
export interface PPTTableElement {
|
||||
type: 'table';
|
||||
|
@ -161,6 +171,7 @@ export interface PPTTableElement {
|
|||
width: number;
|
||||
height: number;
|
||||
outline: PPTElementOutline;
|
||||
theme?: TableTheme;
|
||||
colWidths: number[];
|
||||
data: TableCell[][];
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import mitt, { Emitter } from 'mitt'
|
|||
export enum EmitterEvents {
|
||||
UPDATE_TEXT_STATE = 'UPDATE_TEXT_STATE',
|
||||
EXEC_TEXT_COMMAND = 'EXEC_TEXT_COMMAND',
|
||||
UPDATE_TABLE_TEXT_STATE = 'UPDATE_TABLE_TEXT_STATE',
|
||||
EXEC_TABLE_TEXT_COMMAND = 'EXEC_TABLE_TEXT_COMMAND',
|
||||
SCALE_ELEMENT_STATE = 'SCALE_ELEMENT_STATE',
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
<template v-if="handleElement.chartType === 'line'">
|
||||
<div class="row">
|
||||
<Checkbox
|
||||
@change="e => updateOptions({ showArea: e.target.checked })" :checked="showArea"
|
||||
@change="e => updateOptions({ showArea: e.target.checked })"
|
||||
:checked="showArea"
|
||||
style="flex: 1;"
|
||||
>面积图样式</Checkbox>
|
||||
<Checkbox
|
||||
@change="e => updateOptions({ showLine: !e.target.checked })" :checked="!showLine"
|
||||
@change="e => updateOptions({ showLine: !e.target.checked })"
|
||||
:checked="!showLine"
|
||||
style="flex: 1;"
|
||||
>散点图样式</Checkbox>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
<template>
|
||||
<div class="table-style-panel">
|
||||
<InputGroup compact class="row">
|
||||
<Select
|
||||
style="flex: 3;"
|
||||
:value="textAttrs.fontname"
|
||||
@change="value => emitUpdateTextAttrCommand({ fontname: value })"
|
||||
>
|
||||
<template #suffixIcon><IconFontSize /></template>
|
||||
<SelectOption v-for="font in availableFonts" :key="font.en" :value="font.en">
|
||||
<span :style="{ fontFamily: font.en }">{{font.zh}}</span>
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<Select
|
||||
style="flex: 2;"
|
||||
:value="textAttrs.fontsize"
|
||||
@change="value => emitUpdateTextAttrCommand({ fontsize: value })"
|
||||
>
|
||||
<template #suffixIcon><IconAddText /></template>
|
||||
<SelectOption v-for="fontsize in fontSizeOptions" :key="fontsize" :value="fontsize">
|
||||
{{fontsize}}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</InputGroup>
|
||||
|
||||
<ButtonGroup class="row">
|
||||
<Popover trigger="click">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="textAttrs.color"
|
||||
@update:modelValue="value => emitUpdateTextAttrCommand({ color: value })"
|
||||
/>
|
||||
</template>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="文字颜色">
|
||||
<Button class="text-color-btn" style="flex: 1;">
|
||||
<IconText />
|
||||
<div class="text-color-block" :style="{ backgroundColor: textAttrs.color }"></div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
<Popover trigger="click">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="textAttrs.backcolor"
|
||||
@update:modelValue="value => emitUpdateTextAttrCommand({ backcolor: value })"
|
||||
/>
|
||||
</template>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="单元格填充">
|
||||
<Button class="text-color-btn" style="flex: 1;">
|
||||
<IconFill />
|
||||
<div class="text-color-block" :style="{ backgroundColor: textAttrs.backcolor }"></div>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
</ButtonGroup>
|
||||
|
||||
<CheckboxButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="加粗">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="textAttrs.bold"
|
||||
@click="emitUpdateTextAttrCommand({ bold: !textAttrs.bold })"
|
||||
><IconTextBold /></CheckboxButton>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="斜体">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="textAttrs.em"
|
||||
@click="emitUpdateTextAttrCommand({ em: !textAttrs.em })"
|
||||
><IconTextItalic /></CheckboxButton>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下划线">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="textAttrs.underline"
|
||||
@click="emitUpdateTextAttrCommand({ underline: !textAttrs.underline })"
|
||||
><IconTextUnderline /></CheckboxButton>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="删除线">
|
||||
<CheckboxButton
|
||||
style="flex: 1;"
|
||||
:checked="textAttrs.strikethrough"
|
||||
@click="emitUpdateTextAttrCommand({ strikethrough: !textAttrs.strikethrough })"
|
||||
><IconStrikethrough /></CheckboxButton>
|
||||
</Tooltip>
|
||||
</CheckboxButtonGroup>
|
||||
|
||||
<RadioGroup
|
||||
class="row"
|
||||
button-style="solid"
|
||||
:value="textAttrs.align"
|
||||
@change="e => emitUpdateTextAttrCommand({ align: e.target.value })"
|
||||
>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="左对齐">
|
||||
<RadioButton value="left" style="flex: 1;"><IconAlignTextLeft /></RadioButton>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="居中">
|
||||
<RadioButton value="center" style="flex: 1;"><IconAlignTextCenter /></RadioButton>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="右对齐">
|
||||
<RadioButton value="right" style="flex: 1;"><IconAlignTextRight /></RadioButton>
|
||||
</Tooltip>
|
||||
</RadioGroup>
|
||||
|
||||
<Divider />
|
||||
|
||||
<ElementOutline :fixed="true" />
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row theme-switch">
|
||||
<div style="flex: 2;">启用主题表格:</div>
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch
|
||||
:checked="hasTheme"
|
||||
@change="checked => toggleTheme(checked)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="hasTheme">
|
||||
<div class="row">
|
||||
<Checkbox
|
||||
@change="e => updateTheme({ rowHeader: e.target.checked })"
|
||||
:checked="theme.rowHeader"
|
||||
style="flex: 1;"
|
||||
>标题行</Checkbox>
|
||||
<Checkbox
|
||||
@change="e => updateTheme({ rowFooter: e.target.checked })"
|
||||
:checked="theme.rowFooter"
|
||||
style="flex: 1;"
|
||||
>汇总行</Checkbox>
|
||||
</div>
|
||||
<div class="row">
|
||||
<Checkbox
|
||||
@change="e => updateTheme({ colHeader: e.target.checked })"
|
||||
:checked="theme.colHeader"
|
||||
style="flex: 1;"
|
||||
>第一列</Checkbox>
|
||||
<Checkbox
|
||||
@change="e => updateTheme({ colFooter: e.target.checked })"
|
||||
:checked="theme.colFooter"
|
||||
style="flex: 1;"
|
||||
>最后一列</Checkbox>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="flex: 2;">主题颜色:</div>
|
||||
<Popover trigger="click">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="theme.color"
|
||||
@update:modelValue="value => updateTheme({ color: value })"
|
||||
/>
|
||||
</template>
|
||||
<ColorButton :color="theme.color" style="flex: 3;" />
|
||||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onUnmounted, ref, watch } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { MutationTypes, State } from '@/store'
|
||||
import { PPTTableElement, TableCellStyle, TableTheme } 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'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'table-style-panel',
|
||||
components: {
|
||||
ElementOutline,
|
||||
ColorButton,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore<State>()
|
||||
const handleElement = computed<PPTTableElement>(() => store.getters.handleElement)
|
||||
|
||||
const textAttrs = ref({
|
||||
bold: false,
|
||||
em: false,
|
||||
underline: false,
|
||||
strikethrough: false,
|
||||
color: '#000',
|
||||
backcolor: '#000',
|
||||
fontsize: '12px',
|
||||
fontname: '微软雅黑',
|
||||
align: 'left',
|
||||
})
|
||||
|
||||
const theme = ref<TableTheme>()
|
||||
const hasTheme = ref(false)
|
||||
|
||||
watch(handleElement, () => {
|
||||
if(!handleElement.value) return
|
||||
|
||||
theme.value = handleElement.value.theme
|
||||
hasTheme.value = !!theme.value
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
const updateTextAttrs = (style?: Partial<TableCellStyle>) => {
|
||||
if(!style) {
|
||||
textAttrs.value = {
|
||||
bold: false,
|
||||
em: false,
|
||||
underline: false,
|
||||
strikethrough: false,
|
||||
color: '#000',
|
||||
backcolor: '#000',
|
||||
fontsize: '12px',
|
||||
fontname: '微软雅黑',
|
||||
align: 'left',
|
||||
}
|
||||
}
|
||||
else {
|
||||
textAttrs.value = {
|
||||
bold: !!style.bold,
|
||||
em: !!style.em,
|
||||
underline: !!style.underline,
|
||||
strikethrough: !!style.strikethrough,
|
||||
color: style.color || '#000',
|
||||
backcolor: style.backcolor || '#000',
|
||||
fontsize: style.fontsize || '12px',
|
||||
fontname: style.fontname || '微软雅黑',
|
||||
align: style.align || 'left',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitter.on(EmitterEvents.UPDATE_TABLE_TEXT_STATE, style => updateTextAttrs(style))
|
||||
onUnmounted(() => {
|
||||
emitter.off(EmitterEvents.UPDATE_TABLE_TEXT_STATE, style => updateTextAttrs(style))
|
||||
})
|
||||
|
||||
const availableFonts = computed(() => store.state.availableFonts)
|
||||
const fontSizeOptions = [
|
||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||
]
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
const emitUpdateTextAttrCommand = (textAttrProp: Partial<TableCellStyle>) => {
|
||||
emitter.emit(EmitterEvents.EXEC_TABLE_TEXT_COMMAND, textAttrProp)
|
||||
}
|
||||
|
||||
const updateTheme = (themeProp: Partial<TableTheme>) => {
|
||||
const currentTheme = theme.value || {}
|
||||
const props = { theme: { ...currentTheme, ...themeProp } }
|
||||
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
const toggleTheme = (checked: boolean) => {
|
||||
if(checked) {
|
||||
const props = {
|
||||
theme: {
|
||||
color: '#d14424',
|
||||
rowHeader: true,
|
||||
rowFooter: false,
|
||||
colHeader: false,
|
||||
colFooter: false,
|
||||
}
|
||||
}
|
||||
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
|
||||
}
|
||||
else {
|
||||
store.commit(MutationTypes.REMOVE_ELEMENT_PROPS, { id: handleElement.value.id, propName: 'theme' })
|
||||
}
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
return {
|
||||
availableFonts,
|
||||
fontSizeOptions,
|
||||
textAttrs,
|
||||
emitUpdateTextAttrCommand,
|
||||
theme,
|
||||
hasTheme,
|
||||
toggleTheme,
|
||||
updateTheme,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.theme-switch {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.switch-wrapper {
|
||||
text-align: right;
|
||||
}
|
||||
.text-color-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.text-color-block {
|
||||
width: 16px;
|
||||
height: 3px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
</style>
|
|
@ -18,6 +18,7 @@ import ImageStylePanel from './ImageStylePanel.vue'
|
|||
import ShapeStylePanel from './ShapeStylePanel.vue'
|
||||
import LineStylePanel from './LineStylePanel.vue'
|
||||
import ChartStylePanel from './ChartStylePanel/index.vue'
|
||||
import TableStylePanel from './TableStylePanel.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'element-style-panel',
|
||||
|
@ -34,6 +35,7 @@ export default defineComponent({
|
|||
[ElementTypes.SHAPE]: ShapeStylePanel,
|
||||
[ElementTypes.LINE]: LineStylePanel,
|
||||
[ElementTypes.CHART]: ChartStylePanel,
|
||||
[ElementTypes.TABLE]: TableStylePanel,
|
||||
}
|
||||
return panelMap[handleElement.value.type] || null
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="element-outline">
|
||||
<div class="row">
|
||||
<div class="row" v-if="!fixed">
|
||||
<div style="flex: 2;">启用边框:</div>
|
||||
<div class="switch-wrapper" style="flex: 3;">
|
||||
<Switch
|
||||
|
@ -59,6 +59,12 @@ export default defineComponent({
|
|||
components: {
|
||||
ColorButton,
|
||||
},
|
||||
props: {
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore<State>()
|
||||
const handleElement = computed<PPTElement>(() => store.getters.handleElement)
|
||||
|
@ -81,11 +87,13 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const toggleOutline = (checked: boolean) => {
|
||||
let props: { outline?: PPTElementOutline } = { outline: undefined }
|
||||
if(checked) {
|
||||
props = { outline: { width: 2, color: '#000', style: 'solid' } }
|
||||
}
|
||||
const props = { outline: { width: 2, color: '#000', style: 'solid' } }
|
||||
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
|
||||
}
|
||||
else {
|
||||
store.commit(MutationTypes.REMOVE_ELEMENT_PROPS, { id: handleElement.value.id, propName: 'outline' })
|
||||
}
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
|
|
|
@ -92,11 +92,13 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const toggleShadow = (checked: boolean) => {
|
||||
let props: { shadow?: PPTElementShadow } = { shadow: undefined }
|
||||
if(checked) {
|
||||
props = { shadow: { h: 1, v: 1, blur: 2, color: '#000' } }
|
||||
}
|
||||
const props = { shadow: { h: 1, v: 1, blur: 2, color: '#000' } }
|
||||
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
|
||||
}
|
||||
else {
|
||||
store.commit(MutationTypes.REMOVE_ELEMENT_PROPS, { id: handleElement.value.id, propName: 'shadow' })
|
||||
}
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
:width="elementInfo.width"
|
||||
:colWidths="elementInfo.colWidths"
|
||||
:outline="elementInfo.outline"
|
||||
:theme="elementInfo.theme"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,16 @@
|
|||
@mousedown="$event => handleMousedownColHandler($event, index)"
|
||||
></div>
|
||||
</div>
|
||||
<table>
|
||||
<table
|
||||
:class="{
|
||||
'theme': theme,
|
||||
'row-header': theme?.rowHeader,
|
||||
'row-footer': theme?.rowFooter,
|
||||
'col-header': theme?.colHeader,
|
||||
'col-footer': theme?.colFooter,
|
||||
}"
|
||||
:style="`--themeColor: ${theme?.color}; --subThemeColor1: ${subThemeColor[0]}; --subThemeColor2: ${subThemeColor[1]}`"
|
||||
>
|
||||
<colgroup>
|
||||
<col span="1" v-for="(width, index) in colSizeList" :key="index" :width="width">
|
||||
</colgroup>
|
||||
|
@ -33,6 +42,7 @@
|
|||
borderStyle: outline.style,
|
||||
borderColor: outline.color,
|
||||
borderWidth: outline.width + 'px',
|
||||
...getTextStyle(cell.style),
|
||||
}"
|
||||
v-for="(cell, colIndex) in rowCells"
|
||||
:key="cell.id"
|
||||
|
@ -61,7 +71,8 @@
|
|||
<script lang="ts">
|
||||
import { computed, defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { PPTElementOutline, TableCell } from '@/types/slides'
|
||||
import tinycolor from 'tinycolor2'
|
||||
import { PPTElementOutline, TableCell, TableCellStyle, TableTheme } from '@/types/slides'
|
||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
import { createRandomCode } from '@/utils/common'
|
||||
|
@ -92,6 +103,9 @@ export default defineComponent({
|
|||
type: Object as PropType<PPTElementOutline>,
|
||||
required: true,
|
||||
},
|
||||
theme: {
|
||||
type: Object as PropType<TableTheme>,
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -101,6 +115,19 @@ export default defineComponent({
|
|||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const subThemeColor = ref(['', ''])
|
||||
watch(() => props.theme, () => {
|
||||
if(props.theme) {
|
||||
const rgba = tinycolor(props.theme.color).toRgb()
|
||||
const subRgba1 = { r: rgba.r, g: rgba.g, b: rgba.b, a: rgba.a * 0.3 }
|
||||
const subRgba2 = { r: rgba.r, g: rgba.g, b: rgba.b, a: rgba.a * 0.1 }
|
||||
subThemeColor.value = [
|
||||
`rgba(${[subRgba1.r, subRgba1.g, subRgba1.b, subRgba1.a].join(',')})`,
|
||||
`rgba(${[subRgba2.r, subRgba2.g, subRgba2.b, subRgba2.a].join(',')})`,
|
||||
]
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const tableCells = computed<TableCell[][]>({
|
||||
get() {
|
||||
return props.data
|
||||
|
@ -187,6 +214,10 @@ export default defineComponent({
|
|||
return selectedCells
|
||||
})
|
||||
|
||||
watch(selectedCells, () => {
|
||||
emit('changeSelectedCells', selectedCells.value)
|
||||
})
|
||||
|
||||
const activedCell = computed(() => {
|
||||
if(selectedCells.value.length > 1) return null
|
||||
return selectedCells.value[0]
|
||||
|
@ -446,6 +477,36 @@ export default defineComponent({
|
|||
document.removeEventListener('keydown', keydownListener)
|
||||
})
|
||||
|
||||
const getTextStyle = (style?: TableCellStyle) => {
|
||||
if(!style) return {}
|
||||
const {
|
||||
bold,
|
||||
em,
|
||||
underline,
|
||||
strikethrough,
|
||||
color,
|
||||
backcolor,
|
||||
fontsize,
|
||||
fontname,
|
||||
align,
|
||||
} = style
|
||||
|
||||
return {
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
fontStyle: em ? 'italic' : 'normal',
|
||||
textDecoration: `${underline ? 'underline' : ''} ${strikethrough ? 'line-through' : ''}`,
|
||||
color: color || '#000',
|
||||
backgroundColor: backcolor || '',
|
||||
fontSize: fontsize || '14px',
|
||||
fontFamily: fontname || '微软雅黑',
|
||||
textAlign: align || 'left',
|
||||
}
|
||||
}
|
||||
|
||||
const handleInput = debounce(function() {
|
||||
emit('change', tableCells.value)
|
||||
}, 300, { trailing: true })
|
||||
|
||||
const getEffectiveTableCells = () => {
|
||||
const effectiveTableCells = []
|
||||
|
||||
|
@ -532,11 +593,8 @@ export default defineComponent({
|
|||
]
|
||||
}
|
||||
|
||||
const handleInput = debounce(function() {
|
||||
emit('change', tableCells.value)
|
||||
}, 300, { trailing: true })
|
||||
|
||||
return {
|
||||
getTextStyle,
|
||||
dragLinePosition,
|
||||
tableCells,
|
||||
colSizeList,
|
||||
|
@ -552,6 +610,7 @@ export default defineComponent({
|
|||
handleMousedownColHandler,
|
||||
contextmenus,
|
||||
handleInput,
|
||||
subThemeColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -572,6 +631,40 @@ table {
|
|||
word-wrap: break-word;
|
||||
user-select: none;
|
||||
|
||||
--themeColor: $themeColor;
|
||||
--subThemeColor1: $themeColor;
|
||||
--subThemeColor2: $themeColor;
|
||||
|
||||
&.theme {
|
||||
tr:nth-child(2n) .cell {
|
||||
background-color: var(--subThemeColor1);
|
||||
}
|
||||
tr:nth-child(2n + 1) .cell {
|
||||
background-color: var(--subThemeColor2);
|
||||
}
|
||||
|
||||
&.row-header {
|
||||
tr:first-child .cell {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
&.row-footer {
|
||||
tr:last-child .cell {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
&.col-header {
|
||||
tr .cell:first-child {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
&.col-footer {
|
||||
tr .cell:last-child {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
height: 36px;
|
||||
}
|
||||
|
@ -581,6 +674,7 @@ table {
|
|||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
cursor: default;
|
||||
|
||||
&.selected::after {
|
||||
|
@ -590,7 +684,7 @@ table {
|
|||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba($color: #888, $alpha: .1);
|
||||
background-color: rgba($color: $themeColor, $alpha: .3);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,7 +694,6 @@ table {
|
|||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
cursor: text;
|
||||
|
||||
|
|
|
@ -3,7 +3,16 @@
|
|||
class="static-table"
|
||||
:style="{ width: totalWidth + 'px' }"
|
||||
>
|
||||
<table>
|
||||
<table
|
||||
:class="{
|
||||
'theme': theme,
|
||||
'row-header': theme?.rowHeader,
|
||||
'row-footer': theme?.rowFooter,
|
||||
'col-header': theme?.colHeader,
|
||||
'col-footer': theme?.colFooter,
|
||||
}"
|
||||
:style="`--themeColor: ${theme?.color}; --subThemeColor1: ${subThemeColor[0]}; --subThemeColor2: ${subThemeColor[1]}`"
|
||||
>
|
||||
<colgroup>
|
||||
<col span="1" v-for="(width, index) in colSizeList" :key="index" :width="width">
|
||||
</colgroup>
|
||||
|
@ -18,6 +27,7 @@
|
|||
borderStyle: outline.style,
|
||||
borderColor: outline.color,
|
||||
borderWidth: outline.width + 'px',
|
||||
...getTextStyle(cell.style),
|
||||
}"
|
||||
v-for="(cell, colIndex) in rowCells"
|
||||
:key="cell.id"
|
||||
|
@ -25,7 +35,7 @@
|
|||
:colspan="cell.colspan"
|
||||
v-show="!hideCells.includes(`${rowIndex}_${colIndex}`)"
|
||||
>
|
||||
<div class="cell-text" v-html="cell.content" />
|
||||
<div class="cell-text" v-html="cell.text" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -35,7 +45,8 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, ref, watch } from 'vue'
|
||||
import { PPTElementOutline, TableCell } from '@/types/slides'
|
||||
import tinycolor from 'tinycolor2'
|
||||
import { PPTElementOutline, TableCell, TableCellStyle, TableTheme } from '@/types/slides'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'static-table',
|
||||
|
@ -56,6 +67,9 @@ export default defineComponent({
|
|||
type: Object as PropType<PPTElementOutline>,
|
||||
required: true,
|
||||
},
|
||||
theme: {
|
||||
type: Object as PropType<TableTheme>,
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
@ -93,10 +107,51 @@ export default defineComponent({
|
|||
return hideCells
|
||||
})
|
||||
|
||||
const subThemeColor = ref(['', ''])
|
||||
watch(() => props.theme, () => {
|
||||
if(props.theme) {
|
||||
const rgba = tinycolor(props.theme.color).toRgb()
|
||||
const subRgba1 = { r: rgba.r, g: rgba.g, b: rgba.b, a: rgba.a * 0.3 }
|
||||
const subRgba2 = { r: rgba.r, g: rgba.g, b: rgba.b, a: rgba.a * 0.1 }
|
||||
subThemeColor.value = [
|
||||
`rgba(${[subRgba1.r, subRgba1.g, subRgba1.b, subRgba1.a].join(',')})`,
|
||||
`rgba(${[subRgba2.r, subRgba2.g, subRgba2.b, subRgba2.a].join(',')})`,
|
||||
]
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const getTextStyle = (style?: TableCellStyle) => {
|
||||
if(!style) return {}
|
||||
const {
|
||||
bold,
|
||||
em,
|
||||
underline,
|
||||
strikethrough,
|
||||
color,
|
||||
backcolor,
|
||||
fontsize,
|
||||
fontname,
|
||||
align,
|
||||
} = style
|
||||
|
||||
return {
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
fontStyle: em ? 'italic' : 'normal',
|
||||
textDecoration: `${underline ? 'underline' : ''} ${strikethrough ? 'line-through' : ''}`,
|
||||
color: color || '#000',
|
||||
backgroundColor: backcolor || '',
|
||||
fontSize: fontsize || '14px',
|
||||
fontFamily: fontname || '微软雅黑',
|
||||
textAlign: align || 'left',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
colSizeList,
|
||||
totalWidth,
|
||||
hideCells,
|
||||
getTextStyle,
|
||||
subThemeColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -117,6 +172,40 @@ table {
|
|||
word-wrap: break-word;
|
||||
user-select: none;
|
||||
|
||||
--themeColor: $themeColor;
|
||||
--subThemeColor1: $themeColor;
|
||||
--subThemeColor2: $themeColor;
|
||||
|
||||
&.theme {
|
||||
tr:nth-child(2n) .cell {
|
||||
background-color: var(--subThemeColor1);
|
||||
}
|
||||
tr:nth-child(2n + 1) .cell {
|
||||
background-color: var(--subThemeColor2);
|
||||
}
|
||||
|
||||
&.row-header {
|
||||
tr:first-child .cell {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
&.row-footer {
|
||||
tr:last-child .cell {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
&.col-header {
|
||||
tr .cell:first-child {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
&.col-footer {
|
||||
tr .cell:last-child {
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
height: 36px;
|
||||
}
|
||||
|
@ -126,7 +215,6 @@ table {
|
|||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.cell-text {
|
||||
|
@ -137,7 +225,6 @@ table {
|
|||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -15,29 +15,35 @@
|
|||
>
|
||||
<div
|
||||
class="table-mask"
|
||||
v-if="!editable"
|
||||
@dblclick="editable = true"
|
||||
:class="{ 'lock': elementInfo.lock }"
|
||||
v-if="!editable || elementInfo.lock"
|
||||
@dblclick="startEdit()"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
></div>
|
||||
>
|
||||
<div class="mask-tip" :style="{ transform: `scale(${ 1 / canvasScale })` }">双击编辑</div>
|
||||
</div>
|
||||
|
||||
<EditableTable
|
||||
@mousedown.stop
|
||||
:data="elementInfo.data"
|
||||
:width="elementInfo.width"
|
||||
:colWidths="elementInfo.colWidths"
|
||||
:outline="elementInfo.outline"
|
||||
:theme="elementInfo.theme"
|
||||
:editable="editable"
|
||||
@change="data => updateTableCells(data)"
|
||||
@changeColWidths="widths => updateColWidths(widths)"
|
||||
@changeSelectedCells="cells => updateSelectedCells(cells)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
|
||||
import { computed, defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { MutationTypes, State } from '@/store'
|
||||
import { PPTTableElement, TableCell } from '@/types/slides'
|
||||
import { PPTTableElement, TableCell, TableCellStyle } from '@/types/slides'
|
||||
import emitter, { EmitterEvents } from '@/utils/emitter'
|
||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
|
@ -64,6 +70,8 @@ export default defineComponent({
|
|||
},
|
||||
setup(props) {
|
||||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
|
@ -151,12 +159,64 @@ export default defineComponent({
|
|||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
const selectedCells = ref<string[]>([])
|
||||
|
||||
const emitUpdateTextAttrsState = () => {
|
||||
let rowIndex = 0
|
||||
let colIndex = 0
|
||||
if(selectedCells.value.length) {
|
||||
const selectedCell = selectedCells.value[0]
|
||||
rowIndex = +selectedCell.split('_')[0]
|
||||
colIndex = +selectedCell.split('_')[1]
|
||||
}
|
||||
emitter.emit(EmitterEvents.UPDATE_TABLE_TEXT_STATE, props.elementInfo.data[rowIndex][colIndex].style)
|
||||
}
|
||||
|
||||
const updateTextAttrs = (textAttrProp: Partial<TableCellStyle>) => {
|
||||
const data: TableCell[][] = JSON.parse(JSON.stringify(props.elementInfo.data))
|
||||
|
||||
for(let i = 0; i < data.length; i++) {
|
||||
for(let j = 0; j < data[i].length; j++) {
|
||||
if(!selectedCells.value.length || selectedCells.value.includes(`${i}_${j}`)) {
|
||||
const style = data[i][j].style || {}
|
||||
data[i][j].style = { ...style, ...textAttrProp }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.commit(MutationTypes.UPDATE_ELEMENT, {
|
||||
id: props.elementInfo.id,
|
||||
props: { data },
|
||||
})
|
||||
|
||||
addHistorySnapshot()
|
||||
nextTick(emitUpdateTextAttrsState)
|
||||
}
|
||||
|
||||
const updateSelectedCells = (cells: string[]) => {
|
||||
selectedCells.value = cells
|
||||
nextTick(emitUpdateTextAttrsState)
|
||||
}
|
||||
|
||||
emitter.on(EmitterEvents.EXEC_TABLE_TEXT_COMMAND, state => updateTextAttrs(state))
|
||||
onUnmounted(() => {
|
||||
emitter.off(EmitterEvents.EXEC_TABLE_TEXT_COMMAND, state => updateTextAttrs(state))
|
||||
})
|
||||
|
||||
const startEdit = () => {
|
||||
if(!props.elementInfo.lock) editable.value = true
|
||||
}
|
||||
|
||||
return {
|
||||
elementRef,
|
||||
canvasScale,
|
||||
handleSelectElement,
|
||||
updateTableCells,
|
||||
updateColWidths,
|
||||
editable,
|
||||
startEdit,
|
||||
selectedCells,
|
||||
updateSelectedCells,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -184,5 +244,22 @@ export default defineComponent({
|
|||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
transition: opacity .2s;
|
||||
|
||||
.mask-tip {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
background-color: rgba($color: #000, $alpha: .5);
|
||||
color: #fff;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
&:hover:not(.lock) {
|
||||
opacity: .9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue