feat: 课程详情页

This commit is contained in:
mozzie 2023-03-01 15:24:27 +08:00
parent eb593ef590
commit 1f84e3a682
9 changed files with 833 additions and 434 deletions

View File

@ -16,7 +16,8 @@
"@arco-design/web-react": "2.45.0", "@arco-design/web-react": "2.45.0",
"@ricons/fluent": "0.12.0", "@ricons/fluent": "0.12.0",
"@ricons/utils": "0.1.6", "@ricons/utils": "0.1.6",
"dayjs": "1.11.7" "dayjs": "1.11.7",
"identicon": "3.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.27", "@types/react": "^18.0.27",

View File

@ -1,37 +1,55 @@
.player-container { .vjs-button-icon-custom {
height: 720px; cursor: pointer;
> svg {
.vjs-button-icon-custom { transition: all 0.25s;
cursor: pointer; color: rgba(37, 41, 47, 0.2);
> svg { &:hover {
transition: all 0.25s; color: rgba(37, 41, 47, 1);
color: rgba(37, 41, 47, 0.2);
&:hover {
color: rgba(37, 41, 47, 1);
}
} }
} }
}
.tcp-skin .vjs-control-bar { .tcp-skin .vjs-control-bar {
transform: translateY(39px) !important; // transform: translateY(39px) !important;
// background: rgb(0, 0, 0, 0.777) !important; // background: rgb(0, 0, 0, 0.777) !important;
background: rgba(37, 41, 47, 1) !important; background: rgba(37, 41, 47, 0.3) !important;
}
// 进度条颜色
.tcp-skin .vjs-play-progress {
}
.video-js .vjs-progress-control .vjs-progress-holder {
margin: 0 !important;
}
.tcp-skin .vjs-progress-control {
.vjs-load-progress > div {
left: 0 !important;
} }
.video-js .vjs-progress-control .vjs-progress-holder { .video-js .vjs-progress-control .vjs-progress-holder {
margin: 0 !important; margin: 0 !important;
} .video-js .vjs-slider {
.tcp-skin .vjs-progress-control {
.vjs-load-progress > div {
left: 0 !important;
}
.video-js .vjs-progress-control .vjs-progress-holder {
margin: 0 !important; margin: 0 !important;
.video-js .vjs-slider {
margin: 0 !important;
}
} }
} }
} }
// 圆角
.vjs-poster,
.video-js {
border-radius: 10px !important;
video {
border-radius: 10px !important;
}
}
.video-js {
box-shadow: 0 0 100px #c8c8c8 !important;
}
.vjs-control-bar {
border-radius: 0 0 10px 10px !important;
}

View File

