feat: 课程详情页
This commit is contained in:
parent
1f84e3a682
commit
d66a606053
|
@ -23,11 +23,14 @@ function Nav() {
|
||||||
<span>Backset</span>
|
<span>Backset</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="middle">
|
<div className="middle">
|
||||||
{routerList.map((route) => (
|
{routerList.map(
|
||||||
<span key={route.path} onClick={() => navigate(route.path)}>
|
(route) =>
|
||||||
{route.name}
|
!route.invisible && (
|
||||||
</span>
|
<span key={route.path} onClick={() => navigate(route.path)}>
|
||||||
))}
|
{route.name}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
<InputSearch allowClear placeholder="搜索" style={{ width: 150 }} />
|
<InputSearch allowClear placeholder="搜索" style={{ width: 150 }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="end">
|
<div className="end">
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// 圆角
|
// 圆角
|
||||||
|
|
||||||
.vjs-poster,
|
.vjs-poster,
|
||||||
.video-js {
|
.video-js {
|
||||||
border-radius: 10px !important;
|
border-radius: 10px !important;
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import { useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useLink, useMount, useScript } from "../../hook";
|
import { useLink, useMount, useScript } from "../../hook";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
interface IVideo {
|
export interface IVideo {
|
||||||
fileID: string;
|
fileID: string;
|
||||||
appID: string;
|
appID: string;
|
||||||
psign?: string;
|
psign?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
video: IVideo | null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* demo页面:https://tcplayer.vcube.tencent.com/
|
* demo页面:https://tcplayer.vcube.tencent.com/
|
||||||
*
|
*
|
||||||
|
@ -16,50 +20,44 @@ interface IVideo {
|
||||||
* 有些浏览器环境不支持 Webrtc,播放器会将 Webrtc 流地址自动转换为 HLS 格式地址,因此快直播场景同样需要引入hls.min.x.xx.xm.js。
|
* 有些浏览器环境不支持 Webrtc,播放器会将 Webrtc 流地址自动转换为 HLS 格式地址,因此快直播场景同样需要引入hls.min.x.xx.xm.js。
|
||||||
* <video objectFill /> fill填满变形,cover等比例会裁剪, contain等比例有黑边
|
* <video objectFill /> fill填满变形,cover等比例会裁剪, contain等比例有黑边
|
||||||
*/
|
*/
|
||||||
function Player() {
|
function Player(props: IProps) {
|
||||||
const playerRef = useRef<any>();
|
const playerRef = useRef<any>();
|
||||||
|
|
||||||
|
const initPlayer = (video: IVideo) => {
|
||||||
|
const TCPlayer = (window as any).TCPlayer;
|
||||||
|
|
||||||
|
// 重写播放按钮
|
||||||
|
const Button = TCPlayer.getComponent("Button");
|
||||||
|
const BigPlayButton = TCPlayer.getComponent("BigPlayButton");
|
||||||
|
BigPlayButton.prototype.createEl = function () {
|
||||||
|
const el = Button.prototype.createEl.call(this);
|
||||||
|
const _html =
|
||||||
|
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448s448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372s372 166.6 372 372s-166.6 372-372 372z" fill-opacity=".8" fill="currentColor"></path><path d="M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372s372-166.6 372-372s-166.6-372-372-372zm164.1 378.2L457.7 677.1a8.02 8.02 0 0 1-12.7-6.5V353a8 8 0 0 1 12.7-6.5l218.4 158.8a7.9 7.9 0 0 1 0 12.9z" fill-opacity=".1" fill="currentColor"></path><path d="M676.1 505.3L457.7 346.5A8 8 0 0 0 445 353v317.6a8.02 8.02 0 0 0 12.7 6.5l218.4-158.9a7.9 7.9 0 0 0 0-12.9z" fill-opacity=".8" fill="currentColor"></path></svg>';
|
||||||
|
el.appendChild(
|
||||||
|
TCPlayer.dom.createEl("div", {
|
||||||
|
className: "vjs-button-icon-custom",
|
||||||
|
innerHTML: _html,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
playerRef.current = TCPlayer("player_html5_api", video);
|
||||||
|
};
|
||||||
|
|
||||||
useLink(["/player/tcplayer_pure.min.css"]);
|
useLink(["/player/tcplayer_pure.min.css"]);
|
||||||
useScript(
|
useScript(
|
||||||
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
|
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
|
||||||
() => {
|
() => {
|
||||||
const TCPlayer = (window as any).TCPlayer;
|
console.log("执行了", playerRef.current);
|
||||||
|
playerRef.current = null;
|
||||||
// 重写播放按钮
|
if (props.video) initPlayer(props.video);
|
||||||
const Button = TCPlayer.getComponent("Button");
|
|
||||||
const BigPlayButton = TCPlayer.getComponent("BigPlayButton");
|
|
||||||
BigPlayButton.prototype.createEl = function () {
|
|
||||||
const el = Button.prototype.createEl.call(this);
|
|
||||||
const _html =
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448s448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372s372 166.6 372 372s-166.6 372-372 372z" fill-opacity=".8" fill="currentColor"></path><path d="M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372s372-166.6 372-372s-166.6-372-372-372zm164.1 378.2L457.7 677.1a8.02 8.02 0 0 1-12.7-6.5V353a8 8 0 0 1 12.7-6.5l218.4 158.8a7.9 7.9 0 0 1 0 12.9z" fill-opacity=".1" fill="currentColor"></path><path d="M676.1 505.3L457.7 346.5A8 8 0 0 0 445 353v317.6a8.02 8.02 0 0 0 12.7 6.5l218.4-158.9a7.9 7.9 0 0 0 0-12.9z" fill-opacity=".8" fill="currentColor"></path></svg>';
|
|
||||||
el.appendChild(
|
|
||||||
TCPlayer.dom.createEl("div", {
|
|
||||||
className: "vjs-button-icon-custom",
|
|
||||||
innerHTML: _html,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
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',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* 切换视频
|
|
||||||
*/
|
|
||||||
const changeVideo = (video: IVideo) => {
|
|
||||||
playerRef.current!.loadVideoByID({ ...video });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
id="player"
|
id="player_html5_api"
|
||||||
style={{ width: "100%", height: "100%", objectFit: "contain" }}
|
style={{ width: "100%", height: "100%", objectFit: "contain" }}
|
||||||
preload="auto"
|
preload="auto"
|
||||||
playsInline
|
playsInline
|
||||||
|
|
|
@ -17,5 +17,6 @@ export const routerList = [
|
||||||
path: "/course/detail/:id",
|
path: "/course/detail/:id",
|
||||||
element: <CourseDetail />,
|
element: <CourseDetail />,
|
||||||
name: "课程详情",
|
name: "课程详情",
|
||||||
|
invisible: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -9,6 +9,15 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 360px;
|
height: 360px;
|
||||||
box-shadow: 0 4px 10px rgb(var(--gray-2));
|
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 {
|
.title {
|
||||||
padding-bottom: 20px;
|
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 { useState } from "react";
|
||||||
import Player from "../../components/Player";
|
import Player, { IVideo } from "../../components/Player";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
function CourseDetail() {
|
function CourseDetail() {
|
||||||
const [state, setState] = useState({
|
|
||||||
videoTopMaskVisible: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [toc, setToc] = useState([
|
const [toc, setToc] = useState([
|
||||||
{
|
{
|
||||||
title: "准备",
|
title: "准备",
|
||||||
|
@ -17,12 +13,20 @@ function CourseDetail() {
|
||||||
level: 2,
|
level: 2,
|
||||||
time: "3:23",
|
time: "3:23",
|
||||||
get: false,
|
get: false,
|
||||||
|
video: {
|
||||||
|
fileID: "243791579995468466",
|
||||||
|
appID: "1500018521",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "css 样式的写法",
|
title: "css 样式的写法",
|
||||||
level: 2,
|
level: 2,
|
||||||
time: "4:13",
|
time: "4:13",
|
||||||
get: false,
|
get: false,
|
||||||
|
video: {
|
||||||
|
fileID: "243791580097740418",
|
||||||
|
appID: "1500018521",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "使用CSS",
|
title: "使用CSS",
|
||||||
|
@ -42,10 +46,10 @@ function CourseDetail() {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onMouseLeaveVideo = () => {
|
const [video, setVideo] = useState<IVideo | null>(null);
|
||||||
setTimeout(() => {
|
|
||||||
setState((p) => ({ ...p, videoTopMaskVisible: false }));
|
const onclickItem = (i: any) => {
|
||||||
}, 3 * 1000);
|
setVideo(i.video);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -53,7 +57,7 @@ function CourseDetail() {
|
||||||
<article>
|
<article>
|
||||||
<h4 className="title">01 | 基础架构:一条SQL查询语句是如何执行的?</h4>
|
<h4 className="title">01 | 基础架构:一条SQL查询语句是如何执行的?</h4>
|
||||||
<div className="player-container">
|
<div className="player-container">
|
||||||
<Player />
|
<Player video={video} />
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<aside>
|
<aside>
|
||||||
|
@ -61,10 +65,18 @@ function CourseDetail() {
|
||||||
<div className="toc">
|
<div className="toc">
|
||||||
{toc.map((i) => {
|
{toc.map((i) => {
|
||||||
if (i.level === 1) {
|
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) {
|
} else if (i.level === 2) {
|
||||||
return (
|
return (
|
||||||
<div className="level-2">
|
<div
|
||||||
|
className="level-2"
|
||||||
|
key={i.title}
|
||||||
|
onClick={() => onclickItem(i)}
|
||||||
|
>
|
||||||
<span className="bs-ellipsis">{i.title}</span>
|
<span className="bs-ellipsis">{i.title}</span>
|
||||||
<span className="time">{i.time}</span>
|
<span className="time">{i.time}</span>
|
||||||
<span className="get">get</span>
|
<span className="get">get</span>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user