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",
"@ricons/fluent": "0.12.0",
"@ricons/utils": "0.1.6",
"dayjs": "1.11.7"
"dayjs": "1.11.7",
"identicon": "3.1.1"
},
"devDependencies": {
"@types/react": "^18.0.27",

View File

@ -1,37 +1,55 @@
.player-container {
height: 720px;
.vjs-button-icon-custom {
cursor: pointer;
> svg {
transition: all 0.25s;
color: rgba(37, 41, 47, 0.2);
&:hover {
color: rgba(37, 41, 47, 1);
}
.vjs-button-icon-custom {
cursor: pointer;
> svg {
transition: all 0.25s;
color: rgba(37, 41, 47, 0.2);
&:hover {
color: rgba(37, 41, 47, 1);
}
}
}
.tcp-skin .vjs-control-bar {
transform: translateY(39px) !important;
// background: rgb(0, 0, 0, 0.777) !important;
background: rgba(37, 41, 47, 1) !important;
.tcp-skin .vjs-control-bar {
// transform: translateY(39px) !important;
// background: rgb(0, 0, 0, 0.777) !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 {
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-slider {
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;
appID: string;
psign?: string;
className?: string;
}
/**
* demo页面https://tcplayer.vcube.tencent.com/
*
*
* 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
* <video objectFill /> fill填满变形cover等比例会裁剪, contain等比例有黑边
@ -56,15 +57,13 @@ function Player() {
};
return (
<div className="player-container">
<video
onContextMenu={(e) => e.preventDefault()}
id="player"
style={{ width: "100%", height: "100%", objectFit: "contain" }}
preload="auto"
playsInline
></video>
</div>
<video
onContextMenu={(e) => e.preventDefault()}
id="player"
style={{ width: "100%", height: "100%", objectFit: "contain" }}
preload="auto"
playsInline
></video>
);
}

View File

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

View File

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

View File

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