Merge pull request #4 from PlayEdu/dev

v1.0-beta.4
This commit is contained in:
Teng 2023-05-04 15:24:06 +08:00 committed by GitHub
commit 5d834ab2d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 752 additions and 234 deletions

View File

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.3",
"ahooks": "^3.7.6",
"antd": "^5.3.2", "antd": "^5.3.2",
"axios": "^1.3.4", "axios": "^1.3.4",
"echarts": "^5.4.2", "echarts": "^5.4.2",

View File

@ -109,6 +109,10 @@ export function learnCourses(
}); });
} }
export function learnAllCourses(id: number) {
return client.get(`/backend/v1/user/${id}/all-courses`, {});
}
export function departmentProgress( export function departmentProgress(
id: number, id: number,
page: number, page: number,
@ -121,3 +125,25 @@ export function departmentProgress(
...params, ...params,
}); });
} }
export function learnCoursesProgress(
id: number,
courseId: number,
params: any
) {
return client.get(`/backend/v1/user/${id}/learn-course/${courseId} `, params);
}
export function destroyAllUserLearned(id: number, courseId: number) {
return client.destroy(`/backend/v1/user/${id}/learn-course/${courseId}`);
}
export function destroyUserLearned(
id: number,
courseId: number,
hourId: number
) {
return client.destroy(
`/backend/v1/user/${id}/learn-course/${courseId}/hour/${hourId}`
);
}

View File

