diff --git a/src/hooks/useUniformDisplayElement.ts b/src/hooks/useUniformDisplayElement.ts index 2a2dd9a1..a64d52a8 100644 --- a/src/hooks/useUniformDisplayElement.ts +++ b/src/hooks/useUniformDisplayElement.ts @@ -4,9 +4,33 @@ import { PPTElement, Slide } from '@/types/slides' import { getElementRange, getElementListRange, getRectRotatedOffset } from '@/utils/element' import useHistorySnapshot from './useHistorySnapshot' -interface SortedElementData { +interface ElementItem { + min: number; + max: number; el: PPTElement; +} + +interface GroupItem { + groupId: string; + els: PPTElement[]; +} + +interface GroupElementsItem { + min: number; + max: number; + els: PPTElement[]; +} + +type Item = ElementItem | GroupElementsItem + +interface ElementWithPos { pos: number; + el: PPTElement; +} + +interface LastPos { + min: number; + max: number; } export default () => { @@ -17,44 +41,95 @@ export default () => { const { addHistorySnapshot } = useHistorySnapshot() + const displayItemCount = computed(() => { + let count = 0 + const groupIdList: string[] = [] + for (const el of activeElementList.value) { + if (!el.groupId) count += 1 + else if (!groupIdList.includes(el.groupId)) { + groupIdList.push(el.groupId) + count += 1 + } + } + return count + }) // 水平均匀排列 const uniformHorizontalDisplay = () => { const { minX, maxX } = getElementListRange(activeElementList.value) const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value)) const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements)) - // 将选中的元素按位置(从左到右)排序 - copyOfActiveElementList.sort((elementA, elementB) => { - const { minX: elAMinX } = getElementRange(elementA) - const { minX: elBMinX } = getElementRange(elementB) - return elAMinX - elBMinX - }) + // 分别获取普通元素和组合元素集合,并记录下每一项的范围 + const singleElemetList: ElementItem[] = [] + let groupList: GroupItem[] = [] + for (const el of copyOfActiveElementList) { + if (!el.groupId) { + const { minX, maxX } = getElementRange(el) + singleElemetList.push({ min: minX, max: maxX, el }) + } + else { + const groupEl = groupList.find(item => item.groupId === el.groupId) + if (!groupEl) groupList.push({ groupId: el.groupId, els: [el] }) + else { + groupList = groupList.map(item => item.groupId === el.groupId ? { ...item, els: [...item.els, el] } : item) + } + } + } + const formatedGroupList: GroupElementsItem[] = [] + for (const groupItem of groupList) { + const { minX, maxX } = getElementListRange(groupItem.els) + formatedGroupList.push({ min: minX, max: maxX, els: groupItem.els }) + } + + // 将普通元素和组合元素集合组合在一起,然后将每一项按位置(从左到右)排序 + const list: Item[] = [...singleElemetList, ...formatedGroupList] + list.sort((itemA, itemB) => itemA.min - itemB.min) // 计算元素均匀分布所需要的间隔: // (所选元素整体范围 - 所有所选元素宽度和) / (所选元素数 - 1) let totalWidth = 0 - for (const element of activeElementList.value) { - const { minX: elMinX, maxX: elMaxX } = getElementRange(element) - totalWidth += (elMaxX - elMinX) + for (const item of list) { + const width = item.max - item.min + totalWidth += width } - const span = ((maxX - minX) - totalWidth) / (activeElementList.value.length - 1) + const span = ((maxX - minX) - totalWidth) / (list.length - 1) - // 将所选元素按位置顺序依次计算目标位置 - // 注意pos并非元素目标left值,而是目标位置范围最小值(元素旋转后的left值 ≠ 范围最小值) - const sortedElementData: SortedElementData[] = [] - for (const element of copyOfActiveElementList) { - if (!sortedElementData.length) { - const { minX: firstElMinX } = getElementRange(element) - sortedElementData.push({ el: element, pos: firstElMinX }) - continue + // 按位置顺序依次计算每一个元素的目标位置 + // 第一项中的元素即为起点,无序计算 + // 从第二项开始,每一项的位置应该为:上一项位置 + 上一项宽度 + 间隔 + // 注意此处计算的位置(pos)并非元素最终的left值,而是目标位置范围最小值(元素旋转后的left值 ≠ 范围最小值) + const sortedElementData: ElementWithPos[] = [] + + const firstItem = list[0] + let lastPos: LastPos = { min: firstItem.min, max: firstItem.max } + + if ('el' in firstItem) { + sortedElementData.push({ pos: firstItem.min, el: firstItem.el }) + } + else { + for (const el of firstItem.els) { + const { minX: pos } = getElementRange(el) + sortedElementData.push({ pos, el }) + } + } + + for (let i = 1; i < list.length; i++) { + const item = list[i] + const lastWidth = lastPos.max - lastPos.min + const currentPos = lastPos.min + lastWidth + span + const currentWidth = item.max - item.min + lastPos = { min: currentPos, max: currentPos + currentWidth } + + if ('el' in item) { + sortedElementData.push({ pos: currentPos, el: item.el }) + } + else { + for (const el of item.els) { + const { minX } = getElementRange(el) + const offset = minX - item.min + sortedElementData.push({ pos: currentPos + offset, el }) + } } - - const lastItemElement = sortedElementData[sortedElementData.length - 1].el - const lastItemPos = sortedElementData[sortedElementData.length - 1].pos - const { minX: lastElementMinX, maxX: lastElementMaxX } = getElementRange(lastItemElement) - const lastElementWidth = lastElementMaxX - lastElementMinX - - sortedElementData.push({ el: element, pos: lastItemPos + lastElementWidth + span }) } // 根据目标位置计算元素最终目标left值 @@ -89,33 +164,69 @@ export default () => { const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value)) const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements)) - copyOfActiveElementList.sort((elementA, elementB) => { - const { minY: elAMinY } = getElementRange(elementA) - const { minY: elBMinY } = getElementRange(elementB) - return elAMinY - elBMinY - }) + const singleElemetList: ElementItem[] = [] + let groupList: GroupItem[] = [] + for (const el of copyOfActiveElementList) { + if (!el.groupId) { + const { minY, maxY } = getElementRange(el) + singleElemetList.push({ min: minY, max: maxY, el }) + } + else { + const groupEl = groupList.find(item => item.groupId === el.groupId) + if (!groupEl) groupList.push({ groupId: el.groupId, els: [el] }) + else { + groupList = groupList.map(item => item.groupId === el.groupId ? { ...item, els: [...item.els, el] } : item) + } + } + } + const formatedGroupList: GroupElementsItem[] = [] + for (const groupItem of groupList) { + const { minY, maxY } = getElementListRange(groupItem.els) + formatedGroupList.push({ min: minY, max: maxY, els: groupItem.els }) + } + + const list: Item[] = [...singleElemetList, ...formatedGroupList] + list.sort((itemA, itemB) => itemA.min - itemB.min) let totalHeight = 0 - for (const element of activeElementList.value) { - const { minY: elMinY, maxY: elMaxY } = getElementRange(element) - totalHeight += (elMaxY - elMinY) + for (const item of list) { + const height = item.max - item.min + totalHeight += height } - const span = ((maxY - minY) - totalHeight) / (activeElementList.value.length - 1) + const span = ((maxY - minY) - totalHeight) / (list.length - 1) - const sortedElementData: SortedElementData[] = [] - for (const element of copyOfActiveElementList) { - if (!sortedElementData.length) { - const { minY: firstElMinY } = getElementRange(element) - sortedElementData.push({ el: element, pos: firstElMinY }) - continue + const sortedElementData: ElementWithPos[] = [] + + const firstItem = list[0] + let lastPos: LastPos = { min: firstItem.min, max: firstItem.max } + + if ('el' in firstItem) { + sortedElementData.push({ pos: firstItem.min, el: firstItem.el }) + } + else { + for (const el of firstItem.els) { + const { minY: pos } = getElementRange(el) + sortedElementData.push({ pos, el }) + } + } + + for (let i = 1; i < list.length; i++) { + const item = list[i] + const lastHeight = lastPos.max - lastPos.min + const currentPos = lastPos.min + lastHeight + span + const currentHeight = item.max - item.min + lastPos = { min: currentPos, max: currentPos + currentHeight } + + if ('el' in item) { + sortedElementData.push({ pos: currentPos, el: item.el }) + } + else { + for (const el of item.els) { + const { minY } = getElementRange(el) + const offset = minY - item.min + sortedElementData.push({ pos: currentPos + offset, el }) + } } - - const lastItemElement = sortedElementData[sortedElementData.length - 1].el - const lastItemPos = sortedElementData[sortedElementData.length - 1].pos - const { minY: lastElementMinY, maxY: lastElementMaxY } = getElementRange(lastItemElement) - const lastElementHeight = lastElementMaxY - lastElementMinY - - sortedElementData.push({ el: element, pos: lastItemPos + lastElementHeight + span }) } for (const element of newElementList) { @@ -143,6 +254,7 @@ export default () => { } return { + displayItemCount, uniformHorizontalDisplay, uniformVerticalDisplay, } diff --git a/src/views/Editor/Toolbar/MultiPositionPanel.vue b/src/views/Editor/Toolbar/MultiPositionPanel.vue index 83e65ba0..49b743df 100644 --- a/src/views/Editor/Toolbar/MultiPositionPanel.vue +++ b/src/views/Editor/Toolbar/MultiPositionPanel.vue @@ -22,7 +22,7 @@ - + @@ -55,7 +55,7 @@ export default defineComponent({ const { combineElements, uncombineElements } = useCombineElement() const { alignActiveElement } = useAlignActiveElement() const { alignElementToCanvas } = useAlignElementToCanvas() - const { uniformHorizontalDisplay, uniformVerticalDisplay } = useUniformDisplayElement() + const { displayItemCount, uniformHorizontalDisplay, uniformVerticalDisplay } = useUniformDisplayElement() // 判断当前多选的几个元素是否可以组合 const canCombine = computed(() => { @@ -75,7 +75,7 @@ export default defineComponent({ } return { - activeElementList, + displayItemCount, canCombine, combineElements, uncombineElements,