当前位置: 首页 > article >正文

Vue3学习:番茄钟案例的实现及打包遇到的几个问题

前几天学过一个番茄钟的案例,这个案例跟番茄学习法有关,以25分钟为一个单位,进行倒计时。倒计时的案例我曾经写过,只是时间到了0以后,就停止了,没有响铃提醒。这个案例在倒计时结束后,会有音乐播放,以提醒用户。
只是这个案例是引入的vue库文件,以.html网页的形式实现的。我想把它改成.vue组件的形式,不过组合式我还不太熟,所以用的还是选项式。
起初以为这是分分钟就能搞定的事情,没想到音频播放出现问题,居然折腾了大半天,才算解决。虽然耗费了不少时间,不过倒也挺有收获。
我是用的下面命令创建的vite项目。

npm create vite@latest

问题一 音频不播放

倒计时结束以后,音频会播放,在原网页中运行没有任何问题,到了组件App.vue中,音频piano.mp3却不能播放。

<audio src="./assets/piano.mp3"  id="audio" loop>
</audio>

原案例在tomato.js中定义了一个Tomato类,在html页面中,有下面的代码:

const audio = document.getElementById('audio');
const tomato = new Tomato(audio);
const app = Vue.createApp({
      data() {
        return {
          tomato: tomato,
          editTime: 25,
        }
      },
      ...
    }).mount('#app')

改成.vue组件方式,代码开始部分的audio和tomato应该怎么处理?
我在音频标记中,增加了ref="audioPlayer"属性,去掉id属性,使用this.$refs.audioPlayer的方式来获取网页中的音频,取代getElementById的方式。

<audio src="./assets/piano.mp3"  ref="audioPlayer" loop >
</audio>

把audio和tomato直接放入data()中,代码如下。

const app =Vue.createApp({
      data() {
        return {
          tomato: new Tomato(this.$refs.audioPlayer),
          editTime: 25,
        }
      },
      ...
    }).mount('#app')

起初我感觉是等价替换,不觉得这样做有什么问题,但是运行后音乐总是不播放。多番查找原因后,发现this.refs.audioPlayer是undefined,我才记起来,组件的生命周期中,在data()这个阶段,结构还没渲染,this.refs.audioPlayer不能用,用了也无效,那该怎么办呢?
隐约记得有个$nextTick(),结果看了半天依然不知道该怎么使用。后来灵机一动,抱着试试看的心态添加了钩子函数mounted(),在其中给tomato来了一个重新赋值,没想到音乐居然可以正常播放了。
我曾试着把data()中的tomato去掉,只在mounted()中一次赋值,结果程序直接不运行了。我并不确定这样重新赋值是否有什么不妥,不过经测试,程序各项功能运行都正常。

const app =Vue.createApp({
      data() {
        return {
          tomato: new Tomato(),  //去掉参数 this.$refs.audioPlayer
          editTime: 25,
        }
      },
      mounted(){
        this.tomato=new Tomato(this.$refs.audioPlayer) //挂载后tomato再赋值
      },
      ...
    }).mount('#app')

问题二音频不能打包

程序正常运行后,我又试着运行了一下npm run build命令来进行打包,先是打包后的网页运行一片空白,这个问题我遇见过,只需要配置一下vue.config.js文件,添加

base: './',  

即可。
vue.config.js文件如下:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  base: './',     //此行必须设置,不然显示为空白
})

网页空白的问题解决了,网页倒计时正常运行,开始、暂停、停止、编辑等都没有问题。结果倒计时结束后,音乐又不能播放了。我看了一下打包后dist文件夹里的文件,没有.mp3文件,也就是说,音频没有被打包,那肯定不能播放。
我上网查看关于音频的问题,有各种说法,有让安装file-loader的,有让配置vue.config.js文件、设置publicPath: “./”,以及设置其他规则的,这些我觉得挺有道理,一一试过后却不行。
后来无所适从,东看一点西看一点,最后在public下建了一个子文件夹mp3,把要打包的音频文件拷贝到了public/mp3目录下,又修改了audio标记中的src的路径为’./mp3/piano.mp3’,

<audio src="./mp3/piano.mp3"  ref="audioPlayer" loop>
</audio>

一番倒腾下来,音频居然打包成功,能正常播放了。看来,要随同打包的文件应该放在public文件夹下。

文件目录如下。
在这里插入图片描述

下面是番茄钟完整的代码。
1.tomato.js,代码如下。

export  default class Tomato {
  constructor(audio) {
    this.states = [
      { from: 'stopped', to: 'timing', action: 'start' },
      { from: 'stopped', to: 'editing', action: 'edit' },
      { from: 'timing', to: 'paused', action: 'pause' },
      { from: 'timing', to: 'stopped', action: 'stop' },
      { from: 'paused', to: 'timing', action: 'start' },
      { from: 'paused', to: 'stopped', action: 'stop' },
      { from: 'paused', to: 'editing', action: 'edit' },
      { from: 'editing', to: '', action: 'cancel' },
      { from: 'editing', to: 'stopped', action: 'save' },
    ];

    this.currentState = 'stopped';
    this.totalSeconds = 25 * 60;
    this.remainSeconds = this.totalSeconds;
    this.timer = null;
    this.audio = audio;
    
  }

  get actions() {
    return this.states.filter(_ => _.from === this.currentState)
      .map((value) => value.action);
  }

  start() {
    this.currentState = 'timing';
    this.startTimer();
  }
  stop() {
    this.currentState = 'stopped';
    this.remainSeconds = this.totalSeconds;
    this.stopTimer();
    this.audio.load();

  }
  pause() {
    this.currentState = 'paused';
    this.stopTimer();
    this.audio.pause();
  }
  edit() {
    this.currentState = 'editing';
  }
  save(seconds) {
    this.currentState = 'stopped';
    this.totalSeconds = seconds;
    this.remainSeconds = seconds;
  }
  cancel() {
    this.currentState = this.totalSeconds === this.remainSeconds ? 'stopped' : 'paused';
  }

  startTimer() {
    this.timer = setInterval(() => this.countdown(), 1000);
  }
  stopTimer() {
    clearInterval(this.timer);
    this.timer = null;
  }
  countdown() {
    if (this.remainSeconds == 0) {
      this.stopTimer();
      this.audio.play();
      return;
    }
    this.remainSeconds-=1;
  }
}

2.App.vue,代码如下。

<template>
  <div class="container">
    <div id="app">
        <!-- 显示时间 -->
        <div class="timer">
          <span class="minute">{{ minutes }}</span>
          <span>:</span>
          <span class="seconds">{{ seconds }}</span>
        </div>
        <!-- 控制器 -->
        <div class="controls">
          <div v-for="item in tomato.actions" @click="doAction(item)" :key="item.action">
            <i :class="['fa', getActionIcon(item)]"></i>
          </div>
        </div>
        <!-- 修改时间 -->
        <div class="input" v-if="tomato.currentState === 'editing'">
          <input type="number" min="1" v-model="editTime">
        </div>
        <!-- 倒计时结束后播放音频 -->
        <audio src="./mp3/piano.mp3"  ref="audioPlayer" loop>
        </audio>
      </div>
    </div>
  </template>
<script>
import Tomato from './assets/tomato.js'

export default {
  data() {
    return {
     tomato: new Tomato(), //只有在 Vue 完成必要的 DOM 渲染之后,通过 this.$refs 访问才是可行的。
      editTime: 25,
    }
  },
  methods: {
    doAction(action) {
      if (action === 'save') {
        this.tomato[action](this.editTime * 60);
      } else {
        this.tomato[action]();
      }
    },
    getActionIcon(action) {
      let icons = [
        { action: 'start', icon: 'fa-play' },
        { action: 'pause', icon: 'fa-pause' },
        { action: 'stop', icon: 'fa-stop' },
        { action: 'edit', icon: 'fa-edit' },
        { action: 'save', icon: 'fa-check' },
        { action: 'cancel', icon: 'fa-close' },

      ];
      return icons.find(value => value.action == action).icon;
    },
    // 小于10,前面补零
    formatTime(time) {
      return (time < 10 ? '0' : '') + time
    }
  },
  computed: {
    minutes() {
      const minutes = Math.floor(this.tomato.remainSeconds / 60);
      return this.formatTime(minutes);
    },
    seconds() {
      const seconds = this.tomato.remainSeconds % 60;
      return this.formatTime(seconds);
    }
  },
  mounted(){
    this.tomato=new Tomato(this.$refs.audioPlayer)
  }
}
</script>

<style scoped>
body {
  margin: 0;
  text-align: center;
}

.container {
  height: 100vh;
  width: 100%;
  background-color: #dedede;
  display: flex;
  justify-content: center;
  align-items: center;
}

.timer {
  font-size: 6rem;
  color: #ff8400;
}

.controls {
  color: #666;
  display: flex;
  justify-content: space-evenly;
  width: 100%;
  margin: 1.5rem 0;
}

.controls>div {
  cursor: pointer;
}

.input input {
  background-color: #ccc;
  border: none;
  font-size: 1.5rem;
  padding: .5em;
  text-align: center;
  color: #666;
  outline: none;
}
</style>
  1. main.js,代码如下。
import { createApp } from 'vue'
import App from './App.vue'
import 'font-awesome/css/font-awesome.min.css'
createApp(App).mount('#app')

项目中用到了font-awesome图标,新建项目后需要进行安装,在终端中执行以下命令:

npm install font-awesome -S

然后在main.js中,导入font-awesome。

import 'font-awesome/css/font-awesome.min.css'

http://www.kler.cn/news/368056.html

相关文章:

  • K 个一组反转链表
  • 软考:GPU算力,AI芯片
  • 架构师备考-数据库设计、实施和维护
  • 【go】仅设想,能不能通过pure go编写页面。
  • Spring Boot驱动的厨艺社交平台设计与实现
  • Ubuntu20.04系统安装opencv
  • Python 自动化运维:Python基础知识
  • Vuejs设计与实现 — 渲染器核心:挂载与更新
  • 【C++单调栈 贡献法】907. 子数组的最小值之和|1975
  • 闯关leetcode——171. Excel Sheet Column Number
  • Unity3D 自动化资源打AB包详解
  • Vue项目GET请求正常,POST请求却失效?揭秘Mock服务背后的故事
  • hass docker openwrt配置
  • C++,STL 050(24.10.27)
  • 【uni-app学习-2】
  • Golang | Leetcode Golang题解之第504题七进制数
  • Vue 如何批量注册自定义指令
  • 基础设施即代码(IaC):自动化基础设施管理的未来
  • 1.4_SQL手工注入
  • 2207. 字符串中最多数目的子序列
  • 每日OJ题_牛客_[NOIP2001]装箱问题_01背包_C++_Java
  • 16. 虚拟化
  • C# 标准绘图控件 chart 多 Y 值的编程应用
  • 5G NR GSCN计算SSB中心频率MATLAB实现
  • 香港国际金融市场的多元化投资与风险管理策略
  • 资讯 | 财富通科技政务协同办公管理软件通过麒麟软件适配认证