| <template> | |
| <MoveablePanel | |
| class="select-panel" | |
| :width="200" | |
| :height="360" | |
| :title="`选择(${activeElementIdList.length}/${currentSlide.elements.length})`" | |
| :left="-270" | |
| :top="90" | |
| @close="close()" | |
| > | |
| <div class="handler" v-if="elements.length"> | |
| <div class="btns"> | |
| <Button size="small" style="margin-right: 5px;" @click="showAllElements()">全部显示</Button> | |
| <Button size="small" @click="hideAllElements()">全部隐藏</Button> | |
| </div> | |
| <div class="icon-btns" v-if="handleElement"> | |
| <IconDown class="icon-btn" @click="orderElement(handleElement!, ElementOrderCommands.UP)" /> | |
| <IconUp class="icon-btn" @click="orderElement(handleElement!, ElementOrderCommands.DOWN)" /> | |
| </div> | |
| </div> | |
| <div class="element-list"> | |
| <template v-for="item in elements" :key="item.id"> | |
| <div class="group-els" v-if="item.type === 'group'"> | |
| <div class="group-title">组合</div> | |
| <div | |
| class="item" | |
| :class="{ | |
| 'active': activeElementIdList.includes(groupItem.id), | |
| 'group-active': activeGroupElementId.includes(groupItem.id), | |
| }" | |
| v-for="groupItem in item.elements" | |
| :key="groupItem.id" | |
| @click="selectGroupEl(item, groupItem.id)" | |
| @dblclick="enterEdit(groupItem.id)" | |
| > | |
| <input | |
| :id="`select-panel-input-${groupItem.id}`" | |
| :value="groupItem.name || ELEMENT_TYPE_ZH[groupItem.type]" | |
| class="input" | |
| type="text" | |
| v-if="editingElId === groupItem.id" | |
| @blur="$event => saveElementName($event, groupItem.id)" | |
| @keydown.enter="$event => saveElementName($event, groupItem.id)" | |
| > | |
| <div v-else class="name">{{groupItem.name || ELEMENT_TYPE_ZH[groupItem.type]}}</div> | |
| <div class="icons"> | |
| <IconPreviewClose style="font-size: 17px;" @click.stop="toggleHideElement(groupItem.id)" v-if="hiddenElementIdList.includes(groupItem.id)" /> | |
| <IconPreviewOpen style="font-size: 17px;" @click.stop="toggleHideElement(groupItem.id)" v-else /> | |
| </div> | |
| </div> | |
| </div> | |
| <div | |
| class="item" | |
| :class="{ 'active': activeElementIdList.includes(item.id) }" | |
| v-else | |
| @click="selectElement(item.id)" | |
| @dblclick="enterEdit(item.id)" | |
| > | |
| <input | |
| :id="`select-panel-input-${item.id}`" | |
| :value="item.name || ELEMENT_TYPE_ZH[item.type]" | |
| class="input" | |
| type="text" | |
| v-if="editingElId === item.id" | |
| @blur="$event => saveElementName($event, item.id)" | |
| @keydown.enter="$event => saveElementName($event, item.id)" | |
| > | |
| <div v-else class="name">{{item.name || ELEMENT_TYPE_ZH[item.type]}}</div> | |
| <div class="icons"> | |
| <IconPreviewClose style="font-size: 17px;" @click.stop="toggleHideElement(item.id)" v-if="hiddenElementIdList.includes(item.id)" /> | |
| <IconPreviewOpen style="font-size: 17px;" @click.stop="toggleHideElement(item.id)" v-else /> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| </MoveablePanel> | |
| </template> | |
| <script lang="ts" setup> | |
| import { computed, nextTick, ref } from 'vue' | |
| import { storeToRefs } from 'pinia' | |
| import { useSlidesStore, useMainStore } from '@/store' | |
| import type { PPTElement } from '@/types/slides' | |
| import { ELEMENT_TYPE_ZH } from '@/configs/element' | |
| import useOrderElement from '@/hooks/useOrderElement' | |
| import useHideElement from '@/hooks/useHideElement' | |
| import useSelectElement from '@/hooks/useSelectElement' | |
| import { ElementOrderCommands } from '@/types/edit' | |
| import MoveablePanel from '@/components/MoveablePanel.vue' | |
| import Button from '@/components/Button.vue' | |
| const slidesStore = useSlidesStore() | |
| const mainStore = useMainStore() | |
| const { currentSlide } = storeToRefs(slidesStore) | |
| const { handleElement, handleElementId, activeElementIdList, activeGroupElementId, hiddenElementIdList } = storeToRefs(mainStore) | |
| const { orderElement } = useOrderElement() | |
| const { selectElement } = useSelectElement() | |
| const { toggleHideElement, showAllElements, hideAllElements } = useHideElement() | |
| interface GroupElements { | |
| type: 'group' | |
| id: string | |
| elements: PPTElement[] | |
| } | |
| type ElementItem = PPTElement | GroupElements | |
| const elements = computed<ElementItem[]>(() => { | |
| const _elements: ElementItem[] = [] | |
| for (const el of currentSlide.value.elements) { | |
| if (el.groupId) { | |
| const lastItem = _elements[_elements.length - 1] | |
| if (lastItem && lastItem.type === 'group' && lastItem.id && lastItem.id === el.groupId) { | |
| lastItem.elements.push(el) | |
| } | |
| else _elements.push({ type: 'group', id: el.groupId, elements: [el] }) | |
| } | |
| else _elements.push(el) | |
| } | |
| return _elements | |
| }) | |
| const selectGroupEl = (item: GroupElements, id: string) => { | |
| if (handleElementId.value === id) return | |
| if (hiddenElementIdList.value.includes(id)) return | |
| const idList = item.elements.map(el => el.id) | |
| mainStore.setActiveElementIdList(idList) | |
| mainStore.setHandleElementId(id) | |
| nextTick(() => mainStore.setActiveGroupElementId(id)) | |
| } | |
| const editingElId = ref('') | |
| const saveElementName = (e: FocusEvent | KeyboardEvent, id: string) => { | |
| const name = (e.target as HTMLInputElement).value | |
| slidesStore.updateElement({ id, props: { name } }) | |
| editingElId.value = '' | |
| } | |
| const enterEdit = (id: string) => { | |
| editingElId.value = id | |
| nextTick(() => { | |
| const inputRef = document.querySelector(`#select-panel-input-${id}`) as HTMLInputElement | |
| inputRef.focus() | |
| }) | |
| } | |
| const close = () => { | |
| mainStore.setSelectPanelState(false) | |
| } | |
| </script> | |
| <style lang="scss" scoped> | |
| .select-panel { | |
| height: 100%; | |
| font-size: 12px; | |
| user-select: none; | |
| } | |
| .handler { | |
| height: 24px; | |
| margin-bottom: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| .icon-btns { | |
| height: 100%; | |
| flex: 1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: flex-end; | |
| } | |
| .icon-btn { | |
| width: 16px; | |
| height: 100%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| &:hover { | |
| color: $themeColor; | |
| } | |
| } | |
| } | |
| .element-list { | |
| height: calc(100% - 32px); | |
| padding-right: 10px; | |
| margin-right: -10px; | |
| overflow: auto; | |
| } | |
| .item { | |
| padding: 5px; | |
| font-size: 12px; | |
| border-radius: $borderRadius; | |
| display: flex; | |
| align-items: center; | |
| cursor: pointer; | |
| &.active { | |
| background-color: rgba($color: $themeColor, $alpha: .1); | |
| } | |
| &.group-active { | |
| background-color: rgba($color: $themeColor, $alpha: .2); | |
| } | |
| &:hover { | |
| background-color: rgba($color: $themeColor, $alpha: .25); | |
| } | |
| .name { | |
| height: 18px; | |
| line-height: 18px; | |
| flex: 1; | |
| @include ellipsis-oneline(); | |
| } | |
| .icons { | |
| width: 20px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-left: 5px; | |
| } | |
| } | |
| .group-els { | |
| padding: 5px 0; | |
| .group-title { | |
| margin-bottom: 5px; | |
| padding: 0 5px; | |
| } | |
| .item { | |
| margin-left: 15px; | |
| } | |
| } | |
| .input { | |
| width: 100%; | |
| height: 16px; | |
| border: 0; | |
| outline: 0; | |
| padding-left: 0; | |
| padding-right: 0; | |
| flex: 1; | |
| font-size: 12px; | |
| background-color: transparent; | |
| } | |
| </style> |