feat: 课程详情页
This commit is contained in:
parent
1f84e3a682
commit
d66a606053
|
@ -23,11 +23,14 @@ function Nav() {
|
|||
<span>Backset</span>
|
||||
</div>
|
||||
<div className="middle">
|
||||
{routerList.map((route) => (
|
||||
{routerList.map(
|
||||
(route) =>
|
||||
!route.invisible && (
|
||||
<span key={route.path} onClick={() => navigate(route.path)}>
|
||||
{route.name}
|
||||
</span>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
<InputSearch allowClear placeholder="搜索" style={{ width: 150 }} />
|
||||
</div>
|
||||
<div className="end">
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
}
|
||||
|
||||
// 圆角
|
||||
|
||||
.vjs-poster,
|
||||
.video-js {
|
||||
border-radius: 10px !important;
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import { useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useLink, useMount, useScript } from "../../hook";
|
||||
import "./index.less";
|
||||
|
||||
interface IVideo {
|
||||
export interface IVideo {
|
||||
fileID: string;
|
||||
appID: string;
|
||||
psign?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
video: IVideo | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* demo页面:https://tcplayer.vcube.tencent.com/
|
||||
*
|
||||
|
@ -16,13 +20,10 @@ interface IVideo {
|
|||
* 有些浏览器环境不支持 Webrtc,播放器会将 Webrtc 流地址自动转换为 HLS 格式地址,因此快直播场景同样需要引入hls.min.x.xx.xm.js。
|
||||
* <video objectFill /> fill填满变形,cover等比例会裁剪, contain等比例有黑边
|
||||
*/
|
||||
function Player() {
|
||||
function Player(props: IProps) {
|
||||
const playerRef = useRef<any>();
|
||||
|
||||
useLink(["/player/tcplayer_pure.min.css"]);
|
||||
useScript(
|
||||
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
|
||||
() => {
|
||||
const initPlayer = (video: IVideo) => {
|
||||
const TCPlayer = (window as any).TCPlayer;
|
||||
|
||||
// 重写播放按钮
|
||||
|
@ -40,26 +41,23 @@ function Player() {
|
|||
);
|
||||
return el;
|
||||
};
|
||||
playerRef.current = TCPlayer("player", {
|
||||
fileID: "243791579995468466",
|
||||
appID: "1500018521",
|
||||
//私有加密播放需填写 psign, psign 即播放器签名,签名介绍和生成方式参见链接:https://cloud.tencent.com/document/product/266/42436
|
||||
//psign:'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6MTUwMDAwNTY5NiwiZmlsZUlkIjoiMzcwMTkyNTkyMTI5OTYzNzAxMCIsImN1cnJlbnRUaW1lU3RhbXAiOjE2MjY4NjAxNzYsImV4cGlyZVRpbWVTdGFtcCI6MjYyNjg1OTE3OSwicGNmZyI6InByaXZhdGUiLCJ1cmxBY2Nlc3NJbmZvIjp7InQiOiI5YzkyYjBhYiJ9LCJkcm1MaWNlbnNlSW5mbyI6eyJleHBpcmVUaW1lU3RhbXAiOjI2MjY4NTkxNzksInN0cmljdE1vZGUiOjJ9fQ.Bo5K5ThInc4n8AlzIZQ-CP9a49M2mEr9-zQLH9ocQgI',
|
||||
});
|
||||
playerRef.current = TCPlayer("player_html5_api", video);
|
||||
};
|
||||
|
||||
useLink(["/player/tcplayer_pure.min.css"]);
|
||||
useScript(
|
||||
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
|
||||
() => {
|
||||
console.log("执行了", playerRef.current);
|
||||
playerRef.current = null;
|
||||
if (props.video) initPlayer(props.video);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 切换视频
|
||||
*/
|
||||
const changeVideo = (video: IVideo) => {
|
||||
playerRef.current!.loadVideoByID({ ...video });
|
||||
};
|
||||
|
||||
return (
|
||||
<video
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
id="player"
|
||||
id="player_html5_api"
|
||||
style={{ width: "100%", height: "100%", objectFit: "contain" }}
|
||||
preload="auto"
|
||||
playsInline
|
||||
|
|
|
@ -17,5 +17,6 @@ export const routerList = [
|
|||
path: "/course/detail/:id",
|
||||
element: <CourseDetail />,
|
||||
name: "课程详情",
|
||||
invisible: true,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -9,6 +9,15 @@
|
|||
position: relative;
|
||||
height: 360px;
|
||||
box-shadow: 0 4px 10px rgb(var(--gray-2));
|
||||
&.float {
|
||||
position: fixed !important;
|
||||
left: 0;
|
||||
top: 60px;
|
||||
bottom: 0;
|
||||
height: auto !important;
|
||||
right: calc((100% - 1120px) / 2);
|
||||
z-index: 20;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
padding-bottom: 20px;
|
||||
|
@ -41,17 +50,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.linear-mask {
|
||||
position: absolute;
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
word-break: break-all;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 25%;
|
||||
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), transparent);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import { useState } from "react";
|
||||
import Player from "../../components/Player";
|
||||
import Player, { IVideo } from "../../components/Player";
|
||||
import "./index.less";
|
||||
|
||||
function CourseDetail() {
|
||||
const [state, setState] = useState({
|
||||
videoTopMaskVisible: true,
|
||||
});
|
||||
|
||||
const [toc, setToc] = useState([
|
||||
{
|
||||
title: "准备",
|
||||
|
@ -17,12 +13,20 @@ function CourseDetail() {
|
|||
level: 2,
|
||||
time: "3:23",
|
||||
get: false,
|
||||
video: {
|
||||
fileID: "243791579995468466",
|
||||
appID: "1500018521",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "css 样式的写法",
|
||||
level: 2,
|
||||
time: "4:13",
|
||||
get: false,
|
||||
video: {
|
||||
fileID: "243791580097740418",
|
||||
appID: "1500018521",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "使用CSS",
|
||||
|
@ -42,10 +46,10 @@ function CourseDetail() {
|
|||
},
|
||||
]);
|
||||
|
||||
const onMouseLeaveVideo = () => {
|
||||
setTimeout(() => {
|
||||
setState((p) => ({ ...p, videoTopMaskVisible: false }));
|
||||
}, 3 * 1000);
|
||||
const [video, setVideo] = useState<IVideo | null>(null);
|
||||
|
||||
const onclickItem = (i: any) => {
|
||||
setVideo(i.video);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -53,7 +57,7 @@ function CourseDetail() {
|
|||
<article>
|
||||
<h4 className="title">01 | 基础架构:一条SQL查询语句是如何执行的?</h4>
|
||||
<div className="player-container">
|
||||
<Player />
|
||||
<Player video={video} />
|
||||
</div>
|
||||
</article>
|
||||
<aside>
|
||||
|
@ -61,10 +65,18 @@ function CourseDetail() {
|
|||
<div className="toc">
|
||||
{toc.map((i) => {
|
||||
if (i.level === 1) {
|
||||
return <div className="level-1">{i.title}</div>;
|
||||
return (
|
||||
<div className="level-1" key={i.title}>
|
||||
{i.title}
|
||||
</div>
|
||||
);
|
||||
} else if (i.level === 2) {
|
||||
return (
|
||||
<div className="level-2">
|
||||
<div
|
||||
className="level-2"
|
||||
key={i.title}
|
||||
onClick={() => onclickItem(i)}
|
||||
>
|
||||
<span className="bs-ellipsis">{i.title}</span>
|
||||
<span className="time">{i.time}</span>
|
||||
<span className="get">get</span>
|
||||
|
|
Loading…
Reference in New Issue
Block a user