feat: 课程详情页
This commit is contained in:
parent
eb593ef590
commit
1f84e3a682
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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: "课程详情",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
57
apps/web-main/src/view/CourseDetail/index.less
Normal file
57
apps/web-main/src/view/CourseDetail/index.less
Normal 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;
|
||||
}
|
81
apps/web-main/src/view/CourseDetail/index.tsx
Normal file
81
apps/web-main/src/view/CourseDetail/index.tsx
Normal 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;
|
999
pnpm-lock.yaml
999
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user