CatPtain's picture
Upload 339 files
89ce340 verified
raw
history blame
11.4 kB
<template>
<div class="canvas-tool">
<div class="left-handler">
<IconBack class="handler-item" :class="{ 'disable': !canUndo }" v-tooltip="'撤销(Ctrl + Z)'" @click="undo()" />
<IconNext class="handler-item" :class="{ 'disable': !canRedo }" v-tooltip="'重做(Ctrl + Y)'" @click="redo()" />
<div class="more">
<Divider type="vertical" style="height: 20px;" />
<Popover class="more-icon" trigger="click" v-model:value="moreVisible" :offset="10">
<template #content>
<PopoverMenuItem center @click="toggleNotesPanel(); moreVisible = false">批注面板</PopoverMenuItem>
<PopoverMenuItem center @click="toggleSelectPanel(); moreVisible = false">选择窗格</PopoverMenuItem>
<PopoverMenuItem center @click="toggleSraechPanel(); moreVisible = false">查找替换</PopoverMenuItem>
</template>
<IconMore class="handler-item" />
</Popover>
<IconComment class="handler-item" :class="{ 'active': showNotesPanel }" v-tooltip="'批注面板'" @click="toggleNotesPanel()" />
<IconMoveOne class="handler-item" :class="{ 'active': showSelectPanel }" v-tooltip="'选择窗格'" @click="toggleSelectPanel()" />
<IconSearch class="handler-item" :class="{ 'active': showSearchPanel }" v-tooltip="'查找/替换(Ctrl + F)'" @click="toggleSraechPanel()" />
</div>
</div>
<div class="add-element-handler">
<div class="handler-item group-btn" v-tooltip="'插入文字'">
<IconFontSize class="icon" :class="{ 'active': creatingElement?.type === 'text' }" @click="drawText()" />
<Popover trigger="click" v-model:value="textTypeSelectVisible" style="height: 100%;" :offset="10">
<template #content>
<PopoverMenuItem center @click="() => { drawText(); textTypeSelectVisible = false }"><IconTextRotationNone /> 横向文本框</PopoverMenuItem>
<PopoverMenuItem center @click="() => { drawText(true); textTypeSelectVisible = false }"><IconTextRotationDown /> 竖向文本框</PopoverMenuItem>
</template>
<IconDown class="arrow" />
</Popover>
</div>
<div class="handler-item group-btn" v-tooltip="'插入形状'" :offset="10">
<Popover trigger="click" style="height: 100%;" v-model:value="shapePoolVisible" :offset="10">
<template #content>
<ShapePool @select="shape => drawShape(shape)" />
</template>
<IconGraphicDesign class="icon" :class="{ 'active': creatingCustomShape || creatingElement?.type === 'shape' }" />
</Popover>
<Popover trigger="click" v-model:value="shapeMenuVisible" style="height: 100%;" :offset="10">
<template #content>
<PopoverMenuItem center @click="() => { drawCustomShape(); shapeMenuVisible = false }">自由绘制</PopoverMenuItem>
</template>
<IconDown class="arrow" />
</Popover>
</div>
<FileInput @change="files => insertImageElement(files)">
<IconPicture class="handler-item" v-tooltip="'插入图片'" />
</FileInput>
<Popover trigger="click" v-model:value="linePoolVisible" :offset="10">
<template #content>
<LinePool @select="line => drawLine(line)" />
</template>
<IconConnection class="handler-item" :class="{ 'active': creatingElement?.type === 'line' }" v-tooltip="'插入线条'" />
</Popover>
<Popover trigger="click" v-model:value="chartPoolVisible" :offset="10">
<template #content>
<ChartPool @select="chart => { createChartElement(chart); chartPoolVisible = false }" />
</template>
<IconChartProportion class="handler-item" v-tooltip="'插入图表'" />
</Popover>
<Popover trigger="click" v-model:value="tableGeneratorVisible" :offset="10">
<template #content>
<TableGenerator
@close="tableGeneratorVisible = false"
@insert="({ row, col }) => { createTableElement(row, col); tableGeneratorVisible = false }"
/>
</template>
<IconInsertTable class="handler-item" v-tooltip="'插入表格'" />
</Popover>
<IconFormula class="handler-item" v-tooltip="'插入公式'" @click="latexEditorVisible = true" />
<Popover trigger="click" v-model:value="mediaInputVisible" :offset="10">
<template #content>
<MediaInput
@close="mediaInputVisible = false"
@insertVideo="src => { createVideoElement(src); mediaInputVisible = false }"
@insertAudio="src => { createAudioElement(src); mediaInputVisible = false }"
/>
</template>
<IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" />
</Popover>
</div>
<div class="right-handler">
<IconMinus class="handler-item viewport-size" v-tooltip="'画布缩小(Ctrl + -)'" @click="scaleCanvas('-')" />
<Popover trigger="click" v-model:value="canvasScaleVisible">
<template #content>
<PopoverMenuItem
center
v-for="item in canvasScalePresetList"
:key="item"
@click="applyCanvasPresetScale(item)"
>{{item}}%</PopoverMenuItem>
<PopoverMenuItem center @click="resetCanvas(); canvasScaleVisible = false">适应屏幕</PopoverMenuItem>
</template>
<span class="text">{{canvasScalePercentage}}</span>
</Popover>
<IconPlus class="handler-item viewport-size" v-tooltip="'画布放大(Ctrl + =)'" @click="scaleCanvas('+')" />
<IconFullScreen class="handler-item viewport-size-adaptation" v-tooltip="'适应屏幕(Ctrl + 0)'" @click="resetCanvas()" />
</div>
<Modal
v-model:visible="latexEditorVisible"
:width="880"
>
<LaTeXEditor
@close="latexEditorVisible = false"
@update="data => { createLatexElement(data); latexEditorVisible = false }"
/>
</Modal>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore, useSnapshotStore } from '@/store'
import { getImageDataURL } from '@/utils/image'
import type { ShapePoolItem } from '@/configs/shapes'
import type { LinePoolItem } from '@/configs/lines'
import useScaleCanvas from '@/hooks/useScaleCanvas'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import useCreateElement from '@/hooks/useCreateElement'
import ShapePool from './ShapePool.vue'
import LinePool from './LinePool.vue'
import ChartPool from './ChartPool.vue'
import TableGenerator from './TableGenerator.vue'
import MediaInput from './MediaInput.vue'
import LaTeXEditor from '@/components/LaTeXEditor/index.vue'
import FileInput from '@/components/FileInput.vue'
import Modal from '@/components/Modal.vue'
import Divider from '@/components/Divider.vue'
import Popover from '@/components/Popover.vue'
import PopoverMenuItem from '@/components/PopoverMenuItem.vue'
const mainStore = useMainStore()
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
const { redo, undo } = useHistorySnapshot()
const {
scaleCanvas,
setCanvasScalePercentage,
resetCanvas,
canvasScalePercentage,
} = useScaleCanvas()
const canvasScalePresetList = [200, 150, 125, 100, 75, 50]
const canvasScaleVisible = ref(false)
const applyCanvasPresetScale = (value: number) => {
setCanvasScalePercentage(value)
canvasScaleVisible.value = false
}
const {
createImageElement,
createChartElement,
createTableElement,
createLatexElement,
createVideoElement,
createAudioElement,
} = useCreateElement()
const insertImageElement = (files: FileList) => {
const imageFile = files[0]
if (!imageFile) return
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
}
const shapePoolVisible = ref(false)
const linePoolVisible = ref(false)
const chartPoolVisible = ref(false)
const tableGeneratorVisible = ref(false)
const mediaInputVisible = ref(false)
const latexEditorVisible = ref(false)
const textTypeSelectVisible = ref(false)
const shapeMenuVisible = ref(false)
const moreVisible = ref(false)
// 绘制文字范围
const drawText = (vertical = false) => {
mainStore.setCreatingElement({
type: 'text',
vertical,
})
}
// 绘制形状范围
const drawShape = (shape: ShapePoolItem) => {
mainStore.setCreatingElement({
type: 'shape',
data: shape,
})
shapePoolVisible.value = false
}
// 绘制自定义任意多边形
const drawCustomShape = () => {
mainStore.setCreatingCustomShapeState(true)
shapePoolVisible.value = false
}
// 绘制线条路径
const drawLine = (line: LinePoolItem) => {
mainStore.setCreatingElement({
type: 'line',
data: line,
})
linePoolVisible.value = false
}
// 打开选择面板
const toggleSelectPanel = () => {
mainStore.setSelectPanelState(!showSelectPanel.value)
}
// 打开搜索替换面板
const toggleSraechPanel = () => {
mainStore.setSearchPanelState(!showSearchPanel.value)
}
// 打开批注面板
const toggleNotesPanel = () => {
mainStore.setNotesPanelState(!showNotesPanel.value)
}
</script>
<style lang="scss" scoped>
.canvas-tool {
position: relative;
border-bottom: 1px solid $borderColor;
background-color: #fff;
display: flex;
justify-content: space-between;
padding: 0 10px;
font-size: 13px;
user-select: none;
}
.left-handler, .more {
display: flex;
align-items: center;
}
.more-icon {
display: none;
}
.add-element-handler {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
.handler-item {
width: 32px;
&:not(.group-btn):hover {
background-color: #f1f1f1;
}
&.active {
color: $themeColor;
}
&.group-btn {
width: auto;
margin-right: 5px;
&:hover {
background-color: #f3f3f3;
}
.icon, .arrow {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.icon {
width: 26px;
padding: 0 2px;
&:hover {
background-color: #e9e9e9;
}
&.active {
color: $themeColor;
}
}
.arrow {
font-size: 12px;
&:hover {
background-color: #e9e9e9;
}
}
}
}
}
.handler-item {
height: 30px;
font-size: 14px;
margin: 0 2px;
display: flex;
justify-content: center;
align-items: center;
border-radius: $borderRadius;
overflow: hidden;
cursor: pointer;
&.disable {
opacity: .5;
}
}
.left-handler, .right-handler {
.handler-item {
padding: 0 8px;
&.active,
&:not(.disable):hover {
background-color: #f1f1f1;
}
}
}
.right-handler {
display: flex;
align-items: center;
.text {
display: inline-block;
width: 40px;
text-align: center;
cursor: pointer;
}
.viewport-size {
font-size: 13px;
}
}
@media screen and (width <= 1200px) {
.right-handler .text {
display: none;
}
.more > .handler-item {
display: none;
}
.more-icon {
display: block;
}
}
@media screen and (width <= 1000px) {
.left-handler, .right-handler {
display: none;
}
}
</style>