feat: 课程详情页
This commit is contained in:
parent
eb593ef590
commit
1f84e3a682
|
@ -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",
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
.player-container {
|
|
||||||
height: 720px;
|
|
||||||
|
|
||||||
.vjs-button-icon-custom {
|
.vjs-button-icon-custom {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
> svg {
|
> svg {
|
||||||
|
@ -13,9 +10,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
.video-js .vjs-progress-control .vjs-progress-holder {
|
||||||
|
@ -34,4 +35,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 圆角
|
||||||
|
|
||||||
|
.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,6 +6,7 @@ interface IVideo {
|
||||||
fileID: string;
|
fileID: string;
|
||||||
appID: string;
|
appID: string;
|
||||||
psign?: string;
|
psign?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +57,6 @@ function Player() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="player-container">
|
|
||||||
<video
|
<video
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
id="player"
|
id="player"
|
||||||
|
@ -64,7 +64,6 @@ function Player() {
|
||||||
preload="auto"
|
preload="auto"
|
||||||
playsInline
|
playsInline
|
||||||
></video>
|
></video>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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: "课程详情",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
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