|
<template> |
|
<div class="editor-header"> |
|
<div class="left"> |
|
<Popover trigger="click" placement="bottom-start" v-model:value="mainMenuVisible"> |
|
<template #content> |
|
<PopoverMenuItem @click="createNewPPT()">新建演示文稿</PopoverMenuItem> |
|
<PopoverMenuItem @click="saveCurrentPPT()">保存当前PPT</PopoverMenuItem> |
|
<PopoverMenuItem @click="showPPTManager = true">我的PPT</PopoverMenuItem> |
|
<PopoverMenuItem @click="importSpecificFile">导入 pptist 文件</PopoverMenuItem> |
|
<PopoverMenuItem @click="importPPTXFile">导入 pptx 文件</PopoverMenuItem> |
|
<PopoverMenuItem @click="resetSlides">重置幻灯片</PopoverMenuItem> |
|
<PopoverMenuItem @click="goFullscreen">进入全屏</PopoverMenuItem> |
|
<PopoverMenuItem @click="hotkeyDrawerVisible = true">快捷键</PopoverMenuItem> |
|
<PopoverMenuItem @click="logout" style="color: #d32f2f;">退出登录</PopoverMenuItem> |
|
<PopoverMenuItem @click="openAIPPTDialog(); mainMenuVisible = false">AI 生成 PPT</PopoverMenuItem> |
|
<FileInput accept="application/vnd.openxmlformats-officedocument.presentationml.presentation" @change="files => { |
|
importPPTXFile(files) |
|
mainMenuVisible = false |
|
}"> |
|
<PopoverMenuItem>导入 pptx 文件(测试版)</PopoverMenuItem> |
|
</FileInput> |
|
<FileInput accept=".pptist" @change="files => { |
|
importSpecificFile(files) |
|
mainMenuVisible = false |
|
}"> |
|
<PopoverMenuItem>导入 pptist 文件</PopoverMenuItem> |
|
</FileInput> |
|
<PopoverMenuItem @click="setDialogForExport('pptx')">导出文件</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/blob/master/doc/Q&A.md')">常见问题</PopoverMenuItem> |
|
<PopoverMenuItem @click="mainMenuVisible = false; hotkeyDrawerVisible = true">快捷操作</PopoverMenuItem> |
|
</template> |
|
<div class="menu-item"> |
|
<IconHamburgerButton class="icon" /> |
|
</div> |
|
</Popover> |
|
<div class="title"> |
|
<Input |
|
class="title-input" |
|
ref="titleInputRef" |
|
v-model:value="titleValue" |
|
@blur="handleUpdateTitle()" |
|
v-if="editingTitle" |
|
></Input> |
|
<div |
|
class="title-text" |
|
@click="startEditTitle()" |
|
:title="title" |
|
v-else |
|
>{{ title }}</div> |
|
</div> |
|
</div> |
|
|
|
<div class="right"> |
|
|
|
<div class="menu-item" v-tooltip="'保存PPT (Ctrl+S)'" @click="saveCurrentPPT()"> |
|
<IconWrite class="icon" /> |
|
</div> |
|
|
|
<div class="group-menu-item"> |
|
<div class="menu-item" v-tooltip="'幻灯片放映(F5)'" @click="enterScreening()"> |
|
<IconPpt class="icon" /> |
|
</div> |
|
<Popover trigger="click" center> |
|
<template #content> |
|
<PopoverMenuItem @click="enterScreeningFromStart()">从头开始</PopoverMenuItem> |
|
<PopoverMenuItem @click="enterScreening()">从当前页开始</PopoverMenuItem> |
|
</template> |
|
<div class="arrow-btn"><IconDown class="arrow" /></div> |
|
</Popover> |
|
</div> |
|
|
|
<div class="menu-item" v-tooltip="'AI生成PPT'" @click="openAIPPTDialog(); mainMenuVisible = false"> |
|
<span class="text ai">AI</span> |
|
</div> |
|
<div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')"> |
|
<IconDownload class="icon" /> |
|
</div> |
|
<a class="github-link" v-tooltip="'Copyright © 2020-PRESENT pipipi-pikachu'" href="https://github.com/pipipi-pikachu/PPTist" target="_blank"> |
|
<div class="menu-item"><IconGithub class="icon" /></div> |
|
</a> |
|
</div> |
|
|
|
<Drawer |
|
:width="320" |
|
v-model:visible="hotkeyDrawerVisible" |
|
placement="right" |
|
> |
|
<HotkeyDoc /> |
|
<template v-slot:title>快捷操作</template> |
|
</Drawer> |
|
<FullscreenSpin :loading="exporting" tip="正在导入..." /> |
|
|
|
<Modal |
|
v-model:visible="showPPTManager" |
|
:width="800" |
|
title="PPT管理" |
|
:footer="null" |
|
> |
|
<PPTManager @close="showPPTManager = false" /> |
|
</Modal> |
|
</div> |
|
</template> |
|
|
|
<script lang="ts" setup> |
|
import { nextTick, ref, onMounted, onUnmounted } from 'vue' |
|
import { storeToRefs } from 'pinia' |
|
import { useMainStore, useSlidesStore, useAuthStore } from '@/store' |
|
import useScreening from '@/hooks/useScreening' |
|
import useImport from '@/hooks/useImport' |
|
import useSlideHandler from '@/hooks/useSlideHandler' |
|
import usePPTManager from '@/hooks/usePPTManager' |
|
import type { DialogForExportTypes } from '@/types/export' |
|
|
|
import HotkeyDoc from './HotkeyDoc.vue' |
|
import FileInput from '@/components/FileInput.vue' |
|
import FullscreenSpin from '@/components/FullscreenSpin.vue' |
|
import Drawer from '@/components/Drawer.vue' |
|
import Input from '@/components/Input.vue' |
|
import Popover from '@/components/Popover.vue' |
|
import PopoverMenuItem from '@/components/PopoverMenuItem.vue' |
|
import Modal from '@/components/Modal.vue' |
|
import PPTManager from '@/components/PPTManager.vue' |
|
|
|
const mainStore = useMainStore() |
|
const slidesStore = useSlidesStore() |
|
const authStore = useAuthStore() |
|
const { title } = storeToRefs(slidesStore) |
|
const { enterScreening, enterScreeningFromStart } = useScreening() |
|
const { importSpecificFile, importPPTXFile, exporting } = useImport() |
|
const { resetSlides } = useSlideHandler() |
|
const { createNewPPT: createPPT, saveCurrentPPT: savePPT } = usePPTManager() |
|
|
|
const mainMenuVisible = ref(false) |
|
const hotkeyDrawerVisible = ref(false) |
|
const editingTitle = ref(false) |
|
const titleInputRef = ref<InstanceType<typeof Input>>() |
|
const titleValue = ref('') |
|
const showPPTManager = ref(false) |
|
|
|
const startEditTitle = () => { |
|
titleValue.value = title.value |
|
editingTitle.value = true |
|
nextTick(() => titleInputRef.value?.focus()) |
|
} |
|
|
|
const handleUpdateTitle = () => { |
|
slidesStore.setTitle(titleValue.value) |
|
editingTitle.value = false |
|
} |
|
|
|
const goLink = (url: string) => { |
|
window.open(url) |
|
mainMenuVisible.value = false |
|
} |
|
|
|
const setDialogForExport = (type: DialogForExportTypes) => { |
|
mainStore.setDialogForExport(type) |
|
mainMenuVisible.value = false |
|
} |
|
|
|
const openMarkupPanel = () => { |
|
mainStore.setMarkupPanelState(true) |
|
} |
|
|
|
const openAIPPTDialog = () => { |
|
mainStore.setAIPPTDialogState(true) |
|
} |
|
|
|
|
|
const createNewPPT = async () => { |
|
try { |
|
await createPPT('新建演示文稿') |
|
mainMenuVisible.value = false |
|
} |
|
catch (error) { |
|
// Handle error silently or show user-friendly message |
|
} |
|
} |
|
|
|
|
|
const saveCurrentPPT = async () => { |
|
try { |
|
await savePPT() |
|
mainMenuVisible.value = false |
|
} |
|
catch (error) { |
|
// Handle error silently or show user-friendly message |
|
} |
|
} |
|
|
|
|
|
const goFullscreen = () => { |
|
if (document.documentElement.requestFullscreen) { |
|
document.documentElement.requestFullscreen() |
|
} |
|
mainMenuVisible.value = false |
|
} |
|
|
|
|
|
const logout = () => { |
|
authStore.logout() |
|
mainMenuVisible.value = false |
|
} |
|
|
|
|
|
const handleKeydown = (event: KeyboardEvent) => { |
|
// Ctrl+S 保存 |
|
if (event.ctrlKey && event.key === 's') { |
|
event.preventDefault() |
|
saveCurrentPPT() |
|
} |
|
|
|
else if (event.ctrlKey && event.key === 'n') { |
|
event.preventDefault() |
|
createNewPPT() |
|
} |
|
} |
|
|
|
onMounted(() => { |
|
document.addEventListener('keydown', handleKeydown) |
|
}) |
|
|
|
onUnmounted(() => { |
|
document.removeEventListener('keydown', handleKeydown) |
|
}) |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
.editor-header { |
|
background-color: #fff; |
|
user-select: none; |
|
border-bottom: 1px solid $borderColor; |
|
display: flex; |
|
justify-content: space-between; |
|
padding: 0 5px; |
|
} |
|
.left, .right, .editor-header-left { |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
} |
|
.menu-item { |
|
height: 30px; |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
font-size: 14px; |
|
padding: 0 10px; |
|
border-radius: $borderRadius; |
|
cursor: pointer; |
|
|
|
.icon { |
|
font-size: 18px; |
|
color: #666; |
|
} |
|
.text { |
|
width: 18px; |
|
text-align: center; |
|
font-size: 17px; |
|
} |
|
.ai { |
|
background: linear-gradient(270deg, #d897fd, #33bcfc); |
|
background-clip: text; |
|
color: transparent; |
|
font-weight: 700; |
|
} |
|
|
|
&:hover { |
|
background-color: #f1f1f1; |
|
} |
|
} |
|
.group-menu-item { |
|
height: 30px; |
|
display: flex; |
|
margin: 0 8px; |
|
padding: 0 2px; |
|
border-radius: $borderRadius; |
|
|
|
&:hover { |
|
background-color: #f1f1f1; |
|
} |
|
|
|
.menu-item { |
|
padding: 0 3px; |
|
} |
|
.arrow-btn { |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
cursor: pointer; |
|
} |
|
} |
|
.title { |
|
height: 30px; |
|
margin-left: 2px; |
|
font-size: 13px; |
|
|
|
.title-input { |
|
width: 200px; |
|
height: 100%; |
|
padding-left: 0; |
|
padding-right: 0; |
|
|
|
::v-deep(input) { |
|
height: 28px; |
|
line-height: 28px; |
|
} |
|
} |
|
.title-text { |
|
min-width: 20px; |
|
max-width: 400px; |
|
line-height: 30px; |
|
padding: 0 6px; |
|
border-radius: $borderRadius; |
|
cursor: pointer; |
|
|
|
@include ellipsis-oneline(); |
|
|
|
&:hover { |
|
background-color: #f1f1f1; |
|
} |
|
} |
|
} |
|
.github-link { |
|
display: inline-block; |
|
height: 30px; |
|
} |
|
</style> |