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

View File

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

View File

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

View File

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

View File

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