添加表格单元格样式和主题

This commit is contained in:
pipipi-pikachu 2021-01-24 19:23:05 +08:00
parent a1515e1994
commit 604494cd8e
12 changed files with 647 additions and 48 deletions

View File

@ -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' },
],
],
},

View File

@ -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[][];
}

View File

@ -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',
}

View File

@ -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>

View File

@ -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>

View File

@ -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
})

View File

@ -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()
}

View File

@ -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()
}

View File

@ -13,6 +13,7 @@
:width="elementInfo.width"
:colWidths="elementInfo.colWidths"
:outline="elementInfo.outline"
:theme="elementInfo.theme"
/>
</div>
</div>

View File

@ -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;

View File

@ -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>

View File

@ -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>