|
|
@@ -1,21 +1,64 @@
|
|
|
-// src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.tsx
|
|
|
-import { Button, Drawer, Image, Modal, notification, Space, Table, Tabs } from 'antd';
|
|
|
+import { Button, Drawer, Image, Modal, notification, Space, Table, Tabs, Tag } from 'antd';
|
|
|
import './index.css';
|
|
|
-import { useEffect, useState } from 'react';
|
|
|
+import { useEffect, useState, useRef, useEffect as useLayoutEffect } from 'react';
|
|
|
import type { NotificationPlacement } from 'antd/es/notification/interface';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
+import { changeMvStatusApi, GetFileUrlByMd5Api, GetMvApi } from '@/apis/Mv';
|
|
|
+import { getArtinfo } from '@/utils/artlist';
|
|
|
+import DPlayer from 'dplayer';
|
|
|
|
|
|
const Mv = () => {
|
|
|
const navigate = useNavigate();
|
|
|
const [mvStatus, setMvStatus] = useState(0);
|
|
|
const [mvs, setMvs] = useState<any[]>([]);
|
|
|
const [currentRecord, setCurrentRecord] = useState<any>(null);
|
|
|
+ const [currentVideoUrl, setCurrentVideoUrl] = useState<string | null>(null); // 存储实际的视频URL
|
|
|
const [openDetail, setOpenDetail] = useState(false);
|
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
const [deleteMvId, setDeleteMvId] = useState<number | null>(null);
|
|
|
-
|
|
|
const [api, contextHolder] = notification.useNotification();
|
|
|
+ const dpContainerRef = useRef<HTMLDivElement>(null);
|
|
|
+ const dpRef = useRef<DPlayer | null>(null);
|
|
|
+ const [statusChangingMvId, setStatusChangingMvId] = useState<number | null>(null);
|
|
|
+ const [statusChangingModalOpen, setStatusChangingModalOpen] = useState(false);
|
|
|
+ const [targetStatus, setTargetStatus] = useState<number | null>(null);
|
|
|
+
|
|
|
+ // 添加上架/下架处理函数
|
|
|
+ const handleChangeStatus = async (record: any, newStatus: number) => {
|
|
|
+ setDeleteMvId(record.id);
|
|
|
+ setTargetStatus(newStatus);
|
|
|
+ setStatusChangingModalOpen(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 确认状态变更
|
|
|
+ const handleStatusChangeConfirm = async () => {
|
|
|
+ if (!deleteMvId || targetStatus === null) return;
|
|
|
|
|
|
+ setStatusChangingMvId(deleteMvId);
|
|
|
+ try {
|
|
|
+ const result = await changeMvStatusApi({ id: deleteMvId, status: targetStatus });
|
|
|
+ if (result.code === 200) {
|
|
|
+ api.success({
|
|
|
+ message: targetStatus === 3 ? '上架成功' : '下架成功',
|
|
|
+ placement: 'topRight',
|
|
|
+ });
|
|
|
+ getMvList(); // 重新获取列表
|
|
|
+ } else {
|
|
|
+ api.error({
|
|
|
+ message: result.message || '操作失败',
|
|
|
+ placement: 'topRight',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('状态变更失败:', error);
|
|
|
+ api.error({
|
|
|
+ message: '操作失败',
|
|
|
+ placement: 'topRight',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setStatusChangingModalOpen(false);
|
|
|
+ setStatusChangingMvId(null);
|
|
|
+ };
|
|
|
const mvStatusContent = [
|
|
|
{ key: 0, label: '审核中' },
|
|
|
{ key: 1, label: '审核失败' },
|
|
|
@@ -36,15 +79,29 @@ const Mv = () => {
|
|
|
key: 'mvName',
|
|
|
},
|
|
|
{
|
|
|
- title: '专辑名',
|
|
|
- dataIndex: 'albumName',
|
|
|
- key: 'albumName',
|
|
|
- render: (albumName: string) => albumName || '独立MV'
|
|
|
+ title: '封面',
|
|
|
+ dataIndex: 'coverUrl',
|
|
|
+ key: 'coverUrl',
|
|
|
+ render: (coverUrl: string) => (
|
|
|
+ coverUrl ? (
|
|
|
+ <Image
|
|
|
+ width={60}
|
|
|
+ height={40}
|
|
|
+ src={coverUrl}
|
|
|
+ alt="MV封面"
|
|
|
+ style={{ borderRadius: 4 }}
|
|
|
+ preview={false}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ '无封面'
|
|
|
+ )
|
|
|
+ ),
|
|
|
},
|
|
|
{
|
|
|
- title: '发布时间',
|
|
|
- dataIndex: 'releaseTime',
|
|
|
- key: 'releaseTime',
|
|
|
+ title: '创建时间',
|
|
|
+ dataIndex: 'createTime',
|
|
|
+ key: 'createTime',
|
|
|
+ render: (createTime: string) => new Date(createTime).toLocaleString() || '未知'
|
|
|
},
|
|
|
{
|
|
|
title: '状态',
|
|
|
@@ -69,6 +126,24 @@ const Mv = () => {
|
|
|
<Button size="small" onClick={() => handleViewDetails(record)}>
|
|
|
查看详情
|
|
|
</Button>
|
|
|
+ {record.status === 2 && (
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ onClick={() => handleChangeStatus(record, 3)}
|
|
|
+ >
|
|
|
+ 上架
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ {record.status === 3 && (
|
|
|
+ <Button
|
|
|
+ danger
|
|
|
+ size="small"
|
|
|
+ onClick={() => handleChangeStatus(record, 4)}
|
|
|
+ >
|
|
|
+ 下架
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
<Button danger size="small" onClick={() => handleDelete(record)}>
|
|
|
删除
|
|
|
</Button>
|
|
|
@@ -77,8 +152,25 @@ const Mv = () => {
|
|
|
}
|
|
|
];
|
|
|
|
|
|
- const handleViewDetails = (record: any) => {
|
|
|
+ const handleViewDetails = async (record: any) => {
|
|
|
setCurrentRecord(record);
|
|
|
+ if (record.videoUrl) {
|
|
|
+ try {
|
|
|
+ const result = await GetFileUrlByMd5Api(record.videoUrl);
|
|
|
+ if (result.code === 200) {
|
|
|
+ setCurrentVideoUrl(result.data); // 假设API返回的URL在data字段中
|
|
|
+ } else {
|
|
|
+ console.error('获取视频URL失败:', result.message);
|
|
|
+ setCurrentVideoUrl(null);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取视频URL出错:', error);
|
|
|
+ setCurrentVideoUrl(null);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ setCurrentVideoUrl(null);
|
|
|
+ }
|
|
|
+
|
|
|
setOpenDetail(true);
|
|
|
};
|
|
|
|
|
|
@@ -88,10 +180,21 @@ const Mv = () => {
|
|
|
};
|
|
|
|
|
|
const handleOk = async () => {
|
|
|
- // 模拟删除操作
|
|
|
- console.log('删除MV ID:', deleteMvId);
|
|
|
- setIsModalOpen(false);
|
|
|
- // 实际删除逻辑可以在这里添加
|
|
|
+ try {
|
|
|
+ console.log('删除MV ID:', deleteMvId);
|
|
|
+ setIsModalOpen(false);
|
|
|
+ getMvList();
|
|
|
+ api.success({
|
|
|
+ message: '删除成功',
|
|
|
+ placement: 'topRight',
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除失败:', error);
|
|
|
+ api.error({
|
|
|
+ message: '删除失败',
|
|
|
+ placement: 'topRight',
|
|
|
+ });
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
@@ -108,17 +211,55 @@ const Mv = () => {
|
|
|
|
|
|
const onClose = () => {
|
|
|
setOpenDetail(false);
|
|
|
+ setCurrentVideoUrl(null); // 关闭详情时清空视频URL
|
|
|
+ // 销毁 DPlayer 实例
|
|
|
+ if (dpRef.current) {
|
|
|
+ dpRef.current.destroy();
|
|
|
+ dpRef.current = null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const getMvList = async () => {
|
|
|
+ const id = getArtinfo().id;
|
|
|
+ const res = await GetMvApi({ id, status: mvStatus });
|
|
|
+ setMvs(res.data);
|
|
|
};
|
|
|
|
|
|
- // 模拟获取数据
|
|
|
useEffect(() => {
|
|
|
- // 获取MV列表
|
|
|
- setMvs([
|
|
|
- { id: 1, mvName: 'MV示例1', albumName: '专辑1', releaseTime: '2023-05-15', status: 3 },
|
|
|
- { id: 2, mvName: 'MV示例2', albumName: null, releaseTime: '2023-06-20', status: 0 },
|
|
|
- { id: 3, mvName: 'MV示例3', albumName: '专辑2', releaseTime: '2023-07-10', status: 2 }
|
|
|
- ]);
|
|
|
+ getMvList();
|
|
|
}, [mvStatus]);
|
|
|
+ useLayoutEffect(() => {
|
|
|
+ if (openDetail && currentVideoUrl && dpContainerRef.current) {
|
|
|
+ if (dpRef.current) {
|
|
|
+ dpRef.current.destroy();
|
|
|
+ }
|
|
|
+
|
|
|
+ dpRef.current = new DPlayer({
|
|
|
+ container: dpContainerRef.current,
|
|
|
+ video: {
|
|
|
+ url: currentVideoUrl,
|
|
|
+ type: 'auto'
|
|
|
+ },
|
|
|
+ autoplay: false,
|
|
|
+ theme: '#e63b3b',
|
|
|
+ loop: false,
|
|
|
+ lang: 'zh-cn',
|
|
|
+ screenshot: false,
|
|
|
+ hotkey: true,
|
|
|
+ preload: 'metadata',
|
|
|
+ volume: 0.7,
|
|
|
+ mutex: true,
|
|
|
+ height: 300
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ if (dpRef.current) {
|
|
|
+ dpRef.current.destroy();
|
|
|
+ dpRef.current = null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }, [currentVideoUrl, openDetail]);
|
|
|
|
|
|
return (
|
|
|
<div className="mv-container">
|
|
|
@@ -165,7 +306,18 @@ const Mv = () => {
|
|
|
>
|
|
|
<p>确定要删除选中MV吗?</p>
|
|
|
</Modal>
|
|
|
-
|
|
|
+ <Modal
|
|
|
+ title={targetStatus === 3 ? '确认上架' : '确认下架'}
|
|
|
+ open={statusChangingModalOpen}
|
|
|
+ onOk={handleStatusChangeConfirm}
|
|
|
+ onCancel={() => setStatusChangingModalOpen(false)}
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <p>
|
|
|
+ 确定要{targetStatus === 3 ? '上架' : '下架'}这个MV吗?
|
|
|
+ {targetStatus === 3 ? '上架后用户将可以访问此MV。' : '下架后用户将无法访问此MV。'}
|
|
|
+ </p>
|
|
|
+ </Modal>
|
|
|
<Drawer
|
|
|
title="MV详情"
|
|
|
placement="right"
|
|
|
@@ -176,7 +328,7 @@ const Mv = () => {
|
|
|
>
|
|
|
{currentRecord && (
|
|
|
<div className="mv-detail">
|
|
|
- <div style={{ display: 'flex', marginBottom: 20 }}>
|
|
|
+ <div style={{ display: 'flex', marginBottom: 0 }}>
|
|
|
<div style={{ flex: 1 }}>
|
|
|
<div className="detail-item">
|
|
|
<label>MV名称:</label>
|
|
|
@@ -186,6 +338,10 @@ const Mv = () => {
|
|
|
<label>ID:</label>
|
|
|
<span>{currentRecord.id}</span>
|
|
|
</div>
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>艺术家:</label>
|
|
|
+ <span>{currentRecord.artistName}</span>
|
|
|
+ </div>
|
|
|
<div className="detail-item">
|
|
|
<label>状态:</label>
|
|
|
<span>
|
|
|
@@ -201,13 +357,30 @@ const Mv = () => {
|
|
|
})()}
|
|
|
</span>
|
|
|
</div>
|
|
|
+
|
|
|
+ {currentRecord.status === 1 && currentRecord.auditReason && (
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>审核失败原因:</label>
|
|
|
+ <span style={{ color: '#ff4d4f', fontWeight: 'bold' }}>
|
|
|
+ {currentRecord.auditReason}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>时长:</label>
|
|
|
+ <span>
|
|
|
+ {currentRecord.duration !== null && currentRecord.duration !== undefined
|
|
|
+ ? `${Math.floor(currentRecord.duration / 60)}:${(currentRecord.duration % 60).toString().padStart(2, '0')}`
|
|
|
+ : '未知'}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- {currentRecord.thumbnailUrl && (
|
|
|
+ {currentRecord.coverUrl && (
|
|
|
<div className="mv-cover">
|
|
|
<Image
|
|
|
width={180}
|
|
|
height={180}
|
|
|
- src={currentRecord.thumbnailUrl}
|
|
|
+ src={currentRecord.coverUrl}
|
|
|
alt="MV封面"
|
|
|
style={{ borderRadius: 8 }}
|
|
|
/>
|
|
|
@@ -215,6 +388,17 @@ const Mv = () => {
|
|
|
)}
|
|
|
</div>
|
|
|
|
|
|
+
|
|
|
+ {/* 视频播放器 */}
|
|
|
+ {currentVideoUrl ? (
|
|
|
+ <div className="detail-item" style={{ marginBottom: 16 }}>
|
|
|
+ <label>视频预览:</label>
|
|
|
+ <div style={{ marginTop: 8 }}>
|
|
|
+ <div ref={dpContainerRef} style={{ height: '300px', width: '100%' }} />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : <div>加载中</div>}
|
|
|
+
|
|
|
<div className="detail-item">
|
|
|
<label>描述:</label>
|
|
|
<div style={{
|
|
|
@@ -229,10 +413,99 @@ const Mv = () => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>标签:</label>
|
|
|
+ <div style={{
|
|
|
+ marginTop: 4,
|
|
|
+ display: 'flex',
|
|
|
+ flexWrap: 'wrap',
|
|
|
+ gap: '4px'
|
|
|
+ }}>
|
|
|
+ {currentRecord.tags ? currentRecord.tags.split(',').map((tag: string, index: number) => (
|
|
|
+ <Tag key={index} color='cyan' variant={'solid'}>
|
|
|
+ <span color={'white'}>
|
|
|
+ {tag.trim()}
|
|
|
+ </span>
|
|
|
+ </Tag>
|
|
|
+ )) : '无标签'}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>创建时间:</label>
|
|
|
+ <span>{currentRecord.createTime ? new Date(currentRecord.createTime).toLocaleString() : '未知'}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>更新时间:</label>
|
|
|
+ <span>{currentRecord.updateTime ? new Date(currentRecord.updateTime).toLocaleString() : '未知'}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div className="detail-item">
|
|
|
<label>发布时间:</label>
|
|
|
<span>{currentRecord.releaseTime || '未设置'}</span>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>封面URL:</label>
|
|
|
+ <a href={currentRecord.coverUrl} target="_blank" rel="noopener noreferrer">
|
|
|
+ {currentRecord.coverUrl}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>视频MD5:</label>
|
|
|
+ <span>{currentRecord.videoUrl}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>视频播放URL:</label>
|
|
|
+ {currentVideoUrl ? (
|
|
|
+ <a href={currentVideoUrl} target="_blank" rel="noopener noreferrer">
|
|
|
+ {currentVideoUrl}
|
|
|
+ </a>
|
|
|
+ ) : (
|
|
|
+ <span>加载中...</span>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="detail-item">
|
|
|
+ <label>状态:</label>
|
|
|
+ <div style={{ display: 'flex', alignItems: 'center' }}>
|
|
|
+ <span style={{ marginRight: 16 }}>
|
|
|
+ {(() => {
|
|
|
+ const statusMap: Record<number, string> = {
|
|
|
+ 0: '审核中',
|
|
|
+ 1: '审核失败',
|
|
|
+ 2: '发布中',
|
|
|
+ 3: '已上架',
|
|
|
+ 4: '已下架'
|
|
|
+ };
|
|
|
+ return statusMap[currentRecord.status] || '未知';
|
|
|
+ })()}
|
|
|
+ </span>
|
|
|
+ {currentRecord.status === 2 && (
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ loading={statusChangingMvId === currentRecord.id}
|
|
|
+ onClick={() => handleChangeStatus(currentRecord, 3)}
|
|
|
+ >
|
|
|
+ 上架
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ {currentRecord.status === 3 && (
|
|
|
+ <Button
|
|
|
+ danger
|
|
|
+ size="small"
|
|
|
+ loading={statusChangingMvId === currentRecord.id}
|
|
|
+ onClick={() => handleChangeStatus(currentRecord, 4)}
|
|
|
+ >
|
|
|
+ 下架
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
)}
|
|
|
</Drawer>
|