可编辑表格组件开发
This commit is contained in:
parent
d06c1068b1
commit
b2eefc80df
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div
|
||||
class="editable-div"
|
||||
ref="editableDivRef"
|
||||
:contenteditable="contenteditable"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@input="$event => handleInput($event)"
|
||||
v-html="text"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-div',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contenteditable: {
|
||||
type: [Boolean, String],
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const editableDivRef = ref<HTMLElement>()
|
||||
const text = ref('')
|
||||
const isFocus = ref(false)
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
if(isFocus.value) return
|
||||
text.value = props.modelValue
|
||||
if(editableDivRef.value) editableDivRef.value.innerHTML = props.modelValue
|
||||
}, { immediate: true })
|
||||
|
||||
const handleInput = () => {
|
||||
if(!editableDivRef.value) return
|
||||
const text = editableDivRef.value.innerHTML
|
||||
emit('update:modelValue', text)
|
||||
}
|
||||
|
||||
const handleFocus = () => {
|
||||
isFocus.value = true
|
||||
|
||||
if(!editableDivRef.value) return
|
||||
editableDivRef.value.onpaste = (e: ClipboardEvent) => {
|
||||
e.preventDefault()
|
||||
if(!e.clipboardData) return
|
||||
|
||||
const clipboardDataFirstItem = e.clipboardData.items[0]
|
||||
|
||||
if(clipboardDataFirstItem && clipboardDataFirstItem.kind === 'string' && clipboardDataFirstItem.type === 'text/plain') {
|
||||
clipboardDataFirstItem.getAsString(text => emit('update:modelValue', text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
isFocus.value = false
|
||||
if(editableDivRef.value) editableDivRef.value.onpaste = null
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if(editableDivRef.value) editableDivRef.value.onpaste = null
|
||||
})
|
||||
|
||||
return {
|
||||
editableDivRef,
|
||||
handleFocus,
|
||||
handleInput,
|
||||
handleBlur,
|
||||
text,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
|
@ -3,6 +3,17 @@
|
|||
class="editable-table"
|
||||
:style="{ width: width + 'px' }"
|
||||
>
|
||||
<div class="handler">
|
||||
<div
|
||||
class="drag-line"
|
||||
v-for="(pos, index) in dragLinePosition"
|
||||
:key="index"
|
||||
:style="{
|
||||
left: pos + 'px',
|
||||
}"
|
||||
@mousedown="$event => handleMousedownColHandler($event, index)"
|
||||
></div>
|
||||
</div>
|
||||
<table>
|
||||
<colgroup>
|
||||
<col span="1" v-for="(width, index) in colWidths" :key="index" :width="width">
|
||||
|
@ -28,10 +39,12 @@
|
|||
@mouseenter="handleCellMouseenter(rowIndex, colIndex)"
|
||||
v-contextmenu="el => contextmenus(el)"
|
||||
>
|
||||
<div
|
||||
<EditableDiv
|
||||
class="cell-text"
|
||||
:class="{ 'active': activedCell === `${rowIndex}_${colIndex}` }"
|
||||
:contenteditable="activedCell === `${rowIndex}_${colIndex}` ? 'plaintext-only' : false"
|
||||
></div>
|
||||
v-model="cell.text"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -40,9 +53,12 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { createRandomCode } from '@/utils/common'
|
||||
import { computed, defineComponent, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, defineComponent, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { ContextmenuItem } from './Contextmenu/types'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
import { createRandomCode } from '@/utils/common'
|
||||
|
||||
import EditableDiv from './EditableDiv.vue'
|
||||
|
||||
interface TableCells {
|
||||
id: string;
|
||||
|
@ -62,6 +78,9 @@ interface TableCells {
|
|||
|
||||
export default defineComponent({
|
||||
name: 'editable-table',
|
||||
components: {
|
||||
EditableDiv,
|
||||
},
|
||||
setup() {
|
||||
const tableCells = ref<TableCells[][]>([
|
||||
[
|
||||
|
@ -86,12 +105,22 @@ export default defineComponent({
|
|||
{ id: '15', colspan: 1, rowspan: 1, text: '' },
|
||||
],
|
||||
])
|
||||
const width = 800
|
||||
const colWidths = ref([160, 160, 160, 160, 160])
|
||||
const isStartSelect = ref(false)
|
||||
const startCell = ref<number[]>([])
|
||||
const endCell = ref<number[]>([])
|
||||
|
||||
const width = computed(() => colWidths.value.reduce((a, b) => (a + b)))
|
||||
|
||||
const dragLinePosition = computed(() => {
|
||||
const dragLinePosition: number[] = []
|
||||
for(let i = 1; i < colWidths.value.length + 1; i++) {
|
||||
const pos = colWidths.value.slice(0, i).reduce((a, b) => (a + b))
|
||||
dragLinePosition.push(pos)
|
||||
}
|
||||
return dragLinePosition
|
||||
})
|
||||
|
||||
const hideCells = computed(() => {
|
||||
const hideCells = []
|
||||
|
||||
|
@ -254,6 +283,7 @@ export default defineComponent({
|
|||
item.splice(colIndex, 1)
|
||||
return item
|
||||
})
|
||||
colWidths.value.splice(colIndex, 1)
|
||||
}
|
||||
|
||||
const insertRow = (selectedIndex: number, rowIndex: number) => {
|
||||
|
@ -281,6 +311,7 @@ export default defineComponent({
|
|||
item.splice(colIndex, 0, cell)
|
||||
return item
|
||||
})
|
||||
colWidths.value.splice(colIndex, 0, 160)
|
||||
}
|
||||
|
||||
const mergeCells = () => {
|
||||
|
@ -310,6 +341,89 @@ export default defineComponent({
|
|||
removeSelectedCells()
|
||||
}
|
||||
|
||||
const handleMousedownColHandler = (e: MouseEvent, colIndex: number) => {
|
||||
removeSelectedCells()
|
||||
let isMouseDown = true
|
||||
|
||||
const originWidth = colWidths.value[colIndex]
|
||||
const startPageX = e.pageX
|
||||
|
||||
const minWidth = 50
|
||||
|
||||
document.onmousemove = e => {
|
||||
if(!isMouseDown) return
|
||||
|
||||
const moveX = e.pageX - startPageX
|
||||
const width = originWidth + moveX < minWidth ? minWidth : Math.round(originWidth + moveX)
|
||||
|
||||
colWidths.value[colIndex] = width
|
||||
}
|
||||
document.onmouseup = () => {
|
||||
isMouseDown = false
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
|
||||
const clearSelectedCellText = () => {
|
||||
const _tableCells: TableCells[][] = JSON.parse(JSON.stringify(tableCells.value))
|
||||
|
||||
for(let i = 0; i < _tableCells.length; i++) {
|
||||
for(let j = 0; j < _tableCells[i].length; j++) {
|
||||
if(selectedCells.value.includes(`${i}_${j}`)) {
|
||||
_tableCells[i][j].text = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
tableCells.value = _tableCells
|
||||
}
|
||||
|
||||
const tabActiveCell = () => {
|
||||
const getNextCell = (i: number, j: number): [number, number] | null => {
|
||||
if(!tableCells.value[i]) return null
|
||||
if(!tableCells.value[i][j]) return getNextCell(i + 1, 0)
|
||||
if(isHideCell(i, j)) return getNextCell(i, j + 1)
|
||||
return [i, j]
|
||||
}
|
||||
|
||||
endCell.value = []
|
||||
|
||||
const nextRow = startCell.value[0]
|
||||
const nextCol = startCell.value[1] + 1
|
||||
|
||||
const nextCell = getNextCell(nextRow, nextCol)
|
||||
if(!nextCell) {
|
||||
insertRow(nextRow, nextRow + 1)
|
||||
startCell.value = [nextRow + 1, 0]
|
||||
}
|
||||
else startCell.value = nextCell
|
||||
|
||||
nextTick(() => {
|
||||
const textRef = document.querySelector('.cell-text.active') as HTMLInputElement
|
||||
textRef.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const keydownListener = (e: KeyboardEvent) => {
|
||||
const key = e.key.toUpperCase()
|
||||
if(selectedCells.value.length < 2) {
|
||||
if(key === KEYS.TAB) {
|
||||
e.preventDefault()
|
||||
tabActiveCell()
|
||||
}
|
||||
}
|
||||
else if(key === KEYS.DELETE) {
|
||||
clearSelectedCellText()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', keydownListener)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', keydownListener)
|
||||
})
|
||||
|
||||
const getEffectiveTableCells = () => {
|
||||
const effectiveTableCells = []
|
||||
|
||||
|
@ -390,6 +504,7 @@ export default defineComponent({
|
|||
|
||||
return {
|
||||
width,
|
||||
dragLinePosition,
|
||||
tableCells,
|
||||
colWidths,
|
||||
hideCells,
|
||||
|
@ -400,6 +515,7 @@ export default defineComponent({
|
|||
handleCellMouseenter,
|
||||
selectCol,
|
||||
selectRow,
|
||||
handleMousedownColHandler,
|
||||
contextmenus,
|
||||
}
|
||||
},
|
||||
|
@ -409,6 +525,7 @@ export default defineComponent({
|
|||
<style lang="scss" scoped>
|
||||
.editable-table {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
|
@ -417,6 +534,7 @@ table {
|
|||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
word-wrap: break-word;
|
||||
user-select: none;
|
||||
|
||||
.cell {
|
||||
padding: 5px;
|
||||
|
@ -457,4 +575,20 @@ table {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drag-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background-color: $themeColor;
|
||||
margin-left: -1px;
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
cursor: col-resize;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -84,8 +84,7 @@ export default () => {
|
|||
const DEFAULT_CELL_HEIGHT = 35
|
||||
const DEFAULT_BORDER_WIDTH = 2
|
||||
|
||||
const colSizes: number[] = new Array(colCount).fill(DEFAULT_CELL_WIDTH)
|
||||
const rowSizes: number[] = new Array(rowCount).fill(DEFAULT_CELL_HEIGHT)
|
||||
const colWidths: number[] = new Array(colCount).fill(DEFAULT_CELL_WIDTH)
|
||||
|
||||
createElement({
|
||||
...DEFAULT_TABLE,
|
||||
|
@ -93,8 +92,7 @@ export default () => {
|
|||
id: createRandomCode(),
|
||||
width: colCount * DEFAULT_CELL_WIDTH + DEFAULT_BORDER_WIDTH,
|
||||
height: rowCount * DEFAULT_CELL_HEIGHT + DEFAULT_BORDER_WIDTH,
|
||||
colSizes,
|
||||
rowSizes,
|
||||
colWidths,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -151,10 +151,7 @@ export interface PPTTableElement {
|
|||
groupId?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
borderTheme?: string;
|
||||
theme?: string;
|
||||
rowSizes: number[];
|
||||
colSizes: number[];
|
||||
colWidths: number[];
|
||||
data: TableElementCell[][];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue