mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 添加计时工具(#156)
This commit is contained in:
parent
09acb9a2d5
commit
42541f678d
@ -61,6 +61,7 @@ npm run serve
|
|||||||
- 幻灯片模板
|
- 幻灯片模板
|
||||||
- 翻页动画
|
- 翻页动画
|
||||||
- 元素动画(入场、退场、强调)
|
- 元素动画(入场、退场、强调)
|
||||||
|
- 选择面板(隐藏元素、层级排序、元素命名)
|
||||||
### 幻灯片元素编辑
|
### 幻灯片元素编辑
|
||||||
- 元素添加、删除
|
- 元素添加、删除
|
||||||
- 元素复制粘贴
|
- 元素复制粘贴
|
||||||
@ -140,6 +141,7 @@ npm run serve
|
|||||||
### 幻灯片放映
|
### 幻灯片放映
|
||||||
- 全部幻灯片预览
|
- 全部幻灯片预览
|
||||||
- 画笔、黑板工具
|
- 画笔、黑板工具
|
||||||
|
- 计时器工具
|
||||||
- 激光笔
|
- 激光笔
|
||||||
- 自动放映
|
- 自动放映
|
||||||
- 演讲者视图
|
- 演讲者视图
|
||||||
|
@ -111,6 +111,7 @@ import {
|
|||||||
FormatBrush,
|
FormatBrush,
|
||||||
PreviewOpen,
|
PreviewOpen,
|
||||||
PreviewClose,
|
PreviewClose,
|
||||||
|
StopwatchStart,
|
||||||
} from '@icon-park/vue-next'
|
} from '@icon-park/vue-next'
|
||||||
|
|
||||||
export const icons = {
|
export const icons = {
|
||||||
@ -223,6 +224,7 @@ export const icons = {
|
|||||||
IconFormatBrush: FormatBrush,
|
IconFormatBrush: FormatBrush,
|
||||||
IconPreviewOpen: PreviewOpen,
|
IconPreviewOpen: PreviewOpen,
|
||||||
IconPreviewClose: PreviewClose,
|
IconPreviewClose: PreviewClose,
|
||||||
|
IconStopwatchStart: StopwatchStart,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -25,6 +25,11 @@
|
|||||||
@close="writingBoardToolVisible = false"
|
@close="writingBoardToolVisible = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CountdownTimer
|
||||||
|
v-if="timerlVisible"
|
||||||
|
@close="timerlVisible = false"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="tools-left">
|
<div class="tools-left">
|
||||||
<IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" />
|
<IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execPrev()" />
|
||||||
<IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" />
|
<IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="execNext()" />
|
||||||
@ -43,6 +48,9 @@
|
|||||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="激光笔">
|
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="激光笔">
|
||||||
<IconMagic class="tool-btn" :class="{ 'active': laserPen }" @click="laserPen = !laserPen" />
|
<IconMagic class="tool-btn" :class="{ 'active': laserPen }" @click="laserPen = !laserPen" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="计时器">
|
||||||
|
<IconStopwatchStart class="tool-btn" :class="{ 'active': timerlVisible }" @click="timerlVisible = !timerlVisible" />
|
||||||
|
</Tooltip>
|
||||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="演讲者视图">
|
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="演讲者视图">
|
||||||
<IconListView class="tool-btn" @click="changeViewMode('presenter')" />
|
<IconListView class="tool-btn" @click="changeViewMode('presenter')" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -72,6 +80,7 @@ import useFullscreen from './hooks/useFullscreen'
|
|||||||
import ScreenSlideList from './ScreenSlideList.vue'
|
import ScreenSlideList from './ScreenSlideList.vue'
|
||||||
import SlideThumbnails from './SlideThumbnails.vue'
|
import SlideThumbnails from './SlideThumbnails.vue'
|
||||||
import WritingBoardTool from './WritingBoardTool.vue'
|
import WritingBoardTool from './WritingBoardTool.vue'
|
||||||
|
import CountdownTimer from './CountdownTimer.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
changeViewMode: {
|
changeViewMode: {
|
||||||
@ -104,6 +113,7 @@ const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
|||||||
|
|
||||||
const rightToolsVisible = ref(false)
|
const rightToolsVisible = ref(false)
|
||||||
const writingBoardToolVisible = ref(false)
|
const writingBoardToolVisible = ref(false)
|
||||||
|
const timerlVisible = ref(false)
|
||||||
const slideThumbnailModelVisible = ref(false)
|
const slideThumbnailModelVisible = ref(false)
|
||||||
const laserPen = ref(false)
|
const laserPen = ref(false)
|
||||||
|
|
||||||
|
204
src/views/Screen/CountdownTimer.vue
Normal file
204
src/views/Screen/CountdownTimer.vue
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<MoveablePanel
|
||||||
|
class="countdown-timer"
|
||||||
|
:width="180"
|
||||||
|
:height="110"
|
||||||
|
:left="left"
|
||||||
|
:top="top"
|
||||||
|
>
|
||||||
|
<div class="header">
|
||||||
|
<span class="text-btn" @click="toggle()">{{ inTiming ? '暂停' : '开始'}}</span>
|
||||||
|
<span class="text-btn" @click="reset()">重置</span>
|
||||||
|
<span class="text-btn" @click="toggleCountdown()" :class="{ 'active': isCountdown }">倒计时</span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="timer">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
:value="fillDigit(minute, 2)"
|
||||||
|
:maxlength="3" :disabled="inputEditable"
|
||||||
|
@mousedown.stop
|
||||||
|
@blur="$event => changeTime($event, 'minute')"
|
||||||
|
@keydown.stop
|
||||||
|
@keydown.enter.stop="$event => changeTime($event, 'minute')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="colon">:</div>
|
||||||
|
<div class="timer">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
:value="fillDigit(second, 2)"
|
||||||
|
:maxlength="3" :disabled="inputEditable"
|
||||||
|
@mousedown.stop
|
||||||
|
@blur="$event => changeTime($event, 'second')"
|
||||||
|
@keydown.stop
|
||||||
|
@keydown.enter.stop="$event => changeTime($event, 'second')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="close-btn" @click="emit('close')"><IconClose class="icon" /></div>
|
||||||
|
</MoveablePanel>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, onUnmounted, ref } from 'vue'
|
||||||
|
import { fillDigit } from '@/utils/common'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
left: {
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
top: {
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'close'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const timer = ref<number | null>(null)
|
||||||
|
const inTiming = ref(false)
|
||||||
|
const isCountdown = ref(false)
|
||||||
|
const time = ref(0)
|
||||||
|
const minute = computed(() => Math.floor(time.value / 60))
|
||||||
|
const second = computed(() => time.value % 60)
|
||||||
|
|
||||||
|
const inputEditable = computed(() => {
|
||||||
|
return !isCountdown.value || inTiming.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const clearTimer = () => {
|
||||||
|
if (timer.value) clearInterval(timer.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(clearTimer)
|
||||||
|
|
||||||
|
const pause = () => {
|
||||||
|
clearTimer()
|
||||||
|
inTiming.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
clearTimer()
|
||||||
|
inTiming.value = false
|
||||||
|
|
||||||
|
if (isCountdown.value) time.value = 600
|
||||||
|
else time.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = () => {
|
||||||
|
clearTimer()
|
||||||
|
|
||||||
|
if (isCountdown.value) {
|
||||||
|
timer.value = setInterval(() => {
|
||||||
|
time.value = time.value - 1
|
||||||
|
|
||||||
|
if (time.value <= 0) reset()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
timer.value = setInterval(() => {
|
||||||
|
time.value = time.value + 1
|
||||||
|
|
||||||
|
if (time.value > 36000) pause()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
inTiming.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
if (inTiming.value) pause()
|
||||||
|
else start()
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleCountdown = () => {
|
||||||
|
isCountdown.value = !isCountdown.value
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeTime = (e: FocusEvent | KeyboardEvent, type: 'minute' | 'second') => {
|
||||||
|
const inputRef = e.target as HTMLInputElement
|
||||||
|
let value = inputRef.value
|
||||||
|
const isNumber = /^(\d)+$/.test(value)
|
||||||
|
if (isNumber) {
|
||||||
|
if (type === 'second' && +value >= 60) value = '59'
|
||||||
|
time.value = type === 'minute' ? (+value * 60 + second.value) : (+value + minute.value * 60)
|
||||||
|
}
|
||||||
|
else inputRef.value = type === 'minute' ? fillDigit(minute.value, 2) : fillDigit(second.value, 2)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.countdown-timer {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
height: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.text-btn {
|
||||||
|
margin-right: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover, &.active {
|
||||||
|
color: $themeColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.timer {
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba($color: $themeColor, $alpha: .05);
|
||||||
|
font-size: 22px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.colon {
|
||||||
|
height: 54px;
|
||||||
|
line-height: 54px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
.icon-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.pause, .play {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
.reset {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
@ -4,6 +4,7 @@
|
|||||||
<div class="tool-btn" @click="changeViewMode('base')"><IconListView class="tool-icon" /><span>普通视图</span></div>
|
<div class="tool-btn" @click="changeViewMode('base')"><IconListView class="tool-icon" /><span>普通视图</span></div>
|
||||||
<div class="tool-btn" :class="{ 'active': writingBoardToolVisible }" @click="writingBoardToolVisible = !writingBoardToolVisible"><IconWrite class="tool-icon" /><span>画笔</span></div>
|
<div class="tool-btn" :class="{ 'active': writingBoardToolVisible }" @click="writingBoardToolVisible = !writingBoardToolVisible"><IconWrite class="tool-icon" /><span>画笔</span></div>
|
||||||
<div class="tool-btn" :class="{ 'active': laserPen }" @click="laserPen = !laserPen"><IconMagic class="tool-icon" /><span>激光笔</span></div>
|
<div class="tool-btn" :class="{ 'active': laserPen }" @click="laserPen = !laserPen"><IconMagic class="tool-icon" /><span>激光笔</span></div>
|
||||||
|
<div class="tool-btn" :class="{ 'active': timerlVisible }" @click="timerlVisible = !timerlVisible"><IconStopwatchStart class="tool-icon" /><span>计时器</span></div>
|
||||||
<div class="tool-btn" @click="() => fullscreenState ? manualExitFullscreen() : enterFullscreen()">
|
<div class="tool-btn" @click="() => fullscreenState ? manualExitFullscreen() : enterFullscreen()">
|
||||||
<IconOffScreenOne class="tool-icon" v-if="fullscreenState" />
|
<IconOffScreenOne class="tool-icon" v-if="fullscreenState" />
|
||||||
<IconOffScreenOne class="tool-icon" v-else />
|
<IconOffScreenOne class="tool-icon" v-else />
|
||||||
@ -38,6 +39,12 @@
|
|||||||
v-if="writingBoardToolVisible"
|
v-if="writingBoardToolVisible"
|
||||||
@close="writingBoardToolVisible = false"
|
@close="writingBoardToolVisible = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CountdownTimer
|
||||||
|
v-if="timerlVisible"
|
||||||
|
:left="75"
|
||||||
|
@close="timerlVisible = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="thumbnails"
|
<div class="thumbnails"
|
||||||
ref="thumbnailsRef"
|
ref="thumbnailsRef"
|
||||||
@ -85,6 +92,7 @@ import useFullscreen from './hooks/useFullscreen'
|
|||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
import ScreenSlideList from './ScreenSlideList.vue'
|
import ScreenSlideList from './ScreenSlideList.vue'
|
||||||
import WritingBoardTool from './WritingBoardTool.vue'
|
import WritingBoardTool from './WritingBoardTool.vue'
|
||||||
|
import CountdownTimer from './CountdownTimer.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
changeViewMode: {
|
changeViewMode: {
|
||||||
@ -98,6 +106,7 @@ const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlide
|
|||||||
const slideListWrapRef = ref<HTMLElement>()
|
const slideListWrapRef = ref<HTMLElement>()
|
||||||
const thumbnailsRef = ref<HTMLElement>()
|
const thumbnailsRef = ref<HTMLElement>()
|
||||||
const writingBoardToolVisible = ref(false)
|
const writingBoardToolVisible = ref(false)
|
||||||
|
const timerlVisible = ref(false)
|
||||||
const laserPen = ref(false)
|
const laserPen = ref(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user