Vue3实现视频播放弹窗组件,支持全屏播放,音量控制,进度条自定义样式,适配浏览器小窗播放,视频大小自适配,缓冲loading,代码复制即用

效果图

组件所需VUE3代码

TypeScript 复制代码
<template>
  <div class="video-dialog" :class="fullScreen && 'video-dialog-full-screen'">
    <el-dialog
      v-model="props.visible"
      draggable
      :show-close="false"
      title=""
      center
      align-center
      @close="changeVisible"
    >
      <!-- 视频容器 -->
      <div
        class="video-content"
        :style="{
          backgroundImage: firstFrameUrl ? `url(${firstFrameUrl})` : 'none',
        }"
      >
        <!-- 背景磨砂层 -->
        <div class="video-backdrop" v-if="firstFrameUrl"></div>

        <!-- 视频加载中状态 -->
        <div class="video-loading" v-if="isLoading">
          <div class="loading-spinner"></div>
          <p v-if="loadingText">{{ loadingText }}</p>
        </div>

        <!-- 视频标签 -->
        <video
          ref="videoRef"
          :src="videoSrc"
          @timeupdate="updateProgress"
          @ended="handleEnded"
          @click="togglePlay"
          @volumechange="updateVolume"
          @seeked="updateBackground"
          @loadedmetadata="handleMetadataLoaded"
          @error="handleVideoError"
          @waiting="handleVideoBuffering"
          @playing="handleVideoPlaying"
        ></video>

        <!-- 播放错误提示 -->
        <div class="video-error" v-if="videoError">
          <img :src="DataUrl.ErrorIcon || 'https://picsum.photos/48/48'" alt="播放错误" class="error-icon" />
          <p class="error-text">{{ errorMessage }}</p>
          <p class="error-tips">建议使用MP4格式(H.264编码)重新上传</p>
        </div>

        <!-- 视频数据 -->
        <ul class="video-data" v-if="previewInfo">
          <li>
            <img :src="DataUrl.ZanIcon" alt="" class="data-icon i-1" />
            <p>760</p>
          </li>
          <li>
            <img :src="DataUrl.PingIcon" alt="" class="data-icon i-2" />
            <p>670</p>
          </li>
          <li>
            <img :src="DataUrl.LookEyeIcon" alt="" class="data-icon i-3" />
            <p>890</p>
          </li>
        </ul>

        <!-- 视频基本信息 -->
        <div class="video-base-info" v-if="previewInfo">
          <div class="title flex">
            <h1>网红名称</h1>
            <span></span>
            <p>2025.03.30</p>
          </div>
          <p class="note">
            风掠过耳际时突然懂了:原来旅行不是赶路,是让山川湖海,把心里的褶皱慢慢烫平。
          </p>
        </div>

        <img
          @click="changeVisible"
          :src="DataUrl.CloseRadiuIcon"
          alt=""
          class="close-icon"
        />
      </div>

      <!-- 视频进度条 -->
      <div class="video-progress">
        <div class="progress-bg" @click="seekToPosition">
          <div class="progress-value" :style="{ width: progress + '%' }"></div>
          <div class="progress-pointer" :style="{ left: progress + '%' }"></div>
        </div>
      </div>

      <!-- 视频控制栏 -->
      <div class="video-footer-tool">
        <div class="flex">
          <!-- 播放按钮 -->
          <img
            :src="isPlaying ? DataUrl.PauseIcon : DataUrl.PlayZIcon"
            alt=""
            class="icon-1"
            @click="togglePlay"
          />
          <!-- 下一个按钮 -->
          <img
            :src="DataUrl.PlayNextIcon"
            alt=""
            class="icon-2"
            @click="playNextVideo"
          />
          <!-- 音量控制 -->
          <div class="volume-container">
            <img
              :src="isMuted ? DataUrl.MuteIcon : DataUrl.YlIcon"
              alt=""
              class="icon-3"
              @click="toggleMute"
            />
            <div class="hover-mute-content" @mouseenter="showVolumeSlider">
              <div
                class="hover-mute-content-value"
                :style="{ width: volumePercent + '%' }"
                @mousedown="startVolumeAdjust"
              ></div>
            </div>
          </div>
          <!-- 视频时间 -->
          <p>
            {{ currentTime }} /<span>{{ durationTime }}</span>
          </p>
        </div>
        <!-- 全屏按钮 -->
        <div>
          <img
            :src="fullScreen ? DataUrl.UnFdIcon : DataUrl.FdIcon"
            alt=""
            class="icon-4"
            @click="fullScreen = !fullScreen"
          />
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch, nextTick } from "vue"
import DataUrl from "@/config/data-url.js"

// 定义props
const props = defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
  previewInfo: {
    type: Boolean,
    default: true,
  },
  videoInfo: {
    type: Object,
    default: {},
  },
})

// 视频源
const videoSrc = ref(
  props.videoInfo?.url ||
    "https://umi-rise.oss-ap-southeast-1.aliyuncs.com/20250707/WeChat_20250707174726.mp4"
)

// 组件状态
const videoRef = ref(null)
const isPlaying = ref(false)
const isMuted = ref(false)
const progress = ref(0)
const currentTime = ref("00:00")
const durationTime = ref("00:00")
const showControls = ref(true)
const timer = ref(null)
const fullScreen = ref(false)

// 音量控制状态
const volumePercent = ref(70)
const isAdjustingVolume = ref(false)
const isVolumeSliderVisible = ref(false)

// 背景控制状态
const showLeftBackground = ref(false)
const showRightBackground = ref(false)
const videoAspectRatio = ref(0)
const firstFrameUrl = ref("")
const isFirstFrameLoaded = ref(false)

// 播放错误状态
const videoError = ref(false)
const errorMessage = ref("")

// 新增:加载状态
const isLoading = ref(true)
const loadingText = ref("准备播放...")

const emit = defineEmits(["update-visible", "confim"])

// 关闭对话框
const changeVisible = () => {
  if (videoRef.value) {
    videoRef.value.pause();
    isPlaying.value = false; // 重置播放状态
  }
  emit("update-visible", false)
}

// 播放/暂停视频
const togglePlay = () => {
  if (videoRef.value) {
    if (isPlaying.value) {
      videoRef.value.pause()
    } else {
      videoRef.value.play().catch((err) => {
        console.log("自动播放失败,需要用户交互后才能播放", err)
      })
    }
    isPlaying.value = !isPlaying.value
  }
}

// 更新进度条
const updateProgress = () => {
  if (videoRef.value) {
    const percent = (videoRef.value.currentTime / videoRef.value.duration) * 100
    progress.value = Math.min(100, percent)
    currentTime.value = formatTime(videoRef.value.currentTime)

    if (videoRef.value.duration > 0 && durationTime.value === "00:00") {
      durationTime.value = formatTime(videoRef.value.duration)
    }
  }
}

// 格式化时间
const formatTime = (seconds: number) => {
  const minutes = Math.floor(seconds / 60)
  const secs = Math.floor(seconds % 60)
  return `${minutes.toString().padStart(2, "0")}:${secs
    .toString()
    .padStart(2, "0")}`
}

// 进度条点击跳转
const seekToPosition = (e: MouseEvent) => {
  if (videoRef.value) {
    const progressBar = e.currentTarget as HTMLElement
    const rect = progressBar.getBoundingClientRect()
    const clickX = e.clientX - rect.left
    const percent = (clickX / rect.width) * 100
    videoRef.value.currentTime = (percent / 100) * videoRef.value.duration
  }
}

// 音量控制
const toggleMute = () => {
  if (videoRef.value) {
    isMuted.value = !isMuted.value
    videoRef.value.muted = isMuted.value
    volumePercent.value = isMuted.value ? 0 : volumePercent.value
  }
}

const showVolumeSlider = () => {
  isVolumeSliderVisible.value = true
}

const startVolumeAdjust = (e: MouseEvent) => {
  isAdjustingVolume.value = true
  adjustVolume(e)
  document.addEventListener("mousemove", adjustVolume)
  document.addEventListener("mouseup", endVolumeAdjust)
  e.preventDefault()
}

const adjustVolume = (e: MouseEvent) => {
  if (!isAdjustingVolume.value || !videoRef.value) return

  const volumeBar = document.querySelector(".hover-mute-content") as HTMLElement
  if (!volumeBar) return

  const rect = volumeBar.getBoundingClientRect()
  const clickX = e.clientX - rect.left
  let percent = (clickX / rect.width) * 100
  percent = Math.max(0, Math.min(100, percent))

  volumePercent.value = percent
  videoRef.value.volume = percent / 100

  if (isMuted.value) {
    isMuted.value = false
    videoRef.value.muted = false
  }
}

const endVolumeAdjust = () => {
  isAdjustingVolume.value = false
  document.removeEventListener("mousemove", adjustVolume)
  document.removeEventListener("mouseup", endVolumeAdjust)
}

// 更新音量显示
const updateVolume = () => {
  if (videoRef.value) {
    if (videoRef.value.muted !== isMuted.value) {
      isMuted.value = videoRef.value.muted
    }

    if (!isMuted.value) {
      volumePercent.value = Math.round(videoRef.value.volume * 100)
    }
  }
}

// 更新背景显示
const updateBackground = () => {
  if (!videoRef.value || !videoRef.value.videoWidth) return
  // 计算视频宽高比
  videoAspectRatio.value =
    videoRef.value.videoWidth / videoRef.value.videoHeight
  // 获取容器尺寸
  const container = videoRef.value.parentElement
  if (!container) return
  const containerWidth = container.offsetWidth
  const containerHeight = container.offsetHeight
  // 计算容器宽高比
  const containerAspectRatio = containerWidth / containerHeight

  // 根据宽高比差异调整视频显示逻辑
  const isVideoWider = videoAspectRatio.value > containerAspectRatio
  showLeftBackground.value = isVideoWider
  showRightBackground.value = isVideoWider
}

// 加载第一帧
const loadFirstFrame = async () => {
  if (!videoRef.value) return

  try {
    // 尝试获取视频编码信息(部分浏览器支持)
    const videoTracks = videoRef.value.videoTracks
    if (videoTracks && videoTracks.length > 0) {
      const codec = videoTracks[0].codec || videoTracks[0].kind
      if (codec && !codec.includes("avc1") && !codec.includes("h264")) {
        console.warn("检测到非H.264编码,可能无法播放:", codec)
        // 可在这里显示警告提示
      }
    }

    // 尝试直接使用视频帧
    await captureFrame()
  } catch (error) {
    console.error("直接捕获失败:", error)

    // 尝试通过Fetch和Blob URL绕过跨域
    try {
      const response = await fetch(videoSrc.value)
      const blob = await response.blob()
      const blobUrl = URL.createObjectURL(blob)

      const tempVideo = document.createElement("video")
      tempVideo.crossOrigin = "anonymous"
      tempVideo.src = blobUrl

      await new Promise((resolve, reject) => {
        tempVideo.onloadedmetadata = resolve
        tempVideo.onerror = reject
      })

      tempVideo.currentTime = 0.1

      await new Promise((resolve, reject) => {
        tempVideo.onseeked = resolve
        tempVideo.onerror = reject
      })

      const canvas = document.createElement("canvas")
      canvas.width = tempVideo.videoWidth
      canvas.height = tempVideo.videoHeight

      const ctx = canvas.getContext("2d")
      ctx.drawImage(tempVideo, 0, 0, canvas.width, canvas.height)

      firstFrameUrl.value = canvas.toDataURL("image/jpeg", 0.8)
      isFirstFrameLoaded.value = true

      // 清理资源
      URL.revokeObjectURL(blobUrl)
      tempVideo.remove()
    } catch (error) {
      console.error("Blob方法失败:", error)
      // 回退到默认占位图
      firstFrameUrl.value =
        "https://umi-rise.oss-ap-southeast-1.aliyuncs.com/20250707/cover-video-00008.png"
      isFirstFrameLoaded.value = true
    }
  }
}

const captureFrame = () => {
  return new Promise((resolve, reject) => {
    if (!videoRef.value || !videoRef.value.videoWidth) {
      reject(new Error("视频未加载"))
      return
    }

    const video = videoRef.value
    video.currentTime = 0.5

    const handleSeeked = () => {
      const canvas = document.createElement("canvas")
      canvas.width = video.videoWidth
      canvas.height = video.videoHeight

      const ctx = canvas.getContext("2d")
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)

      try {
        firstFrameUrl.value = canvas.toDataURL("image/jpeg", 0.8)
        isFirstFrameLoaded.value = true
        resolve()
      } catch (error) {
        reject(error)
      } finally {
        video.removeEventListener("seeked", handleSeeked)
      }
    }

    video.addEventListener("seeked", handleSeeked)
  })
}

// 处理视频元数据加载完成
const handleMetadataLoaded = () => {
  loadingText.value = "加载中..."
  // 视频元数据加载完成,但可能还需要缓冲
  // 不立即隐藏loading,等待canplay或playing事件
}

// 处理视频缓冲
const handleVideoBuffering = () => {
  if (!isLoading.value) {
    isLoading.value = true
    loadingText.value = "缓冲中..."
  }
}

// 处理视频开始播放
const handleVideoPlaying = () => {
  // 视频真正开始播放时,隐藏loading
  isLoading.value = false
  isPlaying.value = true
}

// 处理视频播放错误
const handleVideoError = () => {
  if (!videoRef.value) return
  const error = videoRef.value.error
  if (!error) return

  // 根据错误码判断原因
  switch (error.code) {
    case error.MEDIA_ERR_ABORTED:
      errorMessage.value = "视频加载被中断"
      break
    case error.MEDIA_ERR_NETWORK:
      errorMessage.value = "网络错误,无法加载视频"
      break
    case error.MEDIA_ERR_DECODE:
      errorMessage.value = "视频编码不支持,无法播放"
      break
    case error.MEDIA_ERR_SRC_NOT_SUPPORTED:
      errorMessage.value = "视频格式不支持"
      break
    default:
      errorMessage.value = "播放失败,请重试"
  }

  // 显示错误提示,隐藏第一帧背景和loading
  videoError.value = true
  firstFrameUrl.value = ""
  isLoading.value = false
  isPlaying.value = false
}

// 播放下一个视频
const playNextVideo = () => {
  console.log("播放下一个视频")
}

// 视频播放结束
const handleEnded = () => {
  isPlaying.value = false
  console.log("视频播放结束")
}

// 显示/隐藏控制栏
const toggleControls = () => {
  showControls.value = !showControls.value
  if (timer.value) clearTimeout(timer.value)
  timer.value = setTimeout(() => {
    showControls.value = false
  }, 3000)
}

const playVideo = () => {
  videoError.value = false
  isLoading.value = true // 开始加载
  loadingText.value = "准备播放..."
  
  videoRef.value.removeEventListener("click", toggleControls)
  videoRef.value.addEventListener("click", toggleControls)
  videoRef.value.play().catch((err) => {
    console.log("自动播放失败", err)
    isLoading.value = false // 加载失败,隐藏loading
  })
  
  videoRef.value.addEventListener("click", toggleControls)
  nextTick(updateProgress)
  nextTick(updateBackground)

  // 延迟加载第一帧,确保视频元素已初始化
  setTimeout(loadFirstFrame, 100)
}

// 生命周期钩子
onMounted(() => {
  if (videoRef.value) {
    playVideo()
  }

  // 监听窗口大小变化
  window.addEventListener("resize", updateBackground)
})

// 监听全屏状态变化
watch(
  () => fullScreen.value,
  () => {
    nextTick(updateBackground)
  }
)

// 监听视频源变化
watch(
  () => props.videoInfo?.url,
  (newUrl, oldUrl) => {
    if (newUrl && newUrl !== oldUrl) {
      // 更新视频源
      videoSrc.value = newUrl

      // 重置视频状态
      videoError.value = false
      isPlaying.value = false
      progress.value = 0
      currentTime.value = "00:00"
      
      // 加载新视频时显示loading
      isLoading.value = true
      loadingText.value = "准备播放..."

      // 重新加载并播放新视频
      if (videoRef.value) {
        videoRef.value.src = newUrl
        videoRef.value.load()
        videoRef.value.play().catch((err) => {
          console.log("切换视频播放失败:", err)
          isPlaying.value = false
          isLoading.value = false // 加载失败,隐藏loading
        })
        
        // 重新加载第一帧
        loadFirstFrame()
      }
    }
  },
  { immediate: true }
)

// 组件卸载时清理
onUnmounted(() => {
  if (timer.value) clearTimeout(timer.value)
  window.removeEventListener("resize", updateBackground)
})
</script>

<style lang="scss" scoped>
.video-dialog,
.video-dialog-full-screen {
  :deep(.el-overlay) {
    background: rgba(0, 0, 0, 0.66);
  }

  :deep(.el-dialog) {
    width: 900px;
    --el-dialog-border-radius: 20px;
    --el-dialog-padding-primary: 0;
    background: transparent;
    border-radius: 12px;
    padding: 0;
    overflow: hidden;

    .video-content {
      width: 100%;
      height: 503px;
      position: relative;
      overflow: hidden;
      background-size: cover;
      background-position: center;

      // 背景磨砂层
      .video-backdrop {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(0, 0, 0, 0.4);
        backdrop-filter: blur(10px);
        z-index: 1;
      }

      video {
        max-width: 100%;
        max-height: 100%;
        height: 100%;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        object-fit: contain;
        z-index: 2;
      }

      // 加载状态样式
      .video-loading {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 5; // 高于视频但低于错误提示
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        background: rgba(0, 0, 0, 0.6);
        
        .loading-spinner {
          width: 48px;
          height: 48px;
          border: 4px solid rgba(255, 255, 255, 0.3);
          border-radius: 50%;
          border-top-color: #ff3f81;
          animation: spin 1s linear infinite;
          margin-bottom: 16px;
        }
        
        p {
          color: white;
          font-size: 16px;
          font-family: 'PingFang SC', sans-serif;
        }
      }
      
      @keyframes spin {
        to { transform: rotate(360deg); }
      }
      
      // 播放错误提示
      .video-error {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        z-index: 10; // 最高层级
        text-align: center;
        width: 80%;

        .error-icon {
          width: 48px;
          height: 48px;
          margin-bottom: 16px;
        }

        .error-text {
          font-size: 18px;
          color: #ff4d4f;
          margin-bottom: 8px;
        }

        .error-tips {
          font-size: 14px;
          color: rgba(255, 255, 255, 0.7);
        }
      }

      // 视频数据
      .video-data {
        position: absolute;
        right: 24px;
        bottom: 49px;
        z-index: 3;
        li {
          margin-bottom: 17px;
          &:last-child {
            margin-bottom: 0;
          }
        }
        .data-icon {
          width: 24px;
          height: 24px;
          display: block;
          margin: auto;
        }
        p {
          font-family: DIN, DIN;
          color: #ffffff;
          text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          margin-top: 15px;
          font-weight: 400;
          font-size: 16px;
        }
      }

      .close-icon {
        position: absolute;
        right: 24px;
        top: 35px;
        width: 50px;
        height: 50px;
        z-index: 2;
        cursor: pointer;
      }

      .video-base-info {
        position: absolute;
        left: 24px;
        bottom: 12px;
        z-index: 3;
        .title {
          align-items: center;
          h1 {
            font-family: PingFang SC, PingFang SC;
            font-weight: 500;
            font-size: 18px;
            color: #ffffff;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          }
          span {
            margin: 0 8px;
            display: inline-block;
            width: 2px;
            height: 2px;
            background: #ffffff;
            box-shadow: 1px 1px 2px 0px rgba(0, 0, 0, 0.6);
          }
          p {
            font-family: DIN, DIN;
            font-weight: 400;
            font-size: 12px;
            color: #eae9e8;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          }
        }
        .note {
          margin-top: 2px;
          width: 234px;
          font-family: PingFang SC, PingFang SC;
          font-weight: 400;
          font-size: 14px;
          color: #ffffff;
          text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6);
          line-height: 1.5;
        }
      }
    }

    .video-content::before {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      backdrop-filter: blur(8px);
      background: rgba(255, 255, 255, 0.1);
      z-index: 2;
      pointer-events: none;
    }

    // 进度条样式
    .video-progress {
      position: absolute;
      bottom: 56px;
      left: 0;
      right: 0;
      height: 4px;
      z-index: 3;
      .progress-bg {
        width: 100%;
        height: 100%;
        background: rgba(255, 255, 255, 0.2);
        border-radius: 2px;
        cursor: pointer;
        .progress-value {
          height: 100%;
          background: #ff3f81;
          border-radius: 2px;
          width: 0;
          transition: width 0.1s;
          position: relative;
        }
        .progress-value::after {
          content: "";
          position: absolute;
          right: 0;
          top: 50%;
          transform: translateY(-50%);
          width: 12px;
          height: 12px;
          background: #ff3f81;
          border-radius: 50%;
        }
        .progress-pointer {
          position: absolute;
          top: 50%;
          transform: translateY(-50%);
          width: 12px;
          height: 12px;
          background: #ff3f81;
          border-radius: 50%;
          margin-left: -6px;
          box-shadow: 0 0 8px rgba(255, 63, 129, 0.5);
          display: none;
        }
      }
    }

    // 底部控制栏样式
    .video-footer-tool {
      height: 56px;
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: #16110c;
      padding: 0 24px;

      .icon-1,
      .icon-2,
      .icon-3 {
        width: 14.22px;
        height: 16px;
        margin-right: 24px;
        cursor: pointer;
        object-fit: contain;
      }

      .icon-3 {
        width: 16px;
        margin-right: 0;
      }

      .icon-4 {
        width: 18px;
        height: 18px;
        cursor: pointer;
      }

      p {
        font-family: PingFang SC, PingFang SC;
        font-weight: 500;
        font-size: 14px;
        color: #ffffff;
        span {
          color: rgba($color: #ffffff, $alpha: 0.65);
        }
      }

      // 音量控制样式
      .volume-container {
        display: flex;
        align-items: center;
        margin-right: 24px;
        .hover-mute-content {
          width: 68px;
          height: 6px;
          border-radius: 24px;
          background: rgba(255, 255, 255, 0.37);
          cursor: pointer;
          position: relative;
          display: none;
          margin-left: 24px;
          .hover-mute-content-value {
            position: absolute;
            top: 0;
            left: 0;
            width: 50%;
            height: 100%;
            background: #ffffff;
            border-radius: 24px;
            transition: width 0.1s;

            &::after {
              content: "";
              position: absolute;
              right: 0;
              top: 50%;
              transform: translateY(-50%);
              width: 12px;
              height: 12px;
              background: #ffffff;
              border-radius: 50%;
              box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
            }
          }
        }
      }
      .volume-container:hover {
        width: calc(72px + 68px - 24px);
        max-width: calc(72px + 68px - 24px);
        .hover-mute-content {
          display: block;
        }
      }
    }
  }
}

// 全屏模式样式
.video-dialog-full-screen {
  :deep(.el-dialog) {
    width: 100%;
    height: 100%;
    --el-dialog-border-radius: 0;
    background: transparent;
    border-radius: 0;

    .video-content {
      width: 100%;
      height: calc(100vh - 56px);
      video {
        max-width: 100%;
        max-height: 100%;
        height: 100%;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        object-fit: contain;
        z-index: 2;
      }
    }
  }
}
</style>

DataUrl Icon文件

新建或者自定义一个相关文件,将以下ICON.base64代码引入,这边用的是js文件

javascript 复制代码
const dataUrl = {
    "ErrorIcon": ``,    "ZanIcon":`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAYAAACN1PRVAAAAAXNSR0IArs4c6QAAAjdJREFUSEu9lb2O00AUhe/ZH1Z2sOQNsWhSUKRyqpVfwC7SQ+EaqNPAE2DeAJ4A+jTwBPgBUqRKFQkKd5FgkRWH3Q2+5BrbOMbC+TEZKYo947nfnLln7oCO2HBEFm0NY+Z3RPSMiHwieg7gy64L3QrGzDYRfSoEvyaiq12B28IyVUUxbwG82EVdLYyZHxHR54qgPgCnaViVKmE0C0tVSa5EXbk1DhP3ibKqJiaZlAak7yOA94X+EyKK5f2fOWNmyVWVqrpUvQTwRj6yLOt8PB6vAPAGjJlfEZE4TK+LVjN+DeBSvul0OprjONFoNPqZw1KQdyAknw4gia1pWicMQ9neVRGWbZkDwGdmAYvSfdoEwJVMbLVaDxeLxVciuivCOEliuqIDYWKSx8eCvV6vOUmJpmkPLMv67vv+xjY2qewJgA8C03VdHwwGYdkgTcKSvAvMMIz7w+Ew8jwv/l85u1wXaXEgdbtdJQiCH2uz/Tlna+s3pSw/YwLr9XoXs9nsZqOCMPO39DDLPTU5wI3lmnmPiG7LsEPOVfEsyi2e10bbts/EiX/VRmaWevZ0z3IlOZILdaMKua57Kk6sLcT7lI7yHGaWOvHbD00E3DZGESb3zrn8NE27CMPwjIhOFUXBcrlM4imKIuWMoyiCqqpi4GQs7Y+jKFq12+0bVVXvgiC4ZeY4U1WlDK7rnsznc/i+D9M088VMp1MyTTOBZs/yL0365dm27dgwDO73++x5nmxdsn1ZO+o2/gKK/QUrGuu3NgAAAABJRU5ErkJggg==`,
    "PingIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAALlJREFUSEvtld0NgzAMhM8blA3KZt2kZRM2Kxu0GxwxSioURcqVEvqCJV5OyF/8b2hs1tg/FgDJJ4DrzrDJzPoE4M7OF3fmFiM4AcUM/z1FbwD+rbtL1aQi9xHwWsWvanVAqcNULT2oVoMOwAWAD2EyVatHAGCKAIckUzUJ8PNw11J0Ao5ddm3vQV5Nko9wh+6ZPoYVP5iZt6psxZOZATY5/kxz6SkkfcHdwqIbv31x7u+Yoy8ndMOPM/JLdRkQa0DJAAAAAElFTkSuQmCC`,
    "LookEyeIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAU1JREFUSEvFVQtRw1AQ3HWABFAAOKAKGBQACmgV0DpoFVAUgAPigKKASqiDI8vcg7R5nySdwM1kJp93t2/33V6IkYMj18f/A5jZKYBbAHcAdK/Y+LUguc2pkGRgZicAHgFMCzLOSS5Sa6IAvusXABcdz0gsJjE2LQAv/taQoyMGoiB7AEcUD5togRwCvGdkqQB8eKXrDMMNycuA+ANgZjrQeUIPdcveNzPTs3JisSQ504dvADO7AiDdY7Eleebr1KogufZn5Sg3Fjr0KgB8ZiivSE7NTMWfvNK9QOpWXgJ4yG3szwByElUkJwmJcsx/JfLkHN2WW7s2RZ82VY+/ut5yeOpw423qLDTMhri4m9GOBCmPirCNASND4/um07Br9nTBrWHpShOg9sou5ofiH83ZaCycN+aU5Hiuzbke/MNJuLP36yKD3hUPEkYH+AKsa5kZ8eiKFwAAAABJRU5ErkJggg==`,
    "CloseRadiuIcon": `data:image/png;base64,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`,
    "PauseIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAhCAYAAADOHBvaAAAAAXNSR0IArs4c6QAAAMtJREFUWEdjZBggwIhs761btwwYGRkFkMVYWFgeKCoqPiDHfffv3xf48+ePAbpeVVXVA3CLb926dZ6BgQFDEUjTv3//CjU0NCaQYvnNmzcDGBkZ1+PQcwFs8e3btx3+//+/H4/BB9TU1BxJsfjWrVsg8xxw6Rm1GFfIjAY1wXQ2mrjQg2g0O41mJ1gIjBYgowUIRgiMFpmjReZoJTFaSYxWEgQrB5iCwV1kQruT9////4/SRUXy3gI1NbVEor3LwMBw69YtUO8yH5ceADqDdhRm0VQiAAAAAElFTkSuQmCC`,
    "PlayZIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAYAAADud3N8AAAAAXNSR0IArs4c6QAAARpJREFUSEvd1t0NgjAQwHEuAZ5doQ+lMI4jOIG6AZ1A3QA3cATmgJC4gSzQnoGIUcNHC1ce7HOTX67/hytUVbXXWqeIWCPiUQhx8xwfKIoCf4wsCALJGLu7svvQ1gKA1Pf9qwt8EH1N2UwroyjKKKeeQjvrDgA7znlOgZuinUXS2xYl6T0LXdp7CTq7NwVq3ZsSNe5Njpr0doWO9naN9vZeC3335pzLVdFGRsTtf6MAUGutL0KIdK1Jc6XULkmS9mPgGs0BQP6uRCdo85RKKRnH8blv/5KiXbcwDM+MsXpo4VOiX93GfhgUaG83J+hUN1LUtBslatyNArXuNhtd0s0apeg2ipZl+UDEzcclkm5T6AERT57nkXYbQ59nnvPXuWPpvAAAAABJRU5ErkJggg==`,
    "PlayNextIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAgCAYAAADud3N8AAAAAXNSR0IArs4c6QAAAZNJREFUSEvV18ttwkAQANAZy+tzSogPtuBGCUkHSQehAuQO7A6gglACHcQdhCtYlinBd38mGhRHRmziZT9I2RMCvI8dZsZjBACoquohDMOaX7tYZVmu+r5Piaj2PC/F4/G4AIBPADgBQBbH8dYmXFXVomka3n9YNaNvAPA+enMrhMjCMOQfYbyKongioo/xRjKUP6/7vs9ms9naVL0FHSw+7Wscx3tdXAcdLO2Qm6CMayWaKfoTciHEs2qi2UIHfC2E2EzhtlGlkLtAz6cmol0QBIns1M7QId6ImEZRlI3Lyzn6jV1k+b3Qi9pu2/ZRtQ3qNqCr6xBxR0QvKr3XGirb6LeG/39RROTBIL9nePOu65a+798lkU6IuIyiKOf/yHnJEFEWBMF6PG+5RM+hnM/nVyOOdZQThYiSv4Y52+hGCJFOja620BwRsyFRpgraCOVQdl1384RogiqFUnZqHXSPiIlqKI1Q3VBqo3wrats2kdXcVNIoo6OYX7QvHUB2jeQBCpC/6PpR8XA4pJ7nrdjiCvgCWcuQkZ35fVAAAAAASUVORK5CYII=`,
    "MuteIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAhCAYAAAC4JqlRAAAAAXNSR0IArs4c6QAAA7dJREFUWEfFV11S2zAQ1or4nZ6geIbI8FQ4QckJCicoOQHNCZKcAHIC4ATACUhPQHhiLME4NyB9dqxtVmN5ZEXOD2WmfshDoux+2v2+b9fA/vMD/5pfSnkURdEsjuPpR2J9GECWZbvz+byPiL8oMQB02u32eFsQFQClVF9rfQ4A4yiKenEcz5qCZVm2l+f5HWPsyDkzEkIYMNs8BsDr6+up1poCmgcR75MkOQsFUkqdIOI1Y2zP+/1GCNHdJrmpHH1IKc8ZYxS0egBg0G63h+53b29vF0VRXDUk+TgA6mee549eSZnWundwcGASKqUubb8/HQAFfHl52dvZ2SEQtdISuRhjF4h4uqa8wQqkaXrKOb+mtkZRNPTVUlMBSQoAHhFx10k2AwDiw533vY8nCEBKSZc6KQ9PoyjquCCWZBjiA2NsCgBdRKRgTU8QgFLqFyJeOn8iEMdWZUEfSNN0AAB9L9MYAB68YO6RRhL68VyVWRVQ6U3ZEbFPXoCIX6j3HogrRPwTAEfHVqrAB2GNC8iAEHHg15Vzfq61JmNxzcYoY0HW7wFSVgCojQDwrdVqjdx+K6WIR5bMYyFEB6SU2NDUGSJ2AYD6V1MGIp6VVXDBGQBZlh3lef7kkK7qNymt1Wo9WTJTFVYBoBiGfAEFGGV4jmgBkKe820sh4jBJkqrCUkryFdva0ToAFGfCOR+6Vm1vt+BDr9Q48adqgcf8WRRFsWW9Z/uTTQAYgi1u/BxQwIQxNiptvEZCpdS74xtdIcSNY3hZeYnZpgBo3A7WKOOrO4w8A6oAUGKXdxsDoD+SAjjnPxxnMxchcIyx3+4+0ASgnDsVR7YCUCbrhMYxyXZ/f/+WzpT7gi0zfXUshKB20VCjcW4ddWMOuEptVMaiCh1KJKWk5Fa6UyFEbAO4BNVaP2xdAasAznkvpAxftkVRxIeHh9W+6IHrEgAX7YpZs/RT02yYAMAtKSbgAbXFh8BB6U50eBcAaA+kHrnjeBUoMhWaJ/7MWJoLxIv5fF65oPWNpWnYtJg0oVilDLvSlaR0l51pURQdak1wHDtVWbcFGVzlbFiaGeVAeyYrd+cJnU+S5N6oalV9G/aC0F9Cs4HOLQ00nxdrX0wCG00TZrJasmU7Ce25SrZFUQztkmt/XAugtE4au7UyBlAY4jWsdJNyF1x62dkIgDNElrZmB0jF/IbWBTemjQE4IPxXMouhliBNU5qgP91KueTbqgV+ub2lIggg9LLzaQAoY6DMtZEbaJvZAf3L/AVCj3VLrOF/ZQAAAABJRU5ErkJggg==`,
    "YlIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAgCAYAAACcuBHKAAAAAXNSR0IArs4c6QAAAnNJREFUWEfllsFx6jAQhiVsc3YJ+MB6uL10gDsgFSSpIOkgpIK8VJB0kHTw6CDcGEswpoNwxpjNrEdiZI3tyGHM5emWIFmf9P/a/Tk7c2RZNsrz/JUxNkXE6ziOP7p+knddYM5P03Q2GAxeETFU/18AQNL1m7+GkFI+IuLc3hAAOn+z84Isy8I8z58ZY7d1J+4dQun/jzE2arryXiGklFPG2Luhfy1HEwTJxxgLfd9/iaJoay52kmOz2dwXRfHXxXB1EEIIko5eEI1tEASJCdIK8ZP+rp5Yr9ez4/H4bsyvgDRCKP1p4R+XG9BzmuRI03TOOSdJ9FgCwBX9UQshhKCNCaDRgL8xppTyARHpZZUDEZ/iOJ6XEGbV63LqNjlIyv1+Px0Oh0tTfyEEeeterd0FQRCVENYPZ3FoOaSU9JJmthGVzz71LdNtlBBpmr5xzm/O2l0t1hBCiMyQs1LOLVkWvUHYL4JznozH44WSn6rulz50bxBKZqquVORovAHAnd5YCEGSlC+vbwizSFUkEUKcAP8LCCrVutu+AMDDReWw+gWzjElpjF5POXqTwzQeIn7EcXxt3ILplWWfENp426IokslkcmrfVg25KyFWq9XI87zWsOJayMwGRrXC87xFFEU7vd6MhZzz3eFwuKptYOdAtSUrO5dUGljdKQnE9/1nVf9dL4K1JSsrGG8BIDoZs22HmhzQClQHURdqTJ84xTs7B7RRuMQ726hOEKoPOAWdH5JVGATB3DSqkxzmqV0M22vk1zAqlFD0092xos5FIIyqZ8a0E8hFIWjXtgTt/Kab0naXD6hG9cg5DxExAYBll/U09xtsmJNzr7Qi0AAAAABJRU5ErkJggg==`,
    "UnFdIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAAXNSR0IArs4c6QAAAadJREFUWEftWN1RwkAQ3h3IOyWQhwR4ix1AB1qCHWgFQgWOFVgCYwXQgXljcvdwdGCeScjKOsbJREz2wMzocPd6e9lvv9x9+4MgXMaYYZZlSwCIEHEeBMGi6ajW+gEA7ogo9Txv5vv+VuIKJUZso5RaAcC0Yn8VhmF87LwxJsqy7LXcK4riZTweX0t8nQwIEWdBEKyPOdFaT4mIAyjXOgzDmQNUMuAYYibcHaq/iPqzvxyGWIF3u90jIg6adAIRIyL6srFkKAWAoyJa9el53i0qpQwADCWiVbWxBCT9fIxa67dq5NKTHQECTJJkjoicCK1WF4CIaPGRy5RSnMEb71Adbb/fj33f57vxbRljBnmeRzYRclXAyVqcXG0+fo6tA9TGnmPofzL0p549dwdENG+jsr7fhTByN8O5jGzBnFCgSV2kDIjbFStV7RDQFjebzbDX6z23hXBO+YGIKRE1lh9sk+f5vViHLrdibPtV5b5jqI0px5Bj6AcGxDqktV4exK06dBIPrA6Drt+fD30q+oqbgaIonkajUWNCrnQz8X6/v5lMJqKR3jvTkq+FkbX/lwAAAABJRU5ErkJggg==`,
    "FdIcon": `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAAAXNSR0IArs4c6QAAAg5JREFUWEftWMtxwjAQlQD7TAn4gIAbdBA6SCpIqCChEpIKQiqADkIH4caMBGOX4LuRNlmPbBzQGAnyIRnraO2unp6lXe2j5EIHNeEKw7CVJMmVaQ4AYt/3l0EQRC57CsOwmSTJtW3MT8A0oGdCiBFUIWjked4gCILYBpyO+0oIaZXZA8Dc9/0xbjoHZutcCDxijE1tgK3X62ul1MzGlhCCmx7mwIQQMwAwUm0KSCkdttvthc1iQogrAEDGbMciBabZCotelNJYKXXwq/T3l263+2i7CtoJIR4A4BYAmvt+tVqtuf89BWagOpZSDnq9ntMBdwFatF2tVq1Go/FWBJcC45zfEULw0GcjYowFpy50ih/nHP9YfjkqYMdYrBg7xtD+vC1jS8bYwDX4Ofac8zdCSD+LkeUxrGM4kd4KpdTYNU+dA6qQ5yY6zq4kYS7xPK+PRdo2o58LZt9fVwjEMTe+Lr56wVPiVcBcWasYqxhzZcDVvjpj/4+xrO+jlEa/WZKUUk3sW4vNSN73XUwRNzQjl/HsqZqRkqtp+4Kt2reMxD/PWKxlph+RCFA72W63hxIBvvfr9bq1qPLRtEwZY08uZWaz2dxLKe+cRBWtX2CCPSbY5Vi+U4ZCAS9/XWjWjqp+GTIAuOl0OnMb1gx5sswtklLuhDu01HLQxELAS51tZSpdh/O+tQTVQko5wrjvF5yGMz+76SwAAAAASUVORK5CYII=`,
}

export default dataUrl
相关推荐
byzh_rc5 分钟前
[微机原理与系统设计-从入门到入土] 微型计算机基础
开发语言·javascript·ecmascript
m0_471199635 分钟前
【小程序】订单数据缓存 以及针对海量库存数据的 懒加载+数据分片 的具体实现方式
前端·vue.js·小程序
编程大师哥6 分钟前
Java web
java·开发语言·前端
A小码哥8 分钟前
Vibe Coding 提示词优化的四个实战策略
前端
Murrays8 分钟前
【React】01 初识 React
前端·javascript·react.js
大喜xi11 分钟前
ReactNative 使用百分比宽度时,aspectRatio 在某些情况下无法正确推断出高度,导致图片高度为 0,从而无法显示
前端
helloCat12 分钟前
你的前端代码应该怎么写
前端·javascript·架构
电商API_1800790524712 分钟前
大麦网API实战指南:关键字搜索与详情数据获取全解析
java·大数据·前端·人工智能·spring·网络爬虫
康一夏13 分钟前
CSS盒模型(Box Model) 原理
前端·css
web前端12314 分钟前
React Hooks 介绍与实践要点
前端·react.js