useUniformDisplayElement.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import { computed } from 'vue'
  2. import { storeToRefs } from 'pinia'
  3. import { useMainStore, useSlidesStore } from '@/store'
  4. import type { PPTElement } from '@/types/slides'
  5. import { getElementRange, getElementListRange, getRectRotatedOffset } from '@/utils/element'
  6. import useHistorySnapshot from './useHistorySnapshot'
  7. interface ElementItem {
  8. min: number
  9. max: number
  10. el: PPTElement
  11. }
  12. interface GroupItem {
  13. groupId: string
  14. els: PPTElement[]
  15. }
  16. interface GroupElementsItem {
  17. min: number
  18. max: number
  19. els: PPTElement[]
  20. }
  21. type Item = ElementItem | GroupElementsItem
  22. interface ElementWithPos {
  23. pos: number
  24. el: PPTElement
  25. }
  26. interface LastPos {
  27. min: number
  28. max: number
  29. }
  30. export default () => {
  31. const slidesStore = useSlidesStore()
  32. const { activeElementIdList, activeElementList } = storeToRefs(useMainStore())
  33. const { currentSlide } = storeToRefs(slidesStore)
  34. const { addHistorySnapshot } = useHistorySnapshot()
  35. const displayItemCount = computed(() => {
  36. let count = 0
  37. const groupIdList: string[] = []
  38. for (const el of activeElementList.value) {
  39. if (!el.groupId) count += 1
  40. else if (!groupIdList.includes(el.groupId)) {
  41. groupIdList.push(el.groupId)
  42. count += 1
  43. }
  44. }
  45. return count
  46. })
  47. // 水平均匀排列
  48. const uniformHorizontalDisplay = () => {
  49. const { minX, maxX } = getElementListRange(activeElementList.value)
  50. const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value))
  51. const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
  52. // 分别获取普通元素和组合元素集合,并记录下每一项的范围
  53. const singleElemetList: ElementItem[] = []
  54. let groupList: GroupItem[] = []
  55. for (const el of copyOfActiveElementList) {
  56. if (!el.groupId) {
  57. const { minX, maxX } = getElementRange(el)
  58. singleElemetList.push({ min: minX, max: maxX, el })
  59. }
  60. else {
  61. const groupEl = groupList.find(item => item.groupId === el.groupId)
  62. if (!groupEl) groupList.push({ groupId: el.groupId, els: [el] })
  63. else {
  64. groupList = groupList.map(item => item.groupId === el.groupId ? { ...item, els: [...item.els, el] } : item)
  65. }
  66. }
  67. }
  68. const formatedGroupList: GroupElementsItem[] = []
  69. for (const groupItem of groupList) {
  70. const { minX, maxX } = getElementListRange(groupItem.els)
  71. formatedGroupList.push({ min: minX, max: maxX, els: groupItem.els })
  72. }
  73. // 将普通元素和组合元素集合组合在一起,然后将每一项按位置(从左到右)排序
  74. const list: Item[] = [...singleElemetList, ...formatedGroupList]
  75. list.sort((itemA, itemB) => itemA.min - itemB.min)
  76. // 计算元素均匀分布所需要的间隔:
  77. // (所选元素整体范围 - 所有所选元素宽度和) / (所选元素数 - 1)
  78. let totalWidth = 0
  79. for (const item of list) {
  80. const width = item.max - item.min
  81. totalWidth += width
  82. }
  83. const span = ((maxX - minX) - totalWidth) / (list.length - 1)
  84. // 按位置顺序依次计算每一个元素的目标位置
  85. // 第一项中的元素即为起点,无需计算
  86. // 从第二项开始,每一项的位置应该为:上一项位置 + 上一项宽度 + 间隔
  87. // 注意此处计算的位置(pos)并非元素最终的left值,而是目标位置范围最小值(元素旋转后的left值 ≠ 范围最小值)
  88. const sortedElementData: ElementWithPos[] = []
  89. const firstItem = list[0]
  90. let lastPos: LastPos = { min: firstItem.min, max: firstItem.max }
  91. if ('el' in firstItem) {
  92. sortedElementData.push({ pos: firstItem.min, el: firstItem.el })
  93. }
  94. else {
  95. for (const el of firstItem.els) {
  96. const { minX: pos } = getElementRange(el)
  97. sortedElementData.push({ pos, el })
  98. }
  99. }
  100. for (let i = 1; i < list.length; i++) {
  101. const item = list[i]
  102. const lastWidth = lastPos.max - lastPos.min
  103. const currentPos = lastPos.min + lastWidth + span
  104. const currentWidth = item.max - item.min
  105. lastPos = { min: currentPos, max: currentPos + currentWidth }
  106. if ('el' in item) {
  107. sortedElementData.push({ pos: currentPos, el: item.el })
  108. }
  109. else {
  110. for (const el of item.els) {
  111. const { minX } = getElementRange(el)
  112. const offset = minX - item.min
  113. sortedElementData.push({ pos: currentPos + offset, el })
  114. }
  115. }
  116. }
  117. // 根据目标位置计算元素最终目标left值
  118. // 对于旋转后的元素,需要计算旋转前后left的偏移来做校正
  119. for (const element of newElementList) {
  120. if (!activeElementIdList.value.includes(element.id)) continue
  121. for (const sortedItem of sortedElementData) {
  122. if (sortedItem.el.id === element.id) {
  123. if ('rotate' in element && element.rotate) {
  124. const { offsetX } = getRectRotatedOffset({
  125. left: element.left,
  126. top: element.top,
  127. width: element.width,
  128. height: element.height,
  129. rotate: element.rotate,
  130. })
  131. element.left = sortedItem.pos - offsetX
  132. }
  133. else element.left = sortedItem.pos
  134. }
  135. }
  136. }
  137. slidesStore.updateSlide({ elements: newElementList })
  138. addHistorySnapshot()
  139. }
  140. // 垂直均匀排列(逻辑类似水平均匀排列方法)
  141. const uniformVerticalDisplay = () => {
  142. const { minY, maxY } = getElementListRange(activeElementList.value)
  143. const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value))
  144. const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
  145. const singleElemetList: ElementItem[] = []
  146. let groupList: GroupItem[] = []
  147. for (const el of copyOfActiveElementList) {
  148. if (!el.groupId) {
  149. const { minY, maxY } = getElementRange(el)
  150. singleElemetList.push({ min: minY, max: maxY, el })
  151. }
  152. else {
  153. const groupEl = groupList.find(item => item.groupId === el.groupId)
  154. if (!groupEl) groupList.push({ groupId: el.groupId, els: [el] })
  155. else {
  156. groupList = groupList.map(item => item.groupId === el.groupId ? { ...item, els: [...item.els, el] } : item)
  157. }
  158. }
  159. }
  160. const formatedGroupList: GroupElementsItem[] = []
  161. for (const groupItem of groupList) {
  162. const { minY, maxY } = getElementListRange(groupItem.els)
  163. formatedGroupList.push({ min: minY, max: maxY, els: groupItem.els })
  164. }
  165. const list: Item[] = [...singleElemetList, ...formatedGroupList]
  166. list.sort((itemA, itemB) => itemA.min - itemB.min)
  167. let totalHeight = 0
  168. for (const item of list) {
  169. const height = item.max - item.min
  170. totalHeight += height
  171. }
  172. const span = ((maxY - minY) - totalHeight) / (list.length - 1)
  173. const sortedElementData: ElementWithPos[] = []
  174. const firstItem = list[0]
  175. let lastPos: LastPos = { min: firstItem.min, max: firstItem.max }
  176. if ('el' in firstItem) {
  177. sortedElementData.push({ pos: firstItem.min, el: firstItem.el })
  178. }
  179. else {
  180. for (const el of firstItem.els) {
  181. const { minY: pos } = getElementRange(el)
  182. sortedElementData.push({ pos, el })
  183. }
  184. }
  185. for (let i = 1; i < list.length; i++) {
  186. const item = list[i]
  187. const lastHeight = lastPos.max - lastPos.min
  188. const currentPos = lastPos.min + lastHeight + span
  189. const currentHeight = item.max - item.min
  190. lastPos = { min: currentPos, max: currentPos + currentHeight }
  191. if ('el' in item) {
  192. sortedElementData.push({ pos: currentPos, el: item.el })
  193. }
  194. else {
  195. for (const el of item.els) {
  196. const { minY } = getElementRange(el)
  197. const offset = minY - item.min
  198. sortedElementData.push({ pos: currentPos + offset, el })
  199. }
  200. }
  201. }
  202. for (const element of newElementList) {
  203. if (!activeElementIdList.value.includes(element.id)) continue
  204. for (const sortedItem of sortedElementData) {
  205. if (sortedItem.el.id === element.id) {
  206. if ('rotate' in element && element.rotate) {
  207. const { offsetY } = getRectRotatedOffset({
  208. left: element.left,
  209. top: element.top,
  210. width: element.width,
  211. height: element.height,
  212. rotate: element.rotate,
  213. })
  214. element.top = sortedItem.pos - offsetY
  215. }
  216. else element.top = sortedItem.pos
  217. }
  218. }
  219. }
  220. slidesStore.updateSlide({ elements: newElementList })
  221. addHistorySnapshot()
  222. }
  223. return {
  224. displayItemCount,
  225. uniformHorizontalDisplay,
  226. uniformVerticalDisplay,
  227. }
  228. }