@ -6,11 +6,12 @@ interface IVideo {
fileID: string; fileID: string;
appID: string; appID: string;
psign?: string; psign?: string;
className?: string;
} }
/** /**
* demo页面https://tcplayer.vcube.tencent.com/ * demo页面https://tcplayer.vcube.tencent.com/
* *
* Chrome Firefox H5 Webrtc , tcplayer.vx.x.x.min.js TXLivePlayer-x.x.x.min.js * Chrome Firefox H5 Webrtc , tcplayer.vx.x.x.min.js TXLivePlayer-x.x.x.min.js
* 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等比例有黑边
@ -56,15 +57,13 @@ function Player() {
}; };
return ( return (
<div className="player-container"> <video
<video onContextMenu={(e) => e.preventDefault()}
onContextMenu={(e) => e.preventDefault()} id="player"
id="player" style={{ width: "100%", height: "100%", objectFit: "contain" }}
style={{ width: "100%", height: "100%", objectFit: "contain" }} preload="auto"
preload="auto" playsInline
playsInline ></video>
></video>
</div>
); );
} }

View File

@ -1,4 +1,5 @@
import Course from "../view/Course"; import Course from "../view/Course";
import CourseDetail from "../view/CourseDetail";
import Topic from "../view/Topic"; import Topic from "../view/Topic";
export const routerList = [ export const routerList = [
@ -12,4 +13,9 @@ export const routerList = [
element: <Topic />, element: <Topic />,
name: "讨论", name: "讨论",
}, },
{
path: "/course/detail/:id",
element: <CourseDetail />,
name: "课程详情",
},
]; ];

View File

@ -1,6 +1,4 @@
import "./index.less"; import "./index.less";
import Player from "../../components/Player";
import { useMount } from "../../hook";
import { import {
Select, Select,
Message, Message,
@ -15,11 +13,13 @@ import Timeline, { IOnScrollParam } from "./components/Timeline";
import Tab20Regular from "@ricons/fluent/Tab20Regular"; import Tab20Regular from "@ricons/fluent/Tab20Regular";
import Table20Regular from "@ricons/fluent/Table20Regular"; import Table20Regular from "@ricons/fluent/Table20Regular";
import Filter20Regular from "@ricons/fluent/Filter20Regular"; import Filter20Regular from "@ricons/fluent/Filter20Regular";
import { useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { courseTimeListDefault } from "./mock"; import { courseTimeListDefault } from "./mock";
import { Icon } from "@ricons/utils"; import { Icon } from "@ricons/utils";
import { useNavigate } from "react-router-dom";
export default function Index() { export default function Index() {
const navigate = useNavigate();
const thumbnailRef = useRef<HTMLElement | null>(null); const thumbnailRef = useRef<HTMLElement | null>(null);
const scale = useRef<number>(1); // thumbnail / timeline 高度比例 const scale = useRef<number>(1); // thumbnail / timeline 高度比例
const [timeline, setTimeline] = useState({ const [timeline, setTimeline] = useState({
@ -78,6 +78,10 @@ export default function Index() {
setTimeline({ top }); setTimeline({ top });
}; };
const onClickCourseItem = (d: any) => {
navigate(`/course/detail/${d.id}`);
};
return ( return (
<div className="container course"> <div className="container course">
<div className="action-bar"> <div className="action-bar">
@ -127,7 +131,12 @@ export default function Index() {
className={`grid ${actions.find((a) => a.active)?.gridClass}`} className={`grid ${actions.find((a) => a.active)?.gridClass}`}
> >
{item.data.map((d: any) => ( {item.data.map((d: any) => (
<BsCard key={d.time} imgUrl={d.img} title={d.title} /> <BsCard
onClick={() => onClickCourseItem(d)}
key={d.id}
imgUrl={d.img}
title={d.title}
/>
))} ))}
</div> </div>
</section> </section>

View File

@ -2,76 +2,91 @@ import dayjs from "dayjs";
export const courseTimeList = [ export const courseTimeList = [
{ {
id: 1,
title: "这个非常OK啊1", title: "这个非常OK啊1",
time: "1661990400000", time: "1661990400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 2,
title: "这个非常OK啊2", title: "这个非常OK啊2",
time: "1630454400000", time: "1630454400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 3,
title: "这个非常OK啊333", title: "这个非常OK啊333",
time: "1625097600000", time: "1625097600000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 4,
title: "这个非常OK啊444", title: "这个非常OK啊444",
time: "1625284000000", time: "1625284000000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 5,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1598938400000", time: "1598938400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 6,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1528948400000", time: "1528948400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 7,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1538958400000", time: "1538958400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 8,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1538958400100", time: "1538958400100",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 9,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1538958400200", time: "1538958400200",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 10,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1538958400400", time: "1538958400400",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 11,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1538958400500", time: "1538958400500",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 12,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1538958400600", time: "1538958400600",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 13,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1591918400000", time: "1591918400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 15,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1592918400000", time: "1592918400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",
}, },
{ {
id: 16,
title: "这个非常OK啊3", title: "这个非常OK啊3",
time: "1543918400000", time: "1543918400000",
img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp", img: "https://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp",

View File

@ -0,0 +1,57 @@
.course-detail {
padding-top: 60px;
display: grid;
grid-template-columns: 2fr 1fr;
article {
padding: 20px 40px 0 0;
.player-container {
position: relative;
height: 360px;
box-shadow: 0 4px 10px rgb(var(--gray-2));
}
.title {
padding-bottom: 20px;
margin-top: 40px;
color: var(--color-text-2);
font-weight: 500;
line-height: 1.5;
font-size: 22px;
}
}
aside {
padding-left: 20px;
border-left: 2px solid var(--color-border-1);
.toc {
.level-1 {
color: var(--color-text-4);
padding: 30px 0 5px 0;
}
.level-2 {
display: grid;
grid-template-columns: 3fr 1fr 1fr;
color: var(--color-text-2);
.time,
.get {
text-align: right;
}
}
}
}
}
.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

@ -0,0 +1,81 @@
import { useState } from "react";
import Player from "../../components/Player";
import "./index.less";
function CourseDetail() {
const [state, setState] = useState({
videoTopMaskVisible: true,
});
const [toc, setToc] = useState([
{
title: "准备",
level: 1,
},
{
title: "学习 html, css, javascript 前的准备",
level: 2,
time: "3:23",
get: false,
},
{
title: "css 样式的写法",
level: 2,
time: "4:13",
get: false,
},
{
title: "使用CSS",
level: 1,
},
{
title: "使用 css行内样式",
level: 2,
time: "5:55",
get: false,
},
{
title: "使用 css行内样式2",
level: 2,
time: "6:55",
get: false,
},
]);
const onMouseLeaveVideo = () => {
setTimeout(() => {
setState((p) => ({ ...p, videoTopMaskVisible: false }));
}, 3 * 1000);
};
return (
<div className="course-detail container">
<article>
<h4 className="title">01 | SQL查询语句是如何执行的</h4>
<div className="player-container">
<Player />
</div>
</article>
<aside>
<p></p>
<div className="toc">
{toc.map((i) => {
if (i.level === 1) {
return <div className="level-1">{i.title}</div>;
} else if (i.level === 2) {
return (
<div className="level-2">
<span className="bs-ellipsis">{i.title}</span>
<span className="time">{i.time}</span>
<span className="get">get</span>
</div>
);
}
})}
</div>
</aside>
</div>
);
}
export default CourseDetail;

File diff suppressed because it is too large Load Diff