123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- <template>
- <div class="canvas-tool">
- <div class="left-handler">
- <div class="leftHandler-item" :class="{ disable: !canUndo }" v-tooltip="'撤销(Ctrl + Z)'" @click="undo()">
- <img src="./imgs/cx.png" alt="" />
- <div>撤销</div>
- </div>
- <div class="leftHandler-item" :class="{ disable: !canRedo }" v-tooltip="'恢复(Ctrl + Y)'" @click="redo()">
- <img src="./imgs/hf.png" alt="" />
- <div>恢复</div>
- </div>
- <div class="line"></div>
- <div class="leftHandler-item" :class="{ active: showNotesPanel }" v-tooltip="'批注'" @click="toggleNotesPanel()">
- <img src="./imgs/pz.png" alt="" />
- <div>批注</div>
- </div>
- <div class="leftHandler-item" :class="{ active: showSelectPanel }" v-tooltip="'选择'" @click="toggleSelectPanel()">
- <img src="./imgs/xz.png" alt="" />
- <div>选择</div>
- </div>
- <div class="leftHandler-item" :class="{ active: showSearchPanel }" v-tooltip="'查找/替换(Ctrl + F)'" @click="toggleSraechPanel()">
- <img src="./imgs/cz.png" alt="" />
- <div>查找</div>
- </div>
- <div class="line"></div>
- <Popover trigger="click" center>
- <template #content>
- <PopoverMenuItem @click="enterScreeningFromStart()">从头开始</PopoverMenuItem>
- <PopoverMenuItem @click="enterScreening()">从当前页开始</PopoverMenuItem>
- </template>
- <div class="arrow-btn">
- <div>播放</div>
- </div>
- </Popover>
- </div>
- <div class="add-element-handler">
- <FileInput @change="files => insertImageElement(files)">
- <div class="handler-item">
- <img class="itemImg" src="./imgs/sctp.png" alt="" />
- <div class="tit">图片</div>
- </div>
- </FileInput>
- <ElUpload
- action=""
- :show-file-list="false"
- accept=".mp4,.avi,.flv,.mp3,.wav,.m4a"
- :http-request="
- (fileData: any) => {
- handleUpload(fileData)
- return undefined
- }
- "
- >
- <div class="handler-item">
- <img class="itemImg" src="./imgs/scysp.png" alt="" />
- <div class="tit">音视频</div>
- </div>
- </ElUpload>
- <div class="handler-item" @click="cloudCoachVisible = true">
- <img class="itemImg" src="./imgs/yp.png" alt="" />
- <div class="tit">乐谱</div>
- </div>
- <!-- <div class="handler-item">
- <img class="itemImg" src="./imgs/jzlx.png" alt="" />
- <div class="tit">节奏练习</div>
- </div>
- <div class="handler-item">
- <img class="itemImg" src="./imgs/tylx.png" alt="" />
- <div class="tit">听音练习</div>
- </div>
- <div class="handler-item">
- <img class="itemImg" src="./imgs/zyk.png" alt="" />
- <div class="tit">资源库</div>
- </div> -->
- <div class="handler-item" @click="drawText()" :class="{ active: creatingElement?.type === 'text' }">
- <img class="itemImg" src="./imgs/wz.png" alt="" />
- <Popover trigger="click" v-model:value="textTypeSelectVisible" :offset="10">
- <template #content>
- <PopoverMenuItem
- center
- @click="
- () => {
- drawText()
- textTypeSelectVisible = false
- }
- "
- ><IconTextRotationNone /> 横向文本框</PopoverMenuItem
- >
- <PopoverMenuItem
- center
- @click="
- () => {
- drawText(true)
- textTypeSelectVisible = false
- }
- "
- ><IconTextRotationDown /> 竖向文本框</PopoverMenuItem
- >
- </template>
- <div class="charTit tit">
- <div>文字</div>
- <img src="./imgs/jiantou.png" alt="" />
- </div>
- </Popover>
- </div>
- <div class="handler-item" :class="{ active: creatingCustomShape || creatingElement?.type === 'shape' }" @click="shapePoolVisible = true">
- <Popover trigger="click" v-model:value="shapePoolVisible" :offset="10">
- <template #content>
- <ShapePool @select="shape => drawShape(shape)" />
- </template>
- <img class="itemImg" src="./imgs/xz1.png" alt="" />
- </Popover>
- <Popover trigger="click" v-model:value="shapeMenuVisible" :offset="10" @click.stop>
- <template #content>
- <PopoverMenuItem
- center
- @click="
- () => {
- drawCustomShape()
- shapeMenuVisible = false
- }
- "
- >自由绘制</PopoverMenuItem
- >
- </template>
- <div class="charTit tit">
- <div>形状</div>
- <img src="./imgs/jiantou.png" alt="" />
- </div>
- </Popover>
- </div>
- <div class="handler-item" :class="{ active: creatingElement?.type === 'line' }" @click="linePoolVisible = true">
- <img class="itemImg" src="./imgs/xt.png" alt="" />
- <Popover trigger="click" v-model:value="linePoolVisible" :offset="10" @click.stop>
- <template #content>
- <LinePool @select="line => drawLine(line)" />
- </template>
- <div class="tit">线条</div>
- </Popover>
- </div>
- <div class="handler-item" @click="moreToolsVisible = true">
- <img class="itemImg" src="./imgs/gdgj.png" alt="" />
- <Popover trigger="click" v-model:value="moreToolsVisible" :offset="10" @click.stop>
- <template #content>
- <PopoverMenuItem @click="chartPoolVisible = true">
- <Popover trigger="click" v-model:value="chartPoolVisible" placement="right" :offsetOne="50" :offset="36">
- <template #content>
- <ChartPool
- @select="
- chart => {
- createChartElement(chart)
- chartPoolVisible = false
- }
- "
- />
- </template>
- <div class="menuItem">
- <img src="./imgs/tb.png" alt="" />
- <div class="tit">图表</div>
- </div>
- </Popover>
- </PopoverMenuItem>
- <PopoverMenuItem @click="tableGeneratorVisible = true">
- <div class="menuItem">
- <img src="./imgs/bg.png" alt="" />
- <div class="tit">表格</div>
- </div>
- </PopoverMenuItem>
- <PopoverMenuItem
- @click="
- () => {
- moreToolsVisible = false
- latexEditorVisible = true
- }
- "
- >
- <div class="menuItem">
- <img src="./imgs/gs.png" alt="" />
- <div class="tit">公式</div>
- </div>
- </PopoverMenuItem>
- </template>
- <div class="tit">更多工具</div>
- </Popover>
- <Popover trigger="click" v-model:value="tableGeneratorVisible" placement="right" :offsetOne="200" :offset="70">
- <template #content>
- <TableGenerator
- @close="tableGeneratorVisible = false"
- @insert="
- ({ row, col }) => {
- createTableElement(row, col)
- tableGeneratorVisible = false
- }
- "
- />
- </template>
- </Popover>
- </div>
- </div>
- <div class="right-handler">
- <IconMinus class="rightHandler-item" 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()">适应屏幕</PopoverMenuItem>
- </template>
- <div class="text" :class="{ canvasScaleVisible: canvasScaleVisible }">{{ canvasScalePercentage }}</div>
- </Popover>
- <IconPlus class="rightHandler-item" v-tooltip="'画布放大(Ctrl + =)'" @click="scaleCanvas('+')" />
- <IconFullScreen class="rightHandler-item resetCanvas" 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>
- <Modal
- :contentStyle="{
- width: '70%',
- minWidth: '1200px',
- height: '86%',
- boxShadow: '0px 2px 10px 0px rgba(0,0,0,0.08)',
- borderRadius: '16px',
- border: '1px solid #DEDEDE',
- padding: '0'
- }"
- v-model:visible="cloudCoachVisible"
- >
- <cloudCoachList
- @update="handleCloudCoach"
- @close="
- () => {
- cloudCoachVisible = 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 useScreening from "@/hooks/useScreening"
- import ShapePool from "./ShapePool.vue"
- import LinePool from "./LinePool.vue"
- import ChartPool from "./ChartPool.vue"
- import TableGenerator from "./TableGenerator.vue"
- import LaTeXEditor from "@/components/LaTeXEditor/index.vue"
- import FileInput from "@/components/FileInput.vue"
- import Modal from "@/components/Modal.vue"
- import Popover from "@/components/Popover.vue"
- import PopoverMenuItem from "@/components/PopoverMenuItem.vue"
- import { ElUpload, ElMessage, type UploadRequestOptions } from "element-plus"
- import cloudCoachList from "@/views/components/element/cloudCoachElement/cloudCoachList"
- import fileUpload from "@/utils/oss-file-upload"
- import usePptWork from "@/store/pptWork"
- const usePptWorkHook = usePptWork()
- 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 { enterScreening, enterScreeningFromStart } = useScreening()
- const applyCanvasPresetScale = (value: number) => {
- setCanvasScalePercentage(value)
- canvasScaleVisible.value = false
- }
- const {
- createImageElement,
- createChartElement,
- createTableElement,
- createLatexElement,
- createVideoElement,
- createAudioElement,
- createCloudCoachElement
- } = 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 latexEditorVisible = ref(false)
- const textTypeSelectVisible = ref(false)
- const shapeMenuVisible = ref(false)
- const cloudCoachVisible = ref(false)
- const moreToolsVisible = ref(false)
- // 音视频
- function handleUpload(fileData: UploadRequestOptions) {
- const type = /\.(mp3|wav|m4a)$/i.test(fileData.file.name) ? "audio" : "video"
- fileUpload(fileData.file.name, fileData.file, `${usePptWorkHook.id}/`)
- .then(res => {
- if (type === "audio") {
- createAudioElement(res)
- } else {
- createVideoElement(res)
- }
- })
- .catch(() => {
- ElMessage({
- showClose: true,
- message: "上传失败!",
- type: "error"
- })
- })
- }
- // 处理云教练创建
- function handleCloudCoach(id: string, name: string) {
- createCloudCoachElement(id, name)
- cloudCoachVisible.value = 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 24px;
- user-select: none;
- }
- .left-handler {
- margin-left: -6px;
- display: flex;
- align-items: center;
- .leftHandler-item {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- margin-right: 8px;
- padding: 4px 6px;
- cursor: pointer;
- &.disable {
- opacity: 0.5;
- cursor: not-allowed;
- background-color: transparent !important;
- }
- &:hover,
- &.active {
- background: rgba(34, 71, 133, 0.08);
- border-radius: 6px;
- }
- & > img {
- width: 20px;
- height: 20px;
- }
- & > div {
- margin-top: 4px;
- font-weight: 400;
- font-size: 12px;
- color: #131415;
- line-height: 17px;
- }
- }
- .line {
- margin-left: 6px;
- margin-right: 14px;
- width: 1px;
- height: calc(100% - 20px);
- background-color: $borderColor;
- }
- .arrow-btn {
- margin-left: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 60px;
- height: 32px;
- background: linear-gradient(312deg, #1b7af8 0%, #3cbbff 100%);
- border-radius: 6px;
- font-weight: 600;
- font-size: 14px;
- color: #ffffff;
- line-height: 20px;
- cursor: pointer;
- &:hover {
- opacity: 0.8;
- }
- }
- }
- .add-element-handler {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- display: flex;
- .handler-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- width: 68px;
- height: 53px;
- &:hover,
- &.active {
- background: rgba(34, 71, 133, 0.08);
- border-radius: 6px;
- }
- .itemImg {
- width: 20px;
- height: 20px;
- }
- .tit {
- margin-top: 4px;
- font-weight: 400;
- font-size: 12px;
- color: #131415;
- line-height: 17px;
- }
- .charTit {
- display: flex;
- align-items: center;
- padding: 0 6px;
- border-radius: 4px;
- &:hover {
- background: rgba(34, 71, 133, 0.1);
- }
- > img {
- margin-left: 4px;
- width: 7px;
- height: 4px;
- }
- }
- }
- }
- .menuItem {
- display: flex;
- align-items: center;
- & > img {
- width: 20px;
- height: 20px;
- }
- & > .tit {
- margin-left: 16px;
- font-weight: 400;
- font-size: 14px;
- color: #333333;
- }
- }
- .right-handler {
- display: flex;
- align-items: center;
- .text {
- margin: 0 8px;
- width: 57px;
- height: 32px;
- line-height: 32px;
- text-align: center;
- cursor: pointer;
- &:hover,
- &.canvasScaleVisible {
- border-radius: 6px;
- background-color: rgba(34, 71, 133, 0.08);
- }
- }
- .rightHandler-item {
- font-size: 20px;
- color: #131415;
- cursor: pointer;
- &:hover {
- opacity: 0.5;
- }
- }
- .resetCanvas {
- margin-left: 20px;
- }
- }
- </style>
|