diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx
index 57f484d..6522413 100644
--- a/apps/admin/src/App.tsx
+++ b/apps/admin/src/App.tsx
@@ -1,10 +1,9 @@
-import { Navigate, Route, Routes } from "react-router-dom";
+import { Route, Routes } from "react-router-dom";
import "./assets/less/common.less";
import Layout from "./layout";
import Login from "./view/Login";
import { ConfigProvider as AntDesignConfigProvider } from "antd";
-import zhCN from 'antd/locale/zh_CN';
-
+import zhCN from "antd/locale/zh_CN";
function App() {
return (
diff --git a/apps/admin/src/api/index.ts b/apps/admin/src/api/index.ts
index 870baff..26c45eb 100644
--- a/apps/admin/src/api/index.ts
+++ b/apps/admin/src/api/index.ts
@@ -6,4 +6,4 @@ import { IgetVodRequest } from "./dto";
* 腾讯vod媒资
*/
export const getVod = (p: IgetVodRequest) =>
- R.post("/api/vod", { ...p }).then((d: any) => P.getVod(d.data));
+ R.post("/api/vod/media/select", { ...p }).then((d: any) => P.getVod(d.data));
diff --git a/apps/admin/src/api/request.ts b/apps/admin/src/api/request.ts
index d44662b..94f30af 100644
--- a/apps/admin/src/api/request.ts
+++ b/apps/admin/src/api/request.ts
@@ -11,6 +11,7 @@ const instance = axios.create(config);
instance.interceptors.request.use(
(config) => {
+ console.log(config)
return config;
},
(error) => {
diff --git a/apps/admin/src/layout/index.less b/apps/admin/src/layout/index.less
index 7719caa..0854f3f 100644
--- a/apps/admin/src/layout/index.less
+++ b/apps/admin/src/layout/index.less
@@ -1,11 +1,11 @@
.container {
height: 100%;
background: #f1f1f1;
- header {
+ > header {
padding: 0 24px;
display: flex;
align-items: center;
- position: fixed;
+ position: absolute;
top: 0;
left: 0;
right: 0;
@@ -14,7 +14,7 @@
z-index: 19;
.logo {
- width: 320px;
+ width: 176px;
display: flex;
align-items: center;
color: #fff;
@@ -28,15 +28,18 @@
}
}
}
- aside {
- position: fixed;
+ > aside {
+ position: absolute;
left: 0;
top: 46px;
bottom: 0;
width: 200px;
+ > aside {
+ height: 100%;
+ }
}
- main {
- position: fixed;
+ > main {
+ position: absolute;
left: 200px;
right: 0;
top: 46px;
diff --git a/apps/admin/src/layout/index.tsx b/apps/admin/src/layout/index.tsx
index b6a4a06..661e138 100644
--- a/apps/admin/src/layout/index.tsx
+++ b/apps/admin/src/layout/index.tsx
@@ -68,6 +68,7 @@ const Index: React.FC = () => {
theme="dark"
mode="horizontal"
defaultSelectedKeys={["/"]}
+ style={{ flex: 1 }}
items={navMenus}
onClick={onClickNavMenuItem}
/>
diff --git a/apps/admin/src/store/media.tsx b/apps/admin/src/store/media.tsx
new file mode 100644
index 0000000..e5fe691
--- /dev/null
+++ b/apps/admin/src/store/media.tsx
@@ -0,0 +1,24 @@
+import { create } from "zustand";
+import { getVod } from "../api";
+
+export const useMediaStore = create((set) => ({
+ list: [],
+ listFilter: [],
+ getListFilter: (state: any) => {
+ return state.list.length === 0
+ ? getVod({ offset: 0, limit: 5000 }).then((res: any) =>
+ set({ list: res.mediaList, listFilter: res.mediaList })
+ )
+ : state.list;
+ },
+ setList: (newState: any) =>
+ set(() => ({ list: newState, listFilter: newState })),
+ filterList: (keyword: string) =>
+ set((state: any) => ({
+ listFilter: !keyword
+ ? state.list
+ : state.list.filter(
+ (i: any) => i.name.toUpperCase().indexOf(keyword.toUpperCase()) > -1
+ ),
+ })),
+}));
diff --git a/apps/admin/src/view/Course/Create/Appendix/index.tsx b/apps/admin/src/view/Course/Create/Appendix/index.tsx
deleted file mode 100644
index 2501f97..0000000
--- a/apps/admin/src/view/Course/Create/Appendix/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-interface IProps {
- onChange?: Function;
- styles?: React.CSSProperties;
-}
-
-const Appendix = (props: IProps) => {
- return
附件
;
-};
-
-export default Appendix;
diff --git a/apps/admin/src/view/Course/Create/BasicForm/index.less b/apps/admin/src/view/Course/Create/BasicForm/index.less
index 49741eb..de5f900 100644
--- a/apps/admin/src/view/Course/Create/BasicForm/index.less
+++ b/apps/admin/src/view/Course/Create/BasicForm/index.less
@@ -14,7 +14,6 @@
color: #fff;
text-align: center;
margin: 0;
- letter-spacing: 2px;
&.title {
background: rgba(0, 0, 0, 0.7);
font-size: 22px;
diff --git a/apps/admin/src/view/Course/Create/BasicForm/index.tsx b/apps/admin/src/view/Course/Create/BasicForm/index.tsx
index 7892c31..492c276 100644
--- a/apps/admin/src/view/Course/Create/BasicForm/index.tsx
+++ b/apps/admin/src/view/Course/Create/BasicForm/index.tsx
@@ -23,7 +23,7 @@ const BasicForm = (props: IProps) => {
const coverDragger: UploadProps = {
name: "file",
multiple: true,
- action: "/api/upload",
+ action: "/api/vod/course/cover/upload",
onChange(info) {
const { status } = info.file;
if (status !== "uploading") console.log(info.file, info.fileList);
diff --git a/apps/admin/src/view/Course/Create/Chatpter/index.tsx b/apps/admin/src/view/Course/Create/Chatpter/index.tsx
index 27f24f8..33f90c2 100644
--- a/apps/admin/src/view/Course/Create/Chatpter/index.tsx
+++ b/apps/admin/src/view/Course/Create/Chatpter/index.tsx
@@ -40,7 +40,7 @@ const Chatpter = (props: IProps) => {
};
useEffect(() => {
- console.log(chapterList);
+ if (props.onChange) props.onChange(chapterList);
}, [chapterList]);
return (
diff --git a/apps/admin/src/view/Course/Create/Guide/index.less b/apps/admin/src/view/Course/Create/Guide/index.less
new file mode 100644
index 0000000..da01de8
--- /dev/null
+++ b/apps/admin/src/view/Course/Create/Guide/index.less
@@ -0,0 +1,2 @@
+.vditor {
+}
diff --git a/apps/admin/src/view/Course/Create/Guide/index.tsx b/apps/admin/src/view/Course/Create/Guide/index.tsx
new file mode 100644
index 0000000..e4f0029
--- /dev/null
+++ b/apps/admin/src/view/Course/Create/Guide/index.tsx
@@ -0,0 +1,134 @@
+import "vditor/dist/index.css";
+import Vditor from "vditor";
+import { useEffect, useRef, useState } from "react";
+import "./index.less";
+import { message } from "antd";
+
+interface IProps {
+ onChange?: Function;
+ styles?: React.CSSProperties;
+}
+
+const emoji = {
+ "+1": "👍",
+ "-1": "👎",
+ confused: "😕",
+ eyes: "👀️",
+ heart: "❤️",
+ rocket: "🚀️",
+ smile: "😄",
+ tada: "🎉️",
+};
+
+const toolbar = [
+ "emoji",
+ "headings",
+ "bold",
+ "italic",
+ "strike",
+ "link",
+ "|",
+ "list",
+ "ordered-list",
+ "check",
+ "outdent",
+ "indent",
+ "|",
+ "quote",
+ "line",
+ "code",
+ "inline-code",
+ "insert-before",
+ "insert-after",
+ "|",
+ "upload",
+ // "record",
+ "table",
+ "|",
+ "undo",
+ "redo",
+ "|",
+ "fullscreen",
+ "edit-mode",
+ {
+ name: "more",
+ toolbar: [
+ "both",
+ "code-theme",
+ "content-theme",
+ "export",
+ "outline",
+ "preview",
+ "devtools",
+ "info",
+ "help",
+ ],
+ },
+];
+
+const Guide = (props: IProps) => {
+ const vditorRef = useRef();
+
+ const submitTool = {
+ name: "submit",
+ tipPosition: "s",
+ tip: "提交",
+ className: "right",
+ icon: '',
+ click() {
+ const value = vditorRef.current?.getValue();
+ const html = vditorRef.current?.getHTML();
+ if (props.onChange) props.onChange({ value, html });
+ },
+ };
+
+ useEffect(() => {
+ vditorRef.current = new Vditor("vditor", {
+ height: 600,
+ toolbar: [...toolbar, "|", submitTool],
+ hint: {
+ delay: 200,
+ emoji,
+ },
+ preview: { actions: ["desktop", "mobile"] },
+ upload: {
+ accept: "image/*",
+ url: "/api/vod/oss/image/upload",
+ multiple: false,
+ success(_, res) {
+ const { code, data, msg } = JSON.parse(res);
+ console.log(code, data, msg);
+ if (code === 10000) {
+ message.success("上传成功");
+ const { name, url } = data;
+ vditorRef.current?.insertValue(`![${name}](${url})`);
+ } else {
+ message.error(msg);
+ }
+ },
+ },
+ counter: {
+ enable: true,
+ },
+ after: () => {
+ console.log("[info] vditor init success...");
+ },
+ });
+ }, []);
+
+ // useEffect(() => {
+ // if (props.onChange)
+ // props.onChange({
+ // value: vditorRef.current?.getValue(),
+ // html: vditorRef.current?.getHTML(),
+ // });
+ // }, [vditorRef.current?.getValue()]);
+
+ return (
+
+ );
+};
+
+export default Guide;
diff --git a/apps/admin/src/view/Course/Create/index.less b/apps/admin/src/view/Course/Create/index.less
index ac3c6c1..df07870 100644
--- a/apps/admin/src/view/Course/Create/index.less
+++ b/apps/admin/src/view/Course/Create/index.less
@@ -5,3 +5,10 @@
flex: 1;
}
}
+
+.drawer-media-item {
+ border: 1px solid rgba(5, 5, 5, 0.06);
+ border-radius: 6px;
+ padding: 8px;
+ margin-bottom: 8px;
+}
diff --git a/apps/admin/src/view/Course/Create/index.tsx b/apps/admin/src/view/Course/Create/index.tsx
index bd31d38..11d8ca6 100644
--- a/apps/admin/src/view/Course/Create/index.tsx
+++ b/apps/admin/src/view/Course/Create/index.tsx
@@ -1,25 +1,18 @@
-import {
- Button,
- Card,
- Col,
- Form,
- Input,
- InputNumber,
- message,
- Row,
- Space,
- Steps,
-} from "antd";
-import { useState } from "react";
-import Appendix from "./Appendix";
+import { Button, Card, Drawer, Input, message, Steps, Typography } from "antd";
+import { useEffect, useState } from "react";
+import Guide from "./Guide";
import BasicForm from "./BasicForm";
import Chatpter from "./Chatpter";
import "./index.less";
+import { useMediaStore } from "../../../store/media";
+const { Text } = Typography;
const CourseCreate = () => {
const [current, setCurrent] = useState(0);
const [course, setCourse] = useState({
basicInfo: {},
+ chapters: [],
+ guide: {},
});
const onBasicFormChange = (form: any) =>
@@ -33,12 +26,51 @@ const CourseCreate = () => {
title: "章节",
},
{
- title: "附件",
+ title: "导读",
},
];
+ const [createBtnValid, setCreateBtnValid] = useState(true);
+
const items = steps.map((item) => ({ key: item.title, title: item.title }));
+ const onChapterChange = (chapters: any) =>
+ setCourse((p) => ({ ...p, chapters }));
+
+ const onGuideChange = ({ value, html }: { value: string; html: string }) => {
+ setCourse((p) => ({ ...p, guide: { value, html } }));
+ };
+
+ const onClickCreate = () => {
+ console.log(course);
+ message.info("撒打算");
+ };
+
+ useEffect(() => {
+ const basicValid = !Object.values(course.basicInfo).includes("");
+ const chaptersValid = course.chapters.length !== 0;
+ const guideValid = !Object.values(course.guide).includes("");
+ setCreateBtnValid(basicValid && chaptersValid && guideValid);
+ }, [course]);
+
+ const mediaList = useMediaStore((s: any) => s.listFilter);
+
+ const mediaListFilter = useMediaStore((s: any) => s.filterList);
+
+ const [open, setOpen] = useState(false);
+
+ const showDrawer = () => {
+ setOpen(true);
+ };
+
+ const onClose = () => {
+ setOpen(false);
+ };
+
+ const onSearchChange = (e: any) => {
+ mediaListFilter(e.target.value);
+ };
+
return (
@@ -48,10 +80,25 @@ const CourseCreate = () => {
onChange={onBasicFormChange}
styles={{ display: current === 0 ? "block" : "none" }}
/>
-
-
+
+
+ {current === 1 && (
+
+ )}
{current > 0 && (
+
+
+
+ {mediaList.map((media: any) => {
+ return (
+
+
{media.name}
+
{media.key}
+
+ );
+ })}
+
+
);
};
diff --git a/apps/admin/src/view/Course/Library/index.tsx b/apps/admin/src/view/Course/Library/index.tsx
index 16857ae..9c5e338 100644
--- a/apps/admin/src/view/Course/Library/index.tsx
+++ b/apps/admin/src/view/Course/Library/index.tsx
@@ -15,16 +15,15 @@ import {
Tag,
} from "antd";
import dayjs from "dayjs";
-import { useState } from "react";
+import { useContext, useState } from "react";
import { getVod } from "../../../api";
import { useMount } from "../../../hooks";
+import { useMediaStore } from "../../../store/media";
const { Paragraph, Text } = Typography;
const { Search } = Input;
const Library = () => {
const [dataSource, setDataSource] = useState([]);
- const [total, setTotal] = useState("");
- const [loading, setLoading] = useState(false);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const colors = [
@@ -144,16 +143,16 @@ const Library = () => {
}));
};
+ const setMediaList = useMediaStore((s: any) => s.setList);
+
/**
* 最大每次5000条数据,估计这辈子也不可能了
*/
const fetchVod = () => {
- setLoading(true);
getVod({ offset: 0, limit: 5000 }).then((process: any) => {
const { mediaList, total } = process;
setDataSource(computeMediaList(mediaList));
- setTotal(total);
- setLoading(false);
+ setMediaList(computeMediaList(mediaList));
});
};
@@ -176,7 +175,7 @@ const Library = () => {
- 共计 {total} 条媒体资源
+ 共计 {dataSource.length} 条媒体资源
fetchVod()}
@@ -193,7 +192,6 @@ const Library = () => {
diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts
index dc27639..8633f5a 100644
--- a/apps/admin/vite.config.ts
+++ b/apps/admin/vite.config.ts
@@ -9,9 +9,9 @@ export default defineConfig({
port: 5174,
proxy: {
"/api": {
- target: "http://127.0.0.1:7001/api",
- changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
+ target: "http://127.0.0.1:7001/api/v1",
+ changeOrigin: true,
},
},
},
diff --git a/apps/server/.env b/apps/server/.env
index 20609f2..0baf97b 100644
--- a/apps/server/.env
+++ b/apps/server/.env
@@ -4,4 +4,5 @@ OSS_SECRET=12345
SECRET_ID=AKID534tZ7OvYzb2KQMwLYaVEl5FBwUtQWbU
SECRET_KEY=q9HD6lQimeLp9IH5h7NRJzUpNjwxmPq5
-SUBAPPID=1500018521
\ No newline at end of file
+SUBAPPID=1500018521
+SUBAPPID_OSS=1500018944
\ No newline at end of file
diff --git a/apps/server/src/config/config.default.ts b/apps/server/src/config/config.default.ts
index f83a5ec..b7f3559 100644
--- a/apps/server/src/config/config.default.ts
+++ b/apps/server/src/config/config.default.ts
@@ -35,6 +35,7 @@ export default (appInfo: MidwayAppInfo): MidwayConfig => {
keys: '1676532942172_2248',
koa: {
port: 7001,
+ globalPrefix: '/api/v1',
},
upload: {
// mode: UploadMode, 默认为file,即上传到服务器临时目录,可以配置为 stream
diff --git a/apps/server/src/controller/api.controller.ts b/apps/server/src/controller/api.controller.ts
index bebd72c..5063c5f 100644
--- a/apps/server/src/controller/api.controller.ts
+++ b/apps/server/src/controller/api.controller.ts
@@ -1,13 +1,8 @@
-import { Inject, Controller, Post, Body, Files, Fields } from '@midwayjs/core';
+import { Inject, Controller, Post, Body } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { Validate } from '@midwayjs/validate';
import { UserDTO } from '../dto/user.dto';
-import * as tencentcloud from 'tencentcloud-sdk-nodejs';
-import { VodSearchDTO } from '../dto/vod.dto';
-import { BizCode } from '../biz/code';
-import { uploadImagePromise } from '../util/vod';
-
-@Controller('/api')
+@Controller('/')
export class APIController {
@Inject()
ctx: Context;
@@ -19,51 +14,4 @@ export class APIController {
async getUser(@Body() user: UserDTO) {
return { success: true, message: 'OK', data: user };
}
-
- /**
- * 腾讯媒资管理查询
- * API调用demo: https://console.cloud.tencent.com/api/explorer?Product=vod&Version=2018-07-17&Action=SearchMedia
- * 最大返回5000条数据
- */
- @Post('/vod')
- async vod(@Body() param: VodSearchDTO) {
- const { offset: Offset = 0, limit: Limit = 5000 } = param;
- const VodClient = tencentcloud.vod.v20180717.Client;
- const clientConfig = {
- credential: {
- secretId: process.env.SECRET_ID,
- secretKey: process.env.SECRET_KEY,
- },
- profile: {
- httpProfile: {
- endpoint: 'vod.tencentcloudapi.com',
- },
- },
- };
- const client = new VodClient(clientConfig);
- const params = {
- SubAppId: +process.env.SUBAPPID,
- Categories: ['Video'],
- Offset,
- Limit,
- };
- return await client.SearchMedia(params).then(
- data => ({ code: BizCode.OK, data }),
- err => {
- this.ctx.logger.error(err);
- throw new Error(err);
- }
- );
- }
-
- @Post('/upload')
- async upload(@Files() files, @Fields() fields) {
- const tmpPath = files[0].data;
- return await uploadImagePromise(tmpPath)
- .then(data => ({ code: BizCode.OK, data }))
- .catch(err => {
- this.ctx.logger.error(err);
- throw new Error(err);
- });
- }
}
diff --git a/apps/server/src/controller/vod.controller.ts b/apps/server/src/controller/vod.controller.ts
new file mode 100644
index 0000000..768855c
--- /dev/null
+++ b/apps/server/src/controller/vod.controller.ts
@@ -0,0 +1,79 @@
+import { Inject, Post, Body, Files, Controller } from '@midwayjs/core';
+import { Context } from 'koa';
+import { BizCode } from '../biz/code';
+import * as tencentcloud from 'tencentcloud-sdk-nodejs';
+import { VodSearchDTO } from '../dto/vod.dto';
+import { uploadImagePromise } from '../util/vod';
+import { IVodResponse } from '../interface';
+
+@Controller('/vod')
+export class VodController {
+ @Inject()
+ ctx: Context;
+ /**
+ * 腾讯媒资管理查询
+ * API调用demo: https://console.cloud.tencent.com/api/explorer?Product=vod&Version=2018-07-17&Action=SearchMedia
+ * 最大返回5000条数据
+ */
+ @Post('/media/select')
+ async getCourseMediaList(@Body() param: VodSearchDTO) {
+ const { offset: Offset = 0, limit: Limit = 5000 } = param;
+ const VodClient = tencentcloud.vod.v20180717.Client;
+ const clientConfig = {
+ credential: {
+ secretId: process.env.SECRET_ID,
+ secretKey: process.env.SECRET_KEY,
+ },
+ profile: {
+ httpProfile: {
+ endpoint: 'vod.tencentcloudapi.com',
+ },
+ },
+ };
+ const client = new VodClient(clientConfig);
+ const params = {
+ SubAppId: +process.env.SUBAPPID,
+ Categories: ['Video'],
+ Offset,
+ Limit,
+ };
+ return await client.SearchMedia(params).then(
+ data => ({ code: BizCode.OK, data }),
+ err => {
+ this.ctx.logger.error(err);
+ throw new Error(err);
+ }
+ );
+ }
+
+ /**
+ * 后台管理:上传课程封面图
+ */
+ @Post('/course/cover/upload')
+ async uploadCourseCoverImage(@Files() files) {
+ const tmpPath = files[0].data;
+ return await uploadImagePromise(tmpPath, +process.env.SUBAPPID)
+ .then((data: IVodResponse) => ({ code: BizCode.OK, data }))
+ .catch(err => {
+ this.ctx.logger.error(err);
+ throw new Error(err);
+ });
+ }
+
+ /**
+ * 图片单个上传
+ */
+ @Post('/oss/image/upload')
+ async ossUploadImage(@Files() files) {
+ const tmpPath = files[0].data;
+ return await uploadImagePromise(tmpPath, +process.env.SUBAPPID_OSS)
+ .then((data: IVodResponse) => ({
+ code: BizCode.OK,
+ data: { name: data.FileId, url: data.MediaUrl },
+ }))
+ .catch(err => {
+ this.ctx.logger.error(err);
+ return { code: BizCode.ERROR, msg: err };
+ });
+ }
+}
diff --git a/apps/server/src/filter/notfound.filter.ts b/apps/server/src/filter/notfound.filter.ts
index 26b9d3c..8ef07ce 100644
--- a/apps/server/src/filter/notfound.filter.ts
+++ b/apps/server/src/filter/notfound.filter.ts
@@ -1,11 +1,12 @@
import { Catch, httpError, MidwayHttpError } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
+import { BizCode } from '../biz/code';
@Catch(httpError.NotFoundError)
export class NotFoundFilter {
async catch(err: MidwayHttpError, ctx: Context) {
// 404 错误会到这里
// ctx.redirect('/404.html');
- ctx.body = '迷路了'
+ ctx.body = { code: BizCode.ERROR, msg: err };
}
}
diff --git a/apps/server/src/interface.ts b/apps/server/src/interface.ts
index 13daae8..977e8db 100644
--- a/apps/server/src/interface.ts
+++ b/apps/server/src/interface.ts
@@ -4,3 +4,10 @@
export interface IUserOptions {
uid: number;
}
+
+export interface IVodResponse {
+ CoverUrl: string;
+ FileId: string;
+ MediaUrl: string;
+ RequestId: string;
+}
diff --git a/apps/server/src/util/vod.ts b/apps/server/src/util/vod.ts
index 1130485..15a7d1f 100644
--- a/apps/server/src/util/vod.ts
+++ b/apps/server/src/util/vod.ts
@@ -4,12 +4,12 @@ import { VodUploadClient, VodUploadRequest } from 'vod-node-sdk';
* 上传课程封面图片
* @param {string} tmpPath 图片上传midwayjs缓存的地址
*/
-export const uploadImagePromise = (tmpPath: string) => {
+export const uploadImagePromise = (tmpPath: string, subAppId: number) => {
const { SECRET_ID, SECRET_KEY } = process.env;
const client = new VodUploadClient(SECRET_ID, SECRET_KEY);
const req = new VodUploadRequest();
req.MediaFilePath = tmpPath;
- req.SubAppId = +process.env.SUBAPPID;
+ req.SubAppId = subAppId;
return new Promise((resolve, reject) => {
client.upload('ap-shanghai', req, (err, data) => {
err ? reject(err) : resolve(data);
diff --git a/apps/web/src/view/CourseDetail/components/Guide/index.tsx b/apps/web/src/view/CourseDetail/components/Guide/index.tsx
new file mode 100644
index 0000000..b47f245
--- /dev/null
+++ b/apps/web/src/view/CourseDetail/components/Guide/index.tsx
@@ -0,0 +1,9 @@
+import { useLocation } from "react-router-dom";
+
+function Material() {
+ const location = useLocation();
+
+ return {location.pathname}
;
+}
+
+export default Material;
diff --git a/apps/web/src/view/CourseDetail/components/Material/index.tsx b/apps/web/src/view/CourseDetail/components/Material/index.tsx
deleted file mode 100644
index 0236e5f..0000000
--- a/apps/web/src/view/CourseDetail/components/Material/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-function Material() {
- return 这里放课程资料。例如 指标 等文件下载
;
-}
-
-export default Material;
diff --git a/apps/web/src/view/CourseDetail/index.tsx b/apps/web/src/view/CourseDetail/index.tsx
index 2d79e8d..59e4284 100644
--- a/apps/web/src/view/CourseDetail/index.tsx
+++ b/apps/web/src/view/CourseDetail/index.tsx
@@ -2,21 +2,21 @@ import { useEffect, useState } from "react";
import "./index.less";
import { ResizeBox, Space, Result, Button } from "@arco-design/web-react";
import { Icon } from "@ricons/utils";
-import Material from "./components/Material";
+import Guide from "./components/Guide";
import { useMount } from "../../hook";
import Player from "./components/DPlayer";
function CourseDetail() {
const [toc, setToc] = useState([
{
- title: "准备",
+ title: "起步",
level: 1,
},
{
- title: "资料下载",
+ title: "导读",
level: 2,
active: true,
- view: ,
+ view: ,
},
{
title: "第一讲:特殊K线的量化描述",
diff --git a/package.json b/package.json
index b1d046d..ae61234 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,8 @@
"dayjs": "1.11.7"
},
"dependencies": {
- "object-hash": "^3.0.0"
+ "object-hash": "^3.0.0",
+ "vditor": "3.9.0",
+ "zustand": "4.3.6"
}
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 49a1f12..c786e20 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -40,11 +40,15 @@ importers:
ts-loader: 9.4.2
typescript: ^4.9.5
url-loader: 4.1.1
+ vditor: 3.9.0
webpack: ^5.75.0
webpack-bundle-analyzer: 4.8.0
webpack-cli: ^5.0.1
+ zustand: 4.3.6
dependencies:
object-hash: registry.npmmirror.com/object-hash/3.0.0
+ vditor: registry.npmmirror.com/vditor/3.9.0
+ zustand: registry.npmmirror.com/zustand/4.3.6
devDependencies:
'@babel/core': registry.npmmirror.com/@babel/core/7.21.0
'@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.21.0
@@ -7206,6 +7210,12 @@ packages:
wrappy: registry.npmmirror.com/wrappy/1.0.2
dev: true
+ registry.npmmirror.com/diff-match-patch/1.0.5:
+ resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz}
+ name: diff-match-patch
+ version: 1.0.5
+ dev: false
+
registry.npmmirror.com/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz}
name: diff
@@ -13605,6 +13615,14 @@ packages:
prepend-http: registry.npmmirror.com/prepend-http/2.0.0
dev: true
+ registry.npmmirror.com/use-sync-external-store/1.2.0:
+ resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz}
+ name: use-sync-external-store
+ version: 1.2.0
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dev: false
+
registry.npmmirror.com/util-deprecate/1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz}
name: util-deprecate
@@ -13650,6 +13668,14 @@ packages:
version: 1.1.2
engines: {node: '>= 0.8'}
+ registry.npmmirror.com/vditor/3.9.0:
+ resolution: {integrity: sha512-CLLtrexUY/LGN1Lp1iu242Uq9GuNP98UTXFRY9hjTNFkpVH9L4M3jrQ9yIZ711zYwsl78GxKeskuU7WieA96ow==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vditor/-/vditor-3.9.0.tgz}
+ name: vditor
+ version: 3.9.0
+ dependencies:
+ diff-match-patch: registry.npmmirror.com/diff-match-patch/1.0.5
+ dev: false
+
registry.npmmirror.com/verror/1.10.0:
resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/verror/-/verror-1.10.0.tgz}
name: verror
@@ -14118,3 +14144,20 @@ packages:
version: 0.1.0
engines: {node: '>=10'}
dev: true
+
+ registry.npmmirror.com/zustand/4.3.6:
+ resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/zustand/-/zustand-4.3.6.tgz}
+ name: zustand
+ version: 4.3.6
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ immer: '>=9.0'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ immer:
+ optional: true
+ react:
+ optional: true
+ dependencies:
+ use-sync-external-store: registry.npmmirror.com/use-sync-external-store/1.2.0
+ dev: false