线上课编辑优化

This commit is contained in:
禺狨 2023-03-17 16:50:06 +08:00
parent 34246017da
commit 6eb8cb5c60
10 changed files with 808 additions and 16 deletions

View File

@ -9,9 +9,11 @@ interface Option {
}
interface PropInterface {
type: string;
text: string;
categoryCount: any;
resourceTotal: number;
onUpdate: (keys: any, title: any) => void;
}
@ -25,15 +27,20 @@ export const TreeCategory = (props: PropInterface) => {
const categories = res.data.categories;
if (JSON.stringify(categories) !== "{}") {
const new_arr: Option[] = checkArr(categories, 0);
new_arr.unshift({
key: 0,
title: (
<div className="d-flex">
<span className="tree-num">({props.categoryCount[0] || 0})</span>
</div>
),
});
if (props.type === "no-cate") {
new_arr.unshift({
key: 0,
title: (
<div className="d-flex">
<span className="tree-num">
({props.categoryCount[0] || 0})
</span>
</div>
),
});
}
setTreeData(new_arr);
}
});

View File

@ -98,6 +98,7 @@ export const UploadImageButton = (props: PropsInterface) => {
<Row gutter={16}>
<Col span={4}>
<TreeCategory
type="no-cate"
resourceTotal={0}
categoryCount={{}}
text={"图片"}

View File

@ -149,6 +149,7 @@ export const UploadVideoSub = (props: PropsInterface) => {
<Row style={{ width: 752, minHeight: 520 }}>
<Col span={7}>
<TreeCategory
type="no-cate"
resourceTotal={pureTotal}
categoryCount={{ categoryCount }}
text={props.label}

View File

@ -256,12 +256,9 @@ export const CourseCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
};
const delChapterHour = (index: number, id: number) => {
console.log(index, id);
const keys = [...chapterHours];
const data = [...chapters];
const current = data[index].hours.findIndex((i: any) => i.rid === id);
console.log(current);
if (current >= 0) {
data[index].hours.splice(current, 1);
}

View File

@ -38,9 +38,7 @@ export const TreeHours = (props: PropInterface) => {
color: "rgba(0,0,0,0.3)",
}}
/>
<div className="tree-video-title mr-24">
{hours[i].name} {hours[i].rid}
</div>
<div className="tree-video-title mr-24">{hours[i].name}</div>
</div>
<Tooltip placement="top" title="可拖拽排序">
<i

View File

@ -20,3 +20,70 @@
margin-right: 0;
}
}
.hous-box {
width: 502px;
min-height: 56px;
background: #ffffff;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.15);
box-sizing: border-box;
-moz-box-sizing: border-box;
/* Firefox */
-webkit-box-sizing: border-box;
/* Safari */
padding: 16px 16px 16px 0;
margin-left: 42px;
.no-hours {
height: 24px;
font-size: 14px;
font-weight: 400;
color: rgba(0, 0, 0, 0.25);
line-height: 24px;
margin-left: 16px;
}
}
.chapter-item {
width: 502px;
height: auto;
display: flex;
flex-direction: column;
margin-left: 42px;
margin-bottom: 16px;
.label {
width: 78px;
height: 32px;
font-size: 14px;
font-weight: 400;
color: rgba(0, 0, 0, 0.85);
line-height: 32px;
}
.input {
width: 208px;
height: 32px;
margin-right: 24px;
}
.chapter-hous-box {
width: 502px;
min-height: 56px;
background: #ffffff;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.15);
box-sizing: border-box;
-moz-box-sizing: border-box;
/* Firefox */
-webkit-box-sizing: border-box;
/* Safari */
padding: 16px 16px 16px 0;
margin-top: 16px;
.no-hours {
height: 24px;
font-size: 14px;
font-weight: 400;
color: rgba(0, 0, 0, 0.25);
line-height: 24px;
margin-left: 16px;
}
}
}

View File

@ -0,0 +1,706 @@
import React, { useState, useEffect } from "react";
import {
Space,
Radio,
Button,
Drawer,
Form,
Cascader,
Input,
Modal,
message,
Image,
} from "antd";
import styles from "./update.module.less";
import { course, department } from "../../../api/index";
import { UploadImageButton, SelectResource } from "../../../compenents";
import { ExclamationCircleFilled } from "@ant-design/icons";
import { getHost } from "../../../utils/index";
import { TreeHours } from "./hours";
import { duration } from "moment";
const { confirm } = Modal;
interface PropInterface {
id: number;
open: boolean;
onCancel: () => void;
}
interface Option {
value: string | number;
label: string;
children?: Option[];
}
export const CourseUpdate: React.FC<PropInterface> = ({
id,
open,
onCancel,
}) => {
const [form] = Form.useForm();
const defaultThumb1 = getHost() + "thumb/thumb1.png";
const defaultThumb2 = getHost() + "thumb/thumb2.png";
const defaultThumb3 = getHost() + "thumb/thumb3.png";
const [loading, setLoading] = useState<boolean>(true);
const [departments, setDepartments] = useState<any>([]);
const [categories, setCategories] = useState<any>([]);
const [thumb, setThumb] = useState<string>("");
const [type, setType] = useState<string>("open");
const [chapterType, setChapterType] = useState(0);
const [chapters, setChapters] = useState<any>([]);
const [hours, setHours] = useState<any>([]);
const [chapterHours, setChapterHours] = useState<any>([]);
const [videoVisible, setVideoVisible] = useState<boolean>(false);
const [treeData, setTreeData] = useState<any>([]);
const [addvideoCurrent, setAddvideoCurrent] = useState(0);
useEffect(() => {
if (id === 0) {
return;
}
getCategory();
}, [id]);
const getCategory = () => {
course.createCourse().then((res: any) => {
const categories = res.data.categories;
if (JSON.stringify(categories) !== "{}") {
const new_arr: Option[] = checkArr(categories, 0);
setCategories(new_arr);
}
getParams(categories);
});
};
const getParams = (cats: any) => {
department.departmentList().then((res: any) => {
const departments = res.data.departments;
if (JSON.stringify(departments) !== "{}") {
const new_arr: Option[] = checkArr(departments, 0);
setDepartments(new_arr);
}
getDetail(departments, cats);
});
};
const getDetail = (deps: any, cats: any) => {
course.course(id).then((res: any) => {
let box = res.data.dep_ids;
let depIds: any[] = [];
if (box.length > 1) {
for (let i = 0; i < box.length; i++) {
let item = checkChild(deps, box[i]);
let arr: any[] = [];
if (item === undefined) {
arr.push(box[i]);
} else if (item.parent_chain === "") {
arr.push(box[i]);
} else {
let new_arr = item.parent_chain.split(",");
new_arr.map((num: any) => {
arr.push(Number(num));
});
arr.push(box[i]);
}
depIds.push(arr);
}
} else {
depIds = res.data.dep_ids;
}
let box2 = res.data.category_ids;
let categoryIds: any[] = [];
if (box2.length > 1) {
for (let i = 0; i < box2.length; i++) {
let item = checkChild(cats, box2[i]);
let arr: any[] = [];
if (item === undefined) {
arr.push(box2[i]);
} else if (item.parent_chain === "") {
arr.push(box2[i]);
} else {
let new_arr = item.parent_chain.split(",");
new_arr.map((num: any) => {
arr.push(Number(num));
});
arr.push(box2[i]);
}
categoryIds.push(arr);
}
} else {
categoryIds = res.data.category_ids;
}
let chapterType = res.data.chapters.length > 0 ? 1 : 0;
form.setFieldsValue({
title: res.data.course.title,
thumb: res.data.course.thumb,
dep_ids: depIds,
category_ids: categoryIds,
type: "open",
desc: "",
hasChapter: chapterType,
});
setThumb(res.data.course.thumb);
setChapterType(chapterType);
if (chapterType === 1) {
setTreeData([]);
setHours([]);
let hours = res.data.hours;
let chapters = res.data.chapters;
const arr: any = [];
const keys: any = [];
for (let i = 0; i < chapters.length; i++) {
arr.push({
name: chapters[i].name,
hours: resetHours(hours[chapters[i].id]).arr,
});
keys.push(resetHours(hours[chapters[i].id]).keys);
}
setChapters(arr);
setChapterHours(keys);
} else {
setChapters([]);
setChapterHours([]);
let hours = res.data.hours;
const arr: any = resetHours(hours[0]).arr;
const keys: any = resetHours(hours[0]).keys;
setTreeData(arr);
setHours(keys);
}
});
};
const resetHours = (data: any) => {
const arr: any = [];
const keys: any = [];
for (let i = 0; i < data.length; i++) {
arr.push({
duration: data[i].duration,
type: data[i].type,
name: data[i].title,
rid: data[i].rid,
});
keys.push(data[i].rid);
}
return { arr, keys };
};
const checkChild = (departments: any[], id: number) => {
for (let key in departments) {
for (let i = 0; i < departments[key].length; i++) {
if (departments[key][i].id === id) {
return departments[key][i];
}
}
}
};
const checkArr = (departments: any[], id: number) => {
const arr = [];
for (let i = 0; i < departments[id].length; i++) {
if (!departments[departments[id][i].id]) {
arr.push({
label: departments[id][i].name,
value: departments[id][i].id,
});
} else {
const new_arr: Option[] = checkArr(departments, departments[id][i].id);
arr.push({
label: departments[id][i].name,
value: departments[id][i].id,
children: new_arr,
});
}
}
return arr;
};
const onFinish = (values: any) => {
let dep_ids: any[] = [];
if (type === "elective") {
for (let i = 0; i < values.dep_ids.length; i++) {
dep_ids.push(values.dep_ids[i][values.dep_ids[i].length - 1]);
}
}
let category_ids: any[] = [];
for (let j = 0; j < values.category_ids.length; j++) {
category_ids.push(
values.category_ids[j][values.category_ids[j].length - 1]
);
}
course
.storeCourse(
values.title,
values.thumb,
1,
dep_ids,
category_ids,
chapters,
treeData
)
.then((res: any) => {
message.success("保存成功!");
onCancel();
});
};
const onFinishFailed = (errorInfo: any) => {
console.log("Failed:", errorInfo);
};
const getType = (e: any) => {
setType(e.target.value);
};
const selectData = (arr: any, videos: any) => {
setHours(arr);
setTreeData(videos);
setVideoVisible(false);
};
const selectChapterData = (arr: any, videos: any) => {
const data = [...chapters];
const keys = [...chapterHours];
keys[addvideoCurrent] = arr;
data[addvideoCurrent].hours = videos;
setChapters(data);
setChapterHours(keys);
setVideoVisible(false);
};
const getChapterType = (e: any) => {
confirm({
title: "操作确认",
icon: <ExclamationCircleFilled />,
content: "切换列表选项会清空已添加课时,确认切换?",
centered: true,
okText: "确认",
cancelText: "取消",
onOk() {
setChapterType(e.target.value);
setChapters([]);
setHours([]);
setChapterHours([]);
setTreeData([]);
},
onCancel() {
form.setFieldsValue({
hasChapter: chapterType,
});
},
});
};
const delHour = (id: number) => {
const data = [...treeData];
const index = data.findIndex((i: any) => i.rid === id);
if (index >= 0) {
data.splice(index, 1);
}
if (data.length > 0) {
setTreeData(data);
const keys = data.map((item: any) => item.rid);
setHours(keys);
} else {
setTreeData([]);
setHours([]);
}
};
const transHour = (arr: any) => {
setHours(arr);
const data = [...treeData];
const newArr: any = [];
for (let i = 0; i < arr.length; i++) {
data.map((item: any) => {
if (item.rid === arr[i]) {
newArr.push(item);
}
});
}
setTreeData(newArr);
};
const addNewChapter = () => {
const arr = [...chapters];
const keys = [...chapterHours];
arr.push({
name: "",
hours: [],
});
keys.push([]);
setChapters(arr);
setChapterHours(keys);
};
const setChapterName = (index: number, value: string) => {
const arr = [...chapters];
arr[index].name = value;
setChapters(arr);
};
const delChapter = (index: number) => {
const arr = [...chapters];
const keys = [...chapterHours];
confirm({
title: "操作确认",
icon: <ExclamationCircleFilled />,
content: "删除章节会清空已添加课时,确认删除?",
centered: true,
okText: "确认",
cancelText: "取消",
onOk() {
arr.splice(index, 1);
keys.splice(index, 1);
setChapters(arr);
setChapterHours(keys);
},
onCancel() {},
});
};
const delChapterHour = (index: number, id: number) => {
const keys = [...chapterHours];
const data = [...chapters];
const current = data[index].hours.findIndex((i: any) => i.rid === id);
if (current >= 0) {
data[index].hours.splice(current, 1);
}
if (data[index].hours.length > 0) {
setChapters(data);
keys[index] = data[index].hours.map((item: any) => item.rid);
setChapterHours(keys);
} else {
keys[index] = [];
data[index].hours = [];
setChapters(data);
setChapterHours(keys);
}
};
const transChapterHour = (index: number, arr: any) => {
const keys = [...chapterHours];
keys[index] = arr;
setChapterHours(keys);
const data = [...chapters];
const newArr: any = [];
for (let i = 0; i < arr.length; i++) {
data[index].hours.map((item: any) => {
if (item.rid === arr[i]) {
newArr.push(item);
}
});
}
data[index].hours = newArr;
setChapters(data);
};
return (
<>
<Drawer
title="编辑课程"
onClose={onCancel}
maskClosable={false}
open={open}
footer={
<Space className="j-r-flex">
<Button onClick={() => onCancel()}> </Button>
<Button onClick={() => form.submit()} type="primary">
</Button>
</Space>
}
width={634}
>
<div className="float-left mt-24">
<SelectResource
defaultKeys={
chapterType == 0 ? hours : chapterHours[addvideoCurrent]
}
open={videoVisible}
onCancel={() => {
setVideoVisible(false);
}}
onSelected={(arr: any, videos: any) => {
if (chapterType == 0) {
selectData(arr, videos);
} else {
selectChapterData(arr, videos);
}
}}
/>
<Form
form={form}
name="basic"
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
initialValues={{ remember: true }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="课程名称"
name="title"
rules={[{ required: true, message: "请在此处输入课程名称!" }]}
>
<Input
style={{ width: 424 }}
placeholder="请在此处输入课程名称"
/>
</Form.Item>
<Form.Item
label="课程分类"
name="category_ids"
rules={[{ required: true, message: "请选择课程分类!" }]}
>
<Cascader
style={{ width: 424 }}
options={categories}
multiple
maxTagCount="responsive"
placeholder="请选择课程分类"
/>
</Form.Item>
<Form.Item
label="课程类型"
name="type"
rules={[{ required: true, message: "请选择课程类型!" }]}
>
<Radio.Group onChange={getType}>
<Radio value="open"></Radio>
<Radio value="elective"></Radio>
</Radio.Group>
</Form.Item>
{type === "elective" && (
<Form.Item
label="指派部门"
name="dep_ids"
rules={[
{
required: true,
message: "请选择课程指派部门!",
},
]}
>
<Cascader
style={{ width: 424 }}
options={departments}
multiple
maxTagCount="responsive"
placeholder="请选择课程指派部门"
/>
</Form.Item>
)}
<Form.Item
label="课程封面"
name="thumb"
rules={[{ required: true, message: "请上传课程封面!" }]}
>
<div className="d-flex">
<Image
src={thumb}
width={160}
height={120}
style={{ borderRadius: 6 }}
preview={false}
/>
<div className="c-flex ml-8 flex-1">
<div className="d-flex mb-28">
<div
className={
thumb === defaultThumb1
? styles["thumb-item-avtive"]
: styles["thumb-item"]
}
onClick={() => {
setThumb(defaultThumb1);
form.setFieldsValue({
thumb: defaultThumb1,
});
}}
>
<Image
src={defaultThumb1}
width={80}
height={60}
style={{ borderRadius: 6 }}
preview={false}
/>
</div>
<div
className={
thumb === defaultThumb2
? styles["thumb-item-avtive"]
: styles["thumb-item"]
}
onClick={() => {
setThumb(defaultThumb2);
form.setFieldsValue({
thumb: defaultThumb2,
});
}}
>
<Image
src={defaultThumb2}
width={80}
height={60}
style={{ borderRadius: 6 }}
preview={false}
/>
</div>
<div
className={
thumb === defaultThumb3
? styles["thumb-item-avtive"]
: styles["thumb-item"]
}
onClick={() => {
setThumb(defaultThumb3);
form.setFieldsValue({
thumb: defaultThumb3,
});
}}
>
<Image
src={defaultThumb3}
width={80}
height={60}
style={{ borderRadius: 6 }}
preview={false}
/>
</div>
</div>
<div className="d-flex">
<UploadImageButton
onSelected={(url) => {
setThumb(url);
form.setFieldsValue({ thumb: url });
}}
></UploadImageButton>
<span className="helper-text ml-16">
推荐尺寸:400x300px
</span>
</div>
</div>
</div>
</Form.Item>
<Form.Item label="课程简介" name="desc">
<Input.TextArea
style={{ width: 424, height: 80 }}
allowClear
placeholder="请输入课程简介"
/>
</Form.Item>
<Form.Item
label="课时列表"
name="hasChapter"
rules={[{ required: true, message: "请选择课时列表!" }]}
>
<Radio.Group onChange={getChapterType}>
<Radio value={0}></Radio>
<Radio value={1}></Radio>
</Radio.Group>
</Form.Item>
{chapterType === 0 && (
<div className="c-flex">
<Form.Item>
<div className="ml-120">
<Button
onClick={() => setVideoVisible(true)}
type="primary"
>
</Button>
</div>
</Form.Item>
<div className={styles["hous-box"]}>
{treeData.length === 0 && (
<span className={styles["no-hours"]}></span>
)}
{treeData.length > 0 && (
<TreeHours
data={treeData}
onRemoveItem={(id: number) => {
delHour(id);
}}
onUpdate={(arr: any[]) => {
transHour(arr);
}}
/>
)}
</div>
</div>
)}
{chapterType === 1 && (
<div className="c-flex">
{chapters.length > 0 &&
chapters.map((item: any, index: number) => {
return (
<div
key={item.hours.length + "章节" + index}
className={styles["chapter-item"]}
>
<div className="d-flex">
<div className={styles["label"]}>
{index + 1}
</div>
<Input
value={item.name}
className={styles["input"]}
onChange={(e) => {
setChapterName(index, e.target.value);
}}
placeholder="请在此处输入章节名称"
/>
<Button
className="mr-16"
type="primary"
onClick={() => {
setVideoVisible(true);
setAddvideoCurrent(index);
}}
>
</Button>
<Button onClick={() => delChapter(index)}>
</Button>
</div>
<div className={styles["chapter-hous-box"]}>
{item.hours.length === 0 && (
<span className={styles["no-hours"]}>
</span>
)}
{item.hours.length > 0 && (
<TreeHours
data={item.hours}
onRemoveItem={(id: number) => {
delChapterHour(index, id);
}}
onUpdate={(arr: any[]) => {
transChapterHour(index, arr);
}}
/>
)}
</div>
</div>
);
})}
<Form.Item>
<div className="ml-120">
<Button onClick={() => addNewChapter()}></Button>
</div>
</Form.Item>
</div>
)}
</Form>
</div>
</Drawer>
</>
);
};

View File

@ -19,6 +19,7 @@ import { Link, useNavigate } from "react-router-dom";
import { TreeDepartment, TreeCategory, PerButton } from "../../compenents";
import type { TabsProps } from "antd";
import { CourseCreate } from "./compenents/create";
import { CourseUpdate } from "./compenents/update";
const { confirm } = Modal;
@ -58,6 +59,7 @@ export const CoursePage = () => {
children: (
<div className="float-left">
<TreeCategory
type=""
resourceTotal={pureTotal}
text={"课程"}
categoryCount={categoryCount}
@ -132,7 +134,10 @@ export const CoursePage = () => {
class="b-link c-red"
icon={null}
p="course"
onClick={() => navigate(`/course/update/${record.id}`)}
onClick={() => {
setCid(Number(record.id));
setUpdateVisible(true);
}}
disabled={null}
/>
<div className="form-column"></div>
@ -292,6 +297,14 @@ export const CoursePage = () => {
setRefresh(!refresh);
}}
/>
<CourseUpdate
id={cid}
open={updateVisible}
onCancel={() => {
setUpdateVisible(false);
setRefresh(!refresh);
}}
/>
</div>
</div>
</div>

View File

@ -154,6 +154,7 @@ export const ResourceImagesPage = () => {
<div className="tree-main-body">
<div className="left-box">
<TreeCategory
type="no-cate"
text={"图片"}
categoryCount={categoryCount}
resourceTotal={pureTotal}

View File

@ -169,6 +169,7 @@ export const ResourceVideosPage = () => {
<div className="tree-main-body">
<div className="left-box">
<TreeCategory
type="no-cate"
resourceTotal={pureTotal}
text={"视频"}
categoryCount={categoryCount}