From 475b2932efcad751d6f1f4a6e2a2665f8adbd344 Mon Sep 17 00:00:00 2001 From: mozzie Date: Fri, 10 Mar 2023 17:55:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E8=AF=BE=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/src/App.tsx | 5 +- apps/admin/src/api/index.ts | 2 +- apps/admin/src/api/request.ts | 1 + apps/admin/src/layout/index.less | 17 ++- apps/admin/src/layout/index.tsx | 1 + apps/admin/src/store/media.tsx | 24 ++++ .../src/view/Course/Create/Appendix/index.tsx | 10 -- .../view/Course/Create/BasicForm/index.less | 1 - .../view/Course/Create/BasicForm/index.tsx | 2 +- .../src/view/Course/Create/Chatpter/index.tsx | 2 +- .../src/view/Course/Create/Guide/index.less | 2 + .../src/view/Course/Create/Guide/index.tsx | 134 ++++++++++++++++++ apps/admin/src/view/Course/Create/index.less | 7 + apps/admin/src/view/Course/Create/index.tsx | 97 ++++++++++--- apps/admin/src/view/Course/Library/index.tsx | 14 +- apps/admin/vite.config.ts | 4 +- apps/server/.env | 3 +- apps/server/src/config/config.default.ts | 1 + apps/server/src/controller/api.controller.ts | 56 +------- apps/server/src/controller/vod.controller.ts | 79 +++++++++++ apps/server/src/filter/notfound.filter.ts | 3 +- apps/server/src/interface.ts | 7 + apps/server/src/util/vod.ts | 4 +- .../CourseDetail/components/Guide/index.tsx | 9 ++ .../components/Material/index.tsx | 5 - apps/web/src/view/CourseDetail/index.tsx | 8 +- package.json | 4 +- pnpm-lock.yaml | 43 ++++++ 28 files changed, 425 insertions(+), 120 deletions(-) create mode 100644 apps/admin/src/store/media.tsx delete mode 100644 apps/admin/src/view/Course/Create/Appendix/index.tsx create mode 100644 apps/admin/src/view/Course/Create/Guide/index.less create mode 100644 apps/admin/src/view/Course/Create/Guide/index.tsx create mode 100644 apps/server/src/controller/vod.controller.ts create mode 100644 apps/web/src/view/CourseDetail/components/Guide/index.tsx delete mode 100644 apps/web/src/view/CourseDetail/components/Material/index.tsx 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} 条媒体资源