添加表格数据编辑器

This commit is contained in:
pipipi-pikachu 2021-01-17 18:08:22 +08:00
parent 0df6812fd6
commit 706ccf5fc8
5 changed files with 354 additions and 8 deletions

View File

@ -203,10 +203,6 @@ export default defineComponent({
},
{
text: '网格参考线',
children: [
{ text: '网格线', handler: toggleGridLines },
{ text: '参考线' },
],
},
{
text: '清空本页',

View File

@ -5,8 +5,8 @@
<div class="menu-item"><IconSeoFolder /> 文件</div>
<template #overlay>
<Menu>
<MenuItem>导出JSON文件</MenuItem>
<MenuItem>保存到本地</MenuItem>
<MenuItem>重置幻灯片</MenuItem>
<MenuItem>缓存幻灯片</MenuItem>
</Menu>
</template>
</Dropdown>
@ -18,6 +18,7 @@
<MenuItem>重做</MenuItem>
<MenuItem>添加页面</MenuItem>
<MenuItem>删除页面</MenuItem>
<MenuItem>网格参考线</MenuItem>
</Menu>
</template>
</Dropdown>

View File

@ -0,0 +1,296 @@
<template>
<div class="chart-data-editor">
<div class="editor-content">
<div class="range-box">
<div
class="temp-range"
:style="{
width: tempRangeSize.width + 'px',
height: tempRangeSize.height + 'px',
}"
></div>
<div
:class="['range-line', line.type]"
v-for="line in rangeLines"
:key="line.type"
:style="line.style"
></div>
<div
class="resizable"
:style="resizablePointStyle"
@mousedown.stop="changeSelectRange($event)"
></div>
</div>
<table>
<tbody>
<tr v-for="rowIndex in 30" :key="rowIndex">
<td v-for="colIndex in 7" :key="colIndex" :class="{ 'head': colIndex === 1 && rowIndex <= selectedRange[1] }">
<input
:class="['item', { 'selected': rowIndex <= selectedRange[1] && colIndex <= selectedRange[0] }]"
:id="`cell-${rowIndex - 1}-${colIndex - 1}`"
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="btns">
<Button class="btn" @click="closeEditor()">取消</Button>
<Button type="primary" class="btn" @click="getTableData()">确认</Button>
</div>
</div>
</template>
<script lang="ts">
import { ChartData } from '@/types/slides'
import { computed, defineComponent, onMounted, PropType, ref } from 'vue'
const CELL_WIDTH = 100
const CELL_HEIGHT = 32
export default defineComponent({
name: 'chart-data-editor',
props: {
data: {
type: Object as PropType<ChartData>,
required: true,
}
},
setup(props, { emit }) {
const selectedRange = ref([0, 0])
const tempRangeSize = ref({ width: 0, height: 0 })
const rangeLines = computed(() => {
const width = selectedRange.value[0] * CELL_WIDTH
const height = selectedRange.value[1] * CELL_HEIGHT
return [
{ type: 't', style: {width: width + 'px'} },
{ type: 'b', style: {top: height + 'px', width: width + 'px'} },
{ type: 'l', style: {height: height + 'px'} },
{ type: 'r', style: {left: width + 'px', height: height + 'px'} },
]
})
const resizablePointStyle = computed(() => {
const width = selectedRange.value[0] * CELL_WIDTH
const height = selectedRange.value[1] * CELL_HEIGHT
return { left: width + 'px', top: height + 'px' }
})
const initData = () => {
const _data: string[][] = []
const { labels, series } = props.data
const rowCount = labels.length
const colCount = series.length
for(let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const row = [labels[rowIndex]]
for(let colIndex = 0; colIndex < colCount; colIndex++) {
row.push(series[colIndex][rowIndex] + '')
}
_data.push(row)
}
for(let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
for(let colIndex = 0; colIndex < colCount + 1; colIndex++) {
const inputRef = document.querySelector(`#cell-${rowIndex}-${colIndex}`) as HTMLInputElement
if(!inputRef) continue
inputRef.value = _data[rowIndex][colIndex] + ''
}
}
selectedRange.value = [colCount + 1, rowCount]
}
onMounted(initData)
const getTableData = () => {
const [col, row] = selectedRange.value
const labels: string[] = []
const series: number[][] = []
for(let rowIndex = 0; rowIndex < row; rowIndex++) {
let labelsItem = `类别${rowIndex + 1}`
const labelInputRef = document.querySelector(`#cell-${rowIndex}-0`) as HTMLInputElement
if(labelInputRef && labelInputRef.value) labelsItem = labelInputRef.value
labels.push(labelsItem)
}
for(let colIndex = 1; colIndex < col; colIndex++) {
const seriesItem = []
for(let rowIndex = 0; rowIndex < row; rowIndex++) {
const valueInputRef = document.querySelector(`#cell-${rowIndex}-${colIndex}`) as HTMLInputElement
let value = 0
if(valueInputRef && valueInputRef.value && !!(+valueInputRef.value)) {
value = +valueInputRef.value
}
seriesItem.push(value)
}
series.push(seriesItem)
}
const data = { labels, series }
emit('save', data)
}
const closeEditor = () => emit('close')
const changeSelectRange = (e: MouseEvent) => {
let isMouseDown = true
const startPageX = e.pageX
const startPageY = e.pageY
const originWidth = selectedRange.value[0] * CELL_WIDTH
const originHeight = selectedRange.value[1] * CELL_HEIGHT
document.onmousemove = e => {
if(!isMouseDown) return
const currentPageX = e.pageX
const currentPageY = e.pageY
const x = currentPageX - startPageX
const y = currentPageY - startPageY
let width = originWidth + x
let height = originHeight + y
if(width % CELL_WIDTH > CELL_WIDTH * 0.5) width = width + (CELL_WIDTH - width % CELL_WIDTH)
if(height % CELL_HEIGHT > CELL_HEIGHT * 0.5) height = height + (CELL_HEIGHT - height % CELL_HEIGHT)
tempRangeSize.value = { width, height }
}
document.onmouseup = () => {
isMouseDown = false
document.onmousemove = null
document.onmouseup = null
const { width, height } = tempRangeSize.value
let row = Math.round(height / CELL_HEIGHT)
let col = Math.round(width / CELL_WIDTH)
if(row < 3) row = 3
if(col < 2) col = 2
selectedRange.value = [col, row]
tempRangeSize.value = { width: 0, height: 0 }
}
}
return {
tempRangeSize,
rangeLines,
resizablePointStyle,
changeSelectRange,
selectedRange,
getTableData,
closeEditor,
}
},
})
</script>
<style lang="scss" scoped>
.chart-data-editor {
width: 600px;
position: relative;
}
.editor-content {
width: 100%;
height: 360px;
overflow: overlay;
position: relative;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
.range-box {
position: absolute;
top: 0;
left: 0;
z-index: 100;
user-select: none;
}
.temp-range {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
background-color: rgba($color: #888, $alpha: .3);
}
.range-line {
position: absolute;
width: 0;
height: 0;
left: 0;
top: 0;
border: 0 solid $themeColor;
&.t {
border-top-width: 1px;
}
&.b {
border-bottom-width: 1px;
}
&.l {
border-left-width: 1px;
}
&.r {
border-right-width: 1px;
}
}
.resizable {
position: absolute;
width: 8px;
height: 8px;
left: 0;
top: 0;
margin: -4px 0 0 -4px;
background-color: $themeColor;
cursor: nwse-resize;
}
table {
width: 100%;
height: 100%;
user-select: none;
table-layout: fixed;
td {
text-align: center;
border: 1px solid #ccc;
vertical-align: middle;
width: 100px;
height: 32px;
&.head {
background-color: rgba($color: $themeColor, $alpha: .1);
}
}
.item {
width: 100%;
height: 100%;
border: 0;
outline: 0;
font-size: 13px;
text-align: center;
background-color: transparent;
&.selected {
background-color: rgba($color: $themeColor, $alpha: .02);
}
}
}
.btns {
margin-top: 10px;
text-align: right;
.btn {
margin-left: 10px;
}
}
</style>

View File

@ -1,18 +1,63 @@
<template>
<div class="chart-style-panel">
<Button class="full-width-btn" @click="chartDataEditorVisible = true">
<IconEdit class="btn-icon" /> 编辑图表数据
</Button>
<ElementOutline />
<Modal
v-model:visible="chartDataEditorVisible"
:footer="null"
centered
:closable="false"
:width="648"
destroyOnClose
>
<ChartDataEditor
:data="handleElement.data"
@close="chartDataEditorVisible = false"
@save="value => updateData(value)"
/>
</Modal>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { computed, defineComponent, Ref, ref } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, State } from '@/store'
import { ChartData, PPTChartElement } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import ElementOutline from '../common/ElementOutline.vue'
import ChartDataEditor from './ChartDataEditor.vue'
export default defineComponent({
name: 'chart-style-panel',
components: {
ElementOutline,
ChartDataEditor,
},
setup() {
const store = useStore<State>()
const handleElement: Ref<PPTChartElement> = computed(() => store.getters.handleElement)
const chartDataEditorVisible = ref(false)
const { addHistorySnapshot } = useHistorySnapshot()
const updateData = (data: ChartData) => {
chartDataEditorVisible.value = false
const props = { data }
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
addHistorySnapshot()
}
return {
chartDataEditorVisible,
handleElement,
updateData,
}
},
})
</script>
@ -24,4 +69,11 @@ export default defineComponent({
align-items: center;
margin-bottom: 10px;
}
.full-width-btn {
width: 100%;
margin-bottom: 10px;
}
.btn-icon {
margin-right: 3px;
}
</style>

View File

@ -3,7 +3,7 @@
<div v-if="!currentPanelComponent">
请先选中要编辑的元素
</div>
<component :is="currentPanelComponent"></component>
<component v-if="handleElement" :is="currentPanelComponent"></component>
</div>
</template>
@ -39,6 +39,7 @@ export default defineComponent({
})
return {
handleElement,
currentPanelComponent,
}
},