feat: message loadin
This commit is contained in:
parent
3514c1e395
commit
a807f2c557
|
@ -30,7 +30,8 @@
|
||||||
"@alicloud/openapi-client": "0.4.5",
|
"@alicloud/openapi-client": "0.4.5",
|
||||||
"@alicloud/tea-util": "1.4.5",
|
"@alicloud/tea-util": "1.4.5",
|
||||||
"@alicloud/tea-typescript": "1.8.0",
|
"@alicloud/tea-typescript": "1.8.0",
|
||||||
"object-hash": "3.0.0"
|
"object-hash": "3.0.0",
|
||||||
|
"nanoid": "3.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@midwayjs/cli": "^2.0.0",
|
"@midwayjs/cli": "^2.0.0",
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { Body, Context, Controller, Inject, Post } from '@midwayjs/core';
|
import { Body, Controller, Inject, Post } from '@midwayjs/core';
|
||||||
|
import { Context } from '@midwayjs/koa';
|
||||||
import { BizCode } from '../biz/code';
|
import { BizCode } from '../biz/code';
|
||||||
|
import { webSign } from '../config/base.config';
|
||||||
import { CourseCreateDTO } from '../dto/course.dto';
|
import { CourseCreateDTO } from '../dto/course.dto';
|
||||||
import { ChapterService } from '../service/chapter.service';
|
import { ChapterService } from '../service/chapter.service';
|
||||||
import { CourseService } from '../service/course.service';
|
import { CourseService } from '../service/course.service';
|
||||||
import { GuideService } from '../service/guide.service';
|
import { GuideService } from '../service/guide.service';
|
||||||
|
import { UserService } from '../service/user.service';
|
||||||
|
import { decodeToken } from '../util/encrypt';
|
||||||
|
|
||||||
@Controller('/course')
|
@Controller('/course')
|
||||||
export class CourseController {
|
export class CourseController {
|
||||||
|
@ -19,6 +23,9 @@ export class CourseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
guideService: GuideService;
|
guideService: GuideService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
userService: UserService;
|
||||||
|
|
||||||
@Post('/create')
|
@Post('/create')
|
||||||
async create(@Body() param: CourseCreateDTO) {
|
async create(@Body() param: CourseCreateDTO) {
|
||||||
try {
|
try {
|
||||||
|
@ -50,11 +57,22 @@ export class CourseController {
|
||||||
return { code: BizCode.OK, data: courseList };
|
return { code: BizCode.OK, data: courseList };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/chapter/select')
|
@Post('/detail/select')
|
||||||
async selectChapterByCourseId(@Body() params) {
|
async selectDetailByCourseId(@Body() params) {
|
||||||
const { course_id } = params;
|
const { course_id } = params;
|
||||||
const chapterList = await this.chapterService.select(course_id);
|
try {
|
||||||
const guide = await this.guideService.select(course_id);
|
const token = this.ctx.cookies.get(webSign);
|
||||||
return { code: BizCode.OK, data: { chapterList, guide } };
|
const { user_login } = decodeToken(token);
|
||||||
|
const user = await this.userService.select({ user_login });
|
||||||
|
// 用户订阅鉴权
|
||||||
|
if (!user.user_sub)
|
||||||
|
return { code: BizCode.AUTH, msg: '无权访问订阅课程' };
|
||||||
|
const course = await this.courseService.select({ course_id });
|
||||||
|
const chapterList = await this.chapterService.select(course_id);
|
||||||
|
const guide = await this.guideService.select(course_id);
|
||||||
|
return { code: BizCode.OK, data: { chapterList, guide, course } };
|
||||||
|
} catch (error) {
|
||||||
|
return { code: BizCode.ERROR, msg: '[error] /chapter/select error' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,17 +42,9 @@ export class UserController {
|
||||||
if (!verifyCode) return { code: BizCode.ERROR, msg: '验证码无效' };
|
if (!verifyCode) return { code: BizCode.ERROR, msg: '验证码无效' };
|
||||||
// 查询用户是否存在
|
// 查询用户是否存在
|
||||||
const userExist = await this.userService.select(params);
|
const userExist = await this.userService.select(params);
|
||||||
let payload = {};
|
const payload = userExist?.id
|
||||||
if (userExist?.id) {
|
? userExist
|
||||||
const { user_pass, ...rest } = userExist;
|
: await this.userService.createUser(params);
|
||||||
payload = rest;
|
|
||||||
} else {
|
|
||||||
// 新用户注册
|
|
||||||
const { user_pass, ...rest } = await this.userService.createUser(
|
|
||||||
params
|
|
||||||
);
|
|
||||||
payload = rest;
|
|
||||||
}
|
|
||||||
const token = createToken({ ...payload, hasLogin: true });
|
const token = createToken({ ...payload, hasLogin: true });
|
||||||
this.ctx.cookies.set(webSign, token, {
|
this.ctx.cookies.set(webSign, token, {
|
||||||
expires: new Date(Date.now() + webSignExpired),
|
expires: new Date(Date.now() + webSignExpired),
|
||||||
|
@ -112,7 +104,7 @@ export class UserController {
|
||||||
60
|
60
|
||||||
);
|
);
|
||||||
console.log('redis here', res);
|
console.log('redis here', res);
|
||||||
// await this.smsService.send({ code, phoneNumbers });
|
await this.smsService.send({ code, phoneNumbers });
|
||||||
return { code: BizCode.OK };
|
return { code: BizCode.OK };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity('course')
|
@Entity('course')
|
||||||
export class Course {
|
export class Course {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryColumn()
|
||||||
course_id?: string;
|
course_id?: string;
|
||||||
|
|
||||||
@Column({ unique: true })
|
@Column({ unique: true })
|
||||||
|
|
|
@ -8,9 +8,6 @@ export class User {
|
||||||
@Column({ unique: true })
|
@Column({ unique: true })
|
||||||
user_login?: string;
|
user_login?: string;
|
||||||
|
|
||||||
@Column()
|
|
||||||
user_pass?: string;
|
|
||||||
|
|
||||||
@Column({ default: '' })
|
@Column({ default: '' })
|
||||||
user_email?: string;
|
user_email?: string;
|
||||||
|
|
||||||
|
@ -25,4 +22,7 @@ export class User {
|
||||||
|
|
||||||
@Column({ default: '' })
|
@Column({ default: '' })
|
||||||
user_avatar?: string;
|
user_avatar?: string;
|
||||||
|
|
||||||
|
@Column({ default: false })
|
||||||
|
user_sub?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Context, Inject, Provide } from '@midwayjs/core';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { Course } from '../entity/course.entity';
|
import { Course } from '../entity/course.entity';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
|
||||||
export interface ICourseCreate {
|
export interface ICourseCreate {
|
||||||
course_title: string;
|
course_title: string;
|
||||||
|
@ -18,6 +19,7 @@ export class CourseService {
|
||||||
courseModel: Repository<Course>;
|
courseModel: Repository<Course>;
|
||||||
|
|
||||||
async create(course: Course) {
|
async create(course: Course) {
|
||||||
|
course.course_id = nanoid(13);
|
||||||
const courseCreateRes = await this.courseModel.save(course);
|
const courseCreateRes = await this.courseModel.save(course);
|
||||||
return courseCreateRes.course_id;
|
return courseCreateRes.course_id;
|
||||||
}
|
}
|
||||||
|
@ -25,4 +27,9 @@ export class CourseService {
|
||||||
async selectAll() {
|
async selectAll() {
|
||||||
return await this.courseModel.find({ where: { valid: true } });
|
return await this.courseModel.find({ where: { valid: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async select(course: Course) {
|
||||||
|
const { course_id } = course;
|
||||||
|
return await this.courseModel.findOne({ where: { course_id } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Provide } from '@midwayjs/core';
|
import { Provide } from '@midwayjs/core';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { UserWebAuthDTO } from '../dto/user.dto';
|
|
||||||
import { User } from '../entity/user.entity';
|
import { User } from '../entity/user.entity';
|
||||||
import hash from 'object-hash';
|
import hash from 'object-hash';
|
||||||
|
|
||||||
|
@ -10,7 +9,7 @@ export class UserService {
|
||||||
@InjectEntityModel(User)
|
@InjectEntityModel(User)
|
||||||
userModel: Repository<User>;
|
userModel: Repository<User>;
|
||||||
|
|
||||||
async select(p: UserWebAuthDTO): Promise<any> {
|
async select(p: User): Promise<User> {
|
||||||
const { user_login } = p;
|
const { user_login } = p;
|
||||||
const user = await this.userModel.findOne({
|
const user = await this.userModel.findOne({
|
||||||
where: { user_login },
|
where: { user_login },
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
"@ricons/utils": "0.1.6",
|
"@ricons/utils": "0.1.6",
|
||||||
"dplayer": "1.27.1",
|
"dplayer": "1.27.1",
|
||||||
"highlight.js": "11.7.0",
|
"highlight.js": "11.7.0",
|
||||||
"identicon.js": "2.3.3"
|
"identicon.js": "2.3.3",
|
||||||
|
"react-hot-toast": "2.4.0",
|
||||||
|
"react-spinners": "0.13.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.0.27",
|
"@types/react": "^18.0.27",
|
||||||
|
|
|
@ -4,6 +4,8 @@ import "./assets/base.less";
|
||||||
import Nav from "./components/Nav";
|
import Nav from "./components/Nav";
|
||||||
import { commonRouters, lazyRouters } from "./router";
|
import { commonRouters, lazyRouters } from "./router";
|
||||||
import { Guard } from "./router/Guard";
|
import { Guard } from "./router/Guard";
|
||||||
|
import { Toaster } from "react-hot-toast";
|
||||||
|
import Loading from "./components/Loading";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
|
@ -23,7 +25,7 @@ function App() {
|
||||||
key={router.path}
|
key={router.path}
|
||||||
path={router.path}
|
path={router.path}
|
||||||
element={
|
element={
|
||||||
<Suspense fallback={"loading"}>
|
<Suspense fallback={<Loading />}>
|
||||||
<Guard>{<router.element />}</Guard>
|
<Guard>{<router.element />}</Guard>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
|
@ -32,6 +34,7 @@ function App() {
|
||||||
<Route path="*" element={<span>404</span>} />
|
<Route path="*" element={<span>404</span>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
<Toaster />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// import { message } from "antd";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
baseURL: "",
|
baseURL: "",
|
||||||
|
@ -7,7 +7,7 @@ const config = {
|
||||||
headers: {},
|
headers: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const instance = axios.create(config);
|
export const instance = axios.create(config);
|
||||||
|
|
||||||
instance.interceptors.request.use(
|
instance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
@ -36,8 +36,7 @@ instance.interceptors.response.use(
|
||||||
// Message.error(`接口: ${response.config.url}, 遇到错误`);
|
// Message.error(`接口: ${response.config.url}, 遇到错误`);
|
||||||
break;
|
break;
|
||||||
case 40000:
|
case 40000:
|
||||||
console.error(msg);
|
toast.error(msg);
|
||||||
// console.log('登录')
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// TODO ...
|
// TODO ...
|
|
@ -1,10 +1,10 @@
|
||||||
import { ILoginRequest } from "./dto";
|
import { ILoginRequest } from "./dto";
|
||||||
import R from "./request";
|
import R from "./axios";
|
||||||
|
|
||||||
export const getCourseList = () => R.post("/api/course/select/all");
|
export const getCourseList = () => R.post("/api/course/select/all");
|
||||||
|
|
||||||
export const getChapterGuideById = (course_id: string) =>
|
export const getCourseDetailById = (course_id: string) =>
|
||||||
R.post("/api/course/chapter/select", { course_id });
|
R.post("/api/course/detail/select", { course_id });
|
||||||
|
|
||||||
export const userLogin = (p: ILoginRequest) =>
|
export const userLogin = (p: ILoginRequest) =>
|
||||||
R.post("/api/user/web/auth", { ...p });
|
R.post("/api/user/web/auth", { ...p });
|
||||||
|
|
11
apps/web/src/components/Loading/index.less
Normal file
11
apps/web/src/components/Loading/index.less
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.loading {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1999401021;
|
||||||
|
background: #fff;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
10
apps/web/src/components/Loading/index.tsx
Normal file
10
apps/web/src/components/Loading/index.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import "./index.less";
|
||||||
|
import BarLoader from "react-spinners/BarLoader";
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div className="bs loading fc c">
|
||||||
|
<BarLoader color="#24292f" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -103,7 +103,7 @@ function Nav() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="main">
|
<div className="main">
|
||||||
<div
|
{/* <div
|
||||||
className="bs fc sb"
|
className="bs fc sb"
|
||||||
onClick={() => onClickProfileItem("sub")}
|
onClick={() => onClickProfileItem("sub")}
|
||||||
>
|
>
|
||||||
|
@ -111,7 +111,7 @@ function Nav() {
|
||||||
<Icon color="var(--color-text-3)">
|
<Icon color="var(--color-text-3)">
|
||||||
<PremiumPerson20Regular />
|
<PremiumPerson20Regular />
|
||||||
</Icon>
|
</Icon>
|
||||||
</div>
|
</div> */}
|
||||||
<div className="bs fc sb">
|
<div className="bs fc sb">
|
||||||
<span>设置</span>
|
<span>设置</span>
|
||||||
<Icon color="var(--color-text-3)">
|
<Icon color="var(--color-text-3)">
|
||||||
|
|
|
@ -3,21 +3,50 @@ import FlashOff24Regular from "@ricons/fluent/FlashOff24Regular";
|
||||||
import { Icon } from "@ricons/utils";
|
import { Icon } from "@ricons/utils";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function Result() {
|
interface IProps {
|
||||||
const navigate = useNavigate();
|
code: 403 | 404 | 405;
|
||||||
|
}
|
||||||
return (
|
|
||||||
<div className="container result">
|
export default function Result(props: IProps) {
|
||||||
<Icon size={48} color="var(--color-text-3)">
|
const navigate = useNavigate();
|
||||||
<FlashOff24Regular />
|
const table = {
|
||||||
</Icon>
|
403: (
|
||||||
<div className="mt12">访问被禁止</div>
|
<div className="container result">
|
||||||
<button
|
<Icon size={48} color="var(--color-text-3)">
|
||||||
className="bs btn br3 mt24"
|
<FlashOff24Regular />
|
||||||
onClick={() => navigate("/subscribe")}
|
</Icon>
|
||||||
>
|
<div className="mt12">访问被禁止</div>
|
||||||
订阅课程
|
<button
|
||||||
</button>
|
className="bs btn br3 mt24"
|
||||||
</div>
|
onClick={() => navigate("/subscribe")}
|
||||||
);
|
>
|
||||||
|
订阅课程
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
404: (
|
||||||
|
<div className="container result">
|
||||||
|
<Icon size={48} color="var(--color-text-3)">
|
||||||
|
<FlashOff24Regular />
|
||||||
|
</Icon>
|
||||||
|
<div className="mt12">Ooops! 资源被怪兽吃了~</div>
|
||||||
|
<button className="bs btn br3 mt24" onClick={() => navigate(-1)}>
|
||||||
|
返回
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
405: (
|
||||||
|
<div className="container result">
|
||||||
|
<Icon size={48} color="var(--color-text-3)">
|
||||||
|
<FlashOff24Regular />
|
||||||
|
</Icon>
|
||||||
|
<div className="mt12">请先登录</div>
|
||||||
|
<button className="bs btn br3 mt24" onClick={() => navigate("/login")}>
|
||||||
|
登录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{table[props.code]}</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 挂载
|
* 挂载
|
||||||
*/
|
*/
|
||||||
|
|
44
apps/web/src/hook/useAxios.tsx
Normal file
44
apps/web/src/hook/useAxios.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { instance } from "../api/axios";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
axiosInstance?: AxiosInstance;
|
||||||
|
method?: "get" | "post" | "put" | "delete";
|
||||||
|
url: string;
|
||||||
|
requestConfig?: AxiosRequestConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAxios = (props: IProps) => {
|
||||||
|
const [response, setResponse] = useState<AxiosResponse<any, any>>();
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<unknown>();
|
||||||
|
const {
|
||||||
|
axiosInstance = instance,
|
||||||
|
method = "post",
|
||||||
|
url,
|
||||||
|
requestConfig = {},
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axiosInstance[method](url, {
|
||||||
|
...requestConfig,
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
setResponse(res);
|
||||||
|
} catch (error) {
|
||||||
|
setError(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
return controller.abort();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [response, loading, error];
|
||||||
|
};
|
|
@ -23,6 +23,7 @@ export const Guard = (props: IGuardProps) => {
|
||||||
if (!user) fetchUser();
|
if (!user) fetchUser();
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
if (!sign && needAuth) return <Result />;
|
if (!sign && needAuth) return <Result code={405} />;
|
||||||
|
|
||||||
return props.children;
|
return props.children;
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,6 +70,9 @@ export default function Index() {
|
||||||
setTimeline({ top });
|
setTimeline({ top });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击课程
|
||||||
|
*/
|
||||||
const onClickCourseItem = (d: any) => {
|
const onClickCourseItem = (d: any) => {
|
||||||
navigate(`/course/detail/${d.course_id}`);
|
navigate(`/course/detail/${d.course_id}`);
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
border-right: 1px solid var(--color-border-2);
|
border-right: 1px solid var(--color-border-2);
|
||||||
|
|
||||||
> h2 {
|
> h2 {
|
||||||
|
@ -44,7 +45,6 @@
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 300px;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 60px;
|
top: 60px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
@ -4,57 +4,70 @@ import Guide from "./components/Guide";
|
||||||
import { useMount } from "../../hook";
|
import { useMount } from "../../hook";
|
||||||
import Player from "./components/DPlayer";
|
import Player from "./components/DPlayer";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { getChapterGuideById } from "../../api";
|
import { getCourseDetailById } from "../../api";
|
||||||
import { ms2Time } from "./util";
|
import { ms2Time } from "./util";
|
||||||
import Result from "../../components/Result";
|
import Result from "../../components/Result";
|
||||||
import PlayCircle20Regular from "@ricons/fluent/PlayCircle20Regular";
|
import PlayCircle20Regular from "@ricons/fluent/PlayCircle20Regular";
|
||||||
import BookLetter20Regular from "@ricons/fluent/BookLetter20Regular";
|
|
||||||
import { Icon } from "@ricons/utils";
|
import { Icon } from "@ricons/utils";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
function CourseDetail() {
|
function CourseDetail() {
|
||||||
const { id: course_id = "" } = useParams();
|
const { id: course_id = "" } = useParams();
|
||||||
const [toc, setToc] = useState([]);
|
const [toc, setToc] = useState([]);
|
||||||
|
const [course, setCourse] = useState<any>({});
|
||||||
const [view, setView] = useState<any>(null);
|
const [view, setView] = useState<any>(null);
|
||||||
|
|
||||||
useMount(() => {
|
useMount(() => {
|
||||||
if (!!course_id)
|
if (!!course_id)
|
||||||
getChapterGuideById(course_id).then((res) => {
|
getCourseDetailById(course_id).then((res: any) => {
|
||||||
const { data } = res;
|
const { data, code } = res;
|
||||||
const processToc = data?.chapterList.map((item: any) => {
|
if (!data) setToc([]);
|
||||||
return {
|
if (code === 10000) {
|
||||||
title: item.chapter_title,
|
const processToc = data?.chapterList.map((item: any) => {
|
||||||
level: +item.chapter_level,
|
return {
|
||||||
time: ms2Time(+item.media_time),
|
title: item.chapter_title,
|
||||||
icon: !!item.media_url ? (
|
level: +item.chapter_level,
|
||||||
<Icon size={20}>
|
time: ms2Time(+item.media_time),
|
||||||
<PlayCircle20Regular />
|
icon: !!item.media_url ? (
|
||||||
</Icon>
|
<Icon size={20}>
|
||||||
) : null,
|
<PlayCircle20Regular />
|
||||||
active: false,
|
</Icon>
|
||||||
view: (
|
) : null,
|
||||||
<Player
|
active: false,
|
||||||
video={{ url: item.media_url, pic: item.media_cover_url }}
|
view: (
|
||||||
/>
|
<Player
|
||||||
|
video={{ url: item.media_url, pic: item.media_cover_url }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const composeToc = [
|
||||||
|
{
|
||||||
|
title: "导读",
|
||||||
|
level: 1,
|
||||||
|
time: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "介绍 / 下载",
|
||||||
|
level: 2,
|
||||||
|
time: "",
|
||||||
|
active: true,
|
||||||
|
view: <Guide html={data?.guide.guide_html} />,
|
||||||
|
},
|
||||||
|
...processToc,
|
||||||
|
] as any;
|
||||||
|
const { course } = data;
|
||||||
|
setCourse({
|
||||||
|
...course,
|
||||||
|
course_createtime: dayjs(+course.course_createtime).format(
|
||||||
|
"YYYY-MM-DD"
|
||||||
),
|
),
|
||||||
};
|
});
|
||||||
});
|
setToc(composeToc);
|
||||||
const append = [
|
setView(<Guide html={data?.guide.guide_html} />);
|
||||||
{
|
} else if (code === 40000) {
|
||||||
title: "导读",
|
setView(<Result code={403} />);
|
||||||
level: 1,
|
}
|
||||||
time: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "介绍 / 下载",
|
|
||||||
level: 2,
|
|
||||||
time: "",
|
|
||||||
active: true,
|
|
||||||
view: <Guide html={data?.guide.guide_html} />,
|
|
||||||
},
|
|
||||||
...processToc,
|
|
||||||
];
|
|
||||||
setToc(append as any);
|
|
||||||
setView(<Guide html={data?.guide.guide_html} />);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,45 +75,49 @@ function CourseDetail() {
|
||||||
setToc((t: any) =>
|
setToc((t: any) =>
|
||||||
t.map((p: any) => ({ ...p, active: i.title === p.title }))
|
t.map((p: any) => ({ ...p, active: i.title === p.title }))
|
||||||
);
|
);
|
||||||
setView(i.view ?? <Result />);
|
setView(i.view ?? <Result code={404} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="course-detail">
|
<div className="course-detail">
|
||||||
<aside className="table-of-content">
|
{toc.length > 0 && (
|
||||||
<h2>云顶计划:K线</h2>
|
<aside className="table-of-content">
|
||||||
<div>
|
<h2>{course.course_title}</h2>
|
||||||
<div style={{ color: "var(--color-text-3)" }}>
|
<div>
|
||||||
<span>2023年3月2日</span>
|
<div style={{ color: "var(--color-text-3)" }}>
|
||||||
|
<span>{course.course_createtime}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="toc">
|
||||||
<div className="toc">
|
{toc.map((i: any) => {
|
||||||
{toc.map((i: any) => {
|
if (i.level === 1) {
|
||||||
if (i.level === 1) {
|
return (
|
||||||
return (
|
<div className="level-1" key={i.title}>
|
||||||
<div className="level-1" key={i.title}>
|
{i.title}
|
||||||
{i.title}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (i.level === 2) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`level-2 ${i.active ? "active" : ""}`}
|
|
||||||
key={i.title}
|
|
||||||
onClick={() => onclickItem(i)}
|
|
||||||
>
|
|
||||||
<div className="bs ellipsis fc">
|
|
||||||
{i.icon}
|
|
||||||
<span>{i.title}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="time">{i.time}</span>
|
);
|
||||||
</div>
|
} else if (i.level === 2) {
|
||||||
);
|
return (
|
||||||
}
|
<div
|
||||||
})}
|
className={`level-2 ${i.active ? "active" : ""}`}
|
||||||
</div>
|
key={i.title}
|
||||||
</aside>
|
onClick={() => onclickItem(i)}
|
||||||
<div className="content">{view}</div>
|
>
|
||||||
|
<div className="bs ellipsis fc">
|
||||||
|
{i.icon}
|
||||||
|
<span>{i.title}</span>
|
||||||
|
</div>
|
||||||
|
<span className="time">{i.time}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
)}
|
||||||
|
<div className="content" style={{ left: toc.length > 0 ? "300px" : 0 }}>
|
||||||
|
{view}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
808
pnpm-lock.yaml
808
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user