|
<template> |
|
<div class="writing-board-tool"> |
|
<div class="writing-board-wrap" |
|
:style="{ |
|
width: slideWidth + 'px', |
|
height: slideHeight + 'px', |
|
}" |
|
> |
|
<WritingBoard |
|
ref="writingBoardRef" |
|
:color="writingBoardColor" |
|
:blackboard="blackboard" |
|
:model="writingBoardModel" |
|
:penSize="penSize" |
|
:markSize="markSize" |
|
:rubberSize="rubberSize" |
|
:shapeSize="shapeSize" |
|
:shapeType="shapeType" |
|
@end="hanldeWritingEnd()" |
|
/> |
|
</div> |
|
|
|
<MoveablePanel |
|
class="tools-panel" |
|
:width="510" |
|
:height="50" |
|
:left="left" |
|
:top="top" |
|
> |
|
<div class="tools" @mousedown.stop> |
|
<div class="tool-content"> |
|
<Popover placement="top" trigger="manual" :value="sizePopoverType === 'pen'" @hide="sizePopoverType = ''"> |
|
<template #content> |
|
<div class="setting"> |
|
<div class="label">墨迹粗细:</div> |
|
<Slider class="size-slider" :min="4" :max="10" :step="2" v-model:value="penSize" /> |
|
</div> |
|
</template> |
|
<div class="btn" :class="{ 'active': writingBoardModel === 'pen' }" v-tooltip="'画笔'" @click="changeModel('pen')"> |
|
<IconWrite class="icon" /> |
|
</div> |
|
</Popover> |
|
<Popover placement="top" trigger="manual" :value="sizePopoverType === 'shape'" @hide="sizePopoverType = ''"> |
|
<template #content> |
|
<div class="setting shape"> |
|
<div class="shapes"> |
|
<IconSquare class="icon" :class="{ 'active': shapeType === 'rect' }" @click="shapeType = 'rect'" /> |
|
<IconRound class="icon" :class="{ 'active': shapeType === 'circle' }" @click="shapeType = 'circle'" /> |
|
<IconArrowRight class="icon" :class="{ 'active': shapeType === 'arrow' }" @click="shapeType = 'arrow'" /> |
|
</div> |
|
<Divider type="vertical" /> |
|
<div class="label">墨迹粗细:</div> |
|
<Slider class="size-slider" :min="2" :max="8" :step="2" v-model:value="shapeSize" /> |
|
</div> |
|
</template> |
|
<div class="btn" :class="{ 'active': writingBoardModel === 'shape' }" v-tooltip="'形状'" @click="changeModel('shape')"> |
|
<IconGraphicDesign class="icon" /> |
|
</div> |
|
</Popover> |
|
<Popover placement="top" trigger="manual" :value="sizePopoverType === 'mark'" @hide="sizePopoverType = ''"> |
|
<template #content> |
|
<div class="setting"> |
|
<div class="label">墨迹粗细:</div> |
|
<Slider class="size-slider" :min="16" :max="40" :step="4" v-model:value="markSize" /> |
|
</div> |
|
</template> |
|
<div class="btn" :class="{ 'active': writingBoardModel === 'mark' }" v-tooltip="'荧光笔'" @click="changeModel('mark')"> |
|
<IconHighLight class="icon" /> |
|
</div> |
|
</Popover> |
|
<Popover placement="top" trigger="manual" :value="sizePopoverType === 'eraser'" @hide="sizePopoverType = ''"> |
|
<template #content> |
|
<div class="setting"> |
|
<div class="label">橡皮大小:</div> |
|
<Slider class="size-slider" :min="20" :max="200" :step="20" v-model:value="rubberSize" /> |
|
</div> |
|
</template> |
|
<div class="btn" :class="{ 'active': writingBoardModel === 'eraser' }" v-tooltip="'橡皮擦'" @click="changeModel('eraser')"> |
|
<IconErase class="icon" /> |
|
</div> |
|
</Popover> |
|
<div class="btn" v-tooltip="'清除墨迹'" @click="clearCanvas()"> |
|
<IconClear class="icon" /> |
|
</div> |
|
<div class="btn" :class="{ 'active': blackboard }" v-tooltip="'黑板'" @click="blackboard = !blackboard"> |
|
<IconFill class="icon" /> |
|
</div> |
|
<div class="colors"> |
|
<div |
|
class="color" |
|
:class="{ 'active': color === writingBoardColor, 'white': color === '#ffffff' }" |
|
v-for="color in writingBoardColors" |
|
:key="color" |
|
:style="{ backgroundColor: color }" |
|
@click="changeColor(color)" |
|
></div> |
|
</div> |
|
</div> |
|
<div class="btn close" v-tooltip="'关闭画笔'" @click="closeWritingBoard()"> |
|
<IconClose class="icon" /> |
|
</div> |
|
</div> |
|
</MoveablePanel> |
|
</div> |
|
</template> |
|
|
|
<script lang="ts" setup> |
|
import { ref, watch } from 'vue' |
|
import { storeToRefs } from 'pinia' |
|
import { useSlidesStore } from '@/store' |
|
import { db } from '@/utils/database' |
|
|
|
import WritingBoard from '@/components/WritingBoard.vue' |
|
import MoveablePanel from '@/components/MoveablePanel.vue' |
|
import Slider from '@/components/Slider.vue' |
|
import Popover from '@/components/Popover.vue' |
|
import Divider from '@/components//Divider.vue' |
|
|
|
const writingBoardColors = ['#000000', '#ffffff', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c', '#ffff3a'] |
|
|
|
type WritingBoardModel = 'pen' | 'mark' | 'eraser' | 'shape' |
|
|
|
withDefaults(defineProps<{ |
|
slideWidth: number |
|
slideHeight: number |
|
left?: number |
|
top?: number |
|
}>(), { |
|
left: -5, |
|
top: -5, |
|
}) |
|
|
|
const emit = defineEmits<{ |
|
(event: 'close'): void |
|
}>() |
|
|
|
const { currentSlide } = storeToRefs(useSlidesStore()) |
|
|
|
const writingBoardRef = ref<InstanceType<typeof WritingBoard>>() |
|
const writingBoardColor = ref('#e2534d') |
|
const writingBoardModel = ref<WritingBoardModel>('pen') |
|
const blackboard = ref(false) |
|
const sizePopoverType = ref<'' | WritingBoardModel>('') |
|
const shapeType = ref<'rect' | 'circle' | 'arrow'>('rect') |
|
|
|
const penSize = ref(6) |
|
const markSize = ref(24) |
|
const rubberSize = ref(80) |
|
const shapeSize = ref(4) |
|
|
|
const changeModel = (model: WritingBoardModel) => { |
|
writingBoardModel.value = model |
|
sizePopoverType.value = sizePopoverType.value === model ? '' : model |
|
} |
|
|
|
// 清除画布上的墨迹 |
|
const clearCanvas = () => { |
|
writingBoardRef.value!.clearCanvas() |
|
} |
|
|
|
// 修改画笔颜色,如果当前处于橡皮状态则先切换到画笔状态 |
|
const changeColor = (color: string) => { |
|
if (writingBoardModel.value === 'eraser') writingBoardModel.value = 'pen' |
|
writingBoardColor.value = color |
|
} |
|
|
|
// 关闭写字板 |
|
const closeWritingBoard = () => { |
|
emit('close') |
|
} |
|
|
|
// 打开画笔工具或切换页面时,将数据库中存储的墨迹绘制到画布上 |
|
watch(currentSlide, () => { |
|
db.writingBoardImgs.where('id').equals(currentSlide.value.id).toArray().then(ret => { |
|
const currentImg = ret[0] |
|
writingBoardRef.value!.setImageDataURL(currentImg?.dataURL || '') |
|
}) |
|
}, { immediate: true }) |
|
|
|
// 每次绘制完成后将绘制完的图片更新到数据库 |
|
const hanldeWritingEnd = () => { |
|
const dataURL = writingBoardRef.value!.getImageDataURL() |
|
if (!dataURL) return |
|
|
|
db.writingBoardImgs.where('id').equals(currentSlide.value.id).toArray().then(ret => { |
|
const currentImg = ret[0] |
|
if (currentImg) db.writingBoardImgs.update(currentImg, { dataURL }) |
|
else db.writingBoardImgs.add({ id: currentSlide.value.id, dataURL }) |
|
}) |
|
} |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
.writing-board-tool { |
|
font-size: 12px; |
|
z-index: 10; |
|
@include absolute-0(); |
|
|
|
.writing-board-wrap { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
} |
|
|
|
.tools { |
|
width: 100%; |
|
height: 100%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: space-between; |
|
} |
|
.tool-content { |
|
display: flex; |
|
align-items: center; |
|
} |
|
.btn { |
|
padding: 5px; |
|
margin-right: 5px; |
|
border-radius: $borderRadius; |
|
cursor: pointer; |
|
|
|
&:hover { |
|
color: $themeColor; |
|
} |
|
&.active { |
|
background-color: rgba($color: $themeColor, $alpha: .5); |
|
color: #fff; |
|
} |
|
&.close { |
|
margin-right: 0; |
|
margin-left: 5px; |
|
} |
|
} |
|
.icon { |
|
font-size: 20px; |
|
} |
|
.colors { |
|
display: flex; |
|
padding: 0 5px; |
|
} |
|
.color { |
|
width: 16px; |
|
height: 16px; |
|
border-radius: $borderRadius; |
|
cursor: pointer; |
|
|
|
&:hover { |
|
transform: scale(1.15); |
|
} |
|
&.active { |
|
transform: scale(1.3); |
|
} |
|
&.white { |
|
border: 1px solid #f1f1f1; |
|
} |
|
|
|
& + .color { |
|
margin-left: 8px; |
|
} |
|
} |
|
} |
|
.setting { |
|
width: 200px; |
|
display: flex; |
|
align-items: center; |
|
user-select: none; |
|
font-size: 13px; |
|
|
|
&.shape { |
|
width: 280px; |
|
} |
|
|
|
.shapes { |
|
display: flex; |
|
align-items: center; |
|
|
|
.icon { |
|
font-size: 20px; |
|
cursor: pointer; |
|
|
|
& + .icon { |
|
margin-left: 6px; |
|
} |
|
|
|
&.active { |
|
color: $themeColor; |
|
} |
|
} |
|
} |
|
|
|
.label { |
|
width: 70px; |
|
} |
|
.size-slider { |
|
flex: 1; |
|
} |
|
} |
|
</style> |