| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- <template>
- <div class="playlist-view-container">
- <el-row v-if="!permissionDenied" :gutter="10" class="main-content">
- <el-col v-if="video !== null" :md="16" :sm="24" :xs="24" class="video-left-section">
- <el-card class="video-card" shadow="never">
- <div slot="header" class="video-header">
- <div class="title-row">
- <h1 class="video-title" v-html="video.title" />
- <router-link target="_blank" :to="`/video/${video.videoId}`">
- <el-link type="primary" :underline="false" class="origin-link">原视频 <i class="el-icon-right" /></el-link>
- </router-link>
- </div>
- <div class="video-stats">
- <span><i class="el-icon-video-play" /> {{ video.view }}</span>
- <span><i class="el-icon-s-comment" /> {{ video.comment }}</span>
- <span><i class="el-icon-time" /> {{ video.pubDate }}</span>
- </div>
- </div>
- <div class="player-wrapper">
- <div id="dplayer" ref="dplayer" class="dplayer-instance" />
- </div>
- <div class="video-actions">
- <el-button
- type="danger"
- size="small"
- round
- icon="el-icon-collection"
- :disabled="isCollected"
- @click="collection(video.videoId)"
- >
- 收藏 {{ video.favorite }}
- </el-button>
- <el-button
- type="info"
- size="small"
- round
- plain
- icon="el-icon-warning-outline"
- @click="deleteVideo(video)"
- >
- 报错/删除
- </el-button>
- </div>
- </el-card>
- <el-card class="description-card" shadow="never">
- <div class="description-content">
- <p class="desc-text" v-html="video.description || '暂无简介'" />
- </div>
- <el-divider />
- <div class="tag-group">
- <el-tag
- v-for="(tag, index) in video.tags"
- :key="index"
- class="video-tag"
- size="small"
- effect="plain"
- >
- <router-link :to="`/video/tag/${tag}`">{{ tag }}</router-link>
- </el-tag>
- </div>
- </el-card>
- </el-col>
- <el-col :md="8" :sm="24" :xs="24" class="playlist-right-section">
- <el-card class="playlist-card" shadow="never">
- <div slot="header" class="playlist-header">
- <div class="flex-between">
- <h3 class="m0">播放列表</h3>
- <div class="auto-play-switch">
- <span class="label-text">自动播放</span>
- <el-switch v-model="autoPlay" active-color="#13ce66" />
- </div>
- </div>
- </div>
- <div class="playlist-body">
- <el-table
- :data="playList.list"
- :show-header="false"
- highlight-current-row
- :row-class-name="tableRowClassName"
- class="playlist-table"
- @row-click="playItem"
- >
- <el-table-column width="40" type="index" align="center" />
- <el-table-column width="100">
- <template slot-scope="scope">
- <div class="playlist-cover-container">
- <el-image
- lazy
- fit="cover"
- class="playlist-cover-img"
- :src="scope.row.coverUrl"
- />
- <span class="duration-badge">{{ scope.row.duration }}</span>
- </div>
- </template>
- </el-table-column>
- <el-table-column>
- <template slot-scope="scope">
- <span class="playlist-item-title">{{ scope.row.title }}</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </el-card>
- </el-col>
- </el-row>
- <el-row v-else>
- <permission-denied-card :text-object="textObject" />
- </el-row>
- </div>
- </template>
- <script>
- import PermissionDeniedCard from '@/components/card/PermissionDeniedCard'
- import DPlayer from 'dplayer'
- import { videoUrl, videoInfo } from '@/api/video'
- import { getPlaylistItems } from '@/api/collect'
- export default {
- name: 'PlaylistView',
- components: { PermissionDeniedCard },
- data() {
- return {
- video: null,
- user: null,
- isCollected: false,
- permissionDenied: false,
- textObject: { content: '视频', route: '/video' },
- autoPlay: false,
- playList: { current: 0, list: [] },
- danmaku: {
- api: process.env.VUE_APP_SERVER_URL + '/api/comment/danmaku/',
- token: 'tnbapp'
- },
- player: null
- }
- },
- watch: {
- '$route.params.albumId': function() {
- this.initPage()
- }
- },
- created() {
- this.initPage()
- },
- methods: {
- initPage() {
- const albumId = this.$route.params.albumId
- getPlaylistItems(albumId).then(resp => {
- if (resp.code === 0) {
- this.playList.list = resp.data.pageList.list
- document.title = resp.data.albumInfo.albumName
- if (this.playList.list.length > 0) {
- this.getVideoInfo(this.playList.list[0].videoId)
- }
- }
- })
- },
- getVideoInfo(videoId) {
- videoInfo(videoId).then(resp => {
- if (resp.code === 0) {
- this.video = resp.data
- this.getVideoUrl(videoId)
- } else {
- this.permissionDenied = true
- }
- })
- },
- getVideoUrl(videoId) {
- videoUrl(videoId).then(res => {
- if (res.code === 0 && res.data.type === 'mp4') {
- this.initPlayer(res.data.urls, res.data.currentTime)
- }
- })
- },
- initPlayer(urls, pos) {
- if (this.player) this.player.destroy()
- this.player = new DPlayer({
- container: this.$refs.dplayer,
- autoplay: this.autoPlay,
- theme: '#409EFF',
- volume: 0.7,
- video: {
- pic: this.video.coverUrl,
- quality: urls.map(u => ({ name: u.label || '标清', url: u.url, type: 'normal' })),
- defaultQuality: 0
- },
- danmaku: {
- id: this.video.videoId,
- api: this.danmaku.api,
- token: this.danmaku.token,
- user: this.video.userId
- }
- })
- this.player.seek(pos)
- this.player.on('ended', () => {
- if (this.autoPlay) this.playNext()
- })
- },
- playItem(row) {
- this.getVideoInfo(row.videoId)
- // 移动端点击后滚动到顶部播放器
- if (window.innerWidth < 768) {
- window.scrollTo({ top: 0, behavior: 'smooth' })
- }
- },
- playNext() {
- const currentIndex = this.playList.list.findIndex(i => i.videoId === this.video.videoId)
- if (currentIndex < this.playList.list.length - 1) {
- this.playItem(this.playList.list[currentIndex + 1])
- }
- },
- tableRowClassName({ row }) {
- if (this.video && row.videoId === this.video.videoId) {
- return 'current-playing-row'
- }
- return ''
- }
- }
- }
- </script>
- <style scoped>
- .playlist-view-container {
- max-width: 1400px;
- margin: 0 auto;
- padding: 15px;
- background-color: #f4f4f5;
- min-height: 100vh;
- }
- .m0 { margin: 0; }
- .flex-between { display: flex; justify-content: space-between; align-items: center; }
- /* 视频标题与信息 */
- .video-header { padding: 10px 0; }
- .title-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }
- .video-title { font-size: 1.2rem; line-height: 1.4; margin: 0 0 10px 0; color: #1f1f1f; flex: 1; }
- .origin-link { font-size: 14px; white-space: nowrap; }
- .video-stats { color: #909399; font-size: 13px; display: flex; gap: 15px; flex-wrap: wrap; }
- /* 播放器适配 */
- .player-wrapper {
- background: #000;
- border-radius: 4px;
- overflow: hidden;
- position: relative;
- width: 100%;
- }
- /* PC端高度,移动端通过媒体查询调整 */
- .dplayer-instance { height: 500px; width: 100%; }
- .video-actions { padding: 15px 0 5px; display: flex; gap: 10px; }
- /* 简介与标签 */
- .description-card { margin-top: 10px; border: none; }
- .desc-text { font-size: 14px; color: #606266; line-height: 1.6; white-space: pre-wrap; margin: 0; }
- .tag-group { display: flex; flex-wrap: wrap; gap: 8px; }
- .video-tag a { text-decoration: none; color: inherit; }
- /* 播放列表 */
- .playlist-header { padding: 5px 0; }
- .auto-play-switch { display: flex; align-items: center; gap: 8px; }
- .label-text { font-size: 13px; color: #606266; }
- .playlist-body { max-height: 600px; overflow-y: auto; }
- .playlist-table { cursor: pointer; }
- .playlist-cover-container { position: relative; width: 90px; height: 56px; border-radius: 4px; overflow: hidden; }
- .playlist-cover-img { width: 100%; height: 100%; }
- .duration-badge {
- position: absolute; bottom: 2px; right: 2px;
- background: rgba(0,0,0,0.7); color: #fff;
- font-size: 10px; padding: 0 4px; border-radius: 2px;
- }
- .playlist-item-title {
- font-size: 13px; color: #303133;
- display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
- }
- /* 移动端深度适配 */
- @media screen and (max-width: 768px) {
- .playlist-view-container { padding: 0; background-color: #fff; }
- .video-left-section, .playlist-right-section { padding: 0 !important; }
- /* 移动端播放器强制 16:9 */
- .dplayer-instance { height: 56.25vw !important; }
- .video-card { border: none; border-radius: 0; }
- .video-card ::v-deep .el-card__header { padding: 10px 15px; }
- .video-card ::v-deep .el-card__body { padding: 0; }
- .video-title { font-size: 1rem; }
- .video-actions { padding: 10px 15px; }
- .description-card { border-radius: 0; }
- .playlist-card { border: none; border-top: 8px solid #f4f4f5; border-radius: 0; }
- .playlist-body { max-height: none; }
- }
- /* 高亮当前播放行 */
- ::v-deep .current-playing-row {
- background-color: #ecf5ff !important;
- }
- ::v-deep .current-playing-row .playlist-item-title {
- color: #409EFF;
- font-weight: bold;
- }
- </style>
|