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
aeee2b25d2
commit
0d0e729944
@ -67,6 +67,7 @@ npm run dev
|
||||
- 元素动画(入场、退场、强调)
|
||||
- 选择面板(隐藏元素、层级排序、元素命名)
|
||||
- 查找/替换
|
||||
- 批注
|
||||
### 幻灯片元素编辑
|
||||
- 元素添加、删除
|
||||
- 元素复制粘贴
|
||||
|
@ -3,8 +3,8 @@
|
||||
class="moveable-panel"
|
||||
ref="moveablePanelRef"
|
||||
:style="{
|
||||
width: width + 'px',
|
||||
height: height ? height + 'px' : 'auto',
|
||||
width: w + 'px',
|
||||
height: h ? h + 'px' : 'auto',
|
||||
left: x + 'px',
|
||||
top: y + 'px',
|
||||
}"
|
||||
@ -23,6 +23,8 @@
|
||||
<div v-else class="content" @mousedown="$event => startMove($event)">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div class="resizer" v-if="resizeable" @mousedown="$event => startResize($event)"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -32,15 +34,25 @@ import { computed, onMounted, ref } from 'vue'
|
||||
const props = withDefaults(defineProps<{
|
||||
width: number
|
||||
height: number
|
||||
minWidth?: number
|
||||
minHeight?: number
|
||||
maxWidth?: number
|
||||
maxHeight?: number
|
||||
left?: number
|
||||
top?: number
|
||||
title?: string
|
||||
moveable?: boolean
|
||||
resizeable?: boolean
|
||||
}>(), {
|
||||
minWidth: 20,
|
||||
minHeight: 20,
|
||||
maxWidth: 500,
|
||||
maxHeight: 500,
|
||||
left: 10,
|
||||
top: 10,
|
||||
title: '',
|
||||
moveable: true,
|
||||
resizeable: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -49,12 +61,14 @@ const emit = defineEmits<{
|
||||
|
||||
const x = ref(0)
|
||||
const y = ref(0)
|
||||
const w = ref(0)
|
||||
const h = ref(0)
|
||||
const moveablePanelRef = ref<HTMLElement>()
|
||||
const realHeight = computed(() => {
|
||||
if (!props.height) {
|
||||
if (!h.value) {
|
||||
return moveablePanelRef.value?.clientHeight || 0
|
||||
}
|
||||
return props.height
|
||||
return h.value
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@ -63,6 +77,9 @@ onMounted(() => {
|
||||
|
||||
if (props.top >= 0) y.value = props.top
|
||||
else y.value = document.body.clientHeight + props.top - realHeight.value
|
||||
|
||||
w.value = props.width
|
||||
h.value = props.height
|
||||
})
|
||||
|
||||
const startMove = (e: MouseEvent) => {
|
||||
@ -90,7 +107,7 @@ const startMove = (e: MouseEvent) => {
|
||||
|
||||
if (left < 0) left = 0
|
||||
if (top < 0) top = 0
|
||||
if (left + props.width > windowWidth) left = windowWidth - props.width
|
||||
if (left + w.value > windowWidth) left = windowWidth - w.value
|
||||
if (top + realHeight.value > clientHeight) top = clientHeight - realHeight.value
|
||||
|
||||
x.value = left
|
||||
@ -103,6 +120,42 @@ const startMove = (e: MouseEvent) => {
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
|
||||
const startResize = (e: MouseEvent) => {
|
||||
if (!props.resizeable) return
|
||||
|
||||
let isMouseDown = true
|
||||
|
||||
const startPageX = e.pageX
|
||||
const startPageY = e.pageY
|
||||
|
||||
const originWidth = w.value
|
||||
const originHeight = h.value
|
||||
|
||||
document.onmousemove = e => {
|
||||
if (!isMouseDown) return
|
||||
|
||||
const moveX = e.pageX - startPageX
|
||||
const moveY = e.pageY - startPageY
|
||||
|
||||
let width = originWidth + moveX
|
||||
let height = originHeight + moveY
|
||||
|
||||
if (width < props.minWidth) width = props.minWidth
|
||||
if (height < props.minHeight) height = props.minHeight
|
||||
if (width > props.maxWidth) width = props.maxWidth
|
||||
if (height > props.maxHeight) height = props.maxHeight
|
||||
|
||||
w.value = width
|
||||
h.value = height
|
||||
}
|
||||
document.onmouseup = () => {
|
||||
isMouseDown = false
|
||||
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -116,6 +169,27 @@ const startMove = (e: MouseEvent) => {
|
||||
flex-direction: column;
|
||||
z-index: 999;
|
||||
}
|
||||
.resizer {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
cursor: se-resize;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: center;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 6px solid transparent;
|
||||
border-left-color: #e1e1e1;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
|
@ -10,7 +10,12 @@
|
||||
:value="value"
|
||||
:rows="rows"
|
||||
:placeholder="placeholder"
|
||||
:style="{
|
||||
padding: padding ? `${padding}px` : '10px',
|
||||
}"
|
||||
@input="$event => handleInput($event)"
|
||||
@focus="$event => emit('focus', $event)"
|
||||
@blur="$event => emit('blur', $event)"
|
||||
></textarea>
|
||||
</template>
|
||||
|
||||
@ -20,6 +25,7 @@ import { ref } from 'vue'
|
||||
withDefaults(defineProps<{
|
||||
value: string
|
||||
rows?: number
|
||||
padding?: number
|
||||
disabled?: boolean
|
||||
resizable?: boolean
|
||||
placeholder?: string
|
||||
@ -32,6 +38,8 @@ withDefaults(defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:value', payload: string): void
|
||||
(event: 'focus', payload: FocusEvent): void
|
||||
(event: 'blur', payload: FocusEvent): void
|
||||
}>()
|
||||
|
||||
const handleInput = (e: Event) => {
|
||||
|
@ -120,6 +120,8 @@ import {
|
||||
CheckOne,
|
||||
CloseOne,
|
||||
Info,
|
||||
Comment,
|
||||
User,
|
||||
} from '@icon-park/vue-next'
|
||||
|
||||
export interface Icons {
|
||||
@ -245,6 +247,8 @@ export const icons: Icons = {
|
||||
IconCheckOne: CheckOne,
|
||||
IconCloseOne: CloseOne,
|
||||
IconInfo: Info,
|
||||
IconComment: Comment,
|
||||
IconUser: User,
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -37,6 +37,7 @@ export interface MainState {
|
||||
shapeFormatPainter: ShapeFormatPainter | null
|
||||
showSelectPanel: boolean
|
||||
showSearchPanel: boolean
|
||||
showNotesPanel: boolean
|
||||
}
|
||||
|
||||
const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
|
||||
@ -71,6 +72,7 @@ export const useMainStore = defineStore('main', {
|
||||
shapeFormatPainter: null, // 形状格式刷
|
||||
showSelectPanel: false, // 打开选择面板
|
||||
showSearchPanel: false, // 打开查找替换面板
|
||||
showNotesPanel: false, // 打开批注面板
|
||||
}),
|
||||
|
||||
getters: {
|
||||
@ -196,5 +198,9 @@ export const useMainStore = defineStore('main', {
|
||||
setSearchPanelState(show: boolean) {
|
||||
this.showSearchPanel = show
|
||||
},
|
||||
|
||||
setNotesPanelState(show: boolean) {
|
||||
this.showNotesPanel = show
|
||||
},
|
||||
},
|
||||
})
|
@ -656,6 +656,22 @@ export interface SlideBackground {
|
||||
|
||||
export type TurningMode = 'no' | 'fade' | 'slideX' | 'slideY' | 'random' | 'slideX3D' | 'slideY3D' | 'rotate' | 'scaleY' | 'scaleX' | 'scale' | 'scaleReverse'
|
||||
|
||||
export interface NoteReply {
|
||||
id: string
|
||||
content: string
|
||||
time: number
|
||||
user: string
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
id: string
|
||||
content: string
|
||||
time: number
|
||||
user: string
|
||||
elId?: string
|
||||
replies?: NoteReply[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 幻灯片页面
|
||||
*
|
||||
@ -663,6 +679,8 @@ export type TurningMode = 'no' | 'fade' | 'slideX' | 'slideY' | 'random' | 'slid
|
||||
*
|
||||
* elements: 元素集合
|
||||
*
|
||||
* notes: 批注
|
||||
*
|
||||
* remark?: 备注
|
||||
*
|
||||
* background?: 页面背景
|
||||
@ -674,6 +692,7 @@ export type TurningMode = 'no' | 'fade' | 'slideX' | 'slideY' | 'random' | 'slid
|
||||
export interface Slide {
|
||||
id: string
|
||||
elements: PPTElement[]
|
||||
notes: Note[]
|
||||
remark?: string
|
||||
background?: SlideBackground
|
||||
animations?: PPTAnimation[]
|
||||
|
@ -3,10 +3,13 @@
|
||||
<div class="left-handler">
|
||||
<IconBack class="handler-item" :class="{ 'disable': !canUndo }" v-tooltip="'撤销'" @click="undo()" />
|
||||
<IconNext class="handler-item" :class="{ 'disable': !canRedo }" v-tooltip="'重做'" @click="redo()" />
|
||||
<div class="more">
|
||||
<Divider type="vertical" style="height: 20px;" />
|
||||
<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="'查找/替换'" @click="toggleSraechPanel()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="add-element-handler">
|
||||
<div class="handler-item group-btn" v-tooltip="'插入文字'">
|
||||
@ -116,7 +119,7 @@ import Popover from '@/components/Popover.vue'
|
||||
import PopoverMenuItem from '@/components/PopoverMenuItem.vue'
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel } = storeToRefs(mainStore)
|
||||
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
|
||||
const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
|
||||
|
||||
const { redo, undo } = useHistorySnapshot()
|
||||
@ -199,6 +202,11 @@ const toggleSelectPanel = () => {
|
||||
const toggleSraechPanel = () => {
|
||||
mainStore.setSearchPanelState(!showSearchPanel.value)
|
||||
}
|
||||
|
||||
// 打开批注面板
|
||||
const toggleNotesPanel = () => {
|
||||
mainStore.setNotesPanelState(!showNotesPanel.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -212,7 +220,7 @@ const toggleSraechPanel = () => {
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
}
|
||||
.left-handler {
|
||||
.left-handler, .more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@ -311,8 +319,11 @@ const toggleSraechPanel = () => {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (width <= 1024px) {
|
||||
.text {
|
||||
@media screen and (width <= 1200px) {
|
||||
.right-handler .text {
|
||||
display: none;
|
||||
}
|
||||
.more {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
323
src/views/Editor/NotesPanel.vue
Normal file
323
src/views/Editor/NotesPanel.vue
Normal file
@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<MoveablePanel
|
||||
class="notes-panel"
|
||||
:width="300"
|
||||
:height="560"
|
||||
:title="`幻灯片${slideIndex + 1}的批注`"
|
||||
:left="-270"
|
||||
:top="90"
|
||||
:minWidth="300"
|
||||
:minHeight="400"
|
||||
:maxWidth="480"
|
||||
:maxHeight="780"
|
||||
resizeable
|
||||
@close="close()"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="notes">
|
||||
<div class="note" :class="{ 'active': activeNoteId === note.id }" v-for="note in notes" :key="note.id" @click="handleClickNote(note)">
|
||||
<div class="header note-header">
|
||||
<div class="user">
|
||||
<div class="avatar"><IconUser /></div>
|
||||
<div class="user-info">
|
||||
<div class="username">{{ note.user }}</div>
|
||||
<div class="time">{{ new Date(note.time).toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btns">
|
||||
<div class="btn reply" @click="replyNoteId = note.id">回复</div>
|
||||
<div class="btn delete" @click.stop="deleteNote(note.id)">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">{{ note.content }}</div>
|
||||
<div class="replies" v-if="note.replies?.length">
|
||||
<div class="reply-item" v-for="reply in note.replies" :key="reply.id">
|
||||
<div class="header reply-header">
|
||||
<div class="user">
|
||||
<div class="avatar"><IconUser /></div>
|
||||
<div class="user-info">
|
||||
<div class="username">{{ reply.user }}</div>
|
||||
<div class="time">{{ new Date(reply.time).toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btns">
|
||||
<div class="btn delete" @click.stop="deleteReply(note.id, reply.id)">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">{{ reply.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note-reply" v-if="replyNoteId === note.id">
|
||||
<TextArea :padding="6" v-model:value="replyContent" placeholder="输入回复内容" :rows="1" />
|
||||
<div class="reply-btns">
|
||||
<Button class="btn" size="small" @click="replyNoteId = ''">取消</Button>
|
||||
<Button class="btn" size="small" type="primary" @click="createNoteReply()">回复</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="empty" v-if="!notes.length">本页暂无批注</div>
|
||||
</div>
|
||||
<div class="send">
|
||||
<TextArea
|
||||
ref="textAreaRef"
|
||||
v-model:value="content"
|
||||
:padding="6"
|
||||
:placeholder="`输入批注(为${handleElementId ? '选中元素' : '当前页幻灯片' })`"
|
||||
:rows="2"
|
||||
@focus="replyNoteId = ''; activeNoteId = ''"
|
||||
/>
|
||||
<div class="footer">
|
||||
<Button class="btn" v-tooltip="'清空本页批注'" style="flex: 1" @click="clear()"><IconDelete /></Button>
|
||||
<Button type="primary" class="btn" style="flex: 12" @click="createNote()">添加批注</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MoveablePanel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import type { Note } from '@/types/slides'
|
||||
|
||||
import MoveablePanel from '@/components/MoveablePanel.vue'
|
||||
import TextArea from '@/components/TextArea.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
|
||||
const slidesStore = useSlidesStore()
|
||||
const mainStore = useMainStore()
|
||||
const { slideIndex, currentSlide } = storeToRefs(slidesStore)
|
||||
const { handleElementId } = storeToRefs(mainStore)
|
||||
|
||||
const content = ref('')
|
||||
const replyContent = ref('')
|
||||
const notes = computed(() => currentSlide.value?.notes || [])
|
||||
const activeNoteId = ref('')
|
||||
const replyNoteId = ref('')
|
||||
const textAreaRef = ref<InstanceType<typeof TextArea>>()
|
||||
|
||||
const createNote = () => {
|
||||
if (!content.value) {
|
||||
if (textAreaRef.value) textAreaRef.value.focus()
|
||||
return
|
||||
}
|
||||
|
||||
const newNote: Note = {
|
||||
id: nanoid(),
|
||||
content: content.value,
|
||||
time: new Date().getTime(),
|
||||
user: '测试用户',
|
||||
}
|
||||
if (handleElementId.value) newNote.elId = handleElementId.value
|
||||
|
||||
const newNotes = [
|
||||
...notes.value,
|
||||
newNote,
|
||||
]
|
||||
slidesStore.updateSlide({ notes: newNotes })
|
||||
|
||||
content.value = ''
|
||||
}
|
||||
|
||||
const deleteNote = (id: string) => {
|
||||
const newNotes = notes.value.filter(note => note.id !== id)
|
||||
slidesStore.updateSlide({ notes: newNotes })
|
||||
}
|
||||
|
||||
const createNoteReply = () => {
|
||||
if (!replyContent.value) return
|
||||
|
||||
const currentNote = notes.value.find(note => note.id === replyNoteId.value)
|
||||
if (!currentNote) return
|
||||
|
||||
const newReplies = [
|
||||
...currentNote.replies || [],
|
||||
{
|
||||
id: nanoid(),
|
||||
content: replyContent.value,
|
||||
time: new Date().getTime(),
|
||||
user: '测试用户',
|
||||
},
|
||||
]
|
||||
const newNote: Note = {
|
||||
...currentNote,
|
||||
replies: newReplies,
|
||||
}
|
||||
const newNotes = notes.value.map(note => note.id === replyNoteId.value ? newNote : note)
|
||||
slidesStore.updateSlide({ notes: newNotes })
|
||||
|
||||
replyContent.value = ''
|
||||
replyNoteId.value = ''
|
||||
}
|
||||
|
||||
const deleteReply = (noteId: string, replyId: string) => {
|
||||
const currentNote = notes.value.find(note => note.id === noteId)
|
||||
if (!currentNote || !currentNote.replies) return
|
||||
|
||||
const newReplies = currentNote.replies.filter(reply => reply.id !== replyId)
|
||||
const newNote: Note = {
|
||||
...currentNote,
|
||||
replies: newReplies,
|
||||
}
|
||||
const newNotes = notes.value.map(note => note.id === noteId ? newNote : note)
|
||||
slidesStore.updateSlide({ notes: newNotes })
|
||||
}
|
||||
|
||||
const handleClickNote = (note: Note) => {
|
||||
activeNoteId.value = note.id
|
||||
|
||||
if (note.elId) {
|
||||
const elIds = currentSlide.value.elements.map(item => item.id)
|
||||
if (elIds.includes(note.elId)) {
|
||||
mainStore.setActiveElementIdList([note.elId])
|
||||
}
|
||||
else mainStore.setActiveElementIdList([])
|
||||
}
|
||||
else mainStore.setActiveElementIdList([])
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
slidesStore.updateSlide({ notes: [] })
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
mainStore.setNotesPanelState(false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notes-panel {
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.notes {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
margin: 0 -10px;
|
||||
padding: 2px 12px;
|
||||
}
|
||||
.empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.note {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
|
||||
& + .note {
|
||||
margin-top: 10px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:hover {
|
||||
.btns {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background-color: #42ba97;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 14px;
|
||||
}
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
|
||||
.btn {
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: $themeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
.replies {
|
||||
margin-left: 20px;
|
||||
margin-top: 15px;
|
||||
|
||||
.reply-item {
|
||||
margin-top: 10px;
|
||||
|
||||
.content {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.note-reply {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.reply-btns {
|
||||
margin-top: 5px;
|
||||
text-align: right;
|
||||
|
||||
.btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
.send {
|
||||
height: 120px;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
|
||||
.footer {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
|
||||
.btn + .btn {
|
||||
margin-left: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -18,6 +18,7 @@
|
||||
|
||||
<SelectPanel v-if="showSelectPanel" />
|
||||
<SearchPanel v-if="showSearchPanel" />
|
||||
<NotesPanel v-if="showNotesPanel" />
|
||||
|
||||
<Modal
|
||||
:visible="!!dialogForExport"
|
||||
@ -44,10 +45,11 @@ import Remark from './Remark/index.vue'
|
||||
import ExportDialog from './ExportDialog/index.vue'
|
||||
import SelectPanel from './SelectPanel.vue'
|
||||
import SearchPanel from './SearchPanel.vue'
|
||||
import NotesPanel from './NotesPanel.vue'
|
||||
import Modal from '@/components/Modal.vue'
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const { dialogForExport, showSelectPanel, showSearchPanel } = storeToRefs(mainStore)
|
||||
const { dialogForExport, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
|
||||
const closeExportDialog = () => mainStore.setDialogForExport('')
|
||||
|
||||
const remarkHeight = ref(40)
|
||||
|
Loading…
x
Reference in New Issue
Block a user