From c65b90aaa3477a90ebc325024927d80227c0c841 Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期四, 09 四月 2026 14:09:25 +0800
Subject: [PATCH] 测试完成

---
 src/components/AudioPlayer/index.vue |  964 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 964 insertions(+), 0 deletions(-)

diff --git a/src/components/AudioPlayer/index.vue b/src/components/AudioPlayer/index.vue
new file mode 100644
index 0000000..0524c1f
--- /dev/null
+++ b/src/components/AudioPlayer/index.vue
@@ -0,0 +1,964 @@
+<template>
+  <div class="audio-player">
+    <!-- 涓绘挱鏀惧櫒 -->
+    <div class="audio-container" v-if="showDefaultPlayer">
+      <audio
+        ref="audioElement"
+        :src="audioSource"
+        preload="metadata"
+        @timeupdate="updateTime"
+        @loadedmetadata="updateDuration"
+        @play="handlePlay"
+        @pause="handlePause"
+        @ended="handleEnded"
+        @error="handleError"
+        style="display: none"
+      ></audio>
+
+      <!-- 鎾斁鎺у埗 -->
+      <div class="audio-controls">
+        <el-button
+          v-if="!isPlaying"
+          type="text"
+          icon="el-icon-video-play"
+          class="play-btn"
+          @click="playAudio"
+          :loading="loading"
+        ></el-button>
+        <el-button
+          v-else
+          type="text"
+          icon="el-icon-video-pause"
+          class="pause-btn"
+          @click="pauseAudio"
+        ></el-button>
+
+        <!-- 杩涘害鏉� -->
+        <div class="progress-container" @click="seekAudio" @mousemove="updateHoverTime">
+          <div class="progress-background">
+            <!-- 缂撳啿杩涘害 -->
+            <div
+              v-if="buffered.length > 0"
+              class="buffered-progress"
+              :style="{ width: `${bufferedPercent}%` }"
+            ></div>
+
+            <!-- 鎾斁杩涘害 -->
+            <div
+              class="played-progress"
+              :style="{ width: `${progressPercent}%` }"
+            ></div>
+
+            <!-- 鎮仠棰勮 -->
+            <div
+              v-if="showHoverPreview"
+              class="hover-preview"
+              :style="{ left: `${hoverPercent}%` }"
+            >
+              <span class="hover-time">{{ formatTime(hoverTime) }}</span>
+            </div>
+
+            <!-- 鎾斁鐐� -->
+            <div
+              class="playhead"
+              :style="{ left: `${progressPercent}%` }"
+            ></div>
+          </div>
+        </div>
+
+        <!-- 鏃堕棿鏄剧ず -->
+        <div class="time-display">
+          <span class="current-time">{{ formatTime(currentTime) }}</span>
+          <span class="time-separator">/</span>
+          <span class="duration">{{ formatTime(duration) }}</span>
+        </div>
+
+        <!-- 闊抽噺鎺у埗 -->
+        <div class="volume-control" v-if="showVolumeControl">
+          <el-popover
+            placement="top"
+            width="40"
+            trigger="hover"
+            popper-class="volume-popover"
+          >
+            <div class="volume-slider-container" @click.stop>
+              <el-slider
+                v-model="volume"
+                vertical
+                height="100px"
+                :show-tooltip="false"
+                @input="changeVolume"
+              ></el-slider>
+            </div>
+
+            <el-button
+              slot="reference"
+              type="text"
+              :icon="volumeIcon"
+              class="volume-btn"
+              @click.stop
+            ></el-button>
+          </el-popover>
+        </div>
+
+        <!-- 鎾斁閫熷害 -->
+        <el-select
+          v-if="showPlaybackRate"
+          v-model="playbackRate"
+          size="mini"
+          class="playback-rate"
+          @change="changePlaybackRate"
+        >
+          <el-option
+            v-for="rate in playbackRates"
+            :key="rate"
+            :label="`${rate}x`"
+            :value="rate"
+          ></el-option>
+        </el-select>
+
+        <!-- 涓嬭浇鎸夐挳 -->
+        <el-button
+          v-if="showDownload"
+          type="text"
+          icon="el-icon-download"
+          class="download-btn"
+          @click="downloadAudio"
+          title="涓嬭浇闊抽"
+        ></el-button>
+
+        <!-- 鏇村鍔熻兘鎸夐挳 -->
+        <el-dropdown
+          v-if="showMoreOptions"
+          trigger="click"
+          class="more-options"
+        >
+          <el-button type="text" icon="el-icon-more" class="more-btn"></el-button>
+          <el-dropdown-menu slot="dropdown">
+            <el-dropdown-item @click.native="loopAudio = !loopAudio">
+              <i :class="loopAudio ? 'el-icon-refresh' : 'el-icon-refresh-left'"></i>
+              {{ loopAudio ? '鍏抽棴寰幆' : '寮�鍚惊鐜�' }}
+            </el-dropdown-item>
+            <el-dropdown-item @click.native="resetAudio">
+              <i class="el-icon-refresh-right"></i>
+              閲嶇疆
+            </el-dropdown-item>
+            <el-dropdown-item v-if="showDetails" @click.native="showAudioInfo">
+              <i class="el-icon-info"></i>
+              闊抽淇℃伅
+            </el-dropdown-item>
+          </el-dropdown-menu>
+        </el-dropdown>
+      </div>
+    </div>
+
+    <!-- 闊抽淇℃伅瀵硅瘽妗� -->
+    <el-dialog
+      v-if="showDetailsDialog"
+      title="闊抽淇℃伅"
+      :visible.sync="showDetailsDialog"
+      width="400px"
+    >
+      <div class="audio-info">
+        <div class="info-item">
+          <span class="label">鏂囦欢澶у皬锛�</span>
+          <span class="value">{{ fileSize }}</span>
+        </div>
+        <div class="info-item">
+          <span class="label">闊抽鏃堕暱锛�</span>
+          <span class="value">{{ formatTime(duration) }}</span>
+        </div>
+        <div class="info-item">
+          <span class="label">鏂囦欢鏍煎紡锛�</span>
+          <span class="value">{{ audioFormat }}</span>
+        </div>
+        <div class="info-item">
+          <span class="label">闊抽鍦板潃锛�</span>
+          <a :href="audioSource" target="_blank" class="audio-url">{{ truncateUrl(audioSource) }}</a>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 绠�鏄撴挱鏀惧櫒锛堜粎鏄剧ず鎾斁鎸夐挳锛� -->
+    <div v-else class="simple-player">
+      <el-button
+        v-if="!isPlaying"
+        type="text"
+        icon="el-icon-video-play"
+        size="small"
+        class="simple-play-btn"
+        @click="playAudio"
+        :loading="loading"
+      >
+        鎾斁褰曢煶
+      </el-button>
+      <el-button
+        v-else
+        type="text"
+        icon="el-icon-video-pause"
+        size="small"
+        class="simple-pause-btn"
+        @click="pauseAudio"
+      >
+        鏆傚仠鎾斁
+      </el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AudioPlayer',
+  props: {
+    // 闊抽婧愬湴鍧�
+    audioSource: {
+      type: String,
+      default: ''
+    },
+
+    // 鏄惁鏄剧ず瀹屾暣鎾斁鍣�
+    showDefaultPlayer: {
+      type: Boolean,
+      default: true
+    },
+
+    // 鏄惁鏄剧ず闊抽噺鎺у埗
+    showVolumeControl: {
+      type: Boolean,
+      default: true
+    },
+
+    // 鏄惁鏄剧ず鎾斁閫熷害鎺у埗
+    showPlaybackRate: {
+      type: Boolean,
+      default: true
+    },
+
+    // 鏄惁鏄剧ず涓嬭浇鎸夐挳
+    showDownload: {
+      type: Boolean,
+      default: true
+    },
+
+    // 鏄惁鏄剧ず鏇村閫夐」
+    showMoreOptions: {
+      type: Boolean,
+      default: true
+    },
+
+    // 鏄惁鏄剧ず闊抽淇℃伅
+    showDetails: {
+      type: Boolean,
+      default: true
+    },
+
+    // 鍒濆闊抽噺锛�0-100锛�
+    initialVolume: {
+      type: Number,
+      default: 80,
+      validator: (value) => value >= 0 && value <= 100
+    },
+
+    // 鍒濆鎾斁閫熷害
+    initialPlaybackRate: {
+      type: Number,
+      default: 1.0
+    },
+
+    // 鑷姩鎾斁
+    autoplay: {
+      type: Boolean,
+      default: false
+    },
+
+    // 寰幆鎾斁
+    loop: {
+      type: Boolean,
+      default: false
+    }
+  },
+
+  data() {
+    return {
+      // 鎾斁鐘舵��
+      isPlaying: false,
+      isLoading: false,
+      loading: false,
+      error: false,
+
+      // 鏃堕棿鐩稿叧
+      currentTime: 0,
+      duration: 0,
+      buffered: [],
+
+      // 闊抽噺
+      volume: this.initialVolume,
+      isMuted: false,
+
+      // 鎾斁閫熷害
+      playbackRate: this.initialPlaybackRate,
+      playbackRates: [0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
+
+      // 寰幆鎾斁
+      loopAudio: this.loop,
+
+      // 杩涘害鏉℃偓鍋�
+      hoverTime: 0,
+      hoverPercent: 0,
+      showHoverPreview: false,
+
+      // 闊抽淇℃伅
+      showDetailsDialog: false,
+      fileSize: '鏈煡',
+      audioFormat: '鏈煡',
+
+      // 鐩戝惉鍣ㄥ紩鐢�
+      resizeObserver: null
+    };
+  },
+
+  computed: {
+    // 鎾斁杩涘害鐧惧垎姣�
+    progressPercent() {
+      if (this.duration <= 0) return 0;
+      return (this.currentTime / this.duration) * 100;
+    },
+
+    // 缂撳啿杩涘害鐧惧垎姣�
+    bufferedPercent() {
+      if (this.duration <= 0) return 0;
+      if (this.buffered.length === 0) return 0;
+
+      const bufferedEnd = this.buffered.end(this.buffered.length - 1);
+      return (bufferedEnd / this.duration) * 100;
+    },
+
+    // 闊抽噺鍥炬爣
+    volumeIcon() {
+      if (this.isMuted || this.volume === 0) {
+        return 'el-icon-turn-off-microphone';
+      } else if (this.volume <= 30) {
+        return 'el-icon-microphone';
+      } else if (this.volume <= 70) {
+        return 'el-icon-microphone';
+      } else {
+        return 'el-icon-microphone';
+      }
+    }
+  },
+
+  watch: {
+    // 鐩戝惉闊抽婧愬彉鍖�
+    audioSource(newSource) {
+      if (newSource) {
+        this.resetAudio();
+        this.loadAudio();
+      }
+    },
+
+    // 鐩戝惉鑷姩鎾斁
+    autoplay(newVal) {
+      if (newVal && this.audioSource) {
+        this.playAudio();
+      }
+    },
+
+    // 鐩戝惉寰幆鎾斁
+    loopAudio(newVal) {
+      this.$emit('loop-change', newVal);
+    }
+  },
+
+  mounted() {
+    if (this.audioSource) {
+      this.loadAudio();
+    }
+
+    // 鐩戝惉绐楀彛澶у皬鍙樺寲
+    this.resizeObserver = new ResizeObserver(() => {
+      this.updateBuffered();
+    });
+
+    if (this.$refs.audioElement) {
+      this.resizeObserver.observe(this.$refs.audioElement);
+    }
+  },
+
+  beforeDestroy() {
+    if (this.resizeObserver) {
+      this.resizeObserver.disconnect();
+    }
+
+    // 鍋滄闊抽鎾斁
+    this.pauseAudio();
+  },
+
+  methods: {
+    // 鍔犺浇闊抽
+    async loadAudio() {
+      this.loading = true;
+      this.error = false;
+
+      try {
+        const audio = this.$refs.audioElement;
+        if (!audio) return;
+
+        // 璁剧疆闊抽噺
+        audio.volume = this.volume / 100;
+        audio.playbackRate = this.playbackRate;
+        audio.loop = this.loopAudio;
+
+        // 灏濊瘯鑾峰彇鏂囦欢淇℃伅
+        this.getAudioInfo();
+
+        // 濡傛灉璁剧疆浜嗚嚜鍔ㄦ挱鏀撅紝寮�濮嬫挱鏀�
+        if (this.autoplay) {
+          await this.playAudio();
+        }
+      } catch (error) {
+        console.error('鍔犺浇闊抽澶辫触:', error);
+        this.error = true;
+        this.$emit('error', error);
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    // 鎾斁闊抽
+    async playAudio() {
+      try {
+        const audio = this.$refs.audioElement;
+        if (!audio) return;
+
+        await audio.play();
+        this.isPlaying = true;
+        this.$emit('play');
+      } catch (error) {
+        console.error('鎾斁澶辫触:', error);
+        this.error = true;
+        this.isPlaying = false;
+        this.$emit('error', error);
+
+        // 濡傛灉鏄敤鎴蜂氦浜掑紩璧风殑閿欒锛屾彁绀虹敤鎴�
+        if (error.name === 'NotAllowedError') {
+          this.$message.warning('璇锋墜鍔ㄧ偣鍑绘挱鏀炬寜閽紑濮嬫挱鏀�');
+        }
+      }
+    },
+
+    // 鏆傚仠闊抽
+    pauseAudio() {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      audio.pause();
+      this.isPlaying = false;
+      this.$emit('pause');
+    },
+
+    // 璺宠浆鍒版寚瀹氭椂闂�
+    seekAudio(event) {
+      const audio = this.$refs.audioElement;
+      if (!audio || this.duration <= 0) return;
+
+      const progressContainer = event.currentTarget;
+      const rect = progressContainer.getBoundingClientRect();
+      const x = event.clientX - rect.left;
+      const width = rect.width;
+
+      const percent = Math.max(0, Math.min(1, x / width));
+      const time = percent * this.duration;
+
+      audio.currentTime = time;
+      this.currentTime = time;
+      this.$emit('seek', time);
+    },
+
+    // 鏇存柊鎮仠鏃堕棿
+    updateHoverTime(event) {
+      const progressContainer = event.currentTarget;
+      const rect = progressContainer.getBoundingClientRect();
+      const x = event.clientX - rect.left;
+      const width = rect.width;
+
+      const percent = Math.max(0, Math.min(1, x / width));
+      this.hoverPercent = percent * 100;
+      this.hoverTime = percent * this.duration;
+      this.showHoverPreview = true;
+    },
+
+    // 闅愯棌鎮仠棰勮
+    hideHoverPreview() {
+      this.showHoverPreview = false;
+    },
+
+    // 鏇存柊鏃堕棿
+    updateTime() {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      this.currentTime = audio.currentTime;
+      this.$emit('timeupdate', this.currentTime);
+    },
+
+    // 鏇存柊鎬绘椂闀�
+    updateDuration() {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      this.duration = audio.duration;
+      this.updateBuffered();
+      this.$emit('loadedmetadata', this.duration);
+    },
+
+    // 鏇存柊缂撳啿鏁版嵁
+    updateBuffered() {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      this.buffered = audio.buffered;
+    },
+
+    // 澶勭悊鎾斁
+    handlePlay() {
+      this.isPlaying = true;
+      this.$emit('play');
+    },
+
+    // 澶勭悊鏆傚仠
+    handlePause() {
+      this.isPlaying = false;
+      this.$emit('pause');
+    },
+
+    // 澶勭悊鎾斁缁撴潫
+    handleEnded() {
+      this.isPlaying = false;
+      this.currentTime = 0;
+      this.$emit('ended');
+    },
+
+    // 澶勭悊閿欒
+    handleError(event) {
+      console.error('闊抽鎾斁閿欒:', event);
+      this.error = true;
+      this.isPlaying = false;
+      this.$emit('error', event);
+    },
+
+    // 鏀瑰彉闊抽噺
+    changeVolume(value) {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      audio.volume = value / 100;
+      this.volume = value;
+      this.isMuted = value === 0;
+      this.$emit('volume-change', value);
+    },
+
+    // 闈欓煶/鍙栨秷闈欓煶
+    toggleMute() {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      this.isMuted = !this.isMuted;
+      audio.muted = this.isMuted;
+      this.$emit('mute-change', this.isMuted);
+    },
+
+    // 鏀瑰彉鎾斁閫熷害
+    changePlaybackRate(rate) {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      audio.playbackRate = rate;
+      this.playbackRate = rate;
+      this.$emit('playbackrate-change', rate);
+    },
+
+    // 閲嶇疆闊抽
+    resetAudio() {
+      const audio = this.$refs.audioElement;
+      if (!audio) return;
+
+      audio.currentTime = 0;
+      this.currentTime = 0;
+      this.pauseAudio();
+      this.$emit('reset');
+    },
+
+    // 涓嬭浇闊抽
+    downloadAudio() {
+      if (!this.audioSource) {
+        this.$message.warning('闊抽鍦板潃鏃犳晥');
+        return;
+      }
+
+      const link = document.createElement('a');
+      link.href = this.audioSource;
+      link.download = this.getFileNameFromUrl(this.audioSource);
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+
+      this.$emit('download');
+    },
+
+    // 鏄剧ず闊抽淇℃伅
+    async showAudioInfo() {
+      this.showDetailsDialog = true;
+      await this.getAudioInfo();
+    },
+
+    // 鑾峰彇闊抽淇℃伅
+    async getAudioInfo() {
+      if (!this.audioSource) return;
+
+      try {
+        // 鑾峰彇鏂囦欢澶у皬
+        const response = await fetch(this.audioSource, { method: 'HEAD' });
+        const contentLength = response.headers.get('content-length');
+
+        if (contentLength) {
+          this.fileSize = this.formatFileSize(contentLength);
+        }
+
+        // 鑾峰彇鏂囦欢鏍煎紡
+        const url = this.audioSource.toLowerCase();
+        if (url.endsWith('.mp3')) this.audioFormat = 'MP3';
+        else if (url.endsWith('.wav')) this.audioFormat = 'WAV';
+        else if (url.endsWith('.ogg')) this.audioFormat = 'OGG';
+        else if (url.endsWith('.m4a')) this.audioFormat = 'M4A';
+        else if (url.endsWith('.aac')) this.audioFormat = 'AAC';
+        else this.audioFormat = '鏈煡鏍煎紡';
+
+      } catch (error) {
+        console.error('鑾峰彇闊抽淇℃伅澶辫触:', error);
+        this.fileSize = '鏈煡';
+        this.audioFormat = '鏈煡';
+      }
+    },
+
+    // 鏍煎紡鍖栨椂闂�
+    formatTime(time) {
+      if (isNaN(time) || time < 0) return '00:00';
+
+      const hours = Math.floor(time / 3600);
+      const minutes = Math.floor((time % 3600) / 60);
+      const seconds = Math.floor(time % 60);
+
+      if (hours > 0) {
+        return `${hours.toString().padStart(2, '0')}:${minutes
+          .toString()
+          .padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+      } else {
+        return `${minutes.toString().padStart(2, '0')}:${seconds
+          .toString()
+          .padStart(2, '0')}`;
+      }
+    },
+
+    // 鏍煎紡鍖栨枃浠跺ぇ灏�
+    formatFileSize(bytes) {
+      if (bytes === 0) return '0 B';
+
+      const k = 1024;
+      const sizes = ['B', 'KB', 'MB', 'GB'];
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+    },
+
+    // 鎴柇URL鏄剧ず
+    truncateUrl(url, maxLength = 40) {
+      if (!url) return '';
+      if (url.length <= maxLength) return url;
+
+      const start = url.substring(0, maxLength / 2 - 3);
+      const end = url.substring(url.length - maxLength / 2 + 3);
+      return start + '...' + end;
+    },
+
+    // 浠嶶RL鑾峰彇鏂囦欢鍚�
+    getFileNameFromUrl(url) {
+      if (!url) return 'audio';
+
+      const fileName = url.split('/').pop();
+      return fileName || 'audio';
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.audio-player {
+  width: 100%;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+
+  .audio-container {
+    background: #fff;
+    border-radius: 8px;
+    padding: 12px 16px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    border: 1px solid #ebeef5;
+
+    .audio-controls {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+
+      .play-btn,
+      .pause-btn {
+        width: 36px;
+        height: 36px;
+        padding: 0;
+        font-size: 20px;
+        color: #409EFF;
+
+        &:hover {
+          color: #66b1ff;
+        }
+
+        &:active {
+          color: #3a8ee6;
+        }
+      }
+
+      .progress-container {
+        flex: 1;
+        height: 36px;
+        display: flex;
+        align-items: center;
+        cursor: pointer;
+
+        .progress-background {
+          position: relative;
+          width: 100%;
+          height: 6px;
+          background: #e4e7ed;
+          border-radius: 3px;
+          overflow: visible;
+
+          .buffered-progress {
+            position: absolute;
+            top: 0;
+            left: 0;
+            height: 100%;
+            background: #c0c4cc;
+            border-radius: 3px;
+            transition: width 0.2s ease;
+          }
+
+          .played-progress {
+            position: absolute;
+            top: 0;
+            left: 0;
+            height: 100%;
+            background: #409EFF;
+            border-radius: 3px;
+            transition: width 0.2s ease;
+            z-index: 2;
+          }
+
+          .hover-preview {
+            position: absolute;
+            top: -30px;
+            width: 1px;
+            height: 20px;
+            background: #909399;
+            transform: translateX(-50%);
+            z-index: 3;
+            pointer-events: none;
+
+            .hover-time {
+              position: absolute;
+              bottom: 100%;
+              left: 50%;
+              transform: translateX(-50%);
+              background: rgba(0, 0, 0, 0.8);
+              color: white;
+              padding: 2px 6px;
+              border-radius: 3px;
+              font-size: 12px;
+              white-space: nowrap;
+            }
+
+            &::after {
+              content: '';
+              position: absolute;
+              top: 100%;
+              left: 50%;
+              transform: translateX(-50%);
+              width: 0;
+              height: 0;
+              border-left: 4px solid transparent;
+              border-right: 4px solid transparent;
+              border-top: 4px solid rgba(0, 0, 0, 0.8);
+            }
+          }
+
+          .playhead {
+            position: absolute;
+            top: 50%;
+            width: 12px;
+            height: 12px;
+            background: #fff;
+            border: 2px solid #409EFF;
+            border-radius: 50%;
+            transform: translate(-50%, -50%);
+            z-index: 4;
+            cursor: pointer;
+            transition: all 0.2s ease;
+
+            &:hover {
+              transform: translate(-50%, -50%) scale(1.2);
+              box-shadow: 0 0 8px rgba(64, 158, 255, 0.5);
+            }
+          }
+        }
+
+        &:hover {
+          .progress-background {
+            height: 8px;
+
+            .playhead {
+              transform: translate(-50%, -50%) scale(1.1);
+            }
+          }
+        }
+      }
+
+      .time-display {
+        min-width: 100px;
+        font-size: 12px;
+        color: #606266;
+        display: flex;
+        align-items: center;
+        gap: 2px;
+
+        .current-time {
+          color: #303133;
+          font-weight: 500;
+        }
+
+        .time-separator {
+          opacity: 0.6;
+        }
+
+        .duration {
+          opacity: 0.8;
+        }
+      }
+
+      .volume-control {
+        .volume-btn {
+          padding: 0;
+          width: 24px;
+          height: 24px;
+          font-size: 16px;
+          color: #606266;
+
+          &:hover {
+            color: #409EFF;
+          }
+        }
+      }
+
+      .playback-rate {
+        width: 60px;
+
+        ::v-deep .el-input__inner {
+          height: 24px;
+          line-height: 24px;
+          padding: 0 5px;
+          font-size: 12px;
+        }
+      }
+
+      .download-btn,
+      .more-btn {
+        padding: 0;
+        width: 24px;
+        height: 24px;
+        font-size: 16px;
+        color: #606266;
+
+        &:hover {
+          color: #409EFF;
+        }
+      }
+    }
+  }
+
+  .simple-player {
+    .simple-play-btn,
+    .simple-pause-btn {
+      padding: 4px 8px;
+      font-size: 12px;
+      color: #409EFF;
+
+      &:hover {
+        color: #66b1ff;
+      }
+    }
+  }
+
+  .audio-info {
+    .info-item {
+      margin-bottom: 12px;
+      display: flex;
+
+      .label {
+        width: 80px;
+        font-weight: 500;
+        color: #303133;
+      }
+
+      .value {
+        flex: 1;
+        color: #606266;
+      }
+
+      .audio-url {
+        color: #409EFF;
+        text-decoration: none;
+
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+    }
+  }
+}
+
+// 闊抽噺寮圭獥鏍峰紡
+::v-deep .volume-popover {
+  padding: 10px;
+  min-width: 40px;
+
+  .volume-slider-container {
+    height: 100px;
+    display: flex;
+    justify-content: center;
+
+    .el-slider {
+      height: 100%;
+
+      .el-slider__runway {
+        background: #e4e7ed;
+      }
+
+      .el-slider__bar {
+        background: #409EFF;
+      }
+
+      .el-slider__button {
+        border-color: #409EFF;
+        width: 12px;
+        height: 12px;
+      }
+    }
+  }
+}
+</style>

--
Gitblit v1.9.3