feat: 支持页面和部分节点类型标注

This commit is contained in:
pipipi-pikachu 2024-12-04 20:54:29 +08:00
parent db22120523
commit eca440aa8e
7 changed files with 185 additions and 2 deletions

View File

@ -57,6 +57,7 @@ npm run dev
- Transition animations - Transition animations
- Element animations (entrance, exit, emphasis) - Element animations (entrance, exit, emphasis)
- Selection panel (hide elements, layer sorting, element naming) - Selection panel (hide elements, layer sorting, element naming)
- Labels for Page and Node Types (usable for template-related features)
- Find/replace - Find/replace
- Annotations - Annotations
### Slide Element Editing ### Slide Element Editing

View File

@ -53,6 +53,7 @@ npm run dev
- 翻页动画 - 翻页动画
- 元素动画(入场、退场、强调) - 元素动画(入场、退场、强调)
- 选择面板(隐藏元素、层级排序、元素命名) - 选择面板(隐藏元素、层级排序、元素命名)
- 页面和节点类型标注(可用于模板相关功能)
- 查找/替换 - 查找/替换
- 批注 - 批注
### 幻灯片元素编辑 ### 幻灯片元素编辑

View File

@ -38,6 +38,7 @@ export interface MainState {
showSelectPanel: boolean showSelectPanel: boolean
showSearchPanel: boolean showSearchPanel: boolean
showNotesPanel: boolean showNotesPanel: boolean
showMarkupPanel: boolean
} }
const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
@ -73,6 +74,7 @@ export const useMainStore = defineStore('main', {
showSelectPanel: false, // 打开选择面板 showSelectPanel: false, // 打开选择面板
showSearchPanel: false, // 打开查找替换面板 showSearchPanel: false, // 打开查找替换面板
showNotesPanel: false, // 打开批注面板 showNotesPanel: false, // 打开批注面板
showMarkupPanel: false, // 打开类型标注面板
}), }),
getters: { getters: {
@ -202,5 +204,9 @@ export const useMainStore = defineStore('main', {
setNotesPanelState(show: boolean) { setNotesPanelState(show: boolean) {
this.showNotesPanel = show this.showNotesPanel = show
}, },
setMarkupPanelState(show: boolean) {
this.showMarkupPanel = show
},
}, },
}) })

View File

@ -137,6 +137,8 @@ interface PPTBaseElement {
} }
export type TextType = 'title' | 'subtitle' | 'content' | 'item' | 'notes' | 'header' | 'footer'
/** /**
* *
* *
@ -163,6 +165,8 @@ interface PPTBaseElement {
* paragraphSpace?: 段间距 5px * paragraphSpace?: 段间距 5px
* *
* vertical?: 竖向文本 * vertical?: 竖向文本
*
* textType?: 文本类型
*/ */
export interface PPTTextElement extends PPTBaseElement { export interface PPTTextElement extends PPTBaseElement {
type: 'text' type: 'text'
@ -177,6 +181,7 @@ export interface PPTTextElement extends PPTBaseElement {
shadow?: PPTElementShadow shadow?: PPTElementShadow
paragraphSpace?: number paragraphSpace?: number
vertical?: boolean vertical?: boolean
textType?: TextType
} }
@ -289,12 +294,15 @@ export type ShapeTextAlign = 'top' | 'middle' | 'bottom'
* defaultColor: 默认颜色HTML内联样式覆盖 * defaultColor: 默认颜色HTML内联样式覆盖
* *
* align: 文本对齐方向 * align: 文本对齐方向
*
* type:
*/ */
export interface ShapeText { export interface ShapeText {
content: string content: string
defaultFontName: string defaultFontName: string
defaultColor: string defaultColor: string
align: ShapeTextAlign align: ShapeTextAlign
type?: TextType
} }
/** /**
@ -693,6 +701,8 @@ export interface SectionTag {
title?: string title?: string
} }
export type SlideType = 'cover' | 'contents' | 'transition' | 'content' | 'end'
/** /**
* *
* *
@ -700,7 +710,7 @@ export interface SectionTag {
* *
* elements: 元素集合 * elements: 元素集合
* *
* notes: 批注 * notes?: 批注
* *
* remark?: 备注 * remark?: 备注
* *
@ -709,6 +719,8 @@ export interface SectionTag {
* animations?: 元素动画集合 * animations?: 元素动画集合
* *
* turningMode?: 翻页方式 * turningMode?: 翻页方式
*
* slideType?: 页面类型
*/ */
export interface Slide { export interface Slide {
id: string id: string
@ -719,6 +731,7 @@ export interface Slide {
animations?: PPTAnimation[] animations?: PPTAnimation[]
turningMode?: TurningMode turningMode?: TurningMode
sectionTag?: SectionTag sectionTag?: SectionTag
type?: SlideType
} }
/** /**

View File

@ -17,6 +17,7 @@
</FileInput> </FileInput>
<PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem> <PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</PopoverMenuItem>
<PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem> <PopoverMenuItem @click="resetSlides(); mainMenuVisible = false">重置幻灯片</PopoverMenuItem>
<PopoverMenuItem @click="openMarkupPanel(); mainMenuVisible = false">幻灯片类型标注</PopoverMenuItem>
<PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/issues')">意见反馈</PopoverMenuItem> <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/issues')">意见反馈</PopoverMenuItem>
<PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem> <PopoverMenuItem @click="goLink('https://github.com/pipipi-pikachu/PPTist/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem>
<PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem> <PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem>
@ -125,6 +126,10 @@ const setDialogForExport = (type: DialogForExportTypes) => {
mainStore.setDialogForExport(type) mainStore.setDialogForExport(type)
mainMenuVisible.value = false mainMenuVisible.value = false
} }
const openMarkupPanel = () => {
mainStore.setMarkupPanelState(true)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -0,0 +1,155 @@
<template>
<MoveablePanel
class="notes-panel"
:width="300"
:height="130"
title="幻灯片类型标注"
:left="-270"
:top="90"
@close="close()"
>
<div class="container">
<div class="row">
<div style="width: 40%;">当前页面类型</div>
<Select
style="width: 60%;"
:value="slideType"
@update:value="value => updateSlide(value as SlideType | '')"
:options="slideTypeOptions"
/>
</div>
<div class="row" v-if="handleElement && (handleElement.type === 'text' || (handleElement.type === 'shape' && handleElement.text))">
<div style="width: 40%;">当前文本类型</div>
<Select
style="width: 60%;"
:value="textType"
@update:value="value => updateElement(value as TextType | '')"
:options="textTypeOptions"
/>
</div>
<div class="placeholder" v-else>选中文本框或带文本的形状标记文本类型</div>
</div>
</MoveablePanel>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store'
import type { SlideType, TextType } from '@/types/slides'
import MoveablePanel from '@/components/MoveablePanel.vue'
import Select from '@/components/Select.vue'
const slidesStore = useSlidesStore()
const mainStore = useMainStore()
const { currentSlide } = storeToRefs(slidesStore)
const { handleElement, handleElementId } = storeToRefs(mainStore)
const slideTypeOptions = ref<{ label: string; value: SlideType | '' }[]>([
{ label: '未标记类型', value: '' },
{ label: '封面页', value: 'cover' },
{ label: '目录页', value: 'contents' },
{ label: '过渡页', value: 'transition' },
{ label: '内容页', value: 'content' },
{ label: '结束页', value: 'end' },
])
const textTypeOptions = ref<{ label: string; value: TextType | '' }[]>([
{ label: '未标记类型', value: '' },
{ label: '标题', value: 'title' },
{ label: '副标题', value: 'subtitle' },
{ label: '正文', value: 'content' },
{ label: '列表项目', value: 'item' },
{ label: '注释', value: 'notes' },
{ label: '页眉', value: 'header' },
{ label: '页脚', value: 'footer' },
])
const slideType = computed(() => currentSlide.value?.type || '')
const textType = computed(() => {
if (!handleElement.value) return ''
if (handleElement.value.type === 'text') return handleElement.value.textType || ''
if (handleElement.value.type === 'shape' && handleElement.value.text) return handleElement.value.text.type || ''
return ''
})
const updateSlide = (type: SlideType | '') => {
if (type) slidesStore.updateSlide({ type })
else {
slidesStore.removeSlideProps({
id: currentSlide.value.id,
propName: 'type',
})
}
}
const updateElement = (type: TextType | '') => {
if (!handleElement.value) return
if (handleElement.value.type === 'text') {
if (type) {
slidesStore.updateElement({ id: handleElementId.value, props: { textType: type } })
}
else {
slidesStore.removeElementProps({
id: handleElementId.value,
propName: 'textType',
})
}
}
if (handleElement.value.type === 'shape') {
const text = handleElement.value.text
if (!text) return
if (type) {
slidesStore.updateElement({
id: handleElementId.value,
props: { text: { ...text, type } },
})
}
else {
delete text.type
slidesStore.updateElement({
id: handleElementId.value,
props: { text },
})
}
}
}
const close = () => {
mainStore.setMarkupPanelState(false)
}
</script>
<style lang="scss" scoped>
.notes-panel {
height: 100%;
font-size: 12px;
user-select: none;
}
.container {
height: 100%;
display: flex;
flex-direction: column;
}
.row {
width: 100%;
display: flex;
align-items: center;
& + .row {
margin-top: 5px;
}
}
.placeholder {
height: 30px;
line-height: 30px;
text-align: center;
color: #999;
font-style: italic;
border: 1px dashed #ccc;
border-radius: $borderRadius;
margin-top: 5px;
}
</style>

View File

@ -19,6 +19,7 @@
<SelectPanel v-if="showSelectPanel" /> <SelectPanel v-if="showSelectPanel" />
<SearchPanel v-if="showSearchPanel" /> <SearchPanel v-if="showSearchPanel" />
<NotesPanel v-if="showNotesPanel" /> <NotesPanel v-if="showNotesPanel" />
<MarkupPanel v-if="showMarkupPanel" />
<Modal <Modal
:visible="!!dialogForExport" :visible="!!dialogForExport"
@ -46,10 +47,11 @@ import ExportDialog from './ExportDialog/index.vue'
import SelectPanel from './SelectPanel.vue' import SelectPanel from './SelectPanel.vue'
import SearchPanel from './SearchPanel.vue' import SearchPanel from './SearchPanel.vue'
import NotesPanel from './NotesPanel.vue' import NotesPanel from './NotesPanel.vue'
import MarkupPanel from './MarkupPanel.vue'
import Modal from '@/components/Modal.vue' import Modal from '@/components/Modal.vue'
const mainStore = useMainStore() const mainStore = useMainStore()
const { dialogForExport, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore) const { dialogForExport, showSelectPanel, showSearchPanel, showNotesPanel, showMarkupPanel } = storeToRefs(mainStore)
const closeExportDialog = () => mainStore.setDialogForExport('') const closeExportDialog = () => mainStore.setDialogForExport('')
const remarkHeight = ref(40) const remarkHeight = ref(40)