index.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <template>
  2. <div
  3. class="editable-element-table"
  4. ref="elementRef"
  5. :class="{ 'lock': elementInfo.lock }"
  6. :style="{
  7. top: elementInfo.top + 'px',
  8. left: elementInfo.left + 'px',
  9. width: elementInfo.width + 'px',
  10. }"
  11. >
  12. <div
  13. class="element-content"
  14. v-contextmenu="contextmenus"
  15. >
  16. <EditableTable
  17. @mousedown.stop
  18. :data="elementInfo.data"
  19. :width="elementInfo.width"
  20. :colWidths="elementInfo.colWidths"
  21. :outline="elementInfo.outline"
  22. :theme="elementInfo.theme"
  23. :editable="editable"
  24. @change="data => updateTableCells(data)"
  25. @changeColWidths="widths => updateColWidths(widths)"
  26. @changeSelectedCells="cells => updateSelectedCells(cells)"
  27. />
  28. <div
  29. class="table-mask"
  30. :class="{ 'lock': elementInfo.lock }"
  31. v-if="!editable || elementInfo.lock"
  32. @dblclick="startEdit()"
  33. @mousedown="$event => handleSelectElement($event)"
  34. >
  35. <div class="mask-tip" :style="{ transform: `scale(${ 1 / canvasScale })` }">双击编辑</div>
  36. </div>
  37. </div>
  38. </div>
  39. </template>
  40. <script lang="ts">
  41. import { computed, defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
  42. import { MutationTypes, useStore } from '@/store'
  43. import { PPTTableElement, TableCell } from '@/types/slides'
  44. import emitter, { EmitterEvents } from '@/utils/emitter'
  45. import { ContextmenuItem } from '@/components/Contextmenu/types'
  46. import useHistorySnapshot from '@/hooks/useHistorySnapshot'
  47. import EditableTable from './EditableTable.vue'
  48. export default defineComponent({
  49. name: 'editable-element-table',
  50. components: {
  51. EditableTable,
  52. },
  53. props: {
  54. elementInfo: {
  55. type: Object as PropType<PPTTableElement>,
  56. required: true,
  57. },
  58. selectElement: {
  59. type: Function as PropType<(e: MouseEvent, element: PPTTableElement, canMove?: boolean) => void>,
  60. required: true,
  61. },
  62. contextmenus: {
  63. type: Function as PropType<() => ContextmenuItem[]>,
  64. },
  65. },
  66. setup(props) {
  67. const store = useStore()
  68. const canvasScale = computed(() => store.state.canvasScale)
  69. const handleElementId = computed(() => store.state.handleElementId)
  70. const elementRef = ref<HTMLElement>()
  71. const { addHistorySnapshot } = useHistorySnapshot()
  72. const handleSelectElement = (e: MouseEvent) => {
  73. if (props.elementInfo.lock) return
  74. e.stopPropagation()
  75. props.selectElement(e, props.elementInfo)
  76. }
  77. // 更新表格的可编辑状态,表格处于编辑状态时需要禁用全局快捷键
  78. const editable = ref(false)
  79. watch(handleElementId, () => {
  80. if (handleElementId.value !== props.elementInfo.id) editable.value = false
  81. })
  82. watch(editable, () => {
  83. store.commit(MutationTypes.SET_DISABLE_HOTKEYS_STATE, editable.value)
  84. })
  85. const startEdit = () => {
  86. if (!props.elementInfo.lock) editable.value = true
  87. }
  88. // 监听表格元素的尺寸变化,当高度变化时,更新高度到vuex
  89. // 如果高度变化时正处在缩放操作中,则等待缩放操作结束后再更新
  90. const isScaling = ref(false)
  91. const realHeightCache = ref(-1)
  92. const scaleElementStateListener = (state: boolean) => {
  93. isScaling.value = state
  94. if (state) editable.value = false
  95. if (!state && realHeightCache.value !== -1) {
  96. store.commit(MutationTypes.UPDATE_ELEMENT, {
  97. id: props.elementInfo.id,
  98. props: { height: realHeightCache.value },
  99. })
  100. realHeightCache.value = -1
  101. }
  102. }
  103. emitter.on(EmitterEvents.SCALE_ELEMENT_STATE, state => scaleElementStateListener(state))
  104. onUnmounted(() => {
  105. emitter.off(EmitterEvents.SCALE_ELEMENT_STATE, state => scaleElementStateListener(state))
  106. })
  107. const updateTableElementHeight = (entries: ResizeObserverEntry[]) => {
  108. const contentRect = entries[0].contentRect
  109. if (!elementRef.value) return
  110. const realHeight = contentRect.height
  111. if (props.elementInfo.height !== realHeight) {
  112. if (!isScaling.value) {
  113. store.commit(MutationTypes.UPDATE_ELEMENT, {
  114. id: props.elementInfo.id,
  115. props: { height: realHeight },
  116. })
  117. }
  118. else realHeightCache.value = realHeight
  119. }
  120. }
  121. const resizeObserver = new ResizeObserver(updateTableElementHeight)
  122. onMounted(() => {
  123. if (elementRef.value) resizeObserver.observe(elementRef.value)
  124. })
  125. onUnmounted(() => {
  126. if (elementRef.value) resizeObserver.unobserve(elementRef.value)
  127. })
  128. // 更新表格内容数据
  129. const updateTableCells = (data: TableCell[][]) => {
  130. store.commit(MutationTypes.UPDATE_ELEMENT, {
  131. id: props.elementInfo.id,
  132. props: { data },
  133. })
  134. addHistorySnapshot()
  135. }
  136. // 更新表格的列宽数据
  137. const updateColWidths = (widths: number[]) => {
  138. const width = widths.reduce((a, b) => a + b)
  139. const colWidths = widths.map(item => item / width)
  140. store.commit(MutationTypes.UPDATE_ELEMENT, {
  141. id: props.elementInfo.id,
  142. props: { width, colWidths },
  143. })
  144. addHistorySnapshot()
  145. }
  146. // 更新表格当前选中的单元格
  147. const updateSelectedCells = (cells: string[]) => {
  148. nextTick(() => emitter.emit(EmitterEvents.UPDATE_TABLE_SELECTED_CELL, cells))
  149. }
  150. return {
  151. elementRef,
  152. canvasScale,
  153. handleSelectElement,
  154. updateTableCells,
  155. updateColWidths,
  156. editable,
  157. startEdit,
  158. updateSelectedCells,
  159. }
  160. },
  161. })
  162. </script>
  163. <style lang="scss" scoped>
  164. .editable-element-table {
  165. position: absolute;
  166. cursor: move;
  167. &.lock .element-content {
  168. cursor: default;
  169. }
  170. }
  171. .element-content {
  172. width: 100%;
  173. height: 100%;
  174. position: relative;
  175. }
  176. .table-mask {
  177. position: absolute;
  178. top: 0;
  179. bottom: 0;
  180. left: 0;
  181. right: 0;
  182. opacity: 0;
  183. transition: opacity .2s;
  184. .mask-tip {
  185. position: absolute;
  186. top: 5px;
  187. left: 5px;
  188. background-color: rgba($color: #000, $alpha: .5);
  189. color: #fff;
  190. padding: 6px 12px;
  191. font-size: 12px;
  192. transform-origin: 0 0;
  193. }
  194. &:hover:not(.lock) {
  195. opacity: .9;
  196. }
  197. }
  198. </style>