feat: 课程详情页

This commit is contained in:
mozzie 2023-03-01 17:05:06 +08:00
parent 1f84e3a682
commit d66a606053
6 changed files with 74 additions and 66 deletions

View File

@ -23,11 +23,14 @@ function Nav() {
<span>Backset</span>
</div>
<div className="middle">
{routerList.map((route) => (
<span key={route.path} onClick={() => navigate(route.path)}>
{route.name}
</span>
))}
{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">

View File

@ -37,7 +37,6 @@
}
// 圆角
.vjs-poster,
.video-js {
border-radius: 10px !important;

View File

@ -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,50 +20,44 @@ 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>();
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"]);
useScript(
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
() => {
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", {
fileID: "243791579995468466",
appID: "1500018521",
//私有加密播放需填写 psign psign 即播放器签名签名介绍和生成方式参见链接https://cloud.tencent.com/document/product/266/42436
//psign:'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6MTUwMDAwNTY5NiwiZmlsZUlkIjoiMzcwMTkyNTkyMTI5OTYzNzAxMCIsImN1cnJlbnRUaW1lU3RhbXAiOjE2MjY4NjAxNzYsImV4cGlyZVRpbWVTdGFtcCI6MjYyNjg1OTE3OSwicGNmZyI6InByaXZhdGUiLCJ1cmxBY2Nlc3NJbmZvIjp7InQiOiI5YzkyYjBhYiJ9LCJkcm1MaWNlbnNlSW5mbyI6eyJleHBpcmVUaW1lU3RhbXAiOjI2MjY4NTkxNzksInN0cmljdE1vZGUiOjJ9fQ.Bo5K5ThInc4n8AlzIZQ-CP9a49M2mEr9-zQLH9ocQgI',
});
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

View File

@ -17,5 +17,6 @@ export const routerList = [
path: "/course/detail/:id",
element: <CourseDetail />,
name: "课程详情",
invisible: true,
},
];

View File

@ -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;
}

View File

@ -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>