BoxMask.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. <template>
  2. <a-modal
  3. class="audio-preview-wrapper"
  4. v-model:visible="visible"
  5. :footer="null"
  6. :closable="false"
  7. :maskClosable="false"
  8. :width="'100%'"
  9. :bodyStyle="{ padding: 0 }"
  10. :centered="true"
  11. >
  12. <img class="audio-background" :src="musicImgUrl" alt="背景图" />
  13. <!-- 右上角操作 -->
  14. <div class="operate-box">
  15. <a-tooltip placement="bottom">
  16. <template #title>
  17. <div style="line-height: 2">
  18. 操作提示: <br />
  19. 1. 按 Esc 键可退出查看;<br />
  20. 2. 支持键盘控制:<br />
  21. &nbsp;&nbsp;空格 - 暂停/播放<br />
  22. &nbsp;&nbsp;左方向键 - 播放上一个<br />
  23. &nbsp;&nbsp;右方向键 - 播放下一个<br />
  24. &nbsp;&nbsp;上方向键 - 音量调大<br />
  25. &nbsp;&nbsp;下方向键 - 音量减小<br />
  26. </div>
  27. </template>
  28. <question-circle-outlined class="tip-icon" />
  29. </a-tooltip>
  30. <close-outlined class="close-icon" title="关闭(Escape)" @click="handleClosePreview" />
  31. </div>
  32. <audio
  33. ref="audioRef"
  34. :src="activeFileObj.fileUrl"
  35. controls
  36. style="position: fixed; top: 0; left: 0; display: none"
  37. @loadedmetadata="handleLoadedmetadata"
  38. @timeupdate="handleTimeUpdate"
  39. @ended="handleChangeAudioIndex('next')"
  40. />
  41. <div class="audio-list-wrapper">
  42. <!-- 音频列表 -->
  43. <ul class="audio-list">
  44. <li class="audio-list-header">
  45. <span class="name">音频名称</span>
  46. <span class="audio-size">大小</span>
  47. <span class="path">路径</span>
  48. </li>
  49. <div class="audio-list-body">
  50. <li
  51. v-for="(item, index) in audioList"
  52. :key="index"
  53. class="audio-item"
  54. :class="{ active: activeIndex === index }"
  55. :title="isPlay ? '暂停' : '播放'"
  56. @click="handleChangeAudioIndex('manual', index)"
  57. >
  58. <span class="name">
  59. <span class="sequence" v-show="activeIndex !== index">
  60. {{ index + 1 }}
  61. </span>
  62. <img class="wave" :src="activePlayIcon" alt="波浪动图" v-show="activeIndex === index && isPlay" />
  63. <i class="no-wave el-icon-s-data" v-show="activeIndex === index && !isPlay"></i>
  64. <span class="text">{{ item.fileName }}.{{ item.extendName }}</span>
  65. </span>
  66. <i class="play-icon iconfont icon-icon-7" v-show="activeIndex === index && !isPlay"></i>
  67. <i class="pause-icon iconfont icon-icon-3" v-show="activeIndex === index && isPlay"></i>
  68. <a class="download" :href="$file.getDownloadFilePath(item)" target="_blank" title="下载">
  69. <i class="download-icon el-icon-download"></i>
  70. </a>
  71. <i
  72. class="share-icon el-icon-share"
  73. title="分享"
  74. @click.stop="
  75. $openDialog.shareFile({
  76. fileInfo: [
  77. {
  78. userFileId: item.userFileId
  79. }
  80. ]
  81. })
  82. "
  83. ></i>
  84. <span class="audio-size">{{ $file.calculateFileSize(item.fileSize) }}</span>
  85. <span class="path">{{ item.filePath }}</span>
  86. </li>
  87. </div>
  88. </ul>
  89. <!-- 歌曲图片和歌词 -->
  90. <div class="img-and-lyrics">
  91. <img class="audio-img" :src="musicImgUrl" alt="歌曲图片" />
  92. <div class="audio-name">{{ activeFileObj.fileName }}.{{ activeFileObj.extendName }}</div>
  93. <div class="album-artist" v-show="audioInfo.artist">歌手:{{ audioInfo.artist }}</div>
  94. <div class="album-name" v-if="audioInfo.album">专辑:{{ audioInfo.album }}</div>
  95. <ul class="lyrics-list" ref="lyricsListRef" :class="{ one: lyricsList.length === 1 }" v-if="lyricsList.length">
  96. <li
  97. class="lyrics-item"
  98. ref="lyricsLineRef"
  99. v-for="(item, index) in lyricsList"
  100. :key="index"
  101. :class="{
  102. active: currentLyricsLineIndex === index
  103. }"
  104. @click="handleChangeProgress(transferTimeToSeconds(item.time))"
  105. >
  106. {{ item.text }}
  107. </li>
  108. </ul>
  109. </div>
  110. </div>
  111. <!-- 底部音乐控件 -->
  112. <div class="control-wrapper">
  113. <div class="control-left">
  114. <step-backward-outlined
  115. class="operate-icon"
  116. title="上一个(按左方向键)"
  117. @click="handleChangeAudioIndex('pre')"
  118. />
  119. <play-circle-outlined
  120. v-if="!isPlay"
  121. class="operate-icon"
  122. title="播放(按空格键)"
  123. @click="handleClickPlayIcon"
  124. />
  125. <pause-circle-outlined v-else class="operate-icon" title="暂停(按空格键)" @click="handleClickPauseIcon" />
  126. <step-forward-outlined
  127. class="operate-icon"
  128. title="下一个(按右方向键)"
  129. @click="handleChangeAudioIndex('next')"
  130. />
  131. <a-slider
  132. class="progress-bar control-item"
  133. v-model:value="currentTime"
  134. :step="progressStep"
  135. :max="audioInfo.duration"
  136. :tooltip-visible="true"
  137. :tip-formatter="(val) => transferSecondsToTime(val)"
  138. @mousedown="isDrop = true"
  139. @mouseup="isDrop = false"
  140. @change="handleChangeProgress"
  141. />
  142. <span class="time control-item">
  143. {{ transferSecondsToTime(currentTime) }} / {{ transferSecondsToTime(audioInfo.duration) }}
  144. </span>
  145. </div>
  146. <div class="control-right">
  147. <i
  148. class="operate-icon cycle-type iconfont"
  149. :class="cycleTypeMap[String(cycleType)].icon"
  150. :title="cycleTypeMap[String(cycleType)].text"
  151. @click="handleChangeCycleType"
  152. ></i>
  153. <a
  154. class="operate-icon download-link"
  155. :href="$file.getDownloadFilePath(activeFileObj)"
  156. target="_blank"
  157. title="下载"
  158. >
  159. <i class="download-icon el-icon-download"></i>
  160. </a>
  161. <i
  162. class="operate-icon share-icon el-icon-share"
  163. title="分享"
  164. @click.stop="
  165. $openDialog.shareFile({
  166. fileInfo: [
  167. {
  168. userFileId: item.userFileId
  169. }
  170. ]
  171. })
  172. "
  173. ></i>
  174. <i
  175. class="operate-icon volume-icon control-item iconfont"
  176. :class="volume === 0 ? 'icon-jingyin01' : 'icon-yinliang101'"
  177. @click="handleClickVolumeIcon"
  178. ></i>
  179. <el-slider
  180. class="volume-bar control-item"
  181. v-model="volume"
  182. :step="0.01"
  183. :max="1"
  184. :format-tooltip="(val) => Math.floor(val * 100)"
  185. height="100px"
  186. title="可按上下方向键调节音量"
  187. @input="handleChangeVolumeBar"
  188. ></el-slider>
  189. </div>
  190. </div>
  191. </a-modal>
  192. </template>
  193. <script setup>
  194. import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
  195. import { message } from 'ant-design-vue'
  196. import {
  197. QuestionCircleOutlined,
  198. CloseOutlined,
  199. StepBackwardOutlined,
  200. StepForwardOutlined,
  201. PlayCircleOutlined,
  202. PauseCircleOutlined
  203. } from '@ant-design/icons-vue'
  204. import { getFileDetail } from '@/api/myResource/file'
  205. import { Base64 } from 'js-base64'
  206. const props = defineProps({
  207. visible: {
  208. type: Boolean,
  209. default: false
  210. },
  211. audioList: {
  212. type: Array,
  213. default: () => []
  214. },
  215. defaultIndex: {
  216. type: Number,
  217. default: 0
  218. },
  219. callback: {
  220. type: Function,
  221. default: () => {}
  222. }
  223. })
  224. const emit = defineEmits(['update:visible'])
  225. const visible = ref(props.visible)
  226. const activeIndex = ref(0)
  227. const isPlay = ref(false)
  228. const currentTime = ref(0)
  229. const isDrop = ref(false)
  230. const volume = ref(0)
  231. const audioInfo = ref({})
  232. const lyricsList = ref([])
  233. const currentLyricsLineIndex = ref(0)
  234. const activeFileObj = computed(() => {
  235. const res = props.audioList.length ? props.audioList[activeIndex.value] : {}
  236. return res
  237. })
  238. const audioElement = computed(() => {
  239. return refs.audioRef
  240. })
  241. const musicImgUrl = computed(() => {
  242. return audioInfo.value.albumImage
  243. ? `data:image/jpeg;base64,${audioInfo.value.albumImage}`
  244. : require('_a/images/file/file_music.png')
  245. })
  246. const progressStep = computed(() => {
  247. return audioInfo.value.duration / 100
  248. })
  249. watch(
  250. () => props.visible,
  251. (newValue) => {
  252. visible.value = newValue
  253. if (newValue) {
  254. activeIndex.value = props.defaultIndex
  255. getFileDetailData()
  256. document.addEventListener('keyup', handleAddKeyupEvent)
  257. } else {
  258. document.removeEventListener('keyup', handleAddKeyupEvent)
  259. }
  260. }
  261. )
  262. watch(activeIndex, () => {
  263. getFileDetailData()
  264. })
  265. function handleAddKeyupEvent(event) {
  266. switch (event.code) {
  267. // 关闭预览
  268. case 'Escape': {
  269. handleClosePreview()
  270. break
  271. }
  272. // 切换到上一个
  273. case 'ArrowLeft': {
  274. handleChangeAudioIndex('pre')
  275. break
  276. }
  277. // 切换到下一个
  278. case 'ArrowRight': {
  279. handleChangeAudioIndex('next')
  280. break
  281. }
  282. // 音量调大
  283. case 'ArrowUp': {
  284. volume.value = volume.value === 1 ? 1 : volume.value + 0.1
  285. volume.value = Number(volume.value.toFixed(1))
  286. handleChangeVolumeBar(volume.value)
  287. break
  288. }
  289. // 音量调小
  290. case 'ArrowDown': {
  291. volume.value = volume.value === 0 ? 0 : volume.value - 0.1
  292. volume.value = Number(volume.value.toFixed(1))
  293. handleChangeVolumeBar(volume.value)
  294. break
  295. }
  296. // 暂停/播放
  297. case 'Space': {
  298. handleChangeAudioIndex('manual', activeIndex.value)
  299. break
  300. }
  301. }
  302. }
  303. function getFileDetailData() {
  304. handleClickPauseIcon()
  305. loading.value = true
  306. getFileDetail({ userFileId: activeFileObj.value.userFileId })
  307. .then((res) => {
  308. loading.value = false
  309. if (res.success) {
  310. audioInfo.value = {
  311. ...res.data.music,
  312. duration: res.data.music.trackLength
  313. }
  314. // Base64 解码为 lrc 格式的歌词文件
  315. let lyricsStr = Base64.decode(audioInfo.value.lyrics)
  316. if (lyricsStr.includes('[offset:0]')) {
  317. // 有歌词,从标志位 [offset:0] 下一行开始截取
  318. lyricsStr = lyricsStr.split('[offset:0]\n')[1]
  319. }
  320. lyricsList.value = lyricsStr
  321. .split('\n')
  322. .map((item) => {
  323. const line = item.split('[')[1].split(']')
  324. return {
  325. time: line[0], // 当前行歌词开始播放的秒数
  326. text: line[1] // 当前歌词文本
  327. }
  328. })
  329. .filter((item) => item.text !== '')
  330. lyricsList.value = lyricsList.value.map((item, index) => {
  331. return {
  332. ...item,
  333. // 当前行歌词起始秒数
  334. startSeconds: transferTimeToSeconds(item.time),
  335. // 当前行歌词结束秒数
  336. endSeconds:
  337. index < lyricsList.value.length - 1
  338. ? transferTimeToSeconds(lyricsList.value[index + 1].time)
  339. : audioInfo.value.duration
  340. }
  341. })
  342. // 当切换完歌曲时,歌词重新滚动到顶部
  343. refs.lyricsListRef.scrollTo({
  344. top: 0,
  345. behavior: 'smooth'
  346. })
  347. currentLyricsLineIndex.value = 0
  348. }
  349. })
  350. .catch(() => {
  351. loading.value = false
  352. })
  353. }
  354. function handleLoadedmetadata(event) {
  355. const audioDom = event.target
  356. volume.value = audioDom.volume || 0.5
  357. currentTime.value = audioDom.currentTime
  358. handleClickPlayIcon()
  359. }
  360. function transferSecondsToTime(duration) {
  361. const hour = Math.floor(duration / 3600)
  362. const minutes = Math.floor(duration / 60)
  363. const seconds = Math.ceil(duration % 60)
  364. return `${hour < 10 ? `0${hour}` : hour}:${minutes < 10 ? `0${minutes}` : minutes}:${
  365. seconds < 10 ? `0${seconds}` : seconds
  366. }`
  367. }
  368. function transferTimeToSeconds(time) {
  369. const timeList = time.split('.')[0].split(':')
  370. return Number(timeList[1]) + Number(timeList[0]) * 60
  371. }
  372. function handleTimeUpdate(event) {
  373. // 如果正在拖拽进度滑块,函数结束,不计算当前时间
  374. if (isDrop.value) return
  375. currentTime.value = event.target.currentTime
  376. if (lyricsList.value.length) {
  377. // 遍历歌词,当前秒对应的歌词整行添加高亮效果
  378. lyricsList.value.forEach((item, index) => {
  379. if (
  380. item.startSeconds <= currentTime.value &&
  381. currentTime.value < item.endSeconds &&
  382. currentLyricsLineIndex.value !== index
  383. ) {
  384. // 确定高亮歌词行索引
  385. currentLyricsLineIndex.value = index
  386. // 使高亮歌词行永远保持在第二行
  387. if (currentLyricsLineIndex.value > 2) {
  388. // 平滑滚动
  389. refs.lyricsListRef.scrollTo({
  390. top: refs.lyricsLineRef[index].clientHeight * (index - 2),
  391. behavior: 'smooth'
  392. })
  393. }
  394. }
  395. })
  396. }
  397. }
  398. function handleChangeProgress(progress) {
  399. audioElement.value.currentTime = progress
  400. isDrop.value = false
  401. }
  402. function handleChangeCycleType() {
  403. if (cycleType.value === 3) {
  404. cycleType.value = 1
  405. } else if (cycleType.value >= 1) {
  406. cycleType.value++
  407. }
  408. }
  409. function handleClickPlayIcon() {
  410. isPlay.value = true
  411. audioElement.value.play()
  412. }
  413. function handleClickPauseIcon() {
  414. isPlay.value = false
  415. audioElement.value.pause()
  416. }
  417. function handleChangeAudioIndex(type, index) {
  418. // 如果当前手动切换
  419. if (type === 'manual') {
  420. if (activeIndex.value === index) {
  421. if (isPlay.value) {
  422. handleClickPauseIcon()
  423. } else {
  424. handleClickPlayIcon()
  425. }
  426. } else {
  427. activeIndex.value = index
  428. }
  429. } else {
  430. handleClickPauseIcon()
  431. // 判断当前循环播放类型
  432. switch (cycleType.value) {
  433. case 3: {
  434. let activeIndex = 0
  435. do {
  436. activeIndex = Math.floor(Math.random() * (props.audioList.length - 1)) + 1
  437. } while (activeIndex === activeIndex.value)
  438. activeIndex.value = activeIndex
  439. break
  440. }
  441. default: {
  442. if (type === 'pre') {
  443. if (activeIndex.value === 0) {
  444. activeIndex.value = props.audioList.length - 1
  445. } else {
  446. activeIndex.value--
  447. }
  448. } else if (type === 'next') {
  449. if (activeIndex.value === props.audioList.length - 1) {
  450. activeIndex.value = 0
  451. } else {
  452. activeIndex.value++
  453. }
  454. }
  455. break
  456. }
  457. }
  458. }
  459. }
  460. function handleClickVolumeIcon() {
  461. volume.value = volume.value === 0 ? 0.5 : 0
  462. handleChangeVolumeBar(volume.value)
  463. }
  464. function handleChangeVolumeBar(volume) {
  465. audioElement.value.volume = Number(volume.toFixed(1))
  466. }
  467. // 关闭音频预览
  468. function handleClosePreview() {
  469. visible.value = false
  470. props.callback('cancel')
  471. }
  472. onMounted(() => {
  473. visible.value = props.visible
  474. activeIndex.value = props.defaultIndex
  475. })
  476. onUnmounted(() => {
  477. document.removeEventListener('keyup', handleAddKeyupEvent)
  478. })
  479. defineExpose({
  480. visible
  481. })
  482. </script>
  483. <style lang="less" scoped>
  484. @import '@/style/myResource/varibles.less';
  485. @import '@/style/myResource/mixins.less';
  486. .audio-preview-wrapper {
  487. background: @PrimaryText;
  488. position: fixed;
  489. top: 0;
  490. left: 0;
  491. width: 100vw;
  492. height: 100vh;
  493. z-index: 3;
  494. color: @BorderBase;
  495. .audio-background {
  496. position: fixed;
  497. top: -50%;
  498. left: 0;
  499. width: 100vw;
  500. height: auto;
  501. filter: blur(65px);
  502. opacity: 0.6;
  503. z-index: -1;
  504. }
  505. .operate-box {
  506. position: fixed;
  507. top: 16px;
  508. right: 32px;
  509. display: flex;
  510. align-items: center;
  511. .tip-icon,
  512. .close-icon {
  513. margin-left: 16px;
  514. cursor: pointer;
  515. &:hover {
  516. color: @Warning;
  517. }
  518. }
  519. .tip-icon {
  520. font-size: 26px;
  521. }
  522. .close-icon {
  523. font-size: 30px;
  524. }
  525. }
  526. .audio-list-wrapper {
  527. margin: 0 auto;
  528. width: 85%;
  529. height: calc(100vh - 120px);
  530. padding-top: 32px;
  531. display: flex;
  532. justify-content: space-between;
  533. .audio-list {
  534. flex: 1;
  535. list-style: none;
  536. .audio-list-body {
  537. height: calc(100% - 56px);
  538. overflow: auto;
  539. .setScrollbar(8px, transparent, rgba(0, 0, 0, 0.3));
  540. }
  541. .audio-list-header,
  542. .audio-item {
  543. border-radius: 8px;
  544. display: flex;
  545. justify-content: space-between;
  546. align-items: center;
  547. height: 56px;
  548. cursor: pointer;
  549. padding: 0 16px;
  550. &:hover {
  551. background: rgba(0, 0, 0, 0.1);
  552. }
  553. &.active {
  554. background: rgba(0, 0, 0, 0.1);
  555. color: @Warning;
  556. }
  557. .name {
  558. flex: 1;
  559. .sequence {
  560. display: inline-block;
  561. margin-right: 8px;
  562. width: 14px;
  563. text-align: center;
  564. }
  565. .wave {
  566. margin-right: 10px;
  567. width: 12px;
  568. height: 12px;
  569. }
  570. .no-wave {
  571. margin-right: 6px;
  572. font-size: 16px;
  573. }
  574. }
  575. .play-icon,
  576. .pause-icon,
  577. .download-icon,
  578. .share-icon {
  579. margin-right: 16px;
  580. font-size: 22px;
  581. cursor: pointer;
  582. &:hover {
  583. color: @Warning;
  584. }
  585. }
  586. .download {
  587. color: inherit;
  588. &:hover {
  589. color: @Warning;
  590. }
  591. }
  592. .audio-size {
  593. width: 120px;
  594. padding-right: 24px;
  595. text-align: right;
  596. }
  597. .path {
  598. min-width: 120px;
  599. }
  600. }
  601. .audio-list-header {
  602. padding-right: 24px;
  603. .name {
  604. padding-left: 18px;
  605. }
  606. }
  607. }
  608. .img-and-lyrics {
  609. padding: 8px 0 0 16px;
  610. width: 340px;
  611. display: flex;
  612. flex-direction: column;
  613. align-items: center;
  614. text-align: center;
  615. .audio-img {
  616. margin-bottom: 16px;
  617. width: 160px;
  618. height: 160px;
  619. }
  620. .audio-name {
  621. margin-bottom: 8px;
  622. font-size: 18px;
  623. line-height: 2;
  624. }
  625. .album-artist,
  626. .album-name {
  627. margin-bottom: 8px;
  628. }
  629. .lyrics-list {
  630. width: 100%;
  631. flex: 1;
  632. overflow: auto;
  633. .setScrollbar(6px, transparent, rgba(0, 0, 0, 0.3));
  634. -webkit-mask-image: linear-gradient(
  635. 180deg,
  636. hsla(0, 0%, 100%, 0) 0,
  637. hsla(0, 0%, 100%, 0.6) 15%,
  638. #fff 25%,
  639. #fff 75%,
  640. hsla(0, 0%, 100%, 0.6) 85%,
  641. hsla(0, 0%, 100%, 0)
  642. );
  643. &.one {
  644. .lyrics-item {
  645. margin-top: 40px;
  646. }
  647. }
  648. .lyrics-item {
  649. line-height: 40px;
  650. cursor: pointer;
  651. &:not(.active):hover {
  652. color: #fff;
  653. }
  654. &.active {
  655. color: @Warning;
  656. }
  657. }
  658. }
  659. }
  660. }
  661. .control-wrapper {
  662. margin: 0 auto;
  663. width: 85%;
  664. height: 120px;
  665. padding: 24px 0 32px 0;
  666. display: flex;
  667. justify-content: space-between;
  668. align-items: center;
  669. .control-left {
  670. flex: 1;
  671. height: 100%;
  672. display: flex;
  673. align-items: center;
  674. text-align: center;
  675. padding-left: 8px;
  676. .operate-icon {
  677. margin-right: 16px;
  678. font-size: 40px;
  679. cursor: pointer;
  680. &:hover {
  681. color: @Warning;
  682. }
  683. }
  684. .progress-bar {
  685. margin-right: 16px;
  686. flex: 1;
  687. // >>> .el-slider__runway {
  688. // height: 2px;
  689. // .el-slider__button-wrapper {
  690. // top: -17px;
  691. // .el-slider__button {
  692. // border: none;
  693. // }
  694. // }
  695. // .el-slider__bar {
  696. // height: 100%;
  697. // background: @Warning;
  698. // }
  699. // }
  700. }
  701. }
  702. .control-right {
  703. width: 340px;
  704. font-size: 24px;
  705. display: flex;
  706. justify-content: center;
  707. align-items: center;
  708. font-size: 32px;
  709. .operate-icon {
  710. margin-right: 16px;
  711. cursor: pointer;
  712. &:nth-last-of-type {
  713. margin-right: 0;
  714. }
  715. &:hover {
  716. color: @Warning;
  717. }
  718. &.download-link {
  719. font-size: 32px;
  720. color: inherit;
  721. &:hover {
  722. .download-icon {
  723. color: @Warning;
  724. }
  725. }
  726. }
  727. }
  728. .volume-icon {
  729. margin-right: 8px;
  730. }
  731. .volume-bar {
  732. width: 100px;
  733. // >>> .el-slider__runway {
  734. // height: 2px;
  735. // .el-slider__button-wrapper {
  736. // top: -19px;
  737. // .el-slider__button {
  738. // border: none;
  739. // }
  740. // }
  741. // .el-slider__bar {
  742. // height: 100%;
  743. // background: @Warning;
  744. // }
  745. // }
  746. }
  747. }
  748. }
  749. }
  750. </style>