mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 支持页面和部分节点类型标注
This commit is contained in:
parent
db22120523
commit
eca440aa8e
@ -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
|
||||||
|
@ -53,6 +53,7 @@ npm run dev
|
|||||||
- 翻页动画
|
- 翻页动画
|
||||||
- 元素动画(入场、退场、强调)
|
- 元素动画(入场、退场、强调)
|
||||||
- 选择面板(隐藏元素、层级排序、元素命名)
|
- 选择面板(隐藏元素、层级排序、元素命名)
|
||||||
|
- 页面和节点类型标注(可用于模板相关功能)
|
||||||
- 查找/替换
|
- 查找/替换
|
||||||
- 批注
|
- 批注
|
||||||
### 幻灯片元素编辑
|
### 幻灯片元素编辑
|
||||||
|
@ -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
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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>
|
||||||
|
155
src/views/Editor/MarkupPanel.vue
Normal file
155
src/views/Editor/MarkupPanel.vue
Normal 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>
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user