|
|
@@ -1,831 +1,831 @@
|
|
|
-<template>
|
|
|
- <div class="Metronome">
|
|
|
- <div class="MetronomeBox">
|
|
|
- <div class="headTools">
|
|
|
- <img
|
|
|
- src="./imgs/minMet.png"
|
|
|
- @click="
|
|
|
- () => {
|
|
|
- emits('windowMet');
|
|
|
- }
|
|
|
- "
|
|
|
- />
|
|
|
- <img
|
|
|
- src="./imgs/closeMet.png"
|
|
|
- @click="
|
|
|
- () => {
|
|
|
- emits('closeMet');
|
|
|
- }
|
|
|
- "
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="beatWrap">
|
|
|
- <div
|
|
|
- class="dot"
|
|
|
- ref="dotDom"
|
|
|
- @mousedown="handleDotMousedown"
|
|
|
- @touchstart="handleDotTouchstart"
|
|
|
- ></div>
|
|
|
- <div class="round"></div>
|
|
|
- <canvas
|
|
|
- ref="mycanvasDom"
|
|
|
- class="mycanvas"
|
|
|
- width="252"
|
|
|
- height="252"
|
|
|
- @click="handleEventCanvas"
|
|
|
- ></canvas>
|
|
|
- <div class="beatCon">
|
|
|
- <img
|
|
|
- class="optImg"
|
|
|
- src="./imgs/jia.png"
|
|
|
- @click="
|
|
|
- () => {
|
|
|
- if (speedNum < 200) {
|
|
|
- speedNum++;
|
|
|
- }
|
|
|
- }
|
|
|
- "
|
|
|
- />
|
|
|
- <div class="optMid">
|
|
|
- <img class="optImg" src="./imgs/yin.png" />
|
|
|
- <n-input-number
|
|
|
- :disabled="true"
|
|
|
- placeholder=""
|
|
|
- :value="speedNum"
|
|
|
- :show-button="false"
|
|
|
- @update:value="(num:number)=>{
|
|
|
- if(num){
|
|
|
- if(num<=50){
|
|
|
- speedNum=50
|
|
|
- }else if(num>=200){
|
|
|
- speedNum=200
|
|
|
- }else{
|
|
|
- speedNum=num
|
|
|
- }
|
|
|
- }
|
|
|
- }"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <img
|
|
|
- class="optImg"
|
|
|
- src="./imgs/jian.png"
|
|
|
- @click="
|
|
|
- () => {
|
|
|
- if (speedNum > 50) {
|
|
|
- speedNum--;
|
|
|
- }
|
|
|
- }
|
|
|
- "
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="selectCon">
|
|
|
- <n-select
|
|
|
- v-model:value="beatVal"
|
|
|
- :options="beatValOpt"
|
|
|
- :show-checkmark="false"
|
|
|
- :show-arrow="false"
|
|
|
- :placement="'top'"
|
|
|
- :render-tag="
|
|
|
- ({ option }:any) => {
|
|
|
- return h('div',{class:'beatName'},[h('div',option.labelHide),h('div','拍')])
|
|
|
- }
|
|
|
- "
|
|
|
- :render-label="
|
|
|
- (option:any) => {
|
|
|
- return h('div', option.labelHide);
|
|
|
- }
|
|
|
- "
|
|
|
- ></n-select>
|
|
|
- <n-select
|
|
|
- class="beatSymbolSel"
|
|
|
- v-model:value="beatSymbol"
|
|
|
- :options="beatSymbolOpt"
|
|
|
- :show-checkmark="false"
|
|
|
- :show-arrow="false"
|
|
|
- :placement="'top'"
|
|
|
- :render-tag="
|
|
|
- ({ option }:any) => {
|
|
|
- return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
|
|
|
- }
|
|
|
- "
|
|
|
- :render-label="
|
|
|
- (option:any) => {
|
|
|
- return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
|
|
|
- }
|
|
|
- "
|
|
|
- ></n-select>
|
|
|
- </div>
|
|
|
- <div class="sliderList">
|
|
|
- <img src="./imgs/sound.png" alt="" />
|
|
|
- <NSlider v-model:value="volumeNum" :step="1" :tooltip="false">
|
|
|
- <template #thumb>
|
|
|
- <img class="thumbDot" src="./imgs/dotBtn.png" alt="" />
|
|
|
- </template>
|
|
|
- </NSlider>
|
|
|
- <span class="sliderText">{{ volumeNum }}</span>
|
|
|
- </div>
|
|
|
- <div v-if="playState === 'pause'" class="playBtn" @click="startPlay">
|
|
|
- <span>播放</span>
|
|
|
- <img src="./imgs/playtBtn.png" />
|
|
|
- </div>
|
|
|
- <div v-else class="playBtn" @click="pausePlay">
|
|
|
- <span>暂停</span>
|
|
|
- <img class="pauseImg" src="./imgs/pauseBtn.png" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <Dragbom></Dragbom>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup lang="ts">
|
|
|
-import { ref, reactive, onMounted, onUnmounted, h, watch } from 'vue';
|
|
|
-import { NInputNumber, NSelect, NSlider } from 'naive-ui';
|
|
|
-import useMetronome from './useMetronome';
|
|
|
-import Dragbom from '@/hooks/useDrag/dragbom';
|
|
|
-import { getCachePos, setCachePos } from './useMetronome';
|
|
|
-import { useUserStore } from '@/store/modules/users';
|
|
|
-
|
|
|
-const users = useUserStore();
|
|
|
-const emits = defineEmits<{
|
|
|
- (e: 'windowMet'): void;
|
|
|
- (e: 'closeMet'): void;
|
|
|
- (e: 'playStateChange', state: 'play' | 'pause'): void;
|
|
|
-}>();
|
|
|
-
|
|
|
-/* dom */
|
|
|
-const mycanvasDom = ref<HTMLCanvasElement>();
|
|
|
-const dotDom = ref<HTMLElement>();
|
|
|
-/* select */
|
|
|
-const beatVal = ref('1');
|
|
|
-const beatValOpt = [
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '0',
|
|
|
- value: '0',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '1',
|
|
|
- value: '1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '2',
|
|
|
- value: '1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '3',
|
|
|
- value: '1-1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '4',
|
|
|
- value: '1-1-1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '5',
|
|
|
- value: '1-1-1-1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '6',
|
|
|
- value: '1-1-1-1-1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '7',
|
|
|
- value: '1-1-1-1-1-1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '8',
|
|
|
- value: '1-1-1-1-1-1-1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '9',
|
|
|
- value: '1-1-1-1-1-1-1-1-1',
|
|
|
- class: 'beatValOptItem'
|
|
|
- }
|
|
|
-];
|
|
|
-const beatSymbol = ref('1');
|
|
|
-const beatSymbolOpt = [
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '1',
|
|
|
- value: '1',
|
|
|
- class: 'beatSymbolOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '2',
|
|
|
- value: '0.5-0.5',
|
|
|
- class: 'beatSymbolOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '3',
|
|
|
- value: '0.3333333-0.3333333-0.3333333',
|
|
|
- class: 'beatSymbolOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '4',
|
|
|
- value: '0.25-0.25-0.25-0.25',
|
|
|
- class: 'beatSymbolOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '5',
|
|
|
- value: '0.6666666-0.3333333',
|
|
|
- class: 'beatSymbolOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '6',
|
|
|
- value: '0.75-0.25',
|
|
|
- class: 'beatSymbolOptItem'
|
|
|
- },
|
|
|
- {
|
|
|
- label: '',
|
|
|
- labelHide: '7',
|
|
|
- value: '0.5-0.25-0.25',
|
|
|
- class: 'beatSymbolOptItem'
|
|
|
- }
|
|
|
-];
|
|
|
-const { volumeNum, playState, speedNum, startPlay, pausePlay } = useMetronome(
|
|
|
- beatVal,
|
|
|
- beatSymbol
|
|
|
-);
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- const cachePos = getCachePos(users.info.id);
|
|
|
- if (cachePos) {
|
|
|
- beatVal.value = cachePos.beatVal;
|
|
|
- beatSymbol.value = cachePos.beatSymbol;
|
|
|
- volumeNum.value = cachePos.volumeNum;
|
|
|
- speedNum.value = cachePos.speedNum;
|
|
|
- }
|
|
|
- /* n-input-number 有bug input-props设置maxlength不上去 */
|
|
|
- const iptDoms = document.querySelector(
|
|
|
- '.Metronome .MetronomeBox .optMid .n-input__input-el'
|
|
|
- );
|
|
|
- iptDoms?.setAttribute('maxlength', '3');
|
|
|
- getCircleBar(speedToScalc(speedNum.value));
|
|
|
-});
|
|
|
-
|
|
|
-onUnmounted(() => {
|
|
|
- setCachePos(users.info.id, {
|
|
|
- beatVal: beatVal.value,
|
|
|
- beatSymbol: beatSymbol.value,
|
|
|
- volumeNum: volumeNum.value,
|
|
|
- speedNum: speedNum.value
|
|
|
- });
|
|
|
-});
|
|
|
-
|
|
|
-watch(playState, () => {
|
|
|
- emits('playStateChange', playState.value);
|
|
|
-});
|
|
|
-watch(speedNum, () => {
|
|
|
- if (playState.value === 'play') {
|
|
|
- pausePlay();
|
|
|
- }
|
|
|
- getCircleBar(speedToScalc(speedNum.value));
|
|
|
-});
|
|
|
-watch([beatVal, beatSymbol], () => {
|
|
|
- if (playState.value === 'play') {
|
|
|
- pausePlay();
|
|
|
- }
|
|
|
-});
|
|
|
-function speedToScalc(speed: number) {
|
|
|
- return ((speed - 50) / 150) * 100;
|
|
|
-}
|
|
|
-function scalcToSpeed(scalc: number) {
|
|
|
- return Math.round((scalc * 150) / 100 + 50);
|
|
|
-}
|
|
|
-const handleEventCanvas = (e: MouseEvent | TouchEvent) => {
|
|
|
- const circle = mycanvasDom.value!.getBoundingClientRect();
|
|
|
- const centerX = circle.left + circle.width / 2;
|
|
|
- const centerY = circle.top + circle.height / 2;
|
|
|
- const event = isTouchEvent(e) ? e.touches[0] : e;
|
|
|
- const angle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
|
|
|
- const percentage = (angle * (180 / Math.PI) + 180) / 360;
|
|
|
- const scalc = Math.round(percentage * 100) - 25;
|
|
|
- speedNum.value = scalcToSpeed(scalc < 0 ? 100 + scalc : scalc);
|
|
|
-};
|
|
|
-function handleDotMousedown() {
|
|
|
- function onMouseup() {
|
|
|
- document.removeEventListener('mousemove', onMousemove);
|
|
|
- document.removeEventListener('mouseup', onMouseup);
|
|
|
- }
|
|
|
- document.addEventListener('mousemove', onMousemove);
|
|
|
- document.addEventListener('mouseup', onMouseup);
|
|
|
-}
|
|
|
-function handleDotTouchstart() {
|
|
|
- function onTouchend() {
|
|
|
- document.removeEventListener('touchmove', onTouchmove);
|
|
|
- document.removeEventListener('touchend', onTouchend);
|
|
|
- }
|
|
|
- document.addEventListener('touchmove', onTouchmove);
|
|
|
- document.addEventListener('touchend', onTouchend);
|
|
|
-}
|
|
|
-function onMousemove(e: MouseEvent) {
|
|
|
- handleEventCanvas(e);
|
|
|
-}
|
|
|
-function onTouchmove(e: TouchEvent) {
|
|
|
- handleEventCanvas(e);
|
|
|
-}
|
|
|
-// 根据百分比画圆
|
|
|
-function getCircleBar(steps: number) {
|
|
|
- const radius = 121; //半径
|
|
|
- const colorList = ['#51DBFF', '#009FFF'];
|
|
|
- const mycanvas = mycanvasDom.value!;
|
|
|
- const ctx = mycanvas.getContext('2d')!;
|
|
|
- // 每次进来清空画布
|
|
|
- ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);
|
|
|
- //找到画布的中心点
|
|
|
- const canvasX = mycanvas.width / 2;
|
|
|
- const canvasY = mycanvas.height / 2;
|
|
|
- //进度条是100%,所以要把一圈360度分成100份
|
|
|
- const progress = (Math.PI * 2) / 100;
|
|
|
- const ao = -Math.PI / 2 + steps * progress;
|
|
|
- const x1 = radius + Math.cos(ao) * radius;
|
|
|
- const y1 = radius + Math.sin(ao) * radius;
|
|
|
- // 这里开始算坐标
|
|
|
- dotDom.value!.style.left = x1 - 7 + 'Px';
|
|
|
- dotDom.value!.style.top = y1 - 7 + 'Px';
|
|
|
- // 绘制渐变色
|
|
|
- const gradientDirection = {
|
|
|
- x1: 0,
|
|
|
- y1: 0,
|
|
|
- x2: radius * 2, // 半径*2
|
|
|
- y2: 0
|
|
|
- };
|
|
|
- gradientDirection.y2 = radius * 2;
|
|
|
- const gradient = ctx.createLinearGradient(
|
|
|
- gradientDirection.x1,
|
|
|
- gradientDirection.y1,
|
|
|
- gradientDirection.x2,
|
|
|
- gradientDirection.y2
|
|
|
- );
|
|
|
- colorList.map((color, index) => {
|
|
|
- gradient.addColorStop(index, color);
|
|
|
- });
|
|
|
- ctx.strokeStyle = gradient;
|
|
|
- ctx.lineWidth = 10;
|
|
|
- ctx.save();
|
|
|
- ctx.beginPath();
|
|
|
- ctx.arc(
|
|
|
- canvasX,
|
|
|
- canvasY,
|
|
|
- radius,
|
|
|
- -Math.PI / 2,
|
|
|
- -Math.PI / 2 + steps * progress,
|
|
|
- false
|
|
|
- );
|
|
|
- ctx.stroke();
|
|
|
- ctx.closePath();
|
|
|
- ctx.restore();
|
|
|
-}
|
|
|
-function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
|
|
|
- return window.TouchEvent && e instanceof window.TouchEvent;
|
|
|
-}
|
|
|
-defineExpose({
|
|
|
- startPlay,
|
|
|
- pausePlay,
|
|
|
- speedNum,
|
|
|
- beatSymbol
|
|
|
-});
|
|
|
-</script>
|
|
|
-
|
|
|
-<style lang="less" scoped>
|
|
|
-.Metronome {
|
|
|
- width: 565Px;
|
|
|
- height: 554Px;
|
|
|
- background: url('./imgs/bg.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- padding: 26Px 26Px 0;
|
|
|
- &:deep(.bom_drag) {
|
|
|
- position: absolute;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- height: 76Px;
|
|
|
- padding: 0 28Px;
|
|
|
- & > div {
|
|
|
- width: 40Px;
|
|
|
- height: 40Px;
|
|
|
- border-radius: 0 0 18Px 0;
|
|
|
- &:first-child {
|
|
|
- border-radius: 0 0 0 18Px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- .MetronomeBox {
|
|
|
- width: 100%;
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- z-index: 2;
|
|
|
- }
|
|
|
- .headTools {
|
|
|
- position: absolute;
|
|
|
- right: 20Px;
|
|
|
- top: 18Px;
|
|
|
- & > img {
|
|
|
- margin-left: 18Px;
|
|
|
- width: 24Px;
|
|
|
- height: 24Px;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
- }
|
|
|
- .beatWrap {
|
|
|
- margin-top: 30Px;
|
|
|
- width: 252Px;
|
|
|
- height: 252Px;
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- .dot {
|
|
|
- position: absolute;
|
|
|
- width: 24Px;
|
|
|
- height: 24Px;
|
|
|
- background: url('./imgs/dot.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- cursor: pointer;
|
|
|
- z-index: 3;
|
|
|
- }
|
|
|
- .round {
|
|
|
- position: absolute;
|
|
|
- left: 0;
|
|
|
- top: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- border-radius: 50%;
|
|
|
- border: 10Px solid #d1d1d1;
|
|
|
- z-index: 1;
|
|
|
- }
|
|
|
- .mycanvas {
|
|
|
- position: absolute;
|
|
|
- left: 0;
|
|
|
- top: 0;
|
|
|
- z-index: 2;
|
|
|
- }
|
|
|
- .beatCon {
|
|
|
- border-radius: 50%;
|
|
|
- z-index: 4;
|
|
|
- width: 200Px;
|
|
|
- height: 200Px;
|
|
|
- background: url('./imgs/yuan.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- .optMid {
|
|
|
- margin: 5Px 0;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- :deep(.n-input-number) {
|
|
|
- width: 74Px;
|
|
|
- height: 52Px;
|
|
|
- margin-left: 4Px;
|
|
|
- .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: DINA !important;
|
|
|
- font-weight: bold !important;
|
|
|
- font-size: 45Px !important;
|
|
|
- color: #000000 !important;
|
|
|
- }
|
|
|
- &.n-input--disabled {
|
|
|
- cursor: initial;
|
|
|
- .n-input__input-el {
|
|
|
- cursor: initial;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- .optImg {
|
|
|
- cursor: pointer;
|
|
|
- width: 35Px;
|
|
|
- height: 35Px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- .selectCon {
|
|
|
- display: flex;
|
|
|
- margin-top: 22Px;
|
|
|
- :deep(.n-select) {
|
|
|
- width: 130Px;
|
|
|
- .n-base-selection--selected {
|
|
|
- --n-height: 48Px !important;
|
|
|
- --n-border-radius: 24Px !important;
|
|
|
- .n-base-selection__border,
|
|
|
- .n-base-selection__state-border {
|
|
|
- display: none;
|
|
|
- }
|
|
|
- .n-base-selection-input {
|
|
|
- padding: 0;
|
|
|
- .n-base-selection-input__content {
|
|
|
- .beatName {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- padding-top: 2Px;
|
|
|
- & > div:first-child {
|
|
|
- font-weight: 500;
|
|
|
- font-size: 30Px;
|
|
|
- color: #333333;
|
|
|
- }
|
|
|
- & > div:last-child {
|
|
|
- margin-left: 2Px;
|
|
|
- font-weight: 500;
|
|
|
- font-size: 14Px;
|
|
|
- color: #333333;
|
|
|
- margin-top: 4Px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- :deep(.beatSymbolSel.n-select) {
|
|
|
- margin-left: 15Px;
|
|
|
- .n-base-selection-input__content {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- .beatSymbolImg {
|
|
|
- width: 44Px;
|
|
|
- height: 46Px;
|
|
|
- &.beatSymbolImg1 {
|
|
|
- background: url('./imgs/b1.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg2 {
|
|
|
- background: url('./imgs/b2.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg3 {
|
|
|
- background: url('./imgs/b3.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg4 {
|
|
|
- background: url('./imgs/b4.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg5 {
|
|
|
- background: url('./imgs/b5.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg6 {
|
|
|
- background: url('./imgs/b6.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg7 {
|
|
|
- background: url('./imgs/b7.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- .sliderList {
|
|
|
- margin-top: 24Px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- & > img {
|
|
|
- width: 18Px;
|
|
|
- height: 18Px;
|
|
|
- margin-right: 16Px;
|
|
|
- }
|
|
|
- .sliderText {
|
|
|
- width: 32Px;
|
|
|
- font-weight: 500;
|
|
|
- font-size: 16Px;
|
|
|
- color: #1cacf1;
|
|
|
- margin-left: 16Px;
|
|
|
- }
|
|
|
- :deep(.n-slider-rail) {
|
|
|
- height: 6Px;
|
|
|
- line-height: 6Px;
|
|
|
- background: #ffffff;
|
|
|
- border-radius: 4Px;
|
|
|
- width: 194Px;
|
|
|
- .n-slider-rail__fill {
|
|
|
- background: linear-gradient(90deg, #63daff 0%, #1798ff 100%);
|
|
|
- border-radius: 4Px;
|
|
|
- }
|
|
|
- }
|
|
|
- .thumbDot {
|
|
|
- width: 21Px;
|
|
|
- height: 21Px;
|
|
|
- }
|
|
|
- }
|
|
|
- .playBtn {
|
|
|
- cursor: pointer;
|
|
|
- margin-top: 20Px;
|
|
|
- width: 272Px;
|
|
|
- height: 54Px;
|
|
|
- background: #00acff;
|
|
|
- border-radius: 27Px;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- font-weight: 600;
|
|
|
- font-size: 18Px;
|
|
|
- color: #ffffff;
|
|
|
- & > img {
|
|
|
- margin-left: 14Px;
|
|
|
- width: 13Px;
|
|
|
- height: 14Px;
|
|
|
- &.pauseImg {
|
|
|
- width: 12Px;
|
|
|
- height: 13Px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|
|
|
-<style lang="less">
|
|
|
-.n-base-select-menu.n-select-menu {
|
|
|
- .beatValOptItem {
|
|
|
- padding: 0 9Px !important;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- &.n-base-select-option::before {
|
|
|
- transition: initial;
|
|
|
- }
|
|
|
- &.n-base-select-option--selected {
|
|
|
- .n-base-select-option__content {
|
|
|
- color: #47a7fe;
|
|
|
- }
|
|
|
- }
|
|
|
- &.n-base-select-option--pending {
|
|
|
- .n-base-select-option__content {
|
|
|
- color: #ffffff;
|
|
|
- }
|
|
|
- &::before {
|
|
|
- background: #47a7fe !important;
|
|
|
- border-radius: 6Px !important;
|
|
|
- left: 9Px;
|
|
|
- right: 9Px;
|
|
|
- }
|
|
|
- }
|
|
|
- .n-base-select-option__content {
|
|
|
- font-weight: 500;
|
|
|
- font-size: 18Px;
|
|
|
- color: #777777;
|
|
|
- }
|
|
|
- }
|
|
|
- .beatSymbolOptItem {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- &.n-base-select-option::before {
|
|
|
- transition: initial;
|
|
|
- }
|
|
|
- &.n-base-select-option--selected {
|
|
|
- .n-base-select-option__content {
|
|
|
- .beatSymbolImg {
|
|
|
- &.beatSymbolImg1 {
|
|
|
- background: url('./imgs/a1.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg2 {
|
|
|
- background: url('./imgs/a2.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg3 {
|
|
|
- background: url('./imgs/a3.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg4 {
|
|
|
- background: url('./imgs/a4.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg5 {
|
|
|
- background: url('./imgs/a5.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg6 {
|
|
|
- background: url('./imgs/a6.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg7 {
|
|
|
- background: url('./imgs/a7.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- &.n-base-select-option--pending {
|
|
|
- .n-base-select-option__content {
|
|
|
- .beatSymbolImg {
|
|
|
- &.beatSymbolImg1 {
|
|
|
- background: url('./imgs/c1.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg2 {
|
|
|
- background: url('./imgs/c2.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg3 {
|
|
|
- background: url('./imgs/c3.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg4 {
|
|
|
- background: url('./imgs/c4.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg5 {
|
|
|
- background: url('./imgs/c5.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg6 {
|
|
|
- background: url('./imgs/c6.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg7 {
|
|
|
- background: url('./imgs/c7.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- &::before {
|
|
|
- background: #47a7fe !important;
|
|
|
- border-radius: 6Px !important;
|
|
|
- left: 9Px;
|
|
|
- right: 9Px;
|
|
|
- }
|
|
|
- }
|
|
|
- .beatSymbolImg {
|
|
|
- width: 44Px;
|
|
|
- height: 46Px;
|
|
|
- &.beatSymbolImg1 {
|
|
|
- background: url('./imgs/b1.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg2 {
|
|
|
- background: url('./imgs/b2.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg3 {
|
|
|
- background: url('./imgs/b3.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg4 {
|
|
|
- background: url('./imgs/b4.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg5 {
|
|
|
- background: url('./imgs/b5.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg6 {
|
|
|
- background: url('./imgs/b6.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- &.beatSymbolImg7 {
|
|
|
- background: url('./imgs/b7.png') no-repeat;
|
|
|
- background-size: 100% 100%;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-.metronomeConBoxClass_drag
|
|
|
- .v-binder-follower-container
|
|
|
- .n-base-select-menu.n-select-menu {
|
|
|
- --n-border-radius: 10Px !important;
|
|
|
- .n-scrollbar-rail.n-scrollbar-rail--vertical {
|
|
|
- right: 3Px;
|
|
|
- --n-scrollbar-width: 3Px !important;
|
|
|
- opacity: 0.4;
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|
|
|
+<template>
|
|
|
+ <div class="Metronome">
|
|
|
+ <div class="MetronomeBox">
|
|
|
+ <div class="headTools">
|
|
|
+ <img
|
|
|
+ src="./imgs/minMet.png"
|
|
|
+ @click="
|
|
|
+ () => {
|
|
|
+ emits('windowMet');
|
|
|
+ }
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <img
|
|
|
+ src="./imgs/closeMet.png"
|
|
|
+ @click="
|
|
|
+ () => {
|
|
|
+ emits('closeMet');
|
|
|
+ }
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="beatWrap">
|
|
|
+ <div
|
|
|
+ class="dot"
|
|
|
+ ref="dotDom"
|
|
|
+ @mousedown="handleDotMousedown"
|
|
|
+ @touchstart="handleDotTouchstart"
|
|
|
+ ></div>
|
|
|
+ <div class="round"></div>
|
|
|
+ <canvas
|
|
|
+ ref="mycanvasDom"
|
|
|
+ class="mycanvas"
|
|
|
+ width="252"
|
|
|
+ height="252"
|
|
|
+ @click="handleEventCanvas"
|
|
|
+ ></canvas>
|
|
|
+ <div class="beatCon">
|
|
|
+ <img
|
|
|
+ class="optImg"
|
|
|
+ src="./imgs/jia.png"
|
|
|
+ @click="
|
|
|
+ () => {
|
|
|
+ if (speedNum < 200) {
|
|
|
+ speedNum++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <div class="optMid">
|
|
|
+ <img class="optImg" src="./imgs/yin.png" />
|
|
|
+ <n-input-number
|
|
|
+ :disabled="true"
|
|
|
+ placeholder=""
|
|
|
+ :value="speedNum"
|
|
|
+ :show-button="false"
|
|
|
+ @update:value="(num:number)=>{
|
|
|
+ if(num){
|
|
|
+ if(num<=50){
|
|
|
+ speedNum=50
|
|
|
+ }else if(num>=200){
|
|
|
+ speedNum=200
|
|
|
+ }else{
|
|
|
+ speedNum=num
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <img
|
|
|
+ class="optImg"
|
|
|
+ src="./imgs/jian.png"
|
|
|
+ @click="
|
|
|
+ () => {
|
|
|
+ if (speedNum > 50) {
|
|
|
+ speedNum--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="selectCon">
|
|
|
+ <n-select
|
|
|
+ v-model:value="beatVal"
|
|
|
+ :options="beatValOpt"
|
|
|
+ :show-checkmark="false"
|
|
|
+ :show-arrow="false"
|
|
|
+ :placement="'top'"
|
|
|
+ :render-tag="
|
|
|
+ ({ option }:any) => {
|
|
|
+ return h('div',{class:'beatName'},[h('div',option.labelHide),h('div','拍')])
|
|
|
+ }
|
|
|
+ "
|
|
|
+ :render-label="
|
|
|
+ (option:any) => {
|
|
|
+ return h('div', option.labelHide);
|
|
|
+ }
|
|
|
+ "
|
|
|
+ ></n-select>
|
|
|
+ <n-select
|
|
|
+ class="beatSymbolSel"
|
|
|
+ v-model:value="beatSymbol"
|
|
|
+ :options="beatSymbolOpt"
|
|
|
+ :show-checkmark="false"
|
|
|
+ :show-arrow="false"
|
|
|
+ :placement="'top'"
|
|
|
+ :render-tag="
|
|
|
+ ({ option }:any) => {
|
|
|
+ return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
|
|
|
+ }
|
|
|
+ "
|
|
|
+ :render-label="
|
|
|
+ (option:any) => {
|
|
|
+ return h('div', { class: `beatSymbolImg${option.labelHide} beatSymbolImg` });
|
|
|
+ }
|
|
|
+ "
|
|
|
+ ></n-select>
|
|
|
+ </div>
|
|
|
+ <div class="sliderList">
|
|
|
+ <img src="./imgs/sound.png" alt="" />
|
|
|
+ <NSlider v-model:value="volumeNum" :step="1" :tooltip="false">
|
|
|
+ <template #thumb>
|
|
|
+ <img class="thumbDot" src="./imgs/dotBtn.png" alt="" />
|
|
|
+ </template>
|
|
|
+ </NSlider>
|
|
|
+ <span class="sliderText">{{ volumeNum }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="playState === 'pause'" class="playBtn" @click="startPlay">
|
|
|
+ <span>播放</span>
|
|
|
+ <img src="./imgs/playtBtn.png" />
|
|
|
+ </div>
|
|
|
+ <div v-else class="playBtn" @click="pausePlay">
|
|
|
+ <span>暂停</span>
|
|
|
+ <img class="pauseImg" src="./imgs/pauseBtn.png" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <Dragbom></Dragbom>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, reactive, onMounted, onUnmounted, h, watch } from 'vue';
|
|
|
+import { NInputNumber, NSelect, NSlider } from 'naive-ui';
|
|
|
+import useMetronome from './useMetronome';
|
|
|
+import Dragbom from '@/hooks/useDrag/dragbom';
|
|
|
+import { getCachePos, setCachePos } from './useMetronome';
|
|
|
+import { useUserStore } from '@/store/modules/users';
|
|
|
+
|
|
|
+const users = useUserStore();
|
|
|
+const emits = defineEmits<{
|
|
|
+ (e: 'windowMet'): void;
|
|
|
+ (e: 'closeMet'): void;
|
|
|
+ (e: 'playStateChange', state: 'play' | 'pause'): void;
|
|
|
+}>();
|
|
|
+
|
|
|
+/* dom */
|
|
|
+const mycanvasDom = ref<HTMLCanvasElement>();
|
|
|
+const dotDom = ref<HTMLElement>();
|
|
|
+/* select */
|
|
|
+const beatVal = ref('1');
|
|
|
+const beatValOpt = [
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '0',
|
|
|
+ value: '0',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '1',
|
|
|
+ value: '1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '2',
|
|
|
+ value: '1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '3',
|
|
|
+ value: '1-1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '4',
|
|
|
+ value: '1-1-1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '5',
|
|
|
+ value: '1-1-1-1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '6',
|
|
|
+ value: '1-1-1-1-1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '7',
|
|
|
+ value: '1-1-1-1-1-1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '8',
|
|
|
+ value: '1-1-1-1-1-1-1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '9',
|
|
|
+ value: '1-1-1-1-1-1-1-1-1',
|
|
|
+ class: 'beatValOptItem'
|
|
|
+ }
|
|
|
+];
|
|
|
+const beatSymbol = ref('1');
|
|
|
+const beatSymbolOpt = [
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '1',
|
|
|
+ value: '1',
|
|
|
+ class: 'beatSymbolOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '2',
|
|
|
+ value: '0.5-0.5',
|
|
|
+ class: 'beatSymbolOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '3',
|
|
|
+ value: '0.3333333-0.3333333-0.3333333',
|
|
|
+ class: 'beatSymbolOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '4',
|
|
|
+ value: '0.25-0.25-0.25-0.25',
|
|
|
+ class: 'beatSymbolOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '5',
|
|
|
+ value: '0.6666666-0.3333333',
|
|
|
+ class: 'beatSymbolOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '6',
|
|
|
+ value: '0.75-0.25',
|
|
|
+ class: 'beatSymbolOptItem'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '',
|
|
|
+ labelHide: '7',
|
|
|
+ value: '0.5-0.25-0.25',
|
|
|
+ class: 'beatSymbolOptItem'
|
|
|
+ }
|
|
|
+];
|
|
|
+const { volumeNum, playState, speedNum, startPlay, pausePlay } = useMetronome(
|
|
|
+ beatVal,
|
|
|
+ beatSymbol
|
|
|
+);
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ const cachePos = getCachePos(users.info.id);
|
|
|
+ if (cachePos) {
|
|
|
+ beatVal.value = cachePos.beatVal;
|
|
|
+ beatSymbol.value = cachePos.beatSymbol;
|
|
|
+ volumeNum.value = cachePos.volumeNum;
|
|
|
+ speedNum.value = cachePos.speedNum;
|
|
|
+ }
|
|
|
+ /* n-input-number 有bug input-props设置maxlength不上去 */
|
|
|
+ const iptDoms = document.querySelector(
|
|
|
+ '.Metronome .MetronomeBox .optMid .n-input__input-el'
|
|
|
+ );
|
|
|
+ iptDoms?.setAttribute('maxlength', '3');
|
|
|
+ getCircleBar(speedToScalc(speedNum.value));
|
|
|
+});
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ setCachePos(users.info.id, {
|
|
|
+ beatVal: beatVal.value,
|
|
|
+ beatSymbol: beatSymbol.value,
|
|
|
+ volumeNum: volumeNum.value,
|
|
|
+ speedNum: speedNum.value
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+watch(playState, () => {
|
|
|
+ emits('playStateChange', playState.value);
|
|
|
+});
|
|
|
+watch(speedNum, () => {
|
|
|
+ if (playState.value === 'play') {
|
|
|
+ pausePlay();
|
|
|
+ }
|
|
|
+ getCircleBar(speedToScalc(speedNum.value));
|
|
|
+});
|
|
|
+watch([beatVal, beatSymbol], () => {
|
|
|
+ if (playState.value === 'play') {
|
|
|
+ pausePlay();
|
|
|
+ }
|
|
|
+});
|
|
|
+function speedToScalc(speed: number) {
|
|
|
+ return ((speed - 50) / 150) * 100;
|
|
|
+}
|
|
|
+function scalcToSpeed(scalc: number) {
|
|
|
+ return Math.round((scalc * 150) / 100 + 50);
|
|
|
+}
|
|
|
+const handleEventCanvas = (e: MouseEvent | TouchEvent) => {
|
|
|
+ const circle = mycanvasDom.value!.getBoundingClientRect();
|
|
|
+ const centerX = circle.left + circle.width / 2;
|
|
|
+ const centerY = circle.top + circle.height / 2;
|
|
|
+ const event = isTouchEvent(e) ? e.touches[0] : e;
|
|
|
+ const angle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
|
|
|
+ const percentage = (angle * (180 / Math.PI) + 180) / 360;
|
|
|
+ const scalc = Math.round(percentage * 100) - 25;
|
|
|
+ speedNum.value = scalcToSpeed(scalc < 0 ? 100 + scalc : scalc);
|
|
|
+};
|
|
|
+function handleDotMousedown() {
|
|
|
+ function onMouseup() {
|
|
|
+ document.removeEventListener('mousemove', onMousemove);
|
|
|
+ document.removeEventListener('mouseup', onMouseup);
|
|
|
+ }
|
|
|
+ document.addEventListener('mousemove', onMousemove);
|
|
|
+ document.addEventListener('mouseup', onMouseup);
|
|
|
+}
|
|
|
+function handleDotTouchstart() {
|
|
|
+ function onTouchend() {
|
|
|
+ document.removeEventListener('touchmove', onTouchmove);
|
|
|
+ document.removeEventListener('touchend', onTouchend);
|
|
|
+ }
|
|
|
+ document.addEventListener('touchmove', onTouchmove);
|
|
|
+ document.addEventListener('touchend', onTouchend);
|
|
|
+}
|
|
|
+function onMousemove(e: MouseEvent) {
|
|
|
+ handleEventCanvas(e);
|
|
|
+}
|
|
|
+function onTouchmove(e: TouchEvent) {
|
|
|
+ handleEventCanvas(e);
|
|
|
+}
|
|
|
+// 根据百分比画圆
|
|
|
+function getCircleBar(steps: number) {
|
|
|
+ const radius = 121; //半径
|
|
|
+ const colorList = ['#51DBFF', '#009FFF'];
|
|
|
+ const mycanvas = mycanvasDom.value!;
|
|
|
+ const ctx = mycanvas.getContext('2d')!;
|
|
|
+ // 每次进来清空画布
|
|
|
+ ctx.clearRect(0, 0, mycanvas.width, mycanvas.height);
|
|
|
+ //找到画布的中心点
|
|
|
+ const canvasX = mycanvas.width / 2;
|
|
|
+ const canvasY = mycanvas.height / 2;
|
|
|
+ //进度条是100%,所以要把一圈360度分成100份
|
|
|
+ const progress = (Math.PI * 2) / 100;
|
|
|
+ const ao = -Math.PI / 2 + steps * progress;
|
|
|
+ const x1 = radius + Math.cos(ao) * radius;
|
|
|
+ const y1 = radius + Math.sin(ao) * radius;
|
|
|
+ // 这里开始算坐标
|
|
|
+ dotDom.value!.style.left = x1 - 7 + 'Px';
|
|
|
+ dotDom.value!.style.top = y1 - 7 + 'Px';
|
|
|
+ // 绘制渐变色
|
|
|
+ const gradientDirection = {
|
|
|
+ x1: 0,
|
|
|
+ y1: 0,
|
|
|
+ x2: radius * 2, // 半径*2
|
|
|
+ y2: 0
|
|
|
+ };
|
|
|
+ gradientDirection.y2 = radius * 2;
|
|
|
+ const gradient = ctx.createLinearGradient(
|
|
|
+ gradientDirection.x1,
|
|
|
+ gradientDirection.y1,
|
|
|
+ gradientDirection.x2,
|
|
|
+ gradientDirection.y2
|
|
|
+ );
|
|
|
+ colorList.map((color, index) => {
|
|
|
+ gradient.addColorStop(index, color);
|
|
|
+ });
|
|
|
+ ctx.strokeStyle = gradient;
|
|
|
+ ctx.lineWidth = 10;
|
|
|
+ ctx.save();
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(
|
|
|
+ canvasX,
|
|
|
+ canvasY,
|
|
|
+ radius,
|
|
|
+ -Math.PI / 2,
|
|
|
+ -Math.PI / 2 + steps * progress,
|
|
|
+ false
|
|
|
+ );
|
|
|
+ ctx.stroke();
|
|
|
+ ctx.closePath();
|
|
|
+ ctx.restore();
|
|
|
+}
|
|
|
+function isTouchEvent(e: MouseEvent | TouchEvent): e is TouchEvent {
|
|
|
+ return window.TouchEvent && e instanceof window.TouchEvent;
|
|
|
+}
|
|
|
+defineExpose({
|
|
|
+ startPlay,
|
|
|
+ pausePlay,
|
|
|
+ speedNum,
|
|
|
+ beatSymbol
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.Metronome {
|
|
|
+ width: 565px;
|
|
|
+ height: 554px;
|
|
|
+ background: url('./imgs/bg.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ padding: 26px 26px 0;
|
|
|
+ &:deep(.bom_drag) {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ height: 76px;
|
|
|
+ padding: 0 28px;
|
|
|
+ & > div {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 0 0 18px 0;
|
|
|
+ &:first-child {
|
|
|
+ border-radius: 0 0 0 18px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .MetronomeBox {
|
|
|
+ width: 100%;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+ .headTools {
|
|
|
+ position: absolute;
|
|
|
+ right: 20px;
|
|
|
+ top: 18px;
|
|
|
+ & > img {
|
|
|
+ margin-left: 18px;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .beatWrap {
|
|
|
+ margin-top: 30px;
|
|
|
+ width: 252px;
|
|
|
+ height: 252px;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ .dot {
|
|
|
+ position: absolute;
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ background: url('./imgs/dot.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ cursor: pointer;
|
|
|
+ z-index: 3;
|
|
|
+ }
|
|
|
+ .round {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 10px solid #d1d1d1;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+ .mycanvas {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+ .beatCon {
|
|
|
+ border-radius: 50%;
|
|
|
+ z-index: 4;
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ background: url('./imgs/yuan.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ .optMid {
|
|
|
+ margin: 5px 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ :deep(.n-input-number) {
|
|
|
+ width: 74px;
|
|
|
+ height: 52px;
|
|
|
+ margin-left: 4px;
|
|
|
+ .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: DINA !important;
|
|
|
+ font-weight: bold !important;
|
|
|
+ font-size: 45px !important;
|
|
|
+ color: #000000 !important;
|
|
|
+ }
|
|
|
+ &.n-input--disabled {
|
|
|
+ cursor: initial;
|
|
|
+ .n-input__input-el {
|
|
|
+ cursor: initial;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .optImg {
|
|
|
+ cursor: pointer;
|
|
|
+ width: 35px;
|
|
|
+ height: 35px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .selectCon {
|
|
|
+ display: flex;
|
|
|
+ margin-top: 22px;
|
|
|
+ :deep(.n-select) {
|
|
|
+ width: 130px;
|
|
|
+ .n-base-selection--selected {
|
|
|
+ --n-height: 48px !important;
|
|
|
+ --n-border-radius: 24px !important;
|
|
|
+ .n-base-selection__border,
|
|
|
+ .n-base-selection__state-border {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ .n-base-selection-input {
|
|
|
+ padding: 0;
|
|
|
+ .n-base-selection-input__content {
|
|
|
+ .beatName {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ padding-top: 2px;
|
|
|
+ & > div:first-child {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 30px;
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+ & > div:last-child {
|
|
|
+ margin-left: 2px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333333;
|
|
|
+ margin-top: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ :deep(.beatSymbolSel.n-select) {
|
|
|
+ margin-left: 15px;
|
|
|
+ .n-base-selection-input__content {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ .beatSymbolImg {
|
|
|
+ width: 44px;
|
|
|
+ height: 46px;
|
|
|
+ &.beatSymbolImg1 {
|
|
|
+ background: url('./imgs/b1.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg2 {
|
|
|
+ background: url('./imgs/b2.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg3 {
|
|
|
+ background: url('./imgs/b3.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg4 {
|
|
|
+ background: url('./imgs/b4.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg5 {
|
|
|
+ background: url('./imgs/b5.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg6 {
|
|
|
+ background: url('./imgs/b6.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg7 {
|
|
|
+ background: url('./imgs/b7.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .sliderList {
|
|
|
+ margin-top: 24px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ & > img {
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ margin-right: 16px;
|
|
|
+ }
|
|
|
+ .sliderText {
|
|
|
+ width: 32px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #1cacf1;
|
|
|
+ margin-left: 16px;
|
|
|
+ }
|
|
|
+ :deep(.n-slider-rail) {
|
|
|
+ height: 6px;
|
|
|
+ line-height: 6px;
|
|
|
+ background: #ffffff;
|
|
|
+ border-radius: 4px;
|
|
|
+ width: 194px;
|
|
|
+ .n-slider-rail__fill {
|
|
|
+ background: linear-gradient(90deg, #63daff 0%, #1798ff 100%);
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .thumbDot {
|
|
|
+ width: 21px;
|
|
|
+ height: 21px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .playBtn {
|
|
|
+ cursor: pointer;
|
|
|
+ margin-top: 20px;
|
|
|
+ width: 272px;
|
|
|
+ height: 54px;
|
|
|
+ background: #00acff;
|
|
|
+ border-radius: 27px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #ffffff;
|
|
|
+ & > img {
|
|
|
+ margin-left: 14px;
|
|
|
+ width: 13px;
|
|
|
+ height: 14px;
|
|
|
+ &.pauseImg {
|
|
|
+ width: 12px;
|
|
|
+ height: 13px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style lang="less">
|
|
|
+.n-base-select-menu.n-select-menu {
|
|
|
+ .beatValOptItem {
|
|
|
+ padding: 0 9px !important;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ &.n-base-select-option::before {
|
|
|
+ transition: initial;
|
|
|
+ }
|
|
|
+ &.n-base-select-option--selected {
|
|
|
+ .n-base-select-option__content {
|
|
|
+ color: #47a7fe;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.n-base-select-option--pending {
|
|
|
+ .n-base-select-option__content {
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+ &::before {
|
|
|
+ background: #47a7fe !important;
|
|
|
+ border-radius: 6px !important;
|
|
|
+ left: 9px;
|
|
|
+ right: 9px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .n-base-select-option__content {
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #777777;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .beatSymbolOptItem {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ &.n-base-select-option::before {
|
|
|
+ transition: initial;
|
|
|
+ }
|
|
|
+ &.n-base-select-option--selected {
|
|
|
+ .n-base-select-option__content {
|
|
|
+ .beatSymbolImg {
|
|
|
+ &.beatSymbolImg1 {
|
|
|
+ background: url('./imgs/a1.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg2 {
|
|
|
+ background: url('./imgs/a2.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg3 {
|
|
|
+ background: url('./imgs/a3.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg4 {
|
|
|
+ background: url('./imgs/a4.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg5 {
|
|
|
+ background: url('./imgs/a5.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg6 {
|
|
|
+ background: url('./imgs/a6.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg7 {
|
|
|
+ background: url('./imgs/a7.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &.n-base-select-option--pending {
|
|
|
+ .n-base-select-option__content {
|
|
|
+ .beatSymbolImg {
|
|
|
+ &.beatSymbolImg1 {
|
|
|
+ background: url('./imgs/c1.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg2 {
|
|
|
+ background: url('./imgs/c2.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg3 {
|
|
|
+ background: url('./imgs/c3.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg4 {
|
|
|
+ background: url('./imgs/c4.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg5 {
|
|
|
+ background: url('./imgs/c5.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg6 {
|
|
|
+ background: url('./imgs/c6.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg7 {
|
|
|
+ background: url('./imgs/c7.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ &::before {
|
|
|
+ background: #47a7fe !important;
|
|
|
+ border-radius: 6px !important;
|
|
|
+ left: 9px;
|
|
|
+ right: 9px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .beatSymbolImg {
|
|
|
+ width: 44px;
|
|
|
+ height: 46px;
|
|
|
+ &.beatSymbolImg1 {
|
|
|
+ background: url('./imgs/b1.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg2 {
|
|
|
+ background: url('./imgs/b2.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg3 {
|
|
|
+ background: url('./imgs/b3.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg4 {
|
|
|
+ background: url('./imgs/b4.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg5 {
|
|
|
+ background: url('./imgs/b5.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg6 {
|
|
|
+ background: url('./imgs/b6.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ &.beatSymbolImg7 {
|
|
|
+ background: url('./imgs/b7.png') no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.metronomeConBoxClass_drag
|
|
|
+ .v-binder-follower-container
|
|
|
+ .n-base-select-menu.n-select-menu {
|
|
|
+ --n-border-radius: 10px !important;
|
|
|
+ .n-scrollbar-rail.n-scrollbar-rail--vertical {
|
|
|
+ right: 3px;
|
|
|
+ --n-scrollbar-width: 3px !important;
|
|
|
+ opacity: 0.4;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|