index.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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 { addHistorySnapshot } = useHistorySnapshot()
  70. const handleSelectElement = (e: MouseEvent) => {
  71. if(props.elementInfo.lock) return
  72. e.stopPropagation()
  73. props.selectElement(e, props.elementInfo)
  74. }
  75. const editable = ref(false)
  76. const handleElementId = computed(() => store.state.handleElementId)
  77. watch(handleElementId, () => {
  78. if(handleElementId.value !== props.elementInfo.id) editable.value = false
  79. })
  80. watch(editable, () => {
  81. store.commit(MutationTypes.SET_DISABLE_HOTKEYS_STATE, editable.value)
  82. })
  83. const elementRef = ref<HTMLElement>()
  84. const isScaling = ref(false)
  85. const realHeightCache = ref(-1)
  86. const scaleElementStateListener = (state: boolean) => {
  87. isScaling.value = state
  88. if(state) editable.value = false
  89. if(!state && realHeightCache.value !== -1) {
  90. store.commit(MutationTypes.UPDATE_ELEMENT, {
  91. id: props.elementInfo.id,
  92. props: { height: realHeightCache.value },
  93. })
  94. realHeightCache.value = -1
  95. }
  96. }
  97. emitter.on(EmitterEvents.SCALE_ELEMENT_STATE, state => scaleElementStateListener(state))
  98. onUnmounted(() => {
  99. emitter.off(EmitterEvents.SCALE_ELEMENT_STATE, state => scaleElementStateListener(state))
  100. })
  101. const updateTableElementHeight = (entries: ResizeObserverEntry[]) => {
  102. const contentRect = entries[0].contentRect
  103. if(!elementRef.value) return
  104. const realHeight = contentRect.height
  105. if(props.elementInfo.height !== realHeight) {
  106. if(!isScaling.value) {
  107. store.commit(MutationTypes.UPDATE_ELEMENT, {
  108. id: props.elementInfo.id,
  109. props: { height: realHeight },
  110. })
  111. }
  112. else realHeightCache.value = realHeight
  113. }
  114. }
  115. const resizeObserver = new ResizeObserver(updateTableElementHeight)
  116. onMounted(() => {
  117. if(elementRef.value) resizeObserver.observe(elementRef.value)
  118. })
  119. onUnmounted(() => {
  120. if(elementRef.value) resizeObserver.unobserve(elementRef.value)
  121. })
  122. const updateTableCells = (data: TableCell[][]) => {
  123. store.commit(MutationTypes.UPDATE_ELEMENT, {
  124. id: props.elementInfo.id,
  125. props: { data },
  126. })
  127. addHistorySnapshot()
  128. }
  129. const updateColWidths = (widths: number[]) => {
  130. const width = widths.reduce((a, b) => a + b)
  131. const colWidths = widths.map(item => item / width)
  132. store.commit(MutationTypes.UPDATE_ELEMENT, {
  133. id: props.elementInfo.id,
  134. props: { width, colWidths },
  135. })
  136. addHistorySnapshot()
  137. }
  138. const updateSelectedCells = (cells: string[]) => {
  139. nextTick(() => emitter.emit(EmitterEvents.UPDATE_TABLE_SELECTED_CELL, cells))
  140. }
  141. const startEdit = () => {
  142. if(!props.elementInfo.lock) editable.value = true
  143. }
  144. return {
  145. elementRef,
  146. canvasScale,
  147. handleSelectElement,
  148. updateTableCells,
  149. updateColWidths,
  150. editable,
  151. startEdit,
  152. updateSelectedCells,
  153. }
  154. },
  155. })
  156. </script>
  157. <style lang="scss" scoped>
  158. .editable-element-table {
  159. position: absolute;
  160. cursor: move;
  161. &.lock .element-content {
  162. cursor: default;
  163. }
  164. }
  165. .element-content {
  166. width: 100%;
  167. height: 100%;
  168. position: relative;
  169. }
  170. .table-mask {
  171. position: absolute;
  172. top: 0;
  173. bottom: 0;
  174. left: 0;
  175. right: 0;
  176. opacity: 0;
  177. transition: opacity .2s;
  178. .mask-tip {
  179. position: absolute;
  180. top: 5px;
  181. left: 5px;
  182. background-color: rgba($color: #000, $alpha: .5);
  183. color: #fff;
  184. padding: 6px 12px;
  185. font-size: 12px;
  186. transform-origin: 0 0;
  187. }
  188. &:hover:not(.lock) {
  189. opacity: .9;
  190. }
  191. }
  192. </style>