@ -54,6 +54,7 @@ export const CreateResourceCategory = (props: PropInterface) => {
onChange={(e) => { onChange={(e) => {
setName(e.target.value); setName(e.target.value);
}} }}
allowClear
/> />
</Modal> </Modal>
</> </>

View File

@ -8,7 +8,7 @@ export const Footer: React.FC = () => {
style={{ style={{
width: "100%", width: "100%",
backgroundColor: "#F6F6F6", backgroundColor: "#F6F6F6",
height: 232, height: 166,
paddingTop: 80, paddingTop: 80,
textAlign: "center", textAlign: "center",
}} }}

View File

@ -0,0 +1,29 @@
import { useUpdate } from "ahooks";
import { useEffect, useRef } from "react";
import { useLocation, useOutlet } from "react-router-dom";
function KeepAlive() {
const componentList = useRef(new Map());
const outLet = useOutlet();
const { pathname } = useLocation();
const forceUpdate = useUpdate();
useEffect(() => {
if (!componentList.current.has(pathname)) {
componentList.current.set(pathname, outLet);
}
forceUpdate();
}, [pathname]);
return (
<div>
{Array.from(componentList.current).map(([key, component]) => (
<div key={key} style={{ display: pathname === key ? "block" : "none" }}>
{component}
</div>
))}
</div>
);
}
export default KeepAlive;

View File

@ -50,7 +50,7 @@ const items = [
"user", "user",
<i className="iconfont icon-icon-user" />, <i className="iconfont icon-icon-user" />,
[ [
getItem("学员", "/member", null, null, null), getItem("学员", "/member/index", null, null, null),
getItem("部门", "/department", null, null, null), getItem("部门", "/department", null, null, null),
], ],
null null

View File

@ -5,6 +5,7 @@ import { resourceCategory } from "../../api/index";
interface Option { interface Option {
key: string | number; key: string | number;
title: any; title: any;
children?: Option[]; children?: Option[];
} }

View File

@ -18,3 +18,20 @@
line-height: 30px; line-height: 30px;
display: flex; display: flex;
} }
.checked {
width: 16px;
height: 16px;
background: #ff4d4f;
border-radius: 3px;
border: 2px solid #ff4d4f;
position: absolute;
left: 5px;
top: 5px;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
cursor: pointer;
}

View File

@ -12,7 +12,7 @@ import {
import { resource, resourceCategory } from "../../api"; import { resource, resourceCategory } from "../../api";
import styles from "./index.module.less"; import styles from "./index.module.less";
import { CreateResourceCategory } from "../create-rs-category"; import { CreateResourceCategory } from "../create-rs-category";
import { CloseOutlined } from "@ant-design/icons"; import { CloseOutlined, CheckOutlined } from "@ant-design/icons";
import { UploadImageSub } from "./upload-image-sub"; import { UploadImageSub } from "./upload-image-sub";
import { TreeCategory } from "../../compenents"; import { TreeCategory } from "../../compenents";
@ -49,6 +49,7 @@ export const UploadImageButton = (props: PropsInterface) => {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [size, setSize] = useState(15); const [size, setSize] = useState(15);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [selected, setSelected] = useState<string>("");
// 获取图片列表 // 获取图片列表
const getImageList = () => { const getImageList = () => {
@ -97,13 +98,24 @@ export const UploadImageButton = (props: PropsInterface) => {
open={true} open={true}
width={820} width={820}
maskClosable={false} maskClosable={false}
onOk={() => {
if (!selected) {
message.error("请选择图片后确定");
return;
}
props.onSelected(selected);
setShowModal(false);
}}
> >
<Row style={{ width: 752, minHeight: 520, marginTop: 24 }}> <Row style={{ width: 752, minHeight: 520, marginTop: 24 }}>
<Col span={7}> <Col span={7}>
<TreeCategory <TreeCategory
type="no-cate" type="no-cate"
text={"图片"} text={"图片"}
onUpdate={(keys: any) => setCategoryIds(keys)} onUpdate={(keys: any) => {
setSelected("");
setCategoryIds(keys);
}}
/> />
</Col> </Col>
<Col span={17}> <Col span={17}>
@ -129,10 +141,21 @@ export const UploadImageButton = (props: PropsInterface) => {
className="image-item" className="image-item"
style={{ backgroundImage: `url(${item.url})` }} style={{ backgroundImage: `url(${item.url})` }}
onClick={() => { onClick={() => {
props.onSelected(item.url); setSelected(item.url);
setShowModal(false);
}} }}
></div> >
{selected.indexOf(item.url) !== -1 && (
<i
className={styles.checked}
onClick={(e) => {
e.stopPropagation();
setSelected("");
}}
>
<CheckOutlined />
</i>
)}
</div>
))} ))}
</div> </div>
{imageList.length > 0 && ( {imageList.length > 0 && (

View File

@ -614,6 +614,8 @@ textarea.ant-input {
background-size: contain; background-size: contain;
background-position: center center; background-position: center center;
background-color: #f6f6f6; background-color: #f6f6f6;
position: relative;
cursor: pointer;
} }
} }

View File

@ -45,14 +45,14 @@ const ChangePasswordPage = () => {
name="old_password" name="old_password"
rules={[{ required: true, message: "请输入原密码!" }]} rules={[{ required: true, message: "请输入原密码!" }]}
> >
<Input.Password placeholder="请输入原密码" /> <Input.Password placeholder="请输入原密码" allowClear />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="新密码" label="新密码"
name="new_password" name="new_password"
rules={[{ required: true, message: "请输入新密码!" }]} rules={[{ required: true, message: "请输入新密码!" }]}
> >
<Input.Password placeholder="请输入新密码" /> <Input.Password placeholder="请输入新密码" allowClear />
</Form.Item> </Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}> <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">

View File

@ -470,6 +470,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
<Input <Input
style={{ width: 424 }} style={{ width: 424 }}
placeholder="请在此处输入课程名称" placeholder="请在此处输入课程名称"
allowClear
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -683,6 +684,7 @@ export const CourseCreate: React.FC<PropInterface> = ({
onChange={(e) => { onChange={(e) => {
setChapterName(index, e.target.value); setChapterName(index, e.target.value);
}} }}
allowClear
placeholder="请在此处输入章节名称" placeholder="请在此处输入章节名称"
/> />
<Button <Button

View File

@ -451,6 +451,7 @@ export const CourseHourUpdate: React.FC<PropInterface> = ({
saveChapterName(index, e.target.value); saveChapterName(index, e.target.value);
}} }}
placeholder="请在此处输入章节名称" placeholder="请在此处输入章节名称"
allowClear
/> />
<Button <Button
className="mr-16" className="mr-16"

View File

@ -224,6 +224,7 @@ export const CourseUpdate: React.FC<PropInterface> = ({
rules={[{ required: true, message: "请在此处输入课程名称!" }]} rules={[{ required: true, message: "请在此处输入课程名称!" }]}
> >
<Input <Input
allowClear
style={{ width: 424 }} style={{ width: 424 }}
placeholder="请在此处输入课程名称" placeholder="请在此处输入课程名称"
/> />

View File

@ -51,6 +51,7 @@ const CoursePage = () => {
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [dep_ids, setDepIds] = useState<any>([]); const [dep_ids, setDepIds] = useState<any>([]);
const [selLabel, setLabel] = useState<string>("全部分类"); const [selLabel, setLabel] = useState<string>("全部分类");
const [selDepLabel, setDepLabel] = useState<string>("全部部门");
const [course_category_ids, setCourseCategoryIds] = useState<any>({}); const [course_category_ids, setCourseCategoryIds] = useState<any>({});
const [course_dep_ids, setCourseDepIds] = useState<any>({}); const [course_dep_ids, setCourseDepIds] = useState<any>({});
const [categories, setCategories] = useState<any>({}); const [categories, setCategories] = useState<any>({});
@ -95,7 +96,7 @@ const CoursePage = () => {
text={"部门"} text={"部门"}
onUpdate={(keys: any, title: any) => { onUpdate={(keys: any, title: any) => {
setDepIds(keys); setDepIds(keys);
setLabel(title); setDepLabel(title);
}} }}
/> />
</div> </div>
@ -282,11 +283,16 @@ const CoursePage = () => {
}); });
}; };
// 获取视频列表 // 获取列表
const getList = () => { const getList = () => {
setLoading(true); setLoading(true);
let categoryIds = category_ids.join(","); let categoryIds = "";
let depIds = dep_ids.join(","); let depIds = "";
if (tabKey === 1) {
categoryIds = category_ids.join(",");
} else {
depIds = dep_ids.join(",");
}
course course
.courseList(page, size, "", "", title, depIds, categoryIds) .courseList(page, size, "", "", title, depIds, categoryIds)
.then((res: any) => { .then((res: any) => {
@ -314,7 +320,7 @@ const CoursePage = () => {
// 加载列表 // 加载列表
useEffect(() => { useEffect(() => {
getList(); getList();
}, [category_ids, dep_ids, refresh, page, size]); }, [category_ids, dep_ids, refresh, page, size, tabKey]);
const paginationProps = { const paginationProps = {
current: page, //当前页码 current: page, //当前页码
@ -348,7 +354,7 @@ const CoursePage = () => {
</div> </div>
<div className="right-box"> <div className="right-box">
<div className="playedu-main-title float-left mb-24"> <div className="playedu-main-title float-left mb-24">
线 | {selLabel} 线 | {tabKey === 1 ? selLabel : selDepLabel}
</div> </div>
<div className="float-left j-b-flex mb-24"> <div className="float-left j-b-flex mb-24">
<div className="d-flex"> <div className="d-flex">
@ -370,6 +376,7 @@ const CoursePage = () => {
onChange={(e) => { onChange={(e) => {
setTitle(e.target.value); setTitle(e.target.value);
}} }}
allowClear
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入名称关键字" placeholder="请输入名称关键字"
/> />
@ -399,8 +406,8 @@ const CoursePage = () => {
rowKey={(record) => record.id} rowKey={(record) => record.id}
/> />
<CourseCreate <CourseCreate
cateIds={category_ids} cateIds={tabKey === 1 ? category_ids : []}
depIds={dep_ids} depIds={tabKey === 2 ? dep_ids : []}
open={createVisible} open={createVisible}
onCancel={() => { onCancel={() => {
setCreateVisible(false); setCreateVisible(false);

View File

@ -149,13 +149,13 @@ const CourseUserPage = () => {
// 删除学员 // 删除学员
const delItem = () => { const delItem = () => {
if (selectedRowKeys.length === 0) { if (selectedRowKeys.length === 0) {
message.error("请选择学员后再清除"); message.error("请选择学员后再重置");
return; return;
} }
confirm({ confirm({
title: "操作确认", title: "操作确认",
icon: <ExclamationCircleFilled />, icon: <ExclamationCircleFilled />,
content: "确认清除选中学员学习记录?", content: "确认重置选中学员学习记录?",
centered: true, centered: true,
okText: "确认", okText: "确认",
cancelText: "取消", cancelText: "取消",
@ -190,7 +190,7 @@ const CourseUserPage = () => {
<div className="d-flex"> <div className="d-flex">
<PerButton <PerButton
type="primary" type="primary"
text="清除学习记录" text="重置学习记录"
class="mr-16" class="mr-16"
icon={null} icon={null}
p="course" p="course"
@ -206,6 +206,7 @@ const CourseUserPage = () => {
onChange={(e) => { onChange={(e) => {
setName(e.target.value); setName(e.target.value);
}} }}
allowClear
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入姓名关键字" placeholder="请输入姓名关键字"
/> />
@ -217,6 +218,7 @@ const CourseUserPage = () => {
onChange={(e) => { onChange={(e) => {
setEmail(e.target.value); setEmail(e.target.value);
}} }}
allowClear
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入学员邮箱" placeholder="请输入学员邮箱"
/> />

View File

@ -199,7 +199,7 @@ const DashboardPage = () => {
<div <div
className={styles["link-mode"]} className={styles["link-mode"]}
onClick={() => { onClick={() => {
navigate("/member"); navigate("/member/index");
}} }}
> >
<i <i
@ -516,7 +516,7 @@ const DashboardPage = () => {
<div className={styles["usage-guide"]}> <div className={styles["usage-guide"]}>
<img className={styles["banner"]} src={banner} alt="" /> <img className={styles["banner"]} src={banner} alt="" />
<Link <Link
to="https://www.playedu.xyz/docs/docs/intro/" to="https://www.playedu.xyz/docs/docs/guide/"
target="blank" target="blank"
className={styles["link"]} className={styles["link"]}
> >

View File

@ -147,7 +147,11 @@ export const DepartmentCreate: React.FC<PropInterface> = ({
name="name" name="name"
rules={[{ required: true, message: "请输入部门名称!" }]} rules={[{ required: true, message: "请输入部门名称!" }]}
> >
<Input style={{ width: 200 }} placeholder="请输入部门名称" /> <Input
style={{ width: 200 }}
allowClear
placeholder="请输入部门名称"
/>
</Form.Item> </Form.Item>
</Form> </Form>
</div> </div>

View File

@ -194,7 +194,7 @@ const DepartmentPage = () => {
type="link" type="link"
style={{ paddingLeft: 4, paddingRight: 4 }} style={{ paddingLeft: 4, paddingRight: 4 }}
danger danger
onClick={() => navigate("/member")} onClick={() => navigate("/member/index")}
> >
{res.data.users.length} {res.data.users.length}
</Button> </Button>

View File

@ -73,7 +73,6 @@ const LoginPage = () => {
navigate("/", { replace: true }); navigate("/", { replace: true });
} catch (e) { } catch (e) {
message.error("登录出现错误");
console.error("错误信息", e); console.error("错误信息", e);
setLoading(false); setLoading(false);
fetchImageCaptcha(); //刷新图形验证码 fetchImageCaptcha(); //刷新图形验证码
@ -129,6 +128,7 @@ const LoginPage = () => {
style={{ width: 400, height: 54 }} style={{ width: 400, height: 54 }}
placeholder="请输入管理员邮箱账号" placeholder="请输入管理员邮箱账号"
onKeyUp={(e) => keyUp(e)} onKeyUp={(e) => keyUp(e)}
allowClear
/> />
</div> </div>
<div className="login-box d-flex mt-50"> <div className="login-box d-flex mt-50">
@ -137,6 +137,7 @@ const LoginPage = () => {
onChange={(e) => { onChange={(e) => {
setPassword(e.target.value); setPassword(e.target.value);
}} }}
allowClear
style={{ width: 400, height: 54 }} style={{ width: 400, height: 54 }}
placeholder="请输入密码" placeholder="请输入密码"
/> />
@ -149,6 +150,7 @@ const LoginPage = () => {
onChange={(e) => { onChange={(e) => {
setCaptchaVal(e.target.value); setCaptchaVal(e.target.value);
}} }}
allowClear
onKeyUp={(e) => keyUp(e)} onKeyUp={(e) => keyUp(e)}
/> />
<div className={styles["captcha-box"]}> <div className={styles["captcha-box"]}>

View File

@ -8,6 +8,7 @@ import { ValidataCredentials } from "../../../utils/index";
interface PropInterface { interface PropInterface {
open: boolean; open: boolean;
depIds: any;
onCancel: () => void; onCancel: () => void;
} }
@ -17,7 +18,11 @@ interface Option {
children?: Option[]; children?: Option[];
} }
export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => { export const MemberCreate: React.FC<PropInterface> = ({
open,
depIds,
onCancel,
}) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [departments, setDepartments] = useState<any>([]); const [departments, setDepartments] = useState<any>([]);
@ -39,10 +44,10 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
password: "", password: "",
avatar: memberDefaultAvatar, avatar: memberDefaultAvatar,
idCard: "", idCard: "",
dep_ids: [], dep_ids: depIds,
}); });
setAvatar(memberDefaultAvatar); setAvatar(memberDefaultAvatar);
}, [form, open]); }, [form, open, depIds]);
const getParams = () => { const getParams = () => {
department.departmentList().then((res: any) => { department.departmentList().then((res: any) => {
@ -154,7 +159,11 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
name="email" name="email"
rules={[{ required: true, message: "请输入登录邮箱!" }]} rules={[{ required: true, message: "请输入登录邮箱!" }]}
> >
<Input style={{ width: 274 }} placeholder="请输入学员登录邮箱" /> <Input
allowClear
style={{ width: 274 }}
placeholder="请输入学员登录邮箱"
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="登录密码" label="登录密码"
@ -162,6 +171,7 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
rules={[{ required: true, message: "请输入登录密码!" }]} rules={[{ required: true, message: "请输入登录密码!" }]}
> >
<Input.Password <Input.Password
allowClear
style={{ width: 274 }} style={{ width: 274 }}
placeholder="请输入登录密码" placeholder="请输入登录密码"
/> />
@ -182,7 +192,11 @@ export const MemberCreate: React.FC<PropInterface> = ({ open, onCancel }) => {
/> />
</Form.Item> </Form.Item>
<Form.Item label="身份证号" name="idCard"> <Form.Item label="身份证号" name="idCard">
<Input style={{ width: 274 }} placeholder="请填写学员身份证号" /> <Input
style={{ width: 274 }}
allowClear
placeholder="请填写学员身份证号"
/>
</Form.Item> </Form.Item>
</Form> </Form>
</div> </div>

View File

@ -0,0 +1,252 @@
import { useState, useEffect } from "react";
import styles from "./progrss.module.less";
import { Table, Modal, message } from "antd";
import { PerButton, DurationText } from "../../../compenents";
import { user as member } from "../../../api/index";
import type { ColumnsType } from "antd/es/table";
import { dateFormat } from "../../../utils/index";
import { ExclamationCircleFilled } from "@ant-design/icons";
const { confirm } = Modal;
interface DataType {
id: React.Key;
title: string;
type: string;
created_at: string;
duration: number;
finished_duration: number;
is_finished: boolean;
finished_at: boolean;
}
interface PropInterface {
open: boolean;
uid: number;
id: number;
onCancel: () => void;
}
export const MemberLearnProgressDialog: React.FC<PropInterface> = ({
open,
uid,
id,
onCancel,
}) => {
const [loading, setLoading] = useState<boolean>(false);
const [list, setList] = useState<any>([]);
const [records, setRecords] = useState<any>({});
const [refresh, setRefresh] = useState(false);
useEffect(() => {
if (open) {
getData();
}
}, [uid, id, open, refresh]);
const getData = () => {
if (loading) {
return;
}
setLoading(true);
member.learnCoursesProgress(uid, id, {}).then((res: any) => {
setList(res.data.hours);
setRecords(res.data.learn_records);
setLoading(false);
});
};
const column: ColumnsType<DataType> = [
{
title: "课时标题",
dataIndex: "title",
render: (title: string) => (
<>
<span>{title}</span>
</>
),
},
{
title: "总时长",
width: 120,
dataIndex: "duration",
render: (duration: number) => (
<>
<DurationText duration={duration}></DurationText>
</>
),
},
{
title: "已学习时长",
width: 120,
dataIndex: "finished_duration",
render: (_, record: any) => (
<>
{records && records[record.id] ? (
<span>
<DurationText
duration={records[record.id].finished_duration || 0}
></DurationText>
</span>
) : (
<span>-</span>
)}
</>
),
},
{
title: "是否学完",
width: 100,
dataIndex: "is_finished",
render: (_, record: any) => (
<>
{records &&
records[record.id] &&
records[record.id].is_finished === 1 ? (
<span className="c-green"></span>
) : (
<span className="c-red"></span>
)}
</>
),
},
{
title: "开始时间",
width: 150,
dataIndex: "created_at",
render: (_, record: any) => (
<>
{records && records[record.id] ? (
<span>{dateFormat(records[record.id].created_at)}</span>
) : (
<span>-</span>
)}
</>
),
},
{
title: "学完时间",
width: 150,
dataIndex: "finished_at",
render: (_, record: any) => (
<>
{records && records[record.id] ? (
<span>{dateFormat(records[record.id].finished_at)}</span>
) : (
<span>-</span>
)}
</>
),
},
{
title: "操作",
key: "action",
fixed: "right",
width: 70,
render: (_, record: any) => (
<>
{records && records[record.id] ? (
<PerButton
type="link"
text="重置"
class="b-link c-red"
icon={null}
p="user-learn-destroy"
onClick={() => {
clearSingleProgress(records[record.id].hour_id);
}}
disabled={null}
/>
) : (
<span>-</span>
)}
</>
),
},
];
const clearProgress = () => {
confirm({
title: "操作确认",
icon: <ExclamationCircleFilled />,
content: "确认重置此课程下所有课时的学习记录?",
centered: true,
okText: "确认",
cancelText: "取消",
onOk() {
member.destroyAllUserLearned(uid, id).then((res: any) => {
message.success("操作成功");
setRefresh(!refresh);
});
},
onCancel() {
console.log("Cancel");
},
});
};
const clearSingleProgress = (hour_id: number) => {
if (hour_id === 0) {
return;
}
confirm({
title: "操作确认",
icon: <ExclamationCircleFilled />,
content: "确认重置此课时的学习记录?",
centered: true,
okText: "确认",
cancelText: "取消",
onOk() {
member.destroyUserLearned(uid, id, hour_id).then((res: any) => {
message.success("操作成功");
setRefresh(!refresh);
});
},
onCancel() {
console.log("Cancel");
},
});
};
return (
<>
<Modal
title="课时学习进度"
centered
forceRender
open={open}
width={1000}
onOk={() => onCancel()}
onCancel={() => onCancel()}
maskClosable={false}
footer={null}
>
<div className="d-flex mt-24">
<PerButton
type="primary"
text="重置学习记录"
class="c-white"
icon={null}
p="user-learn-destroy"
onClick={() => {
clearProgress();
}}
disabled={null}
/>
</div>
<div
className="d-flex mt-24"
style={{ maxHeight: 800, overflowY: "auto" }}
>
<Table
columns={column}
dataSource={list}
loading={loading}
rowKey={(record) => record.id}
pagination={false}
/>
</div>
</Modal>
</>
);
};

View File

@ -181,18 +181,27 @@ export const MemberUpdate: React.FC<PropInterface> = ({
name="name" name="name"
rules={[{ required: true, message: "请输入学员姓名!" }]} rules={[{ required: true, message: "请输入学员姓名!" }]}
> >
<Input style={{ width: 274 }} placeholder="请填写学员姓名" /> <Input
allowClear
style={{ width: 274 }}
placeholder="请填写学员姓名"
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="登录邮箱" label="登录邮箱"
name="email" name="email"
rules={[{ required: true, message: "请输入登录邮箱!" }]} rules={[{ required: true, message: "请输入登录邮箱!" }]}
> >
<Input style={{ width: 274 }} placeholder="请输入学员登录邮箱" /> <Input
style={{ width: 274 }}
allowClear
placeholder="请输入学员登录邮箱"
/>
</Form.Item> </Form.Item>
<Form.Item label="登录密码" name="password"> <Form.Item label="登录密码" name="password">
<Input.Password <Input.Password
style={{ width: 274 }} style={{ width: 274 }}
allowClear
placeholder="请输入登录密码" placeholder="请输入登录密码"
/> />
</Form.Item> </Form.Item>
@ -212,7 +221,11 @@ export const MemberUpdate: React.FC<PropInterface> = ({
/> />
</Form.Item> </Form.Item>
<Form.Item label="身份证号" name="idCard"> <Form.Item label="身份证号" name="idCard">
<Input style={{ width: 274 }} placeholder="请填写学员身份证号" /> <Input
allowClear
style={{ width: 274 }}
placeholder="请填写学员身份证号"
/>
</Form.Item> </Form.Item>
</Form> </Form>
</div> </div>

View File

@ -9,12 +9,14 @@ import {
Space, Space,
message, message,
Table, Table,
Select,
} from "antd"; } from "antd";
import { useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import { BackBartment, DurationText } from "../../compenents"; import { BackBartment, DurationText } from "../../compenents";
import { dateFormat } from "../../utils/index"; import { dateFormat } from "../../utils/index";
import { user as member } from "../../api/index"; import { user as member } from "../../api/index";
const { Column, ColumnGroup } = Table; const { Column, ColumnGroup } = Table;
import * as XLSX from "xlsx";
interface DataType { interface DataType {
id: React.Key; id: React.Key;
@ -40,8 +42,20 @@ const MemberDepartmentProgressPage = () => {
const [name, setName] = useState<string>(""); const [name, setName] = useState<string>("");
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [id_card, setIdCard] = useState<string>(""); const [id_card, setIdCard] = useState<string>("");
const [showMode, setShowMode] = useState<string>("all");
const [did, setDid] = useState(Number(result.get("id"))); const [did, setDid] = useState(Number(result.get("id")));
const [title, setTitle] = useState(String(result.get("title"))); const [title, setTitle] = useState(String(result.get("title")));
const [exportLoading, setExportLoading] = useState(false);
const modes = [
{ label: "全部", value: "all" },
{ label: "不显示公开课", value: "only_dep" },
];
useEffect(() => {
setDid(Number(result.get("id")));
setTitle(String(result.get("title")));
resetData();
}, [result.get("id"), result.get("title")]);
useEffect(() => { useEffect(() => {
getData(); getData();
@ -59,6 +73,7 @@ const MemberDepartmentProgressPage = () => {
name: name, name: name,
email: email, email: email,
id_card: id_card, id_card: id_card,
show_mode: showMode,
}) })
.then((res: any) => { .then((res: any) => {
setList(res.data.data); setList(res.data.data);
@ -81,6 +96,7 @@ const MemberDepartmentProgressPage = () => {
setName(""); setName("");
setEmail(""); setEmail("");
setIdCard(""); setIdCard("");
setShowMode("all");
setPage(1); setPage(1);
setSize(10); setSize(10);
setList([]); setList([]);
@ -125,14 +141,80 @@ const MemberDepartmentProgressPage = () => {
} }
}; };
const exportExcel = () => {
if (exportLoading) {
return;
}
setExportLoading(true);
let filter = {
sort_field: "",
sort_algo: "",
name: name,
email: email,
id_card: id_card,
show_mode: showMode,
};
member.departmentProgress(did, page, total, filter).then((res: any) => {
if (res.data.total === 0) {
message.error("数据为空");
setExportLoading(false);
return;
}
let filename = title + "学习进度.xlsx";
let sheetName = "sheet1";
let data = [];
let arr = ["学员"];
courses.map((item: any) => {
arr.push(item.title);
});
arr.push("总计课时");
data.push(arr);
res.data.data.forEach((item: any) => {
let arr = [item.name];
courses.map((it: any) => {
if (records && records[item.id] && records[item.id][it.id]) {
if (records && records[item.id][it.id].is_finished === 1) {
arr.push("已学完");
} else {
arr.push(
records &&
records[item.id][it.id].finished_count + " / " + it.class_hour
);
}
} else {
arr.push(0 + " / " + it.class_hour);
}
});
arr.push(getFinishedHours(records[item.id]) + " / " + totalHour);
data.push(arr);
});
const jsonWorkSheet = XLSX.utils.json_to_sheet(data);
const workBook: XLSX.WorkBook = {
SheetNames: [sheetName],
Sheets: {
[sheetName]: jsonWorkSheet,
},
};
XLSX.writeFile(workBook, filename);
setExportLoading(false);
});
};
return ( return (
<div className="playedu-main-body"> <div className="playedu-main-body">
<div className="float-left mb-24"> <div className="float-left mb-24">
<BackBartment title={title + "学习进度"} /> <BackBartment title={title + "学习进度"} />
</div> </div>
<div className="float-left j-b-flex mb-24"> <div className="float-left j-b-flex mb-24">
<div className="d-flex helper-text "> <div className="d-flex">
/ <Button type="default" onClick={() => exportExcel()}>
</Button>
<div className="helper-text ml-24">
/
</div>
</div> </div>
<div className="d-flex"> <div className="d-flex">
<div className="d-flex mr-24 "> <div className="d-flex mr-24 ">
@ -142,30 +224,32 @@ const MemberDepartmentProgressPage = () => {
onChange={(e) => { onChange={(e) => {
setName(e.target.value); setName(e.target.value);
}} }}
allowClear
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入姓名关键字" placeholder="请输入姓名关键字"
/> />
</div> </div>
<div className="d-flex mr-24"> {/* <div className="d-flex mr-24">
<Typography.Text></Typography.Text> <Typography.Text></Typography.Text>
<Input <Input
value={email} value={email}
onChange={(e) => { onChange={(e) => {
setEmail(e.target.value); setEmail(e.target.value);
}} }}
allowClear
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入邮箱" placeholder="请输入邮箱"
/> />
</div> </div>
{/* <div className="d-flex mr-24"> <div className="d-flex mr-24">
<Typography.Text></Typography.Text> <Typography.Text></Typography.Text>
<Input <Select
value={id_card}
onChange={(e) => {
setIdCard(e.target.value);
}}
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入身份证号" allowClear
placeholder="请选择"
value={showMode}
onChange={(value: string) => setShowMode(value)}
options={modes}
/> />
</div> */} </div> */}
<div className="d-flex"> <div className="d-flex">
@ -198,7 +282,7 @@ const MemberDepartmentProgressPage = () => {
title="学员" title="学员"
dataIndex="name" dataIndex="name"
key="name" key="name"
width={100} width={150}
render={(_, record: any) => ( render={(_, record: any) => (
<> <>
<Image <Image
@ -218,12 +302,12 @@ const MemberDepartmentProgressPage = () => {
ellipsis={true} ellipsis={true}
dataIndex="id" dataIndex="id"
key={item.id} key={item.id}
width={100} width={168}
render={(_, record: any) => ( render={(_, record: any) => (
<> <>
{records[record.id] && records[record.id][item.id] ? ( {records[record.id] && records[record.id][item.id] ? (
records[record.id][item.id].is_finished === 1 ? ( records[record.id][item.id].is_finished === 1 ? (
<span></span> <span></span>
) : ( ) : (
<> <>
<span> <span>
@ -243,10 +327,10 @@ const MemberDepartmentProgressPage = () => {
))} ))}
<Column <Column
fixed="right" fixed="right"
title="所有课程总课时" title="课时"
dataIndex="id" dataIndex="id"
key="id" key="id"
width={100} width={150}
render={(_, record: any) => ( render={(_, record: any) => (
<> <>
<span>{getFinishedHours(records[record.id])}</span> /{" "} <span>{getFinishedHours(records[record.id])}</span> /{" "}

View File

@ -58,6 +58,7 @@ const MemberImportPage = () => {
user user
.storeBatch(2, data) .storeBatch(2, data)
.then(() => { .then(() => {
setErrorData([]);
message.success("导入成功!"); message.success("导入成功!");
navigate(-1); navigate(-1);
}) })
@ -92,9 +93,9 @@ const MemberImportPage = () => {
{errorData && {errorData &&
errorData.map((item: any, index: number) => { errorData.map((item: any, index: number) => {
return ( return (
<span key={index} className="c-red mb-10"> <div key={index} className="c-red mb-10">
{item} {item}
</span> </div>
); );
})} })}
</div> </div>

View File

@ -142,7 +142,7 @@ const MemberPage = () => {
<Space size="small"> <Space size="small">
<Link <Link
style={{ textDecoration: "none" }} style={{ textDecoration: "none" }}
to={`/member/learn?id=${record.id}`} to={`/member/learn?id=${record.id}&name=${record.name}`}
> >
<PerButton <PerButton
type="link" type="link"
@ -320,6 +320,7 @@ const MemberPage = () => {
}} }}
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入姓名关键字" placeholder="请输入姓名关键字"
allowClear
/> />
</div> </div>
<div className="d-flex mr-24"> <div className="d-flex mr-24">
@ -331,6 +332,7 @@ const MemberPage = () => {
}} }}
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入邮箱账号" placeholder="请输入邮箱账号"
allowClear
/> />
</div> </div>
<div className="d-flex"> <div className="d-flex">
@ -359,6 +361,7 @@ const MemberPage = () => {
/> />
<MemberCreate <MemberCreate
open={createVisible} open={createVisible}
depIds={dep_ids}
onCancel={() => { onCancel={() => {
setCreateVisible(false); setCreateVisible(false);
setRefresh(!refresh); setRefresh(!refresh);

View File

@ -1,13 +1,13 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import styles from "./learn.module.less"; import styles from "./learn.module.less";
import { Row, Image, Table } from "antd"; import { Row, Image, Table, Button, Select } from "antd";
import { useLocation } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { BackBartment, DurationText } from "../../compenents"; import { BackBartment, DurationText } from "../../compenents";
import { dateFormat } from "../../utils/index"; import { dateFormat } from "../../utils/index";
import { user as member } from "../../api/index"; import { user as member } from "../../api/index";
import * as echarts from "echarts"; import * as echarts from "echarts";
import type { ColumnsType } from "antd/es/table"; import type { ColumnsType } from "antd/es/table";
import { duration } from "moment"; import { MemberLearnProgressDialog } from "./compenents/progress";
interface DataType { interface DataType {
id: React.Key; id: React.Key;
@ -21,22 +21,29 @@ interface DataType {
const MemberLearnPage = () => { const MemberLearnPage = () => {
let chartRef = useRef(null); let chartRef = useRef(null);
const navigate = useNavigate();
const result = new URLSearchParams(useLocation().search); const result = new URLSearchParams(useLocation().search);
const [loading, setLoading] = useState<boolean>(false);
const [page, setPage] = useState(1);
const [size, setSize] = useState(10);
const [list, setList] = useState<any>([]);
const [hours, setHours] = useState<any>({});
const [total, setTotal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [loading2, setLoading2] = useState<boolean>(false); const [loading2, setLoading2] = useState<boolean>(false);
const [page2, setPage2] = useState(1);
const [size2, setSize2] = useState(10);
const [list2, setList2] = useState<any>([]); const [list2, setList2] = useState<any>([]);
const [courses, setCourses] = useState<any>({}); const [courses, setCourses] = useState<any>({});
const [deps, setDeps] = useState<any>([]);
const [depValue, setDepValue] = useState<number>(0);
const [currentCourses, setCurrentCourses] = useState<any>([]);
const [openCourses, setOpenCourses] = useState<any>([]);
const [records, setRecords] = useState<any>({});
const [total2, setTotal2] = useState(0); const [total2, setTotal2] = useState(0);
const [refresh2, setRefresh2] = useState(false); const [refresh2, setRefresh2] = useState(false);
const [uid, setUid] = useState(Number(result.get("id"))); const [uid, setUid] = useState(Number(result.get("id")));
const [userName, setUserName] = useState<string>(String(result.get("name")));
const [visiable, setVisiable] = useState(false);
const [courseId, setcourseId] = useState<number>(0);
useEffect(() => {
setUid(Number(result.get("id")));
setUserName(String(result.get("name")));
setLoading2(false);
setRefresh2(!refresh2);
}, [result.get("id"), result.get("name")]);
useEffect(() => { useEffect(() => {
getZxtData(); getZxtData();
@ -46,12 +53,22 @@ const MemberLearnPage = () => {
}, [uid]); }, [uid]);
useEffect(() => { useEffect(() => {
getLearnHours(); getLearnCourses();
}, [refresh, page, size]); }, [refresh2, uid]);
useEffect(() => { useEffect(() => {
getLearnCourses(); if (depValue === 0) {
}, [refresh2, page2, size2]); return;
}
let arr = [...courses[depValue]];
let arr2 = [...openCourses];
if (arr2.length > 0) {
var data = arr.concat(arr2);
setCurrentCourses(data);
} else {
setCurrentCourses(arr);
}
}, [depValue]);
const getZxtData = () => { const getZxtData = () => {
member.learnStats(uid).then((res: any) => { member.learnStats(uid).then((res: any) => {
@ -127,125 +144,34 @@ const MemberLearnPage = () => {
}; };
}; };
const getLearnHours = () => {
if (loading) {
return;
}
setLoading(true);
member
.learnHours(uid, page, size, {
sort_field: "",
sort_algo: "",
is_finished: "",
})
.then((res: any) => {
setList(res.data.data);
setHours(res.data.hours);
setTotal(res.data.total);
setLoading(false);
});
};
const getLearnCourses = () => { const getLearnCourses = () => {
if (loading2) { if (loading2) {
return; return;
} }
setLoading2(true); setLoading2(true);
member member.learnAllCourses(uid).then((res: any) => {
.learnCourses(uid, page2, size2, { setList2(res.data.departments);
sort_field: "", setCourses(res.data.dep_courses);
sort_algo: "", setOpenCourses(res.data.open_courses);
is_finished: "", setRecords(res.data.user_course_records);
}) if (res.data.departments.length > 0) {
.then((res: any) => { let box: any = [];
setList2(res.data.data); res.data.departments.map((item: any) => {
setCourses(res.data.courses); box.push({
setTotal2(res.data.total); label: item.name,
setLoading2(false); value: String(item.id),
}); });
});
setDepValue(Number(box[0].value));
setDeps(box);
} else {
setDepValue(0);
setDeps([]);
}
setLoading2(false);
});
}; };
const paginationProps = {
current: page, //当前页码
pageSize: size,
total: total, // 总条数
onChange: (page: number, pageSize: number) =>
handlePageChange(page, pageSize), //改变页码的函数
showSizeChanger: true,
};
const handlePageChange = (page: number, pageSize: number) => {
setPage(page);
setSize(pageSize);
};
const paginationProps2 = {
current: page2, //当前页码
pageSize: size2,
total: total2, // 总条数
onChange: (page: number, pageSize: number) =>
handlePageChange2(page, pageSize), //改变页码的函数
showSizeChanger: true,
};
const handlePageChange2 = (page: number, pageSize: number) => {
setPage2(page);
setSize2(pageSize);
};
const columns: ColumnsType<DataType> = [
{
title: "课时标题",
dataIndex: "title",
render: (_, record: any) => (
<>
<span>{hours[record.hour_id].title}</span>
</>
),
},
{
title: "课时类型",
dataIndex: "type",
render: (_, record: any) => (
<>
<span>{hours[record.hour_id].type}</span>
</>
),
},
{
title: "总时长",
dataIndex: "total_duration",
render: (_, record: any) => (
<>
<DurationText duration={record.total_duration}></DurationText>
</>
),
},
{
title: "已学习时长",
dataIndex: "finished_duration",
render: (_, record: any) => (
<>
<DurationText duration={record.finished_duration || 0}></DurationText>
</>
),
},
{
title: "状态",
dataIndex: "is_finished",
render: (_, record: any) => (
<>
{record.is_finished === 1 ? <span></span> : <span></span>}
</>
),
},
{
title: "时间",
dataIndex: "created_at",
render: (text: string) => <span>{dateFormat(text)}</span>,
},
];
const column2: ColumnsType<DataType> = [ const column2: ColumnsType<DataType> = [
{ {
title: "课程名称", title: "课程名称",
@ -253,13 +179,13 @@ const MemberLearnPage = () => {
render: (_, record: any) => ( render: (_, record: any) => (
<div className="d-flex"> <div className="d-flex">
<Image <Image
src={courses[record.course_id].thumb} src={record.thumb}
preview={false} preview={false}
width={80} width={80}
height={60} height={60}
style={{ borderRadius: 6 }} style={{ borderRadius: 6 }}
/> />
<span className="ml-8">{courses[record.course_id].title}</span> <span className="ml-8">{record.title}</span>
</div> </div>
), ),
}, },
@ -269,7 +195,9 @@ const MemberLearnPage = () => {
render: (_, record: any) => ( render: (_, record: any) => (
<> <>
<span> <span>
{record.finished_count} / {record.hour_count}
{(records[record.id] && records[record.id].finished_count) ||
0} / {record.class_hour}
</span> </span>
</> </>
), ),
@ -277,38 +205,93 @@ const MemberLearnPage = () => {
{ {
title: "第一次学习时间", title: "第一次学习时间",
dataIndex: "created_at", dataIndex: "created_at",
render: (text: string) => <span>{dateFormat(text)}</span>, render: (_, record: any) => (
<>
{records[record.id] ? (
<span>{dateFormat(records[record.id].created_at)}</span>
) : (
<span>-</span>
)}
</>
),
}, },
{ {
title: "学习完成时间", title: "学习完成时间",
dataIndex: "finished_at", dataIndex: "finished_at",
render: (text: string) => <span>{dateFormat(text)}</span>, render: (_, record: any) => (
<>
{records[record.id] ? (
<span>{dateFormat(records[record.id].finished_at)}</span>
) : (
<span>-</span>
)}
</>
),
}, },
{ {
title: "学习进度", title: "学习进度",
dataIndex: "is_finished", dataIndex: "is_finished",
render: (_, record: any) => ( render: (_, record: any) => (
<> <>
<span {records[record.id] ? (
className={ <span
Math.floor((record.finished_count / record.hour_count) * 100) >= className={
100 Math.floor(
? "c-green" (records[record.id].finished_count /
: "c-red" records[record.id].hour_count) *
} 100
> ) >= 100
{Math.floor((record.finished_count / record.hour_count) * 100)}% ? "c-green"
</span> : "c-red"
}
>
{Math.floor(
(records[record.id].finished_count /
records[record.id].hour_count) *
100
)}
%
</span>
) : (
<span className="c-red">0%</span>
)}
</> </>
), ),
}, },
{
title: "操作",
key: "action",
fixed: "right",
width: 100,
render: (_, record: any) => (
<Button
type="link"
className="b-link c-red"
onClick={() => {
setcourseId(record.id);
setVisiable(true);
}}
>
</Button>
),
},
]; ];
return ( return (
<> <>
<Row className="playedu-main-top mb-24"> <Row className="playedu-main-top mb-24">
<MemberLearnProgressDialog
open={visiable}
uid={uid}
id={courseId}
onCancel={() => {
setVisiable(false);
setRefresh2(!refresh2);
}}
></MemberLearnProgressDialog>
<div className="float-left mb-24"> <div className="float-left mb-24">
<BackBartment title="学员学习" /> <BackBartment title={userName + "的学习明细"} />
</div> </div>
<div className={styles["charts"]}> <div className={styles["charts"]}>
<div <div
@ -321,27 +304,28 @@ const MemberLearnPage = () => {
></div> ></div>
</div> </div>
<div className="float-left mt-24"> <div className="float-left mt-24">
{list2.length > 1 && (
<div className="d-flex mb-24">
<span></span>
<Select
style={{ width: 160 }}
allowClear
placeholder="请选择部门"
value={String(depValue)}
onChange={(value: string) => setDepValue(Number(value))}
options={deps}
/>
</div>
)}
<Table <Table
columns={column2} columns={column2}
dataSource={list2} dataSource={currentCourses}
loading={loading2} loading={loading2}
pagination={paginationProps2} pagination={false}
rowKey={(record) => record.id} rowKey={(record) => record.id}
/> />
</div> </div>
</Row> </Row>
{/* <div className="playedu-main-top mb-24">
<div className={styles["large-title"]}></div>
<div className="float-left mt-24">
<Table
columns={columns}
dataSource={list}
loading={loading}
pagination={paginationProps}
rowKey={(record) => record.id}
/>
</div>
</div> */}
</> </>
); );
}; };

View File

@ -147,7 +147,11 @@ export const ResourceCategoryCreate: React.FC<PropInterface> = ({
name="name" name="name"
rules={[{ required: true, message: "请输入分类名称!" }]} rules={[{ required: true, message: "请输入分类名称!" }]}
> >
<Input style={{ width: 200 }} placeholder="请输入分类名称" /> <Input
style={{ width: 200 }}
allowClear
placeholder="请输入分类名称"
/>
</Form.Item> </Form.Item>
</Form> </Form>
</div> </div>

View File

@ -169,7 +169,11 @@ export const ResourceCategoryUpdate: React.FC<PropInterface> = ({
name="name" name="name"
rules={[{ required: true, message: "请输入分类名称!" }]} rules={[{ required: true, message: "请输入分类名称!" }]}
> >
<Input style={{ width: 200 }} placeholder="请输入分类名称" /> <Input
style={{ width: 200 }}
allowClear
placeholder="请输入分类名称"
/>
</Form.Item> </Form.Item>
</Form> </Form>
</div> </div>

View File

@ -125,14 +125,22 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
name="name" name="name"
rules={[{ required: true, message: "请输入管理员姓名!" }]} rules={[{ required: true, message: "请输入管理员姓名!" }]}
> >
<Input style={{ width: 200 }} placeholder="请输入管理员姓名" /> <Input
allowClear
style={{ width: 200 }}
placeholder="请输入管理员姓名"
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="邮箱" label="邮箱"
name="email" name="email"
rules={[{ required: true, message: "请输入学员邮箱!" }]} rules={[{ required: true, message: "请输入学员邮箱!" }]}
> >
<Input style={{ width: 200 }} placeholder="请输入学员邮箱" /> <Input
allowClear
style={{ width: 200 }}
placeholder="请输入学员邮箱"
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="密码" label="密码"
@ -140,6 +148,7 @@ export const SystemAdministratorCreate: React.FC<PropInterface> = ({
rules={[{ required: true, message: "请输入登录密码!" }]} rules={[{ required: true, message: "请输入登录密码!" }]}
> >
<Input.Password <Input.Password
allowClear
style={{ width: 200 }} style={{ width: 200 }}
placeholder="请输入登录密码" placeholder="请输入登录密码"
/> />

View File

@ -133,18 +133,27 @@ export const SystemAdministratorUpdate: React.FC<PropInterface> = ({
name="name" name="name"
rules={[{ required: true, message: "请输入管理员姓名!" }]} rules={[{ required: true, message: "请输入管理员姓名!" }]}
> >
<Input style={{ width: 200 }} placeholder="请输入管理员姓名" /> <Input
allowClear
style={{ width: 200 }}
placeholder="请输入管理员姓名"
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="邮箱" label="邮箱"
name="email" name="email"
rules={[{ required: true, message: "请输入学员邮箱!" }]} rules={[{ required: true, message: "请输入学员邮箱!" }]}
> >
<Input style={{ width: 200 }} placeholder="请输入学员邮箱" /> <Input
allowClear
style={{ width: 200 }}
placeholder="请输入学员邮箱"
/>
</Form.Item> </Form.Item>
<Form.Item label="密码" name="password"> <Form.Item label="密码" name="password">
<Input.Password <Input.Password
style={{ width: 200 }} style={{ width: 200 }}
allowClear
placeholder="请输入登录密码" placeholder="请输入登录密码"
/> />
</Form.Item> </Form.Item>

View File

@ -283,6 +283,7 @@ const SystemAdministratorPage = () => {
onChange={(e) => { onChange={(e) => {
setName(e.target.value); setName(e.target.value);
}} }}
allowClear
style={{ width: 160 }} style={{ width: 160 }}
placeholder="请输入管理员姓名" placeholder="请输入管理员姓名"
/> />

View File

@ -180,6 +180,7 @@ export const SystemAdminrolesCreate: React.FC<PropInterface> = ({
<Input <Input
style={{ width: 424 }} style={{ width: 424 }}
placeholder="请在此处输入角色名称" placeholder="请在此处输入角色名称"
allowClear
/> />
</Form.Item> </Form.Item>
<Form.Item label="操作权限" name="action_ids"> <Form.Item label="操作权限" name="action_ids">

View File

@ -191,7 +191,11 @@ export const SystemAdminrolesUpdate: React.FC<PropInterface> = ({
name="name" name="name"
rules={[{ required: true, message: "请输入角色名!" }]} rules={[{ required: true, message: "请输入角色名!" }]}
> >
<Input style={{ width: 424 }} placeholder="请输入角色名" /> <Input
style={{ width: 424 }}
allowClear
placeholder="请输入角色名"
/>
</Form.Item> </Form.Item>
<Form.Item label="操作权限" name="action_ids"> <Form.Item label="操作权限" name="action_ids">
<TreeSelect <TreeSelect

View File

@ -31,7 +31,7 @@ const SystemConfigPage = () => {
useEffect(() => { useEffect(() => {
getDetail(); getDetail();
}, []); }, [tabKey]);
const getDetail = () => { const getDetail = () => {
appConfig.appConfig().then((res: any) => { appConfig.appConfig().then((res: any) => {
@ -244,14 +244,22 @@ const SystemConfigPage = () => {
label="网站标题" label="网站标题"
name="system.name" name="system.name"
> >
<Input style={{ width: 274 }} placeholder="请填写网站标题" /> <Input
style={{ width: 274 }}
allowClear
placeholder="请填写网站标题"
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
style={{ marginBottom: 30 }} style={{ marginBottom: 30 }}
label="网站页脚" label="网站页脚"
name="system.pc_index_footer_msg" name="system.pc_index_footer_msg"
> >
<Input style={{ width: 274 }} placeholder="请填写网站页脚" /> <Input
style={{ width: 274 }}
allowClear
placeholder="请填写网站页脚"
/>
</Form.Item> </Form.Item>
{/* <Form.Item {/* <Form.Item
style={{ marginBottom: 30 }} style={{ marginBottom: 30 }}
@ -315,7 +323,11 @@ const SystemConfigPage = () => {
<Form.Item style={{ marginBottom: 30 }} label="跑马灯内容"> <Form.Item style={{ marginBottom: 30 }} label="跑马灯内容">
<Space align="baseline" style={{ height: 32 }}> <Space align="baseline" style={{ height: 32 }}>
<Form.Item name="player.bullet_secret_text"> <Form.Item name="player.bullet_secret_text">
<Input style={{ width: 274 }} placeholder="自定义跑马灯内容" /> <Input
style={{ width: 274 }}
allowClear
placeholder="自定义跑马灯内容"
/>
</Form.Item> </Form.Item>
<Checkbox <Checkbox
checked={nameChecked} checked={nameChecked}

View File

@ -4,6 +4,7 @@ import { login, system } from "../api";
import InitPage from "../pages/init"; import InitPage from "../pages/init";
import { getToken } from "../utils"; import { getToken } from "../utils";
import KeepAlive from "../compenents/keep-alive";
import LoginPage from "../pages/login"; import LoginPage from "../pages/login";
import HomePage from "../pages/home"; import HomePage from "../pages/home";
@ -94,19 +95,22 @@ const routes: RouteObject[] = [
}, },
{ {
path: "/member", path: "/member",
element: <MemberPage />, element: <KeepAlive />,
}, children: [
{ { path: "/member/index", element: <MemberPage /> },
path: "/member/import", {
element: <MemberImportPage />, path: "/member/import",
}, element: <MemberImportPage />,
{ },
path: "/member/learn", {
element: <MemberLearnPage />, path: "/member/learn",
}, element: <MemberLearnPage />,
{ },
path: "/member/departmentUser", {
element: <MemberDepartmentProgressPage />, path: "/member/departmentUser",
element: <MemberDepartmentProgressPage />,
},
],
}, },
{ {
path: "/system/config/index", path: "/system/config/index",