|
@@ -0,0 +1,474 @@
|
|
|
+<template>
|
|
|
+ <div class="timerMeter">
|
|
|
+ <div class="timeCon">
|
|
|
+ <div class="timeBox">
|
|
|
+ <div class="timeInput mmTimeIpt" :class="{ timeFocus: mmFocus }">
|
|
|
+ <div
|
|
|
+ class="timeInputBtn"
|
|
|
+ v-if="timeType === 'countdown'"
|
|
|
+ :class="{ palyDisabled: playState === 'play' }"
|
|
|
+ >
|
|
|
+ <img src="./img/upBtn.png" @click="handleMMTime(10)" />
|
|
|
+ <img src="./img/upBtn.png" @click="handleMMTime(1)" />
|
|
|
+ </div>
|
|
|
+ <div class="timeInputBox">
|
|
|
+ <n-input-number
|
|
|
+ :disabled="timeType === 'countup' || playState === 'play'"
|
|
|
+ @blur="handlemmblur"
|
|
|
+ @focus="handlemmfocus"
|
|
|
+ placeholder=""
|
|
|
+ v-model:value="mmValue"
|
|
|
+ :format="(value: number | null)=>{
|
|
|
+ return value?(value<10?`0${value}`:value+''):'00'
|
|
|
+ }"
|
|
|
+ :min="0"
|
|
|
+ :max="59"
|
|
|
+ :show-button="false"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="timeInputBtn"
|
|
|
+ v-if="timeType === 'countdown'"
|
|
|
+ :class="{ palyDisabled: playState === 'play' }"
|
|
|
+ >
|
|
|
+ <img src="./img/downBtn.png" @click="handleMMTime(-10)" />
|
|
|
+ <img src="./img/downBtn.png" @click="handleMMTime(-1)" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <img class="midBg" src="./img/midBg.png" />
|
|
|
+ <div class="timeInput ssTimeIpt" :class="{ timeFocus: ssFocus }">
|
|
|
+ <div
|
|
|
+ class="timeInputBtn"
|
|
|
+ v-if="timeType === 'countdown'"
|
|
|
+ :class="{ palyDisabled: playState === 'play' }"
|
|
|
+ >
|
|
|
+ <img src="./img/upBtn.png" @click="handleSSTime(10)" />
|
|
|
+ <img src="./img/upBtn.png" @click="handleSSTime(1)" />
|
|
|
+ </div>
|
|
|
+ <div class="timeInputBox">
|
|
|
+ <n-input-number
|
|
|
+ :disabled="timeType === 'countup' || playState === 'play'"
|
|
|
+ @blur="handlessblur"
|
|
|
+ @focus="handlessfocus"
|
|
|
+ placeholder=""
|
|
|
+ v-model:value="ssValue"
|
|
|
+ :format="(value: number | null)=>{
|
|
|
+ return value?(value<10?`0${value}`:value+''):'00'
|
|
|
+ }"
|
|
|
+ :min="0"
|
|
|
+ :max="59"
|
|
|
+ :show-button="false"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="timeInputBtn"
|
|
|
+ v-if="timeType === 'countdown'"
|
|
|
+ :class="{ palyDisabled: playState === 'play' }"
|
|
|
+ >
|
|
|
+ <img src="./img/downBtn.png" @click="handleSSTime(-10)" />
|
|
|
+ <img src="./img/downBtn.png" @click="handleSSTime(-1)" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="timeTools">
|
|
|
+ <div class="timeType">
|
|
|
+ <div class="timeTypeBox">
|
|
|
+ <div
|
|
|
+ class="timeTypeName"
|
|
|
+ :class="{ timeTypeActive: timeType === 'countdown' }"
|
|
|
+ @click="handleChangeTimeType('countdown')"
|
|
|
+ >
|
|
|
+ 倒计时
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="timeTypeName"
|
|
|
+ :class="{ timeTypeActive: timeType === 'countup' }"
|
|
|
+ @click="handleChangeTimeType('countup')"
|
|
|
+ >
|
|
|
+ 正计时
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="playState">
|
|
|
+ <img
|
|
|
+ v-if="playState === 'play'"
|
|
|
+ @click="handlePause"
|
|
|
+ src="./img/play.png"
|
|
|
+ />
|
|
|
+ <img v-else @click="handlePlay" src="./img/pause.png" />
|
|
|
+ </div>
|
|
|
+ <div class="resetting">
|
|
|
+ <img @click="handleInitTime" src="./img/resetting.png" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="timeBomCon">
|
|
|
+ <div
|
|
|
+ v-if="timeType === 'countdown'"
|
|
|
+ class="timeDownBom"
|
|
|
+ :class="{ palyDisabled: playState === 'play' }"
|
|
|
+ >
|
|
|
+ <div @click="handleTimeNum(300)">5分</div>
|
|
|
+ <div @click="handleTimeNum(60)">1分</div>
|
|
|
+ <div @click="handleTimeNum(30)">30秒</div>
|
|
|
+ <div @click="handleTimeNum(5)">5秒</div>
|
|
|
+ </div>
|
|
|
+ <img v-else class="timeUpBom" src="./img/timeUpBom.png" />
|
|
|
+ <Dragbom />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, watch, onUnmounted } from 'vue';
|
|
|
+import { NInputNumber } from 'naive-ui';
|
|
|
+import soundWav from './timer.wav';
|
|
|
+import Dragbom from '@/hooks/useDrag/dragbom';
|
|
|
+
|
|
|
+const soundVIdeo = new Audio(soundWav);
|
|
|
+onUnmounted(() => {
|
|
|
+ clearInterval(_time);
|
|
|
+ soundVIdeo.pause();
|
|
|
+});
|
|
|
+
|
|
|
+// input 状态
|
|
|
+const mmFocus = ref(false);
|
|
|
+const ssFocus = ref(false);
|
|
|
+function handlemmblur() {
|
|
|
+ mmFocus.value = false;
|
|
|
+}
|
|
|
+function handlessblur() {
|
|
|
+ ssFocus.value = false;
|
|
|
+}
|
|
|
+function handlemmfocus() {
|
|
|
+ mmFocus.value = true;
|
|
|
+}
|
|
|
+function handlessfocus() {
|
|
|
+ ssFocus.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+// 计时类型
|
|
|
+const timeType = ref<'countdown' | 'countup'>('countdown');
|
|
|
+function handleChangeTimeType(type: 'countdown' | 'countup') {
|
|
|
+ if (timeType.value === type) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ timeType.value = type;
|
|
|
+ handleInitTime();
|
|
|
+}
|
|
|
+// 清空状态
|
|
|
+function handleInitTime() {
|
|
|
+ handlePause();
|
|
|
+ timeNum.value = 0;
|
|
|
+ mmValue.value = 0;
|
|
|
+ ssValue.value = 0;
|
|
|
+}
|
|
|
+// 播放状态
|
|
|
+const playState = ref<'pause' | 'play'>('pause');
|
|
|
+//统计时间
|
|
|
+const timeNum = ref(0);
|
|
|
+const mmValue = ref(0);
|
|
|
+const ssValue = ref(0);
|
|
|
+watch(timeNum, () => {
|
|
|
+ mmValue.value = Math.floor(timeNum.value / 60);
|
|
|
+ ssValue.value = timeNum.value % 60;
|
|
|
+});
|
|
|
+function handleMMTime(num: number) {
|
|
|
+ if (playState.value === 'play') return;
|
|
|
+ let newMmValue = mmValue.value + num;
|
|
|
+ if (newMmValue > 59) {
|
|
|
+ newMmValue = 59;
|
|
|
+ }
|
|
|
+ if (newMmValue < 0) {
|
|
|
+ newMmValue = 0;
|
|
|
+ }
|
|
|
+ mmValue.value = newMmValue;
|
|
|
+}
|
|
|
+function handleSSTime(num: number) {
|
|
|
+ if (playState.value === 'play') return;
|
|
|
+ let newSsValue = ssValue.value + num;
|
|
|
+ if (newSsValue > 59) {
|
|
|
+ newSsValue = 59;
|
|
|
+ }
|
|
|
+ if (newSsValue < 0) {
|
|
|
+ newSsValue = 0;
|
|
|
+ }
|
|
|
+ ssValue.value = newSsValue;
|
|
|
+}
|
|
|
+function handleTimeNum(num: number) {
|
|
|
+ if (playState.value === 'play') return;
|
|
|
+ timeNum.value = num;
|
|
|
+}
|
|
|
+// 开始
|
|
|
+function handlePlay() {
|
|
|
+ if (timeType.value === 'countdown') {
|
|
|
+ const timeNumNow = mmValue.value * 60 + ssValue.value;
|
|
|
+ if (timeNumNow <= 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ timeNum.value = timeNumNow;
|
|
|
+ playState.value = 'play';
|
|
|
+ handleCountdownStart();
|
|
|
+ } else {
|
|
|
+ if (timeNum.value >= 3599) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ playState.value = 'play';
|
|
|
+ handleCountUpStart();
|
|
|
+ }
|
|
|
+}
|
|
|
+// 暂停
|
|
|
+function handlePause() {
|
|
|
+ clearInterval(_time);
|
|
|
+ playState.value = 'pause';
|
|
|
+ soundVIdeo.pause();
|
|
|
+}
|
|
|
+
|
|
|
+let _time: NodeJS.Timer;
|
|
|
+// 倒计时
|
|
|
+function handleCountdownStart() {
|
|
|
+ soundVIdeo.currentTime = 0;
|
|
|
+ if (timeNum.value <= 4) {
|
|
|
+ soundVIdeo.currentTime = 4 - timeNum.value;
|
|
|
+ soundVIdeo.play();
|
|
|
+ }
|
|
|
+ _time = setInterval(() => {
|
|
|
+ timeNum.value--;
|
|
|
+ if (timeNum.value === 4) {
|
|
|
+ soundVIdeo.play();
|
|
|
+ }
|
|
|
+ if (timeNum.value <= 0) {
|
|
|
+ handlePause();
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+}
|
|
|
+// 正计时
|
|
|
+function handleCountUpStart() {
|
|
|
+ _time = setInterval(() => {
|
|
|
+ timeNum.value++;
|
|
|
+ if (timeNum.value >= 3599) {
|
|
|
+ handlePause();
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.timerMeter {
|
|
|
+ width: 620px;
|
|
|
+ height: 316px;
|
|
|
+ background: url('./img/bg.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ padding: 46px 25px 0 24px;
|
|
|
+ .timeCon {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ .timeBox {
|
|
|
+ width: 432px;
|
|
|
+ height: 190px;
|
|
|
+ background: #3a3939;
|
|
|
+ box-shadow: 0px 3px 0px 0px rgba(255, 255, 255, 0.46),
|
|
|
+ 0px -1px 3px 0px rgba(255, 255, 255, 0.65),
|
|
|
+ inset 0px 3px 8px 0px rgba(0, 0, 0, 0.5);
|
|
|
+ border-radius: 53px;
|
|
|
+ padding: 20px 46px 20px 42px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ .midBg {
|
|
|
+ width: 10px;
|
|
|
+ height: 36px;
|
|
|
+ }
|
|
|
+ .timeInput {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ width: 132px;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ padding: 6px 0 2px;
|
|
|
+ &.timeFocus::after {
|
|
|
+ content: '';
|
|
|
+ width: 1px;
|
|
|
+ height: 94px;
|
|
|
+ position: absolute;
|
|
|
+ background-color: #fff;
|
|
|
+ left: -10px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ animation: blink 1.1s infinite;
|
|
|
+ @keyframes blink {
|
|
|
+ 0% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.mmTimeIpt,
|
|
|
+ &.ssTimeIpt {
|
|
|
+ &::before {
|
|
|
+ content: 'M';
|
|
|
+ position: absolute;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #ffffff;
|
|
|
+ top: 9px;
|
|
|
+ right: -16px;
|
|
|
+ }
|
|
|
+ &.ssTimeIpt::before {
|
|
|
+ right: -10px;
|
|
|
+ content: 'S' !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .timeInputBox {
|
|
|
+ flex-grow: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ :deep(.n-input-number) {
|
|
|
+ height: 100%;
|
|
|
+ .n-input {
|
|
|
+ height: 100%;
|
|
|
+ --n-height: 100% !important;
|
|
|
+ --n-color-focus: initial !important;
|
|
|
+ --n-color: initial !important;
|
|
|
+ --n-padding-left: initial !important;
|
|
|
+ --n-padding-right: initial !important;
|
|
|
+ --n-color-disabled: initial !important;
|
|
|
+ --n-caret-color: #3a3939 !important;
|
|
|
+ .n-input__border,
|
|
|
+ .n-input__state-border {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ .n-input__input-el {
|
|
|
+ text-align: center;
|
|
|
+ font-family: DINAlternate, DINAlternate !important;
|
|
|
+ font-weight: bold !important;
|
|
|
+ font-size: 126px !important;
|
|
|
+ color: #ffffff !important;
|
|
|
+ }
|
|
|
+ &.n-input--disabled {
|
|
|
+ cursor: initial;
|
|
|
+ .n-input__input-el {
|
|
|
+ cursor: initial;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .timeInputBtn {
|
|
|
+ width: 78px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ & > img {
|
|
|
+ cursor: pointer;
|
|
|
+ width: 12px;
|
|
|
+ height: 8px;
|
|
|
+ }
|
|
|
+ &.palyDisabled > img {
|
|
|
+ opacity: 0.4;
|
|
|
+ cursor: initial;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .timeTools {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 138px;
|
|
|
+ .timeType {
|
|
|
+ width: 100%;
|
|
|
+ height: 40px;
|
|
|
+ background: rgba(229, 229, 299, 0.42);
|
|
|
+ border-radius: 40px;
|
|
|
+ padding: 6px;
|
|
|
+ .timeTypeBox {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: #e3e3e3;
|
|
|
+ border-radius: 14px;
|
|
|
+ display: flex;
|
|
|
+ .timeTypeName {
|
|
|
+ width: 50%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #7c7c7c;
|
|
|
+ border-radius: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ &.timeTypeActive {
|
|
|
+ color: #fff;
|
|
|
+ background: linear-gradient(90deg, #999999 0%, #7c7c7c 100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .playState,
|
|
|
+ .resetting {
|
|
|
+ margin-top: 12px;
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ & > img {
|
|
|
+ cursor: pointer;
|
|
|
+ width: 78px;
|
|
|
+ height: 59px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .timeBomCon {
|
|
|
+ width: 432px;
|
|
|
+ height: calc(100% - 190px);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+ .timeDownBom {
|
|
|
+ width: 100%;
|
|
|
+ height: 56px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 18px 30px 0;
|
|
|
+ & > div {
|
|
|
+ width: 85px;
|
|
|
+ height: 38px;
|
|
|
+ background: url('./img/timeNum.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #7c7c7c;
|
|
|
+ line-height: 38px;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+ &.palyDisabled > div {
|
|
|
+ opacity: 0.4;
|
|
|
+ cursor: initial;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .timeUpBom {
|
|
|
+ margin-top: 25px;
|
|
|
+ width: 332px;
|
|
|
+ height: 21px;
|
|
|
+ }
|
|
|
+ :global(.timerMeter .timeBomCon .bom_drag) {
|
|
|
+ position: absolute;
|
|
|
+ left: -24px;
|
|
|
+ bottom: 0;
|
|
|
+ width: 620px;
|
|
|
+ height: 40px;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+ :global(.timerMeter .timeBomCon .bom_drag > div) {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|