提交 77cf6ebc authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): move legacy implementations into modules

上级 9ec935b8
import { Outlet } from 'react-router'
const AccessLayout = () => <Outlet />
export default AccessLayout
......@@ -100,3 +100,7 @@ export function setRolePermissions(roleId: number, powerList: Array<string | num
export function getAllPermissions() {
return getPayload<PermissionRecord[]>("/system/power/getListAll", {});
}
export function getTeacherRoles() {
return getPayload<RoleRow[]>("/user/role/getAllList", { type: 2 });
}
......@@ -2,7 +2,7 @@ import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const AccessLayout = lazy(() => import("@/pages/jurisdiction/index"));
const AccessLayout = lazy(() => import("./Layout"));
const AdminRoutes = lazy(() => import("./admin/routes"));
const RoleRoutes = lazy(() => import("./role/routes"));
......
import axios from "@/utils/request.js";
import qs from "qs";
import { message } from "antd";
import { get } from "lodash-es";
// oss
const getAliOSSSTSToken = async (obj) => {
try {
const res = await axios.post("/common/OssUpload/getUploadToken", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "获取OSS令牌失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
export { getAliOSSSTSToken };
export { default } from "@/pages/user-module/userInfo";
import { useState, useEffect } from "react";
import { Form, Input, Button, Space, Row, Col, Drawer } from "antd";
import users from "@/assets/images/icon/userinfo.png";
import { changePass, changeInfo, getUserInfo } from "@/layout/request";
import { useNavigate } from "react-router";
import UploadImage from "@/components/uploadImage";
const UserInfo = () => {
const navigator = useNavigate();
const [imageUrl, setImageUrl] = useState("");
const [form] = Form.useForm();
const [form2] = Form.useForm();
const [showModal, setShowModal] = useState(false);
const fetchData = async (obj) => {
try {
const userData = await getUserInfo(obj);
form.setFieldsValue({
real_name: userData.real_name,
tel: userData.tel,
pic: userData.pic,
});
const imageUrl = userData.pic;
setImageUrl(imageUrl);
} catch (error) {
console.error("Error fetching user data:", error);
}
};
useEffect(() => {
fetchData();
}, []);
const changePasswrd = async (obj) => {
await changePass(obj);
setShowModal(false);
};
const submitForm = async (obj) => {
const updatedData = await changeInfo({ ...obj });
if (updatedData) {
await fetchData();
}
};
return (
<div style={{ padding: "20px 0" }}>
<Form form={form} labelCol={{ span: 2 }} onFinish={submitForm}>
<Form.Item label="头像" className="uploadPar" name="pic">
<UploadImage
value={imageUrl ? imageUrl : users}
onChange={(url) => setImageUrl(url)}
></UploadImage>
</Form.Item>
<Form.Item
label="管理员名称"
name="real_name"
rules={[{ required: true, message: "请输入管理员名称" }]}
>
<Input autoComplete="off" placeholder="请输入管理员名称" style={{ width: 200 }}></Input>
</Form.Item>
<Form.Item label="手机号" name="tel" rules={[{ required: true, message: "请输入手机号" }]}>
<Input autoComplete="off" placeholder="请输入手机号" style={{ width: 200 }}></Input>
</Form.Item>
<Form.Item label="密码">
<Button type="primary" ghost onClick={() => setShowModal(true)}>
修改密码
</Button>
</Form.Item>
<Form.Item wrapperCol={{ offset: 2 }}>
<Space size={20}>
<Button onClick={() => navigator(-1)}>取消</Button>
<Button type="primary" htmlType="submit">
保存
</Button>
</Space>
</Form.Item>
</Form>
<Drawer
open={showModal}
onClose={() => {
setShowModal(false);
}}
mask={false}
maskClosable={false}
footer={null}
centered
destroyOnClose
keyboard={false}
>
<Form labelCol={{ span: 5 }} form={form2} onFinish={changePasswrd}>
<Form.Item
label="旧密码"
name="old_password"
rules={[{ required: true, message: "请输入旧密码" }]}
>
<Input.Password autoComplete="off" placeholder="请输入旧密码"></Input.Password>
</Form.Item>
<Form.Item
label="新密码"
name="password"
rules={[
{ required: true, message: "请输入新密码" },
{
pattern:
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
message:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!",
trigger: ["change", "blur"],
},
{
validator(_, value) {
if (!value || form2.getFieldValue("re_password") === value) {
form2.setFields([{ name: "re_password", errors: [""] }]);
return Promise.resolve();
}
form2.setFields([{ name: "re_password", errors: ["两次输入的密码不一致"] }]);
return Promise.reject();
},
},
]}
>
<Input.Password autoComplete="off" placeholder="请输入新密码"></Input.Password>
</Form.Item>
<Form.Item
label="确认新密码"
name="re_password"
rules={[
{ required: true, message: "请确认新密码" },
{
pattern:
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
message:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!",
trigger: ["change", "blur"],
},
{
validator(_, value) {
if (!value || form2.getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(new Error("两次输入的密码不一致"));
},
},
]}
>
<Input.Password autoComplete="off" placeholder="请确认新密码"></Input.Password>
</Form.Item>
<Form.Item wrapperCol={{ offset: 5 }}>
<Row justify={"center"}>
<Space size={20}>
<Col>
<Button onClick={() => setShowModal(false)}>取消</Button>
</Col>
<Col>
<Button type="primary" htmlType="submit">
确认
</Button>
</Col>
</Space>
</Row>
</Form.Item>
</Form>
</Drawer>
</div>
);
};
export default UserInfo;
import { Outlet } from 'react-router'
const AdvertisementsLayout = () => <Outlet />
export default AdvertisementsLayout
......@@ -2,7 +2,6 @@ import { message } from "antd";
// @ts-expect-error The legacy workspace does not install the qs declaration package.
import qs from "qs";
import { getPositionList as getLegacyPositionList } from "@/pages/advertisement/adsense/request.js";
import request from "@/utils/request.js";
import type { AdvertQuery } from "./query";
......@@ -90,7 +89,7 @@ export function changeAdvertStatus(id: number, status: "0" | "1") {
}
export async function getAdvertPositions(): Promise<AdvertPosition[] | false> {
const result = await getLegacyPositionList();
const result = await getPayload<{ list?: AdvertPosition[] }>("/ad/position/getList", {});
if (!result) return false;
return (result.list ?? []) as AdvertPosition[];
}
......@@ -5,7 +5,7 @@ import type { UploadProps } from "antd";
import dayjs from "dayjs";
import { useEffect, useState } from "react";
import { getAliOSSSTSToken } from "@/pages/setting/help/addedit/requet";
import { getAliOSSSTSToken } from "@/modules/settings/help/request";
interface OssClient {
put: (path: string, file: File) => Promise<{ url: string }>;
......
......@@ -2,7 +2,7 @@ import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const AdvertisementsLayout = lazy(() => import("@/pages/advertisement/index"));
const AdvertisementsLayout = lazy(() => import("./Layout"));
const AdvertRoutes = lazy(() => import("./advert/routes"));
const AdsenseRoutes = lazy(() => import("./adsense/routes"));
......
export * from "@/pages/user-module/login/request";
export * from "./request/login";
const api = {
login: "/user/login",
getCode: "/user/login/verify",
};
export default api;
import axios from "@/utils/request.js";
import qs from "qs";
import api from "./login-api.js";
import { get } from "lodash-es";
import { message } from "antd";
const login = async (obj) => {
try {
const res = await axios.post(api.login, qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (msg === "Undefined index: verify") {
message.error("请先点击按钮进行验证");
return false;
}
if (code === 200) {
return {
code,
...data,
};
} else {
message.error(msg);
return {
msg,
};
}
} catch (error) {
console.error(error);
let msg = get(error.response, "data.msg", "");
if (msg) {
message.error(msg);
}
return false;
}
};
export { login };
const api = {
getCaptcha: "/users/generate_image_code", // 获取验证码 GET
sendSMS: "/docapi/users/send_sms", // 发送手机验证码
loginByPhone: "/docapi/users/phone_login", // 手机号码形式登录
loginByUsername: "/docapi/users/login", // 用户名密码登录
checkSSOLogin: "/user/checkSsoLogin", // sso 登录
};
export default api;
import axios from "@/utils/request";
import { message } from "antd";
import api from "./sso-api";
import { get } from "lodash-es";
import qs from "qs";
// 获取图形验证码
const getCaptcha = async (obj) => {
try {
const res = await axios.get(api.getCaptcha, obj);
const status = get(res, "status", 0);
const data = get(res, "data", "");
if (status === 200) {
return data;
}
} catch (error) {
console.error(error);
let msg = get(error.response, "data.msg", "");
if (msg) {
message.error(msg);
}
return false;
}
};
// 发送短信验证码
const sendSMS = async (data) => {
try {
const res = await axios.post(api.sendSMS, data);
const code = get(res, "data.code", 0);
const msg = get(res, "data.message", "发送失败");
if (code === 200) {
message.success("验证码发送成功!");
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
console.error(error);
let msg = get(error.response, "data.msg", "");
if (msg) {
message.error(msg);
}
return false;
}
};
// 用户名登录
const userNameLogin = async (obj) => {
try {
const res = await axios.post(api.loginByUsername, obj);
const code = get(res, "data.code", 0);
const msg = get(res, "data.message", "登录失败");
const data = get(res, "data.data", "");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
console.error(error);
let msg = get(error.response, "data.msg", "");
if (msg) {
message.error(msg);
}
return false;
}
};
// 手机号登录
const phoneLogin = async (obj) => {
try {
const res = await axios.post(api.loginByPhone, obj);
const code = get(res, "data.code", 0);
const msg = get(res, "data.message", "登录失败");
const data = get(res, "data.data", "");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
console.error(error);
let msg = get(error.response, "data.msg", "");
if (msg) {
message.error(msg);
}
return false;
}
};
const checkSSOLogin = async (obj) => {
try {
const res = await axios.post(api.checkSSOLogin, qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (msg === "Undefined index: verify") {
message.error("请先点击按钮进行验证");
return false;
}
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
console.error(error);
let msg = get(error.response, "data.msg", "");
if (msg) {
message.error(msg);
}
return false;
}
};
export { getCaptcha, sendSMS, userNameLogin, phoneLogin, checkSSOLogin };
export { default } from "@/pages/user-module";
import { Outlet } from "react-router";
import "./AuthLayout.less";
const User = () => {
return (
<div className="user-content">
<div className="form-container">
<div className="left-container"></div>
<div className="form-field-box">
<div className="form-field">
<Outlet />
</div>
</div>
</div>
</div>
);
};
export default User;
export { default } from "@/pages/user-module/login";
import React, { useState, useEffect, useRef } from "react";
import { Form, Input, Button, Row, Col, Space, Image, message } from "antd";
import { CloseOutlined, UserOutlined, LockOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router";
import { checkSSOLogin } from "../request/sso";
import "./LoginView.less";
import loginBtn from "@/assets/images/loginBtn.png";
import loginBtn2 from "@/assets/images/loginBtn2.png";
import Icon1 from "@/assets/login/1.png";
import Icon2 from "@/assets/login/2.png";
import Icon3 from "@/assets/login/3.png";
import Icon4 from "@/assets/login/4.png";
import Icon5 from "@/assets/login/5.png";
import Icon6 from "@/assets/login/6.png";
import Icon7 from "@/assets/login/7.png";
import Icon8 from "@/assets/login/8.png";
import Icon9 from "@/assets/login/9.png";
import Icon10 from "@/assets/login/10.png";
import Icon11 from "@/assets/login/11.png";
import Icon12 from "@/assets/login/12.png";
import Icon13 from "@/assets/login/13.png";
import Icon14 from "@/assets/login/14.png";
import Icon15 from "@/assets/login/15.png";
import Icon16 from "@/assets/login/16.png";
import Icon17 from "@/assets/login/17.png";
import Icon18 from "@/assets/login/18.png";
import Icon19 from "@/assets/login/19.png";
import Icon20 from "@/assets/login/20.png";
import Icon21 from "@/assets/login/21.png";
import Icon22 from "@/assets/login/22.png";
import Icon23 from "@/assets/login/23.png";
import { login } from "../request/login";
import logo from "@/assets/images/icon_logo_login.png";
import { RotateVerify } from "../../../utils/captcha";
const Login = () => {
const navigate = useNavigate();
const [form] = Form.useForm();
const [isActive, setIsActive] = useState(0);
const [loading, setLoading] = useState(false);
const [captchaURL, setCaptchaURL] = useState("");
const [captchaStatus, setCaptchaStatus] = useState(false);
const [captchaText, setCaptchaText] = useState("发送验证码");
const [showCaptcha, setShowCaptcha] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false); //登录图片切换
const [isLoginBtn, setIsLoginBtn] = useState(false); //是否点击了登录按钮
const [code, setCode] = useState("");
const [disLf, setDisLf] = useState("");
const [initialValues, setinitialValues] = useState({
tel: "",
password: "",
});
const timer = useRef();
const getSSOInfo = async () => {
const isSSOData = await checkSSOLogin();
if (isSSOData.run === "login") {
} else if (isSSOData.token) {
localStorage.setItem("kiwi.token", isSSOData.token);
navigate("/");
return;
}
};
useEffect(() => {
getSSOInfo();
}, []);
// 登录
const toLayout = async (obj) => {
setLoading(true);
if (isLoginBtn) {
const data = await login({
...obj,
code: code,
disLf: disLf,
});
if (data.code === 200) {
localStorage.setItem("kiwi.token", data.token);
navigate("/");
return;
} else {
form.setFields([{ name: "password", errors: [data.msg] }]);
}
} else {
message.error("请先完成按钮验证");
}
// 刷新验证码
// await refreshCaptcha();
setLoading(false);
};
const handleStartVerification = () => {
setShowCaptcha(true);
};
// 点击×
const handleGoBack = () => {
setShowCaptcha(false);
};
useEffect(() => {
if (showCaptcha) {
const rotateWrapEle = document.querySelector("#rotateWrap1");
// 确保 DOM 元素存在之后再进行初始化
if (rotateWrapEle) {
const myRotateVerify = new RotateVerify("#rotateWrap1", {
initText: "滑动将图片转正",
slideImage: [
Icon1,
Icon2,
Icon3,
Icon4,
Icon5,
Icon6,
Icon7,
Icon8,
Icon9,
Icon10,
Icon11,
Icon12,
Icon13,
Icon14,
Icon15,
Icon16,
Icon17,
Icon18,
Icon4,
Icon19,
Icon20,
Icon21,
Icon22,
Icon23,
],
initUrl: "/user/login/initData",
verifyUrl: "/user/login/checkVerify",
getSuccessState: function (data) {
console.log("验证成功", data);
setDisLf(data.disLf);
setCode(data.code);
if (data.disLf) {
setShowCaptcha(false);
setIsLoggedIn(true);
setIsLoginBtn(true);
}
},
});
// 如果需要,在这里处理卸载时清理资源
return () => {
console.log(myRotateVerify.initUrl);
// 清理或销毁 RotateVerify 实例等操作
if (myRotateVerify && typeof myRotateVerify.destroy === "function") {
myRotateVerify.destroy();
}
};
}
}
}, [showCaptcha]);
return (
<div className={showCaptcha ? "captcha-container" : "normal-container"}>
{showCaptcha && (
<div className="rotateWrap">
<div className="cuo" onClick={handleGoBack}>
<CloseOutlined />
</div>
<span className="top-s">身份验证</span>
<span className="top-x">拖动滑块,使图片角度为正</span>
<div id="rotateWrap1">
<div className="rotateverify-contaniner">
<div className="rotate-can-wrap">
<canvas className="rotateCan rotate-can" width="200" height="200"></canvas>
<div className="statusBg status-bg"></div>
</div>
<div className="control-wrap slideDragWrap">
<div className="control-tips">
<p className="c-tips-txt cTipsTxt">滑动将图片转正</p>
</div>
<div className="control-bor-wrap controlBorWrap"></div>
<div className="control-btn slideDragBtn">
{/* <i className='control-btn-ico'></i> */}
<div className="control-btn-ico"></div>
</div>
</div>
</div>
</div>
</div>
)}
{!showCaptcha && (
<div className="img-logo">
<img src={logo} className="img" />
</div>
)}
{!showCaptcha && <div className="login-header">欢迎登录</div>}{" "}
{!showCaptcha && (
<div className="form-data-container">
<Form form={form} onFinish={toLayout} autoComplete="off" initialValues={initialValues}>
<>
<Form.Item
name="tel"
rules={[
{ required: true, message: "请输入手机号!" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确格式的手机号" },
]}
>
<Input
autoComplete="off"
prefix={<UserOutlined />}
size="large"
placeholder="请输入手机号"
/>
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: "请输入密码!" }]}>
<Input.Password
autoComplete="off"
prefix={<LockOutlined />}
size="large"
placeholder="请输入密码"
/>
</Form.Item>
{/* <Form.Item name='verify' rules={[{ required: true, message: '请输入验证码!' }]}>
<Row gutter={16}>
<Col span={16}>
<Input
prefix={<SetIcon src={IconCaptcha} />}
size='large'
placeholder='请输入验证码'
/>
</Col>
<Col span={8}>
<img
src={captchaURL}
alt='图形验证码'
className='captcha'
onClick={refreshCaptcha}
/>
</Col>
</Row>
</Form.Item> */}
<Form.Item name="verify" onClick={handleStartVerification}>
<div style={{ width: "100%", height: "100%", pointerEvents: "none" }}>
{isLoggedIn ? (
<Image
src={loginBtn2}
style={{
width: "100%",
height: "100%",
}}
/>
) : (
<Image
src={loginBtn}
style={{
width: "100%",
height: "100%",
}}
/>
)}
</div>
</Form.Item>
</>
<Form.Item className="login-submit-btn">
<Button htmlType="submit" loading={loading} type="primary" size="large">
登录
</Button>
</Form.Item>
</Form>
</div>
)}
</div>
);
};
export default Login;
import React from "react";
import { Outlet } from "react-router";
const Book = () => {
return <Outlet />;
};
export default Book;
.auditPanel {
background: #fbfbfb;
margin-bottom: 30px;
}
.auditdetail {
.ant-steps-item-icon {
width: 50px !important;
height: 100px;
}
.imgel {
.user {
width: 50px;
height: 50px;
position: relative;
background-size: cover;
}
.status {
width: 20px;
height: 20px;
position: absolute;
right: 0;
bottom: 0;
}
}
// .ant-steps-item-description {
// height: 150px;
// }
.ant-steps-item-tail {
display: none !important;
}
}
import { Navigate, Outlet, useRoutes } from "react-router";
import AuditDetailView from "./views/AuditDetailView";
import AuditListView from "./views/AuditListView";
import "@/pages/books/audit/index.less";
import "./audit.less";
function Layout() { return <Outlet />; }
export default function AuditRoutes() { return useRoutes([{ Component: Layout, children: [{ index: true, Component: AuditListView }, { path: "dataset", Component: AuditListView }, { path: "detail", Component: AuditDetailView }, { path: "*", element: <Navigate to="." replace /> }] }]); }
......@@ -4,7 +4,7 @@ import { useLocation, useNavigate } from "react-router";
import { useSelector } from "react-redux";
import { addExamine, getExamineList } from "../api";
import AuditHistory from "../components/AuditHistory";
import "@/pages/books/audit/index.less";
import "../audit.less";
export default function AuditDetailView() {
const location = useLocation(); const navigate = useNavigate(); const id = (location.state as { id?: string | number } | null)?.id; const { operationPermissionsList = [] } = useSelector((state: any) => state.user); const [form] = Form.useForm(); const [history, setHistory] = useState<any[]>([]); const [loading, setLoading] = useState(false); const [forceOpen, setForceOpen] = useState(false); const [forceMessage, setForceMessage] = useState("");
const loadHistory = async () => { if (!id) return; const data: any = await getExamineList(id); if (data) setHistory(data); };
......
......@@ -3,6 +3,7 @@ import { message } from "antd";
import qs from "qs";
import request from "@/utils/request.js";
import legacyAxios from "@/utils/axios";
import type { ManagementQuery } from "./query";
......@@ -80,6 +81,19 @@ export function deleteManagementBook(id: number) {
return mutate("/book/teacher/del", { id });
}
// 编辑页仍沿用旧接口封装的返回值形状;保留在管理模块中,避免页面反向依赖旧目录。
export function getBook(params: ApiParams) {
return legacyAxios.post("/api/book/teacher/getInfoById", params);
}
export function addBook(params: ApiParams) {
return legacyAxios.post("/api/book/teacher/add", params);
}
export function updateBook(params: ApiParams) {
return legacyAxios.post("/api/book/teacher/edit", params);
}
export function exportManagementBook(id: number) {
return getPayload<ExportBookResult>("/book/teacher/export", { id });
}
......
import axios from "@/utils/axios";
// 获取书籍详情
export function getBook(data) {
return axios.post("/api/book/teacher/getInfoById", data);
}
// 获取章节列表
export function getChapterList(data) {
return axios.post("/api/book/teacher/chapter/getAllList", data);
}
// 获取章节详情
export function getChapter(data) {
return axios.post("/api/book/teacher/chapter/getInfoById", data);
}
// 添加章节
export function addChapter(data) {
return axios.post("/api/book/teacher/chapter/add", data);
}
// 修改章节
export function updateChapter(data) {
return axios.post("/api/book/teacher/chapter/edit", data);
}
// 删除章节
export function delChapter(data) {
return axios.post("/api/book/teacher/chapter/del", data);
}
// 章节排序
export function sortChapter(data) {
return axios.post("/api/book/teacher/chapter/dragOrder", data);
}
// 获取章节编辑者信息
export function getChapterEditors(data) {
return axios.post("/api/book/teacher/chapter/getEditors", data);
}
// 保存章节编辑者
export function updateChapterEditors(data) {
return axios.post("/api/book/teacher/chapter/saveEditors", data);
}
// 知识图谱列表
export function getTagList(params) {
return axios.get("/api/resource/v1/course/course/tag-list", { params });
}
// 知识图谱关联书籍章节
export function bindTag(data) {
return axios.post("/api/resource/v1/course/course/tag-add-book", data);
}
import { useState, useEffect, useCallback } from "react";
import { Modal, Tree, App, Button, Flex, Empty } from "antd";
import { getTagList, bindTag } from "../api";
function getIds(nodes) {
let ids = [];
nodes.forEach((node) => {
ids.push(node.id);
if (node.children && node.children.length > 0) {
ids = ids.concat(getIds(node.children));
}
});
return ids;
}
const BindKnowledgeGraph = ({ chapter = {}, ...props }) => {
const { message } = App.useApp();
const [treeData, setTreeData] = useState([]);
const [expandedKeys, setExpandedKeys] = useState([]);
const fetchList = useCallback(() => {
getTagList({ type: 2, parent_id: chapter.book_id, chapter_id: chapter.id }).then((res) => {
setTreeData(res.data);
setExpandedKeys(getIds(res.data));
});
}, [chapter]);
useEffect(() => {
fetchList();
}, [fetchList]);
const toggleBind = async (node) => {
await bindTag({
tag_id: node.id,
book_id: chapter.book_id,
chapter_id: chapter.id,
option_type: node.is_checked ? "delete" : "add",
});
message.success("操作成功");
fetchList();
};
const titleRender = (node) => {
return (
<Flex justify="space-between">
<span>{node.name}</span>
<Button color="primary" variant="link" size="small" onClick={() => toggleBind(node)}>
{node.is_checked ? "取消关联" : "关联"}
</Button>
</Flex>
);
};
return (
<Modal title="关联知识图谱" centered {...props} footer={null}>
{treeData.length > 0 ? (
<Tree
blockNode
selectable={false}
treeData={treeData}
fieldNames={{ title: "name", key: "id" }}
expandedKeys={expandedKeys}
titleRender={titleRender}
onExpand={setExpandedKeys}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无知识图谱" />
)}
</Modal>
);
};
export default BindKnowledgeGraph;
import { useState, useEffect } from "react";
import { Modal, Form, Radio, Space, App } from "antd";
import { getChapterEditors, updateChapterEditors } from "../api";
const EditChapterEditors = ({ chapter = {}, onChange, ...props }) => {
const { message } = App.useApp();
const [editors, setEditors] = useState([]);
const [value, setValue] = useState("");
useEffect(() => {
getChapterEditors({ book_id: chapter.book_id, chapter_id: chapter.id }).then((res) => {
setEditors(res.data.editors || []);
const value = res.data.selected_editor_ids[0] || "";
setValue(value);
});
}, [chapter]);
const handleSubmit = async () => {
await updateChapterEditors({
book_id: chapter.book_id,
chapter_id: chapter.id,
editor_id: value,
});
message.success("设置成功");
onChange && onChange();
};
return (
<Modal title="设置章节编写者" centered {...props} onOk={handleSubmit}>
<Form labelCol={{ span: 8 }}>
<Form.Item label="当前章节">{chapter.title}</Form.Item>
<Form.Item label="编写者">
<Radio.Group value={value} onChange={(e) => setValue(e.target.value)}>
<Space direction="vertical">
{editors.map((item) => {
return (
<Radio value={item.editor_id} key={item.editor_id}>
{item.real_name}({item.tel})
</Radio>
);
})}
</Space>
</Radio.Group>
</Form.Item>
</Form>
</Modal>
);
};
export default EditChapterEditors;
import { useState, useEffect, useRef } from "react";
import { Form, Button, Space, Input } from "antd";
import { setTreeChapter } from "@/store/modules/user";
import { useDispatch } from "react-redux";
import { chapterEdit, chapterAdd, sectionAdd } from "../request";
const EditChapterTitle = (props) => {
const {
editValue,
setEditValue,
editKey,
setEditKey,
parentId,
bookId,
getChapterTreeList,
expandedKeys,
setExpandedKeys,
editorLoading,
newChapterChange,
} = props;
const dispatch = useDispatch();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [initValues, setInitValues] = useState({ title: editValue });
const inputRef = useRef();
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, [editorLoading]);
useEffect(() => {
form.setFieldsValue({ title: editValue });
setInitValues({ title: editValue });
}, [editValue]);
const onFinish = async (values) => {
setLoading(true);
let data = null;
if (parentId) {
data = await chapterAdd({
pid: parentId === -1 ? 0 : parentId,
book_id: bookId,
name: values.title,
});
} else {
data = await chapterEdit({ id: editKey, name: values.title });
}
await dispatch(setTreeChapter({ saveBookId: bookId, saveChapterId: data.id }));
if (data) {
if (parentId) {
await sectionAdd({ book_id: bookId, chapter_id: data.id, content: "", word_count: 0 });
}
setEditKey(false);
setEditValue(null);
await getChapterTreeList();
if (parentId) {
let temp = [...expandedKeys, parentId];
setExpandedKeys(temp);
await newChapterChange(data.id, values.title);
}
}
setLoading(false);
};
return (
<Form layout="vertical" form={form} onFinish={onFinish} initialValues={initValues}>
<Form.Item
label="章节标题"
name="title"
rules={[{ required: true, message: "请输入章节标题" }]}
extra="最多30个字符"
>
<Input ref={inputRef} autoFocus maxLength={30} placeholder="" allowClear />
</Form.Item>
<Form.Item className="editor-form-buttons" wrapperCol={{ offset: 9, span: 16 }}>
<Space size={20}>
<Button type="default" disabled={loading} onClick={() => setEditKey(false)}>
取消
</Button>
<Button type="primary" loading={loading} htmlType="submit">
确认
</Button>
</Space>
</Form.Item>
</Form>
);
};
export default EditChapterTitle;
.examine {
height: 100%;
.content-box {
height: 100%;
box-sizing: border-box;
overflow: hidden;
.book-content-row {
height: 100%;
overflow: hidden;
display: flex;
gap: 0;
.book-content-tree {
height: 100%;
overflow: hidden;
transition: width 0s;
}
.book-content-editor {
height: 100%;
overflow: hidden;
}
}
.resize-handle {
width: 6px;
cursor: col-resize;
background: transparent;
position: relative;
flex-shrink: 0;
z-index: 10;
transition: background 0.2s;
&:hover,
&:active {
background: #1890ff;
border-radius: 3px;
}
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 30px;
background: #d9d9d9;
border-radius: 1px;
transition: background 0.2s;
}
&:hover::after,
&:active::after {
background: #fff;
}
}
.ant-descriptions-item-label {
padding: 10px !important;
}
.ant-descriptions-view {
border: none;
}
.border {
border: 1px solid #e5e5e5;
height: 100%;
display: flex;
overflow-x: hidden;
overflow-y: auto;
flex-direction: column;
border-radius: 6px;
.section-left-top {
height: 85px;
}
.ant-descriptions.ant-descriptions-bordered > .ant-descriptions-view .ant-descriptions-row {
border-bottom: 1px solid #e5e5e5;
&:nth-of-type(2) {
display: none;
}
}
}
.editor-right {
height: 100%;
.ant-spin-nested-loading,
.ant-spin-container {
height: 100%;
}
}
}
.draggable-tree {
flex: 1;
height: calc(100vh - 285px);
overflow-y: auto;
overflow-x: hidden;
width: 98%;
&.ant-tree .ant-tree-node-content-wrapper {
width: 100%;
}
.ant-tree-switcher-noop {
// display: none;
width: 10px;
}
&.ant-tree .ant-tree-node-content-wrapper {
overflow: hidden;
}
}
.chapter-tree-item {
display: flex;
justify-content: space-between;
overflow: hidden;
.title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 10px;
max-height: 24px;
line-height: 24px;
white-space: nowrap;
}
}
}
.chapter-title-modal {
.ant-modal {
.ant-modal-close {
inset-inline-end: 17px;
top: 16px;
}
.ant-modal-content {
padding: 15px 18px;
}
.ant-divider {
margin: 15px 0;
}
}
}
import axios from "@/utils/request.js";
import qs from "qs";
import { get } from "lodash-es";
import { message } from "antd";
// 用户信息
const getUserInfo = async (obj) => {
try {
const res = await axios.post("/user/user/getUserInfo", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 获取角色已选权限
const getPowerByRoleId = async (obj) => {
try {
const res = await axios.post("/user/role/getPowerByRoleId", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 章节列表
const getAllList = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/getAllList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return {
code,
data,
};
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 添加章节
const chapterAdd = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/add", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
// message.success('添加成功');
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 章节详情
const getInfoById = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/getInfoById", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 修改章节
const chapterEdit = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/edit", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 删除章节
const chapterDel = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/del", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 拖拽章节排序
const dragOrder = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/dragOrder", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 获取章节内容
const getInfoByChapterId = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/getInfoByChapterId", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 添加章节内容
const sectionAdd = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/add", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 修改章节内容
const sectionEdit = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/edit", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
// message.success(msg);
return true;
} else {
message.error(msg);
return { code, msg, data };
}
} catch (error) {
return false;
}
};
// 获取章节的题库列表
const getQuestionList = async (obj) => {
try {
const res = await axios.post("/book/chapter/question/getQuestionList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 移除课后题
const questionDel = async (obj) => {
try {
const res = await axios.post("/book/chapter/question/del", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 添加课后题
const questionAdd = async (obj) => {
try {
const res = await axios.post("/book/chapter/question/add", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 发布审核
const release = async (obj) => {
try {
const res = await axios.post("/book/teacher/release", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 模板列表
const templateGetList = async (obj) => {
try {
const res = await axios.post("/book/template/getList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 模板导入
const templateImport = async (obj) => {
try {
const res = await axios.post("/book/template/import", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 书籍导入
const teacherImport = async (obj) => {
try {
const res = await axios.post("/book/teacher/import", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 保存历史记录
const getRecordList = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/getRecordList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
export {
getAllList,
chapterAdd,
getInfoById,
chapterEdit,
chapterDel,
dragOrder,
getInfoByChapterId,
sectionAdd,
sectionEdit,
getQuestionList,
questionDel,
questionAdd,
release,
templateGetList,
templateImport,
teacherImport,
getRecordList,
getPowerByRoleId,
getUserInfo,
};
export { default } from "@/pages/books/management/addedit";
import { useState, useEffect } from "react";
import { Button, Form, Input, Flex, message } from "antd";
import WangEditor from "@/components/editor";
import UploadImage from "@/components/uploadImage";
import { useLocation, useNavigate } from "react-router";
import { addBook, updateBook, getBook } from "../api";
import SelectTeacher from "@/components/selectTeacher";
const BookAddEdit = () => {
const navigate = useNavigate();
const location = useLocation();
const isEdit = !!location.state?.id;
const [form] = Form.useForm();
const [img, setImg] = useState("");
// 编辑器内容
const [html, setHtml] = useState("");
// 详情信息
const fetchInfo = async () => {
const { data } = await getBook({ id: location.state.id });
setImg(data.img);
setHtml(data.content);
const formFields = {
...data,
editors: data.editors_list.map((item) => {
return { label: item.real_name, value: item.editor_id };
}),
};
form.setFieldsValue(formFields);
};
useEffect(() => {
if (isEdit) fetchInfo();
}, [isEdit]);
const onFinish = async (values) => {
const editors = values.editors?.map((item) => item.value) || [];
const params = { ...values, img, editors };
if (editors.length === 0) delete params.editors;
const res = isEdit
? await updateBook({ id: location.state.id, ...params })
: await addBook(params);
message.success(res.message);
navigate(-1);
};
return (
<Form form={form} onFinish={onFinish} labelCol={{ span: 2 }}>
<Form.Item
label="书籍名称"
name="name"
rules={[
{ required: true, message: "请输入书籍名称" },
{ max: 30, message: "最大30字符" },
]}
>
<Input autoComplete="off" placeholder="请输入书籍名称" style={{ width: 260 }}></Input>
</Form.Item>
<Form.Item
label="作者"
name="authors"
rules={[
{ required: true, message: "请输入作者" },
{ max: 30, message: "最大30字符" },
]}
>
<Input autoComplete="off" placeholder="请输入作者" style={{ width: 260 }}></Input>
</Form.Item>
<Form.Item name="img" label="缩略图" className="uploadPar">
<UploadImage value={img} onChange={(url) => setImg(url)}></UploadImage>
</Form.Item>
<Form.Item
label="编辑推荐"
name="introduction"
rules={[{ max: 65535, message: "最多65535字符" }]}
>
<Input.TextArea
placeholder="请输入编辑推荐"
autoSize={{ maxRows: 8, minRows: 5 }}
style={{ width: 910 }}
/>
</Form.Item>
<Form.Item label="编写者" name="editors">
<SelectTeacher placeholder="请选择编写者" style={{ width: 260 }}></SelectTeacher>
</Form.Item>
<Form.Item label="简介" name="content">
<WangEditor
value={html}
onChange={(editor) => form.setFieldValue("content", editor.getHtml())}
></WangEditor>
</Form.Item>
<Form.Item wrapperCol={{ span: 24 }}>
<Flex gap={20} justify="center">
<Button
onClick={() => {
navigate(-1);
}}
>
取消
</Button>
<Button type="primary" htmlType="submit">
确认
</Button>
</Flex>
</Form.Item>
</Form>
);
};
export default BookAddEdit;
export { default } from "@/pages/books/section";
export { default } from "../chapters";
......@@ -2,7 +2,7 @@ import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const BooksLayout = lazy(() => import("@/pages/books/index"));
const BooksLayout = lazy(() => import("./BooksLayout"));
const ManagementRoutes = lazy(() => import("./management/routes"));
const AuditRoutes = lazy(() => import("./audit/routes"));
const ClassifyRoutes = lazy(() => import("./classify/routes"));
......
......@@ -8,3 +8,24 @@ async function post(path: string, data: Record<string, unknown>, notify = false)
export const getSaleList = (query: SaleQuery) => post("/book/book/getList", query);
export const changeSaleStatus = (id: string | number, status: string) => post("/book/book/changeStatus", { id, status }, true);
export const getSaleDiscussionList = (query: SaleDiscussionQuery) => post("/comment/comment/getCommentList", query);
export const getListAll = (data: Record<string, unknown>) =>
post("/book/chapter/getListAll", data);
export const getInfoById = (data: Record<string, unknown>) =>
post("/book/book/getInfoById", data);
export const publisherList = (data: Record<string, unknown>) =>
post("/book/publisher/getListAll", data);
export const publisherAdd = (data: Record<string, unknown>) =>
post("/book/publisher/add", data, true);
export const editBook = (data: Record<string, unknown>) =>
post("/book/book/edit", data, true);
export const getCommentInfo = (data: Record<string, unknown>) =>
post("/comment/comment/getCommentInfo", data);
export const commentDel = async (data: Record<string, unknown>) => {
const result = await post("/comment/comment/del", data, true);
return result !== false;
};
export const commentReply = async (data: Record<string, unknown>) => {
const result = await post("/comment/comment/reply", data, true);
return result !== false;
};
export { default } from "@/pages/books/sale/discussDetail";
import { Card, Input, Button, Space, Row, Col, Modal, Form } from "antd";
import { DeleteOutlined, FacebookFilled, MessageOutlined } from "@ant-design/icons";
import "./SaleDiscussionDetail.less";
import User from "@/assets/images/icon/user.png";
import { useState, useEffect } from "react";
import { useLocation } from "react-router";
import { getCommentInfo, commentReply, commentDel } from "../api";
const { TextArea } = Input;
const DiscussDetail = () => {
const [showReply, setShowReply] = useState(false);
const [delMoadal, setDelModal] = useState(false);
const {
state: { id, name, book_id },
} = useLocation();
const [form] = Form.useForm();
const [data, setData] = useState({});
const [loading, setLoading] = useState(false);
const [commentId, setcommentId] = useState(0);
// 获取列表
const init = async () => {
setLoading(true);
const data = await getCommentInfo({ id, book_id });
console.log(data);
setData(data);
setLoading(false);
};
useEffect(() => {
init();
}, []);
useEffect(() => {
if (!showReply) {
form.setFieldsValue({ content: "" });
form.resetFields();
}
}, [showReply]);
// 显示回复弹框
const showReplyFn = (id) => {
setcommentId(id);
setShowReply(true);
};
// 点击确认回复
const handleReply = async (obj) => {
const bool = await commentReply({ ...obj, id: commentId, book_id });
if (!bool) return;
setShowReply(false);
init();
};
const handleDel = async () => {
const bool = await commentDel({ id: commentId, book_id });
if (!bool) return;
setDelModal(false);
init();
};
return (
<div className="DiscussDetail">
<Modal
open={showReply}
onCancel={() => {
setShowReply(false);
}}
maskClosable={false}
footer={null}
centered
closeIcon={null}
keyboard={false}
>
<br />
<Form form={form} onFinish={handleReply}>
<Form.Item
label="内容"
name="content"
rules={[{ required: true, message: "请输入内容" }]}
>
<TextArea
placeholder="请输入内容"
autoSize={{
minRows: 3,
maxRows: 5,
}}
></TextArea>
</Form.Item>
<Form.Item>
<Row gutter={20} justify={"center"}>
<Col>
<Button onClick={() => setShowReply(false)}>取消</Button>
</Col>
<Col>
<Button type="primary" htmlType="submit">
确认
</Button>
</Col>
</Row>
</Form.Item>
</Form>
</Modal>
<Modal
mask={false}
centered
open={delMoadal}
onOk={handleDel}
onCancel={() => setDelModal(false)}
footer={(_, { OkBtn, CancelBtn }) => {
return (
<Row justify={"center"} className="delModal">
<Space>
<CancelBtn />
<OkBtn />
</Space>
</Row>
);
}}
okText="确认"
>
<p style={{ textAlign: "center", padding: "50px 0 30px", fontSize: 16 }}>
评论将永久删除,是否继续?
</p>
</Modal>
<Card className="card" title={`书名:《${name}》`} loading={loading}>
<div className="discussBox">
<Row className="discussItem" gutter={10}>
<Col className="img">
<img
src={data.person_pic || User}
width={28}
height={28}
style={{ borderRadius: "50%", objectFit: "cover" }}
/>
</Col>
<Col className="discuss" flex={1}>
<div className="name">{data.person_name}</div>
{/* <div className='content'>{data?.content && data.content[0].content}</div> */}
<div className="content">
{data?.content &&
data.content.map((item, index) => {
const type = item.types;
const value = item.content;
if (type === "audio") {
return (
<div key={index}>
<span>
<audio controls>
<source src={value} type="audio/mp3" />
Your browser does not support the audio element.
</audio>
</span>
</div>
);
} else if (type === "text") {
return <span key={index}>{value}</span>;
} else if (type === "image") {
return <img height={100} src={value}></img>;
}
})}
</div>
<Space>
<Button
type="text"
className="reply"
onClick={() => showReplyFn(data.id)}
icon={<MessageOutlined />}
>
回复
</Button>
<Button
type="text"
className="del"
icon={<DeleteOutlined />}
onClick={() => {
setDelModal(true);
setcommentId(data.id);
}}
>
删除
</Button>
</Space>
</Col>
</Row>
{data.child &&
data.child.map((item) => (
<Row className="discussItem reply" key={item.id} gutter={10}>
<Col span={1}></Col>
<Col className="img">
<img
src={item.person_pic || User}
width={28}
height={28}
style={{ borderRadius: "50%", objectFit: "cover" }}
/>
</Col>
<Col className="discuss" flex={1}>
<div className="name">{item.person_name}</div>
<div className="content">
{item.content &&
item.content.map((item, index) => (
<div key={index}>
{item.types == "text" && <span>{item.content}</span>}
{item.types == "image" && <img src={item.content}></img>}
{item.types == "audio" && <audio controls src={item.content} />}
</div>
))}
</div>
<Space>
<Button
type="text"
className="reply"
onClick={() => showReplyFn(item.id)}
icon={<MessageOutlined />}
>
回复
</Button>
<Button
type="text"
className="del"
icon={<DeleteOutlined />}
onClick={() => {
setDelModal(true);
setcommentId(item.id);
}}
>
删除
</Button>
</Space>
</Col>
</Row>
))}
</div>
{/* <div className="discussCreate">
<Row gutter={10}>
<Col><div className="img"><img src={User} width={28} height={28} /></div></Col>
<Col><Input autoComplete="off" placeholder="发布你的评论" style={{width:754}}></Input ></Col>
<Col>
<Space>
<Button>取消</Button>
<Button type="primary">回复</Button>
</Space>
</Col>
</Row>
</div> */}
</Card>
</div>
);
};
export default DiscussDetail;
.DiscussDetail {
.reply {
color: #aa1941;
}
.del {
color: #999999;
}
.card {
.ant-card-body {
height: calc(100vh - 260px);
overflow: auto;
}
}
.discussItem {
.discuss {
&::after {
display: block;
content: "";
width: 100%;
height: 1px;
background: #e5e5e57f;
margin-top: 16px;
margin-bottom: 10px;
}
.name {
font-size: 12px;
color: #333333;
font-weight: 500;
margin-bottom: 4px;
}
.content {
font-size: 12px;
color: #333333;
}
}
}
}
import { Outlet } from 'react-router'
const MembersLayout = () => <Outlet />
export default MembersLayout
......@@ -2,7 +2,7 @@ import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const MembersLayout = lazy(() => import("@/pages/member/index"));
const MembersLayout = lazy(() => import("./Layout"));
const MemberListRoutes = lazy(() => import("./list/routes"));
const MemberLevelRoutes = lazy(() => import("./level/routes"));
......
import React from "react";
import { Outlet } from "react-router";
const Role = () => {
return (
<div>
<Outlet />
</div>
);
};
export default Role;
......@@ -21,7 +21,7 @@ import dayjs from "dayjs";
import Add from "@/assets/images/icon/add.png";
import { useSelector } from "react-redux";
import AliOSS from "ali-oss";
import { getAliOSSSTSToken } from "@/pages/setting/help/addedit/requet";
import { getAliOSSSTSToken } from "../../help/request";
const AppVersion = () => {
const [data, setData] = useState([]);
const [page_size, setpage_size] = useState(10);
......
import axios from "@/utils/request.js";
import qs from "qs";
import { message } from "antd";
import { get } from "lodash-es";
// oss
const getAliOSSSTSToken = async (obj) => {
try {
const res = await axios.post("/common/OssUpload/getUploadToken", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "获取OSS令牌失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
export { getAliOSSSTSToken };
......@@ -11,7 +11,7 @@ import { uploadFiles } from "@/utils/upload";
import AliOSS from "ali-oss";
import dayjs from "dayjs";
import { getAliOSSSTSToken } from "@/pages/setting/help/addedit/requet";
import { getAliOSSSTSToken } from "../request";
import "./HelpAddEditView.less";
......
......@@ -2,7 +2,7 @@ import { lazy } from "react";
import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const SettingsLayout = lazy(() => import("@/pages/setting/index"));
const SettingsLayout = lazy(() => import("./SettingsLayout"));
const HelpRoutes = lazy(() => import("./help/routes"));
const AppliedRoutes = lazy(() => import("./applied/routes"));
const PersonalizedRoutes = lazy(() => import("./personalized/routes"));
......
export * from "@/pages/student/book/request";
export * from "./request";
import axios from "@/utils/request.js";
import qs from "qs";
import { get } from "lodash-es";
import { message } from "antd";
// 用户信息
const getUserInfo = async (obj) => {
try {
const res = await axios.post("/user/user/getUserInfo", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 获取角色已选权限
const getPowerByRoleId = async (obj) => {
try {
const res = await axios.post("/user/role/getPowerByRoleId", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 章节列表
const getAllList = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/getAllList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return {
code,
data,
};
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 添加章节
const chapterAdd = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/add", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
// message.success('添加成功');
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 章节详情
const getInfoById = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/getInfoById", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 修改章节
const chapterEdit = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/edit", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 删除章节
const chapterDel = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/del", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 拖拽章节排序
const dragOrder = async (obj) => {
try {
const res = await axios.post("/book/teacher/chapter/dragOrder", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 获取章节内容
const getInfoByChapterId = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/getInfoByChapterId", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 添加章节内容
const sectionAdd = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/add", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 修改章节内容
const sectionEdit = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/edit", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
// message.success(msg);
return true;
} else {
message.error(msg);
return { code, msg, data };
}
} catch (error) {
return false;
}
};
// 获取章节的题库列表
const getQuestionList = async (obj) => {
try {
const res = await axios.post("/book/chapter/question/getQuestionList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 移除课后题
const questionDel = async (obj) => {
try {
const res = await axios.post("/book/chapter/question/del", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 添加课后题
const questionAdd = async (obj) => {
try {
const res = await axios.post("/book/chapter/question/add", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 发布审核
const release = async (obj) => {
try {
const res = await axios.post("/book/teacher/release", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 模板列表
const templateGetList = async (obj) => {
try {
const res = await axios.post("/book/template/getList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 模板导入
const templateImport = async (obj) => {
try {
const res = await axios.post("/book/template/import", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 书籍导入
const teacherImport = async (obj) => {
try {
const res = await axios.post("/book/teacher/import", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 保存历史记录
const getRecordList = async (obj) => {
try {
const res = await axios.post("/book/teacher/section/getRecordList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
export {
getAllList,
chapterAdd,
getInfoById,
chapterEdit,
chapterDel,
dragOrder,
getInfoByChapterId,
sectionAdd,
sectionEdit,
getQuestionList,
questionDel,
questionAdd,
release,
templateGetList,
templateImport,
teacherImport,
getRecordList,
getPowerByRoleId,
getUserInfo,
};
差异被折叠。
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论