feat: 课程创建
This commit is contained in:
parent
15537d5c13
commit
ddc823cf9e
|
@ -24,3 +24,16 @@ ul {
|
||||||
font-family: "bs";
|
font-family: "bs";
|
||||||
src: url("./backset.woff");
|
src: url("./backset.woff");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bs-scrollbar {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 14px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
border: 4px solid transparent;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-radius: 7px;
|
||||||
|
background-color: #d2d2d2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
.logo {
|
.container {
|
||||||
float: left;
|
height: 100%;
|
||||||
width: 120px;
|
background: #f1f1f1;
|
||||||
height: 31px;
|
header {
|
||||||
margin: 16px 24px 16px 0;
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 46px;
|
||||||
|
background: #001529;
|
||||||
|
z-index: 19;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 320px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -14,4 +26,25 @@
|
||||||
font-family: "bs";
|
font-family: "bs";
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aside {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 46px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
position: fixed;
|
||||||
|
left: 200px;
|
||||||
|
right: 0;
|
||||||
|
top: 46px;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
.view {
|
||||||
|
width: 1120px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ const Index: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ height: "100%" }}>
|
<div className="container">
|
||||||
<Header className="header">
|
<header>
|
||||||
<div className="logo">
|
<div className="logo">
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
|
@ -71,8 +71,8 @@ const Index: React.FC = () => {
|
||||||
items={navMenus}
|
items={navMenus}
|
||||||
onClick={onClickNavMenuItem}
|
onClick={onClickNavMenuItem}
|
||||||
/>
|
/>
|
||||||
</Header>
|
</header>
|
||||||
<Layout>
|
<aside>
|
||||||
<Sider width={200} style={{ background: colorBgContainer }}>
|
<Sider width={200} style={{ background: colorBgContainer }}>
|
||||||
<Menu
|
<Menu
|
||||||
mode="inline"
|
mode="inline"
|
||||||
|
@ -83,15 +83,9 @@ const Index: React.FC = () => {
|
||||||
onClick={onClickSideMenuItem}
|
onClick={onClickSideMenuItem}
|
||||||
/>
|
/>
|
||||||
</Sider>
|
</Sider>
|
||||||
<Layout className="site-layout">
|
</aside>
|
||||||
<Content
|
<main className="bs-scrollbar">
|
||||||
style={{
|
<div className="view">
|
||||||
margin: "24px 16px",
|
|
||||||
padding: 24,
|
|
||||||
minHeight: 280,
|
|
||||||
background: colorBgContainer,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Routes>
|
<Routes>
|
||||||
{[...navRoutes, ...sideMenuRoutes].map((router) => (
|
{[...navRoutes, ...sideMenuRoutes].map((router) => (
|
||||||
<Route
|
<Route
|
||||||
|
@ -106,10 +100,9 @@ const Index: React.FC = () => {
|
||||||
))}
|
))}
|
||||||
<Route path="*" element={<span>404</span>} />
|
<Route path="*" element={<span>404</span>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Content>
|
</div>
|
||||||
</Layout>
|
</main>
|
||||||
</Layout>
|
</div>
|
||||||
</Layout>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
const Appendix = () => {
|
|
||||||
return <div>附件</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Appendix;
|
|
10
apps/admin/src/view/Course/Create/Appendix/index.tsx
Normal file
10
apps/admin/src/view/Course/Create/Appendix/index.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
interface IProps {
|
||||||
|
onChange?: Function;
|
||||||
|
styles?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Appendix = (props: IProps) => {
|
||||||
|
return <div style={{ ...props.styles }}>附件</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Appendix;
|
|
@ -3,7 +3,7 @@
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
height: 100%;
|
height: 360px;
|
||||||
.mask {
|
.mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
@ -14,16 +14,19 @@
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
letter-spacing: 2px;
|
||||||
&.title {
|
&.title {
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
}
|
}
|
||||||
&.summary {
|
&.summary {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,24 @@
|
||||||
import { InboxOutlined } from "@ant-design/icons";
|
import { InboxOutlined } from "@ant-design/icons";
|
||||||
import {
|
import { Col, Form, Input, message, Row, Upload, UploadProps } from "antd";
|
||||||
Card,
|
import { useEffect, useState } from "react";
|
||||||
Col,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
message,
|
|
||||||
Row,
|
|
||||||
Upload,
|
|
||||||
UploadProps,
|
|
||||||
} from "antd";
|
|
||||||
import { useState } from "react";
|
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
const { Dragger } = Upload;
|
const { Dragger } = Upload;
|
||||||
const { Meta } = Card;
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onChange: Function;
|
onChange: Function;
|
||||||
|
styles?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicForm = (props: IProps) => {
|
const BasicForm = (props: IProps) => {
|
||||||
const [preview, setPreivew] = useState({
|
const [preview, setPreivew] = useState({
|
||||||
coverUrl: "",
|
coverUrl: "",
|
||||||
title: "标题",
|
title: "",
|
||||||
summary: "摘要",
|
summary: "",
|
||||||
});
|
});
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
const onValuesChange = (_: any, all: any) => {
|
const onValuesChange = (_: any, all: any) =>
|
||||||
setPreivew((p) => ({ ...p, ...all }));
|
setPreivew((p) => ({ ...p, ...all }));
|
||||||
};
|
|
||||||
|
|
||||||
const coverDragger: UploadProps = {
|
const coverDragger: UploadProps = {
|
||||||
name: "file",
|
name: "file",
|
||||||
|
@ -53,36 +43,12 @@ const BasicForm = (props: IProps) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => props.onChange(preview), [preview]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ ...props.styles }}>
|
||||||
<Row gutter={24}>
|
<Row style={{ marginBottom: 24 }}>
|
||||||
<Col span={12}>
|
<Col span={24}>
|
||||||
<Form form={form} onValuesChange={onValuesChange}>
|
|
||||||
<Form.Item>
|
|
||||||
<Dragger {...coverDragger} maxCount={1}>
|
|
||||||
<p className="ant-upload-drag-icon">
|
|
||||||
<InboxOutlined />
|
|
||||||
</p>
|
|
||||||
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
|
||||||
<p className="ant-upload-hint">支持单个或批量上传</p>
|
|
||||||
</Dragger>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="title" rules={[{ required: true }]}>
|
|
||||||
<Input type="text" placeholder="标题" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="summary"
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
>
|
|
||||||
<Input.TextArea
|
|
||||||
placeholder="摘要"
|
|
||||||
style={{ height: 120, resize: "none" }}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<div
|
<div
|
||||||
className="preview-course"
|
className="preview-course"
|
||||||
style={{
|
style={{
|
||||||
|
@ -99,12 +65,42 @@ const BasicForm = (props: IProps) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mask">
|
<div className="mask">
|
||||||
<p className="title">{preview.title}</p>
|
<p className="title">{!preview.title ? "标题" : preview.title}</p>
|
||||||
<p className="summary">{preview.summary} </p>
|
<p className="summary">
|
||||||
|
{!preview.summary ? "摘要" : preview.summary}{" "}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Dragger {...coverDragger} maxCount={1}>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<InboxOutlined />
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">上传课程封面图</p>
|
||||||
|
<p className="ant-upload-hint">点击或拖拽文件到此处</p>
|
||||||
|
</Dragger>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form form={form} onValuesChange={onValuesChange}>
|
||||||
|
<Form.Item name="title" rules={[{ required: true }]}>
|
||||||
|
<Input size="large" type="text" placeholder="标题" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="summary"
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
<Input.TextArea
|
||||||
|
placeholder="摘要"
|
||||||
|
style={{ height: 130, resize: "none" }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
10
apps/admin/src/view/Course/Create/Chatpter/index.tsx
Normal file
10
apps/admin/src/view/Course/Create/Chatpter/index.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
interface IProps {
|
||||||
|
onChange?: Function;
|
||||||
|
styles?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chatpter = (props: IProps) => {
|
||||||
|
return <div style={{ ...props.styles }}>123</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chatpter;
|
|
@ -1,5 +0,0 @@
|
||||||
const MediaBind = () => {
|
|
||||||
return <div>123</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MediaBind;
|
|
|
@ -1,6 +1,5 @@
|
||||||
.create-course {
|
.create-course {
|
||||||
display: flex;
|
padding: 24px 0;
|
||||||
flex-direction: column;
|
|
||||||
.content {
|
.content {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
|
@ -11,28 +13,27 @@ import {
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Appendix from "./Appendix";
|
import Appendix from "./Appendix";
|
||||||
import BasicForm from "./BasicForm";
|
import BasicForm from "./BasicForm";
|
||||||
|
import Chatpter from "./Chatpter";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
import MediaBind from "./MediaBind";
|
|
||||||
|
|
||||||
const CourseCreate = () => {
|
const CourseCreate = () => {
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
|
const [course, setCourse] = useState({
|
||||||
|
basicInfo: {},
|
||||||
|
});
|
||||||
|
|
||||||
const onBasicFormChange = (form: any) => {
|
const onBasicFormChange = (form: any) =>
|
||||||
console.log(form);
|
setCourse((p) => ({ ...p, basicInfo: form }));
|
||||||
};
|
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: "基本信息",
|
title: "基本信息",
|
||||||
content: <BasicForm onChange={onBasicFormChange} />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "媒体资源",
|
title: "章节",
|
||||||
content: <MediaBind />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "附件",
|
title: "附件",
|
||||||
content: <Appendix />,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -40,18 +41,25 @@ const CourseCreate = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="create-course">
|
<div className="create-course">
|
||||||
|
<Card>
|
||||||
<Steps current={current} items={items} />
|
<Steps current={current} items={items} />
|
||||||
<div className="content">{steps[current].content}</div>
|
<div className="content">
|
||||||
<div style={{ textAlign: "right" }}>
|
<BasicForm
|
||||||
|
onChange={onBasicFormChange}
|
||||||
|
styles={{ display: current === 0 ? "block" : "none" }}
|
||||||
|
/>
|
||||||
|
<Chatpter styles={{ display: current === 1 ? "block" : "none" }} />
|
||||||
|
<Appendix styles={{ display: current === 2 ? "block" : "none" }} />
|
||||||
|
</div>
|
||||||
|
<div style={{ textAlign: "right", marginTop: "40px" }}>
|
||||||
|
{current > 0 && (
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{ marginRight: "12px" }}
|
||||||
margin: "0 8px",
|
|
||||||
visibility: current > 0 ? "visible" : "hidden",
|
|
||||||
}}
|
|
||||||
onClick={() => setCurrent(current - 1)}
|
onClick={() => setCurrent(current - 1)}
|
||||||
>
|
>
|
||||||
上一步
|
上一步
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
{current < steps.length - 1 && (
|
{current < steps.length - 1 && (
|
||||||
<Button type="primary" onClick={() => setCurrent(current + 1)}>
|
<Button type="primary" onClick={() => setCurrent(current + 1)}>
|
||||||
下一步
|
下一步
|
||||||
|
@ -66,19 +74,7 @@ const CourseCreate = () => {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* <Form>
|
</Card>
|
||||||
<Form.Item
|
|
||||||
wrapperCol={{ span: 24 }}
|
|
||||||
name="title"
|
|
||||||
rules={[{ required: true, message: "Please input your username!" }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="标题" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item><InputNumber placeholder="售价" /></Form.Item>
|
|
||||||
<Form.Item>
|
|
||||||
<Button type="primary">提交</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</Form> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,7 +48,9 @@ const Library = () => {
|
||||||
<Row>{record.name}</Row>
|
<Row>{record.name}</Row>
|
||||||
<Row style={{ paddingTop: "5px" }}>
|
<Row style={{ paddingTop: "5px" }}>
|
||||||
{record.m3u8SubStreamList.map((item: any, index: number) => (
|
{record.m3u8SubStreamList.map((item: any, index: number) => (
|
||||||
<Tag color={colors[index]}>{item}</Tag>
|
<Tag key={item} color={colors[index]}>
|
||||||
|
{item}
|
||||||
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{ paddingTop: "5px" }}>
|
<Row style={{ paddingTop: "5px" }}>
|
||||||
|
@ -160,7 +162,7 @@ const Library = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Card style={{ marginTop: 24 }}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col span={14}>
|
<Col span={14}>
|
||||||
<Space>
|
<Space>
|
||||||
|
@ -197,7 +199,7 @@ const Library = () => {
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user