rank_backend/frontend/src/DramaDetail.vue
Qyir 91761b6754 添加短剧版权方认证页面
添加申请管理页面上传至TOS
2025-11-13 17:49:48 +08:00

593 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import axios from 'axios'
const router = useRouter()
const route = useRoute()
// 响应式数据
const isExpanded = ref(false)
const loading = ref(false)
const dramaData = ref({
title: '',
mix_name: '',
mix_id: '',
classification_type: '', // 女频/玄等
episodes: '',
release_date: '', // 上线日期
cover_image_url: '',
desc: '', // 剧情介绍使用desc字段
Copyright_field: '', // 版权方
Manufacturing_Field: '', // 承制方
comments_summary: '' // 用户评论总结
})
// API基础URL
const API_BASE_URL = 'http://localhost:8443/api' // 本地服务器
// 返回上一页(直接返回首页)
const goBack = () => {
router.push('/')
}
// 切换展开/收起
const toggleExpanded = () => {
isExpanded.value = !isExpanded.value
}
// 处理带【】格式的评论总结返回HTML
const formatCommentsSummary = (text, spacing = 1) => {
if (!text) return ''
// 简单方法:将【】加粗,然后在每个【前面加间距
let result = text.replace(/【([^】]+)】/g, '<strong>【$1】</strong>')
// 使用margin-top来精确控制间距支持小数
const spacingPx = spacing * 10 // spacing=1.5 -> 15px
// 在每个【前面添加带margin的div除了第一个
let isFirst = true
result = result.replace(/(<strong>【)/g, (match) => {
if (isFirst) {
isFirst = false
return match
}
return `<div style="margin-top: ${spacingPx}px">${match}`
})
// 为每个添加了开始div的地方添加结束div
const parts = result.split(/(<div style="margin-top: \d+px">)/)
let finalResult = ''
for (let i = 0; i < parts.length; i++) {
if (parts[i].includes('margin-top')) {
finalResult += parts[i]
if (i + 1 < parts.length) {
finalResult += parts[i + 1] + '</div>'
i++ // 跳过下一个part
}
} else {
finalResult += parts[i]
}
}
return finalResult
}
// 获取短剧详细信息
const fetchDramaDetail = async (dramaId) => {
loading.value = true
try {
const response = await axios.get(`${API_BASE_URL}/rank/drama/${dramaId}`)
if (response.data.success) {
const data = response.data.data
dramaData.value = {
title: data.mix_name || data.title || '',
mix_name: data.mix_name || '',
mix_id: data.mix_id || '',
classification_type: data.classification_type || '',
episodes: data.updated_to_episode ? `${data.updated_to_episode}` : '',
release_date: data.release_date || '',
cover_image_url: data.cover_image_url || '',
desc: data.desc || '', // 使用desc字段作为剧情介绍
Copyright_field: data.Copyright_field || '',
Manufacturing_Field: data.Manufacturing_Field || '',
comments_summary: data.comments_summary || '',
series_author: data.series_author || ''
}
// 如果URL中有hash参数滚动到对应位置
if (route.hash) {
await nextTick()
const element = document.querySelector(route.hash)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
} else {
console.error('获取短剧详情失败:', response.data.message)
}
} catch (error) {
console.error('API调用失败:', error)
} finally {
loading.value = false
}
}
// 跳转到认领页面
const goToClaim = (type) => {
// type: 'copyright' 或 'manufacturing'
const dramaId = route.params.id
if (dramaId) {
router.push(`/claim/${dramaId}?type=${type}`)
} else {
console.error('无法获取短剧ID')
}
}
// 页面加载时获取短剧数据
onMounted(() => {
// 从路由参数获取短剧ID
const dramaId = route.params.id
console.log('短剧详情页ID:', dramaId)
if (dramaId) {
fetchDramaDetail(dramaId)
}
})
</script>
<template>
<div class="page">
<div class="card">
<!-- 顶部导航栏 -->
<div class="header">
<button class="icon-button" @click="goBack">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="15 18 9 12 15 6" />
</svg>
</button>
<p class="title-header">短剧详情</p>
<div class="header-spacer" />
</div>
<!-- 内容区域 -->
<div class="content">
<!-- 加载状态 -->
<div v-if="loading" class="loading">
<div class="loading-spinner"></div>
<p>加载中...</p>
</div>
<!-- 短剧基本信息 -->
<div v-else class="block">
<div class="drama-info">
<div class="cover">
<img
:src="dramaData.cover_image_url || '/placeholder-poster.svg'"
:alt="dramaData.title"
class="cover-img"
/>
</div>
<div class="info">
<div class="title-row">
<p class="title">{{ dramaData.title || '短剧名称' }}</p>
</div>
<p class="small-text">
<span class="field-label">类型/元素</span>
<span class="field-value">{{ dramaData.classification_type || '' }}</span>
</p>
<p class="small-text">
<span class="field-label">集数</span>
<span class="field-value">{{ dramaData.episodes || '' }}</span>
</p>
<p class="small-text">
<span class="field-label">上线日期</span>
<span class="field-value">{{ dramaData.release_date || '' }}</span>
</p>
</div>
</div>
<!-- 短剧介绍 -->
<div class="description">
<p class="description-label">剧情介绍</p>
<p class="description-text" v-if="dramaData.desc">
{{ isExpanded ? dramaData.desc : (dramaData.desc.length > 100 ? dramaData.desc.substring(0, 100) + '...' : dramaData.desc) }}
</p>
<p class="description-text description-empty" v-else></p>
<button v-if="dramaData.desc && dramaData.desc.length > 100" class="toggle-btn" @click="toggleExpanded">
{{ isExpanded ? '收起' : '展开' }}
</button>
</div>
</div>
<!-- 关联方信息 -->
<div v-if="!loading" class="block">
<p class="section-title">关联方</p>
<div class="assoc-group">
<p class="label">版权方</p>
<div class="assoc-row">
<div>
<span v-if="dramaData.Copyright_field" class="chip-blue">{{ dramaData.Copyright_field }}</span>
<span v-else class="chip-empty"></span>
</div>
<p v-if="!dramaData.Copyright_field" class="claim" @click="goToClaim('copyright')">我要认领</p>
</div>
</div>
<div class="assoc-group">
<p class="label">承制方</p>
<div class="assoc-row">
<div>
<span v-if="dramaData.Manufacturing_Field" class="chip-red">{{ dramaData.Manufacturing_Field }}</span>
<span v-else class="chip-empty"></span>
</div>
<p v-if="!dramaData.Manufacturing_Field" class="claim" @click="goToClaim('manufacturing')">我要认领</p>
</div>
</div>
</div>
<!-- 用户评论 -->
<div v-if="!loading" id="comments" class="block">
<p class="section-title-sm">抖音用户整体评论</p>
<div class="comment-row">
<img
src="https://oss.xintiao85.com/story/materials/image/6912ff82dc05328e25f9442c/1762926632_cfc62d9f.png"
alt="用户评论"
class="avatar"
/>
<div class="comment-text" v-if="dramaData.comments_summary" v-html="formatCommentsSummary(dramaData.comments_summary, 1.5)"></div>
<p class="comment-text comment-empty" v-else></p>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
* {
box-sizing: border-box;
}
.page {
display: flex;
flex-direction: column;
align-items: center;
background: #ebedf2;
padding: 14px;
min-height: 100vh;
}
.card {
display: flex;
flex-direction: column;
border-radius: 0;
overflow: hidden;
background: #f3f4f6;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
width: 100%;
max-width: 428px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
background: #ffffff;
padding: 12px 16px;
}
.icon-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px;
color: #111827;
background: none;
border: none;
cursor: pointer;
transition: opacity 0.2s;
}
.icon-button:hover {
opacity: 0.7;
}
.icon-button-sm {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px;
background: none;
border: none;
cursor: pointer;
transition: opacity 0.2s;
}
.icon-button-sm:hover {
opacity: 0.7;
}
.title-header {
color: #1f2937;
font-size: 16px;
font-weight: 600;
margin: 0;
}
.header-spacer {
width: 24px;
height: 24px;
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
}
.block {
background: #ffffff;
border-radius: 12px;
padding: 20px;
}
.drama-info {
display: flex;
gap: 16px;
margin-bottom: 0;
}
.cover {
width: 84px;
height: 112px;
border-radius: 8px;
overflow: hidden;
background: #fce7f3;
flex-shrink: 0;
}
.cover-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.info {
flex: 1;
}
.title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 26px;
}
.title {
color: #111827;
font-size: 18px;
font-weight: 600;
margin: 0;
}
.small-text {
font-size: 12px;
color: #6b7280;
margin: 8px 0;
}
.description {
display: flex;
flex-direction: column;
margin-top: 16px;
}
.description-text {
color: #4b5563;
font-size: 14px;
margin-bottom: 6px;
line-height: 1.6;
}
.description-empty {
color: #9ca3af;
min-height: 20px;
}
.toggle-btn {
color: #3b82f6;
font-size: 12px;
background: transparent;
border: none;
padding: 0;
cursor: pointer;
text-align: left;
transition: opacity 0.2s;
}
.toggle-btn:hover {
opacity: 0.7;
}
.section-title {
color: #111827;
font-size: 18px;
font-weight: 600;
margin: 0 0 16px 0;
padding-bottom: 12px;
border-bottom: 1px solid #e5e7eb;
}
.assoc-group {
margin-bottom: 16px;
}
.assoc-group:last-child {
margin-bottom: 0;
}
.assoc-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
}
.label {
color: #374151;
font-size: 14px;
margin: 0 0 0 0;
}
.claim {
color: #ef4444;
font-size: 14px;
margin: 0;
cursor: pointer;
transition: opacity 0.2s;
}
.claim:hover {
opacity: 0.7;
}
.chip-blue {
display: inline-block;
padding: 6px 14px;
border-radius: 8px;
background: #eff6ff;
color: #2563eb;
font-size: 14px;
}
.chip-red {
display: inline-block;
padding: 6px 14px;
border-radius: 8px;
background: #fef2f2;
color: #dc2626;
font-size: 14px;
}
.chip-green {
display: inline-block;
padding: 6px 14px;
border-radius: 8px;
background: #ecfdf5;
color: #059669;
font-size: 14px;
}
.section-title-sm {
color: #1f2937;
font-size: 14px;
font-weight: 600;
margin: 0 0 12px 0;
padding: 16px 0 12px 0;
border-bottom: 1px solid #e5e7eb;
}
.qr-box {
display: flex;
align-items: center;
justify-content: center;
width: 128px;
height: 128px;
margin: 0 auto;
border: 1px solid #d1d5db;
border-radius: 8px;
overflow: hidden;
}
.qr-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.comment-row {
display: flex;
gap: 12px;
}
.avatar {
width: 32px;
height: 32px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
}
.comment-text {
font-size: 13px;
color: #4b5563;
margin: 0;
line-height: 2;
}
.comment-text strong {
font-weight: 600;
color: #1f2937;
}
.comment-empty {
color: #9ca3af;
font-style: italic;
}
.chip-empty {
display: inline-block;
padding: 4px 10px;
border-radius: 8px;
background: #f3f4f6;
color: #9ca3af;
font-size: 14px;
}
.description-label {
color: #374151;
font-size: 14px;
font-weight: 600;
margin: 0 0 8px 0;
}
/* 加载状态 */
.loading {
text-align: center;
padding: 40px 20px;
color: #666;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #4a90e2;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 响应式设计 */
@media (max-width: 480px) {
.page {
padding: 0;
}
.card {
max-width: 100%;
border-radius: 0;
}
}
</style>