mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
完成基础表格元素
This commit is contained in:
parent
e3f20147db
commit
a1515e1994
@ -3,7 +3,7 @@ import { MutationTypes } from '@/store'
|
|||||||
import { createRandomCode } from '@/utils/common'
|
import { createRandomCode } from '@/utils/common'
|
||||||
import { getImageSize } from '@/utils/image'
|
import { getImageSize } from '@/utils/image'
|
||||||
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||||
import { ChartType, PPTElement, TableCell } from '@/types/slides'
|
import { ChartType, PPTElement } from '@/types/slides'
|
||||||
import { ShapePoolItem } from '@/configs/shapes'
|
import { ShapePoolItem } from '@/configs/shapes'
|
||||||
import { LinePoolItem } from '@/configs/lines'
|
import { LinePoolItem } from '@/configs/lines'
|
||||||
import {
|
import {
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
DEFAULT_SHAPE,
|
DEFAULT_SHAPE,
|
||||||
DEFAULT_LINE,
|
DEFAULT_LINE,
|
||||||
DEFAULT_CHART,
|
DEFAULT_CHART,
|
||||||
DEFAULT_TABLE,
|
|
||||||
} from '@/configs/element'
|
} from '@/configs/element'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
@ -77,24 +76,7 @@ export default () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createTableElement = (rowCount: number, colCount: number) => {
|
const createTableElement = (rowCount: number, colCount: number) => {
|
||||||
const row: TableCell[] = new Array(colCount).fill({ colspan: 1, rowspan: 1, content: '' })
|
console.log(rowCount, colCount)
|
||||||
const data: TableCell[][] = new Array(rowCount).fill(row)
|
|
||||||
|
|
||||||
const DEFAULT_CELL_WIDTH = 80
|
|
||||||
const DEFAULT_CELL_HEIGHT = 35
|
|
||||||
const DEFAULT_BORDER_WIDTH = 2
|
|
||||||
|
|
||||||
const colWidths: number[] = new Array(colCount).fill(DEFAULT_CELL_WIDTH)
|
|
||||||
|
|
||||||
createElement({
|
|
||||||
...DEFAULT_TABLE,
|
|
||||||
type: 'table',
|
|
||||||
id: createRandomCode(),
|
|
||||||
width: colCount * DEFAULT_CELL_WIDTH + DEFAULT_BORDER_WIDTH,
|
|
||||||
height: rowCount * DEFAULT_CELL_HEIGHT + DEFAULT_BORDER_WIDTH,
|
|
||||||
colWidths,
|
|
||||||
data,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const createTextElement = (position: CommonElementPosition) => {
|
const createTextElement = (position: CommonElementPosition) => {
|
||||||
|
@ -25,8 +25,13 @@ export const slides: Slide[] = [
|
|||||||
left: 20,
|
left: 20,
|
||||||
top: 20,
|
top: 20,
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 90,
|
height: 108,
|
||||||
colWidths: [0.25, 0.25, 0.25, 0.25],
|
colWidths: [0.25, 0.25, 0.25, 0.25],
|
||||||
|
outline: {
|
||||||
|
width: 1,
|
||||||
|
style: 'solid',
|
||||||
|
color: '#999',
|
||||||
|
},
|
||||||
data: [
|
data: [
|
||||||
[
|
[
|
||||||
{ id: '1', colspan: 1, rowspan: 1, text: '' },
|
{ id: '1', colspan: 1, rowspan: 1, text: '' },
|
||||||
|
@ -160,6 +160,7 @@ export interface PPTTableElement {
|
|||||||
groupId?: string;
|
groupId?: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
outline: PPTElementOutline;
|
||||||
colWidths: number[];
|
colWidths: number[];
|
||||||
data: TableCell[][];
|
data: TableCell[][];
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,9 @@ export default defineComponent({
|
|||||||
const store = useStore<State>()
|
const store = useStore<State>()
|
||||||
const canvasScale = computed(() => store.state.canvasScale)
|
const canvasScale = computed(() => store.state.canvasScale)
|
||||||
|
|
||||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
const outlineWidth = computed(() => props.elementInfo.outline.width || 1)
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => (props.elementInfo.width + outlineWidth.value) * canvasScale.value)
|
||||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||||
|
|
||||||
const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||||
|
@ -24,6 +24,7 @@ import BaseTextElement from '@/views/components/element/TextElement/BaseTextElem
|
|||||||
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
|
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
|
||||||
import BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'
|
import BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'
|
||||||
import ScreenChartElement from '@/views/components/element/ChartElement/ScreenChartElement.vue'
|
import ScreenChartElement from '@/views/components/element/ChartElement/ScreenChartElement.vue'
|
||||||
|
import BaseTableElement from '@/views/components/element/TableElement/BaseTableElement.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'screen-element',
|
name: 'screen-element',
|
||||||
@ -49,6 +50,7 @@ export default defineComponent({
|
|||||||
[ElementTypes.SHAPE]: BaseShapeElement,
|
[ElementTypes.SHAPE]: BaseShapeElement,
|
||||||
[ElementTypes.LINE]: BaseLineElement,
|
[ElementTypes.LINE]: BaseLineElement,
|
||||||
[ElementTypes.CHART]: ScreenChartElement,
|
[ElementTypes.CHART]: ScreenChartElement,
|
||||||
|
[ElementTypes.TABLE]: BaseTableElement,
|
||||||
}
|
}
|
||||||
return elementTypeMap[props.elementInfo.type] || null
|
return elementTypeMap[props.elementInfo.type] || null
|
||||||
})
|
})
|
||||||
|
@ -20,6 +20,7 @@ import BaseTextElement from '@/views/components/element/TextElement/BaseTextElem
|
|||||||
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
|
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
|
||||||
import BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'
|
import BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'
|
||||||
import BaseChartElement from '@/views/components/element/ChartElement/BaseChartElement.vue'
|
import BaseChartElement from '@/views/components/element/ChartElement/BaseChartElement.vue'
|
||||||
|
import BaseTableElement from '@/views/components/element/TableElement/BaseTableElement.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'base-element',
|
name: 'base-element',
|
||||||
@ -41,6 +42,7 @@ export default defineComponent({
|
|||||||
[ElementTypes.SHAPE]: BaseShapeElement,
|
[ElementTypes.SHAPE]: BaseShapeElement,
|
||||||
[ElementTypes.LINE]: BaseLineElement,
|
[ElementTypes.LINE]: BaseLineElement,
|
||||||
[ElementTypes.CHART]: BaseChartElement,
|
[ElementTypes.CHART]: BaseChartElement,
|
||||||
|
[ElementTypes.TABLE]: BaseTableElement,
|
||||||
}
|
}
|
||||||
return elementTypeMap[props.elementInfo.type] || null
|
return elementTypeMap[props.elementInfo.type] || null
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="base-element-shape"
|
<div
|
||||||
|
class="base-element-shape"
|
||||||
:style="{
|
:style="{
|
||||||
top: elementInfo.top + 'px',
|
top: elementInfo.top + 'px',
|
||||||
left: elementInfo.left + 'px',
|
left: elementInfo.left + 'px',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="editable-element-shape"
|
<div
|
||||||
|
class="editable-element-shape"
|
||||||
:class="{ 'lock': elementInfo.lock }"
|
:class="{ 'lock': elementInfo.lock }"
|
||||||
:style="{
|
:style="{
|
||||||
top: elementInfo.top + 'px',
|
top: elementInfo.top + 'px',
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="base-element-table"
|
||||||
|
:style="{
|
||||||
|
top: elementInfo.top + 'px',
|
||||||
|
left: elementInfo.left + 'px',
|
||||||
|
width: elementInfo.width + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="element-content">
|
||||||
|
<StaticTable
|
||||||
|
:data="elementInfo.data"
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:colWidths="elementInfo.colWidths"
|
||||||
|
:outline="elementInfo.outline"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue'
|
||||||
|
import { PPTTableElement } from '@/types/slides'
|
||||||
|
|
||||||
|
import StaticTable from './StaticTable.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'base-element-table',
|
||||||
|
components: {
|
||||||
|
StaticTable,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
elementInfo: {
|
||||||
|
type: Object as PropType<PPTTableElement>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.base-element-table {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.element-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="editable-div"
|
class="custom-textarea"
|
||||||
ref="editableDivRef"
|
ref="textareaRef"
|
||||||
:contenteditable="contenteditable"
|
:contenteditable="contenteditable"
|
||||||
@focus="handleFocus"
|
@focus="handleFocus"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
@ -14,7 +14,7 @@
|
|||||||
import { defineComponent, onUnmounted, ref, watch } from 'vue'
|
import { defineComponent, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editable-div',
|
name: 'custom-textarea',
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -26,27 +26,27 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const editableDivRef = ref<HTMLElement>()
|
const textareaRef = ref<HTMLElement>()
|
||||||
const text = ref('')
|
const text = ref('')
|
||||||
const isFocus = ref(false)
|
const isFocus = ref(false)
|
||||||
|
|
||||||
watch(() => props.modelValue, () => {
|
watch(() => props.modelValue, () => {
|
||||||
if(isFocus.value) return
|
if(isFocus.value) return
|
||||||
text.value = props.modelValue
|
text.value = props.modelValue
|
||||||
if(editableDivRef.value) editableDivRef.value.innerHTML = props.modelValue
|
if(textareaRef.value) textareaRef.value.innerHTML = props.modelValue
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
const handleInput = () => {
|
const handleInput = () => {
|
||||||
if(!editableDivRef.value) return
|
if(!textareaRef.value) return
|
||||||
const text = editableDivRef.value.innerHTML
|
const text = textareaRef.value.innerHTML
|
||||||
emit('update:modelValue', text)
|
emit('update:modelValue', text)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFocus = () => {
|
const handleFocus = () => {
|
||||||
isFocus.value = true
|
isFocus.value = true
|
||||||
|
|
||||||
if(!editableDivRef.value) return
|
if(!textareaRef.value) return
|
||||||
editableDivRef.value.onpaste = (e: ClipboardEvent) => {
|
textareaRef.value.onpaste = (e: ClipboardEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if(!e.clipboardData) return
|
if(!e.clipboardData) return
|
||||||
|
|
||||||
@ -60,15 +60,15 @@ export default defineComponent({
|
|||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
isFocus.value = false
|
isFocus.value = false
|
||||||
if(editableDivRef.value) editableDivRef.value.onpaste = null
|
if(textareaRef.value) textareaRef.value.onpaste = null
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if(editableDivRef.value) editableDivRef.value.onpaste = null
|
if(textareaRef.value) textareaRef.value.onpaste = null
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
editableDivRef,
|
textareaRef,
|
||||||
handleFocus,
|
handleFocus,
|
||||||
handleInput,
|
handleInput,
|
||||||
handleBlur,
|
handleBlur,
|
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="editable-table"
|
class="editable-table"
|
||||||
:style="{ width: width + 'px' }"
|
:style="{ width: totalWidth + 'px' }"
|
||||||
>
|
>
|
||||||
<div class="handler">
|
<div class="handler" v-if="editable">
|
||||||
<div
|
<div
|
||||||
class="drag-line"
|
class="drag-line"
|
||||||
v-for="(pos, index) in dragLinePosition"
|
v-for="(pos, index) in dragLinePosition"
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col span="1" v-for="(width, index) in colWidths" :key="index" :width="width">
|
<col span="1" v-for="(width, index) in colSizeList" :key="index" :width="width">
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
@ -29,6 +29,11 @@
|
|||||||
'selected': selectedCells.includes(`${rowIndex}_${colIndex}`) && selectedCells.length > 1,
|
'selected': selectedCells.includes(`${rowIndex}_${colIndex}`) && selectedCells.length > 1,
|
||||||
'active': activedCell === `${rowIndex}_${colIndex}`,
|
'active': activedCell === `${rowIndex}_${colIndex}`,
|
||||||
}"
|
}"
|
||||||
|
:style="{
|
||||||
|
borderStyle: outline.style,
|
||||||
|
borderColor: outline.color,
|
||||||
|
borderWidth: outline.width + 'px',
|
||||||
|
}"
|
||||||
v-for="(cell, colIndex) in rowCells"
|
v-for="(cell, colIndex) in rowCells"
|
||||||
:key="cell.id"
|
:key="cell.id"
|
||||||
:rowspan="cell.rowspan"
|
:rowspan="cell.rowspan"
|
||||||
@ -39,7 +44,7 @@
|
|||||||
@mouseenter="handleCellMouseenter(rowIndex, colIndex)"
|
@mouseenter="handleCellMouseenter(rowIndex, colIndex)"
|
||||||
v-contextmenu="el => contextmenus(el)"
|
v-contextmenu="el => contextmenus(el)"
|
||||||
>
|
>
|
||||||
<EditableDiv
|
<CustomTextarea
|
||||||
class="cell-text"
|
class="cell-text"
|
||||||
:class="{ 'active': activedCell === `${rowIndex}_${colIndex}` }"
|
:class="{ 'active': activedCell === `${rowIndex}_${colIndex}` }"
|
||||||
:contenteditable="activedCell === `${rowIndex}_${colIndex}` ? 'plaintext-only' : false"
|
:contenteditable="activedCell === `${rowIndex}_${colIndex}` ? 'plaintext-only' : false"
|
||||||
@ -54,39 +59,48 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, nextTick, onMounted, onUnmounted, PropType, ref } from 'vue'
|
import { computed, defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
import { TableCell } from '@/types/slides'
|
import { PPTElementOutline, TableCell } from '@/types/slides'
|
||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
import { KEYS } from '@/configs/hotkey'
|
import { KEYS } from '@/configs/hotkey'
|
||||||
import { createRandomCode } from '@/utils/common'
|
import { createRandomCode } from '@/utils/common'
|
||||||
|
|
||||||
import EditableDiv from './EditableDiv.vue'
|
import CustomTextarea from './CustomTextarea.vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { State } from '@/store'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editable-table',
|
name: 'editable-table',
|
||||||
components: {
|
components: {
|
||||||
EditableDiv,
|
CustomTextarea,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
data: {
|
data: {
|
||||||
type: Array as PropType<TableCell[][]>,
|
type: Array as PropType<TableCell[][]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
outlineWidth: {
|
width: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1,
|
required: true,
|
||||||
},
|
},
|
||||||
outlineColor: {
|
colWidths: {
|
||||||
type: String,
|
type: Array as PropType<number[]>,
|
||||||
default: '#41464b',
|
required: true,
|
||||||
},
|
},
|
||||||
outlineStyle: {
|
outline: {
|
||||||
type: String,
|
type: Object as PropType<PPTElementOutline>,
|
||||||
default: 'solid',
|
required: true,
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
|
const store = useStore<State>()
|
||||||
|
const canvasScale = computed(() => store.state.canvasScale)
|
||||||
|
|
||||||
const tableCells = computed<TableCell[][]>({
|
const tableCells = computed<TableCell[][]>({
|
||||||
get() {
|
get() {
|
||||||
return props.data
|
return props.data
|
||||||
@ -96,17 +110,32 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const colWidths = ref([160, 160, 160, 160, 160])
|
const colSizeList = ref<number[]>([])
|
||||||
|
const totalWidth = computed(() => colSizeList.value.reduce((a, b) => a + b))
|
||||||
|
watch([
|
||||||
|
() => props.colWidths,
|
||||||
|
() => props.width,
|
||||||
|
], () => {
|
||||||
|
colSizeList.value = props.colWidths.map(item => item * props.width)
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
const isStartSelect = ref(false)
|
const isStartSelect = ref(false)
|
||||||
const startCell = ref<number[]>([])
|
const startCell = ref<number[]>([])
|
||||||
const endCell = ref<number[]>([])
|
const endCell = ref<number[]>([])
|
||||||
|
|
||||||
const width = computed(() => colWidths.value.reduce((a, b) => (a + b)))
|
const removeSelectedCells = () => {
|
||||||
|
startCell.value = []
|
||||||
|
endCell.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.editable, () => {
|
||||||
|
if(!props.editable) removeSelectedCells()
|
||||||
|
})
|
||||||
|
|
||||||
const dragLinePosition = computed(() => {
|
const dragLinePosition = computed(() => {
|
||||||
const dragLinePosition: number[] = []
|
const dragLinePosition: number[] = []
|
||||||
for(let i = 1; i < colWidths.value.length + 1; i++) {
|
for(let i = 1; i < colSizeList.value.length + 1; i++) {
|
||||||
const pos = colWidths.value.slice(0, i).reduce((a, b) => (a + b))
|
const pos = colSizeList.value.slice(0, i).reduce((a, b) => (a + b))
|
||||||
dragLinePosition.push(pos)
|
dragLinePosition.push(pos)
|
||||||
}
|
}
|
||||||
return dragLinePosition
|
return dragLinePosition
|
||||||
@ -207,11 +236,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
const isHideCell = (rowIndex: number, colIndex: number) => hideCells.value.includes(`${rowIndex}_${colIndex}`)
|
const isHideCell = (rowIndex: number, colIndex: number) => hideCells.value.includes(`${rowIndex}_${colIndex}`)
|
||||||
|
|
||||||
const removeSelectedCells = () => {
|
|
||||||
startCell.value = []
|
|
||||||
endCell.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectCol = (index: number) => {
|
const selectCol = (index: number) => {
|
||||||
const maxRow = tableCells.value.length - 1
|
const maxRow = tableCells.value.length - 1
|
||||||
startCell.value = [0, index]
|
startCell.value = [0, index]
|
||||||
@ -274,12 +298,15 @@ export default defineComponent({
|
|||||||
item.splice(colIndex, 1)
|
item.splice(colIndex, 1)
|
||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
colWidths.value.splice(colIndex, 1)
|
colSizeList.value.splice(colIndex, 1)
|
||||||
|
emit('changeColWidths', colSizeList.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertRow = (selectedIndex: number, rowIndex: number) => {
|
const insertRow = (rowIndex: number) => {
|
||||||
|
const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
|
||||||
|
|
||||||
const rowCells: TableCell[] = []
|
const rowCells: TableCell[] = []
|
||||||
for(let i = 0; i < tableCells.value[0].length; i++) {
|
for(let i = 0; i < _tableCells[0].length; i++) {
|
||||||
rowCells.push({
|
rowCells.push({
|
||||||
colspan: 1,
|
colspan: 1,
|
||||||
rowspan: 1,
|
rowspan: 1,
|
||||||
@ -288,10 +315,11 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCells.value.splice(rowIndex, 0, rowCells)
|
_tableCells.splice(rowIndex, 0, rowCells)
|
||||||
|
tableCells.value = _tableCells
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertCol = (selectedIndex: number, colIndex: number) => {
|
const insertCol = (colIndex: number) => {
|
||||||
tableCells.value = tableCells.value.map(item => {
|
tableCells.value = tableCells.value.map(item => {
|
||||||
const cell = {
|
const cell = {
|
||||||
colspan: 1,
|
colspan: 1,
|
||||||
@ -302,7 +330,8 @@ export default defineComponent({
|
|||||||
item.splice(colIndex, 0, cell)
|
item.splice(colIndex, 0, cell)
|
||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
colWidths.value.splice(colIndex, 0, 160)
|
colSizeList.value.splice(colIndex, 0, 100)
|
||||||
|
emit('changeColWidths', colSizeList.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mergeCells = () => {
|
const mergeCells = () => {
|
||||||
@ -336,7 +365,7 @@ export default defineComponent({
|
|||||||
removeSelectedCells()
|
removeSelectedCells()
|
||||||
let isMouseDown = true
|
let isMouseDown = true
|
||||||
|
|
||||||
const originWidth = colWidths.value[colIndex]
|
const originWidth = colSizeList.value[colIndex]
|
||||||
const startPageX = e.pageX
|
const startPageX = e.pageX
|
||||||
|
|
||||||
const minWidth = 50
|
const minWidth = 50
|
||||||
@ -344,15 +373,17 @@ export default defineComponent({
|
|||||||
document.onmousemove = e => {
|
document.onmousemove = e => {
|
||||||
if(!isMouseDown) return
|
if(!isMouseDown) return
|
||||||
|
|
||||||
const moveX = e.pageX - startPageX
|
const moveX = (e.pageX - startPageX) / canvasScale.value
|
||||||
const width = originWidth + moveX < minWidth ? minWidth : Math.round(originWidth + moveX)
|
const width = originWidth + moveX < minWidth ? minWidth : Math.round(originWidth + moveX)
|
||||||
|
|
||||||
colWidths.value[colIndex] = width
|
colSizeList.value[colIndex] = width
|
||||||
}
|
}
|
||||||
document.onmouseup = () => {
|
document.onmouseup = () => {
|
||||||
isMouseDown = false
|
isMouseDown = false
|
||||||
document.onmousemove = null
|
document.onmousemove = null
|
||||||
document.onmouseup = null
|
document.onmouseup = null
|
||||||
|
|
||||||
|
emit('changeColWidths', colSizeList.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,14 +415,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
const nextCell = getNextCell(nextRow, nextCol)
|
const nextCell = getNextCell(nextRow, nextCol)
|
||||||
if(!nextCell) {
|
if(!nextCell) {
|
||||||
insertRow(nextRow, nextRow + 1)
|
insertRow(nextRow + 1)
|
||||||
startCell.value = [nextRow + 1, 0]
|
startCell.value = [nextRow + 1, 0]
|
||||||
}
|
}
|
||||||
else startCell.value = nextCell
|
else startCell.value = nextCell
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const textRef = document.querySelector('.cell-text.active') as HTMLInputElement
|
const textRef = document.querySelector('.cell-text.active') as HTMLInputElement
|
||||||
textRef.focus()
|
if(textRef) textRef.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,15 +484,15 @@ export default defineComponent({
|
|||||||
{
|
{
|
||||||
text: '插入列',
|
text: '插入列',
|
||||||
children: [
|
children: [
|
||||||
{ text: '到左侧', handler: () => insertCol(colIndex, colIndex) },
|
{ text: '到左侧', handler: () => insertCol(colIndex) },
|
||||||
{ text: '到右侧', handler: () => insertCol(colIndex, colIndex + 1) },
|
{ text: '到右侧', handler: () => insertCol(colIndex + 1) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '插入行',
|
text: '插入行',
|
||||||
children: [
|
children: [
|
||||||
{ text: '到上方', handler: () => insertRow(rowIndex, rowIndex) },
|
{ text: '到上方', handler: () => insertRow(rowIndex) },
|
||||||
{ text: '到下方', handler: () => insertRow(rowIndex, rowIndex + 1) },
|
{ text: '到下方', handler: () => insertRow(rowIndex + 1) },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -506,10 +537,10 @@ export default defineComponent({
|
|||||||
}, 300, { trailing: true })
|
}, 300, { trailing: true })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
|
||||||
dragLinePosition,
|
dragLinePosition,
|
||||||
tableCells,
|
tableCells,
|
||||||
colWidths,
|
colSizeList,
|
||||||
|
totalWidth,
|
||||||
hideCells,
|
hideCells,
|
||||||
selectedCells,
|
selectedCells,
|
||||||
activedCell,
|
activedCell,
|
||||||
@ -537,16 +568,19 @@ table {
|
|||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
border: 0;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
.cell {
|
.cell {
|
||||||
padding: 5px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
&.selected::after {
|
&.selected::after {
|
||||||
@ -561,7 +595,8 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cell-text {
|
.cell-text {
|
||||||
min-height: 22px;
|
min-height: 32px;
|
||||||
|
padding: 5px;
|
||||||
border: 0;
|
border: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@ -572,11 +607,6 @@ table {
|
|||||||
&.active {
|
&.active {
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
|
||||||
background-color: rgba(27, 110, 232, 0.3);
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
143
src/views/components/element/TableElement/StaticTable.vue
Normal file
143
src/views/components/element/TableElement/StaticTable.vue
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="static-table"
|
||||||
|
:style="{ width: totalWidth + 'px' }"
|
||||||
|
>
|
||||||
|
<table>
|
||||||
|
<colgroup>
|
||||||
|
<col span="1" v-for="(width, index) in colSizeList" :key="index" :width="width">
|
||||||
|
</colgroup>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(rowCells, rowIndex) in data"
|
||||||
|
:key="rowIndex"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="cell"
|
||||||
|
:style="{
|
||||||
|
borderStyle: outline.style,
|
||||||
|
borderColor: outline.color,
|
||||||
|
borderWidth: outline.width + 'px',
|
||||||
|
}"
|
||||||
|
v-for="(cell, colIndex) in rowCells"
|
||||||
|
:key="cell.id"
|
||||||
|
:rowspan="cell.rowspan"
|
||||||
|
:colspan="cell.colspan"
|
||||||
|
v-show="!hideCells.includes(`${rowIndex}_${colIndex}`)"
|
||||||
|
>
|
||||||
|
<div class="cell-text" v-html="cell.content" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, PropType, ref, watch } from 'vue'
|
||||||
|
import { PPTElementOutline, TableCell } from '@/types/slides'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'static-table',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Array as PropType<TableCell[][]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
colWidths: {
|
||||||
|
type: Array as PropType<number[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
type: Object as PropType<PPTElementOutline>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const colSizeList = ref<number[]>([])
|
||||||
|
const totalWidth = computed(() => colSizeList.value.reduce((a, b) => a + b))
|
||||||
|
|
||||||
|
watch([
|
||||||
|
() => props.colWidths,
|
||||||
|
() => props.width,
|
||||||
|
], () => {
|
||||||
|
colSizeList.value = props.colWidths.map(item => item * props.width)
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
const hideCells = computed(() => {
|
||||||
|
const hideCells = []
|
||||||
|
|
||||||
|
for(let i = 0; i < props.data.length; i++) {
|
||||||
|
const rowCells = props.data[i]
|
||||||
|
|
||||||
|
for(let j = 0; j < rowCells.length; j++) {
|
||||||
|
const cell = rowCells[j]
|
||||||
|
|
||||||
|
if(cell.colspan > 1 || cell.rowspan > 1) {
|
||||||
|
for(let row = i; row < i + cell.rowspan; row++) {
|
||||||
|
for(let col = row === i ? j + 1 : j; col < j + cell.colspan; col++) {
|
||||||
|
hideCells.push(`${row}_${col}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hideCells
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
colSizeList,
|
||||||
|
totalWidth,
|
||||||
|
hideCells,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.static-table {
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
position: relative;
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-text {
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,45 +1,61 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="editable-element-shape"
|
<div
|
||||||
|
class="editable-element-table"
|
||||||
|
ref="elementRef"
|
||||||
:class="{ 'lock': elementInfo.lock }"
|
:class="{ 'lock': elementInfo.lock }"
|
||||||
:style="{
|
:style="{
|
||||||
top: elementInfo.top + 'px',
|
top: elementInfo.top + 'px',
|
||||||
left: elementInfo.left + 'px',
|
left: elementInfo.left + 'px',
|
||||||
width: elementInfo.width + 'px',
|
width: elementInfo.width + 'px',
|
||||||
}"
|
}"
|
||||||
@mousedown="$event => handleSelectElement($event)"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="element-content"
|
class="element-content"
|
||||||
v-contextmenu="contextmenus"
|
v-contextmenu="contextmenus"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="table-mask"
|
||||||
|
v-if="!editable"
|
||||||
|
@dblclick="editable = true"
|
||||||
|
@mousedown="$event => handleSelectElement($event)"
|
||||||
|
></div>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
|
@mousedown.stop
|
||||||
:data="elementInfo.data"
|
:data="elementInfo.data"
|
||||||
|
:width="elementInfo.width"
|
||||||
:colWidths="elementInfo.colWidths"
|
:colWidths="elementInfo.colWidths"
|
||||||
|
:outline="elementInfo.outline"
|
||||||
|
:editable="editable"
|
||||||
@change="data => updateTableCells(data)"
|
@change="data => updateTableCells(data)"
|
||||||
|
@changeColWidths="widths => updateColWidths(widths)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
|
||||||
import { PPTShapeElement, TableCell } from '@/types/slides'
|
import { useStore } from 'vuex'
|
||||||
|
import { MutationTypes, State } from '@/store'
|
||||||
|
import { PPTTableElement, TableCell } from '@/types/slides'
|
||||||
|
import emitter, { EmitterEvents } from '@/utils/emitter'
|
||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
import EditableTable from './EditableTable.vue'
|
import EditableTable from './EditableTable.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editable-element-shape',
|
name: 'editable-element-table',
|
||||||
components: {
|
components: {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTShapeElement>,
|
type: Object as PropType<PPTTableElement>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
selectElement: {
|
selectElement: {
|
||||||
type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, canMove?: boolean) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTTableElement, canMove?: boolean) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
contextmenus: {
|
contextmenus: {
|
||||||
@ -47,27 +63,107 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const store = useStore<State>()
|
||||||
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const handleSelectElement = (e: MouseEvent) => {
|
const handleSelectElement = (e: MouseEvent) => {
|
||||||
if(props.elementInfo.lock) return
|
if(props.elementInfo.lock) return
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
props.selectElement(e, props.elementInfo)
|
props.selectElement(e, props.elementInfo)
|
||||||
}
|
}
|
||||||
|
const editable = ref(false)
|
||||||
|
const handleElementId = computed(() => store.state.handleElementId)
|
||||||
|
|
||||||
|
watch(handleElementId, () => {
|
||||||
|
if(handleElementId.value !== props.elementInfo.id) editable.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(editable, () => {
|
||||||
|
store.commit(MutationTypes.SET_DISABLE_HOTKEYS_STATE, editable.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const elementRef = ref<HTMLElement>()
|
||||||
|
|
||||||
|
const isScaling = ref(false)
|
||||||
|
const realHeightCache = ref(-1)
|
||||||
|
|
||||||
|
const scaleElementStateListener = (state: boolean) => {
|
||||||
|
isScaling.value = state
|
||||||
|
|
||||||
|
if(state) editable.value = false
|
||||||
|
|
||||||
|
if(!state && realHeightCache.value !== -1) {
|
||||||
|
store.commit(MutationTypes.UPDATE_ELEMENT, {
|
||||||
|
id: props.elementInfo.id,
|
||||||
|
props: { height: realHeightCache.value },
|
||||||
|
})
|
||||||
|
realHeightCache.value = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter.on(EmitterEvents.SCALE_ELEMENT_STATE, state => scaleElementStateListener(state))
|
||||||
|
onUnmounted(() => {
|
||||||
|
emitter.off(EmitterEvents.SCALE_ELEMENT_STATE, state => scaleElementStateListener(state))
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateTableElementHeight = (entries: ResizeObserverEntry[]) => {
|
||||||
|
const contentRect = entries[0].contentRect
|
||||||
|
if(!elementRef.value) return
|
||||||
|
|
||||||
|
const realHeight = contentRect.height
|
||||||
|
|
||||||
|
if(props.elementInfo.height !== realHeight) {
|
||||||
|
if(!isScaling.value) {
|
||||||
|
store.commit(MutationTypes.UPDATE_ELEMENT, {
|
||||||
|
id: props.elementInfo.id,
|
||||||
|
props: { height: realHeight },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else realHeightCache.value = realHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(updateTableElementHeight)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if(elementRef.value) resizeObserver.observe(elementRef.value)
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
if(elementRef.value) resizeObserver.unobserve(elementRef.value)
|
||||||
|
})
|
||||||
|
|
||||||
const updateTableCells = (data: TableCell[][]) => {
|
const updateTableCells = (data: TableCell[][]) => {
|
||||||
console.log(data)
|
store.commit(MutationTypes.UPDATE_ELEMENT, {
|
||||||
|
id: props.elementInfo.id,
|
||||||
|
props: { data },
|
||||||
|
})
|
||||||
|
addHistorySnapshot()
|
||||||
|
}
|
||||||
|
const updateColWidths = (widths: number[]) => {
|
||||||
|
const width = widths.reduce((a, b) => a + b)
|
||||||
|
const colWidths = widths.map(item => item / width)
|
||||||
|
|
||||||
|
store.commit(MutationTypes.UPDATE_ELEMENT, {
|
||||||
|
id: props.elementInfo.id,
|
||||||
|
props: { width, colWidths },
|
||||||
|
})
|
||||||
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
elementRef,
|
||||||
handleSelectElement,
|
handleSelectElement,
|
||||||
updateTableCells,
|
updateTableCells,
|
||||||
|
updateColWidths,
|
||||||
|
editable,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.editable-element-shape {
|
.editable-element-table {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
|
||||||
@ -81,4 +177,12 @@ export default defineComponent({
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.table-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user