feat: 用户管理&订阅国企
This commit is contained in:
parent
aab88ebc0f
commit
50dfe31d32
|
@ -39,7 +39,7 @@ export const selectChapterList = ({
|
||||||
chapter_course_id: string;
|
chapter_course_id: string;
|
||||||
}) => R.post("/api/course/chapter/select", { chapter_course_id });
|
}) => R.post("/api/course/chapter/select", { chapter_course_id });
|
||||||
|
|
||||||
export const updateChapter = (chapter: any) =>
|
export const updateChapter = (chapter: IChapter) =>
|
||||||
R.post("/api/course/chapter/update", chapter);
|
R.post("/api/course/chapter/update", chapter);
|
||||||
|
|
||||||
export const removeCourse = (course: ICourseBasic) =>
|
export const removeCourse = (course: ICourseBasic) =>
|
||||||
|
@ -50,3 +50,7 @@ export const createChapter = (chapterList: IChapter[]) =>
|
||||||
|
|
||||||
export const removeChapter = (chapter: IChapter) =>
|
export const removeChapter = (chapter: IChapter) =>
|
||||||
R.post("/api/course/chapter/remove", chapter);
|
R.post("/api/course/chapter/remove", chapter);
|
||||||
|
|
||||||
|
export const selectUserList = () => R.post("/api/user/admin/select/all");
|
||||||
|
|
||||||
|
export const updateUser = (user: any) => R.post("/api/user/admin/update", user);
|
||||||
|
|
|
@ -1,5 +1,192 @@
|
||||||
|
import {
|
||||||
|
CheckOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
SketchOutlined,
|
||||||
|
StopOutlined,
|
||||||
|
UserSwitchOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
DatePicker,
|
||||||
|
Popconfirm,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tooltip,
|
||||||
|
} from "antd";
|
||||||
|
import { RangePickerProps } from "antd/es/date-picker";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { selectUserList, updateUser } from "../../api";
|
||||||
|
import { useMount } from "../../hooks";
|
||||||
|
|
||||||
|
interface IEditUser {
|
||||||
|
key: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultEditUser: IEditUser = {
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
};
|
||||||
|
|
||||||
const User = () => {
|
const User = () => {
|
||||||
return <div>用户据</div>;
|
const [userList, setUserList] = useState([]);
|
||||||
|
const [editUser, setEditUser] = useState<IEditUser>(defaultEditUser);
|
||||||
|
|
||||||
|
const onConfirmEditUser = (user: any) => {
|
||||||
|
updateUser(user).then((res: any) => {
|
||||||
|
if (res?.code === 10000) renderUserList();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabledDate: RangePickerProps["disabledDate"] = (current) => {
|
||||||
|
return current && current < dayjs().endOf("day");
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "id",
|
||||||
|
dataIndex: "id",
|
||||||
|
key: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "用户",
|
||||||
|
dataIndex: "user_login",
|
||||||
|
key: "user_login",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "创建时间",
|
||||||
|
dataIndex: "user_create_time",
|
||||||
|
key: "user_create_time",
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{dayjs(+record.user_create_time).format("YYYY-MM-DD HH:mm:ss")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "状态",
|
||||||
|
dataIndex: "user_status",
|
||||||
|
key: "user_status",
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
return record.user_status ? (
|
||||||
|
<CheckOutlined style={{ color: "#52c41a" }} />
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "订阅",
|
||||||
|
dataIndex: "user_sub",
|
||||||
|
key: "user_sub",
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
return record.user_sub ? (
|
||||||
|
<CheckOutlined style={{ color: "#52c41a" }} />
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "订阅到期",
|
||||||
|
dataIndex: "user_sub_expired",
|
||||||
|
key: "user_sub_expired",
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
return record.user_sub ? (
|
||||||
|
<Space>
|
||||||
|
<span>
|
||||||
|
{dayjs(+record.user_sub_expired).format("YYYY-MM-DD HH:mm:ss")}
|
||||||
|
</span>
|
||||||
|
<Popconfirm
|
||||||
|
placement="top"
|
||||||
|
title="订阅到期时间"
|
||||||
|
description={
|
||||||
|
<DatePicker
|
||||||
|
onChange={(date: any) => {
|
||||||
|
setEditUser({
|
||||||
|
key: "user_sub_expired",
|
||||||
|
value: "" + new Date(date).getTime(),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
presets={[
|
||||||
|
{ label: "订阅 - 年", value: dayjs().add(1, "year") },
|
||||||
|
{ label: "订阅 - 季度", value: dayjs().add(3, "month") },
|
||||||
|
{ label: "订阅 - 月", value: dayjs().add(1, "month") },
|
||||||
|
]}
|
||||||
|
disabledDate={disabledDate}
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
showTime={{ defaultValue: dayjs("00:00:00", "HH:mm:ss") }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onConfirm={() =>
|
||||||
|
onConfirmEditUser({ ...record, [editUser.key]: editUser.value })
|
||||||
|
}
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<Button type="text" icon={<EditOutlined />} />
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
dataIndex: "operation",
|
||||||
|
key: "operation",
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Tooltip title="封号">
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
danger
|
||||||
|
icon={<StopOutlined />}
|
||||||
|
onClick={() =>
|
||||||
|
onConfirmEditUser({
|
||||||
|
...record,
|
||||||
|
user_status: !record.user_status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="订阅">
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
icon={<UserSwitchOutlined />}
|
||||||
|
onClick={() =>
|
||||||
|
onConfirmEditUser({ ...record, user_sub: !record.user_sub })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderUserList = () => {
|
||||||
|
selectUserList().then((res: any) => {
|
||||||
|
const { data = [] } = res;
|
||||||
|
setUserList(data.map((u: any) => ({ ...u, key: u.id })));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useMount(() => renderUserList());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 24 }}>
|
||||||
|
<Card>
|
||||||
|
<Table dataSource={userList} columns={columns} />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
|
|
@ -5,4 +5,5 @@ export enum BizCode {
|
||||||
OK = 10000,
|
OK = 10000,
|
||||||
ERROR = 20000,
|
ERROR = 20000,
|
||||||
AUTH = 40000,
|
AUTH = 40000,
|
||||||
|
FORBID = 50000,
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,9 @@ export class CourseController {
|
||||||
const user = await this.userService.select({ user_login });
|
const user = await this.userService.select({ user_login });
|
||||||
// 用户订阅鉴权
|
// 用户订阅鉴权
|
||||||
if (!user.user_sub)
|
if (!user.user_sub)
|
||||||
return { code: BizCode.AUTH, msg: '无权访问订阅课程' };
|
return { code: BizCode.FORBID, msg: '无权访问订阅课程' };
|
||||||
|
if (+user.user_sub_expired < Date.now())
|
||||||
|
return { code: BizCode.FORBID, msg: '订阅已过期' };
|
||||||
const course = await this.courseService.select({ course_id });
|
const course = await this.courseService.select({ course_id });
|
||||||
const chapterList = await this.chapterService.select(course_id);
|
const chapterList = await this.chapterService.select(course_id);
|
||||||
const guide = await this.guideService.select(course_id);
|
const guide = await this.guideService.select(course_id);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { SmsDTO } from '../dto/sms.dto';
|
||||||
import { RedisService } from '@midwayjs/redis';
|
import { RedisService } from '@midwayjs/redis';
|
||||||
import * as CryptoJS from 'crypto-js';
|
import * as CryptoJS from 'crypto-js';
|
||||||
import { ADMIN, WEB } from '../config/base.config';
|
import { ADMIN, WEB } from '../config/base.config';
|
||||||
|
import { User } from '../entity/user.entity';
|
||||||
@Controller('/user')
|
@Controller('/user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
@Inject()
|
@Inject()
|
||||||
|
@ -37,6 +38,9 @@ export class UserController {
|
||||||
if (!verifyCode) return { code: BizCode.ERROR, msg: '验证码无效' };
|
if (!verifyCode) return { code: BizCode.ERROR, msg: '验证码无效' };
|
||||||
// 查询用户是否存在
|
// 查询用户是否存在
|
||||||
const userExist = await this.userService.select(params);
|
const userExist = await this.userService.select(params);
|
||||||
|
// 用户是否被封号
|
||||||
|
if (!userExist?.user_status)
|
||||||
|
return { code: BizCode.FORBID, msg: '您的账号被封禁' };
|
||||||
const payload = userExist?.id
|
const payload = userExist?.id
|
||||||
? userExist
|
? userExist
|
||||||
: await this.userService.createUser(params);
|
: await this.userService.createUser(params);
|
||||||
|
@ -96,6 +100,11 @@ export class UserController {
|
||||||
try {
|
try {
|
||||||
const token = this.ctx.cookies.get(WEB.SIGN);
|
const token = this.ctx.cookies.get(WEB.SIGN);
|
||||||
const user = decodeToken(token);
|
const user = decodeToken(token);
|
||||||
|
const { user_login } = user;
|
||||||
|
// 查询用户是否被封号
|
||||||
|
const user_current = await this.userService.select({ user_login });
|
||||||
|
const { user_status } = user_current;
|
||||||
|
if (!user_status) return { code: BizCode.FORBID, msg: '您的账号被封禁' };
|
||||||
return { code: BizCode.OK, data: user };
|
return { code: BizCode.OK, data: user };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.ctx.logger.error(error);
|
this.ctx.logger.error(error);
|
||||||
|
@ -107,17 +116,39 @@ export class UserController {
|
||||||
async verifyCode(@Body() params: SmsDTO) {
|
async verifyCode(@Body() params: SmsDTO) {
|
||||||
try {
|
try {
|
||||||
const { phoneNumber: phoneNumbers, sign } = params;
|
const { phoneNumber: phoneNumbers, sign } = params;
|
||||||
|
// 查询手机号是否被封禁
|
||||||
|
const user = await this.userService.select({ user_login: phoneNumbers });
|
||||||
|
if (user && !user.user_status)
|
||||||
|
return { code: BizCode.FORBID, msg: '您的账号被封禁' };
|
||||||
|
// 防止接口调用 start
|
||||||
const decrypted = CryptoJS.AES.decrypt(sign, phoneNumbers);
|
const decrypted = CryptoJS.AES.decrypt(sign, phoneNumbers);
|
||||||
const hackAction = decrypted.toString(CryptoJS.enc.Utf8) !== phoneNumbers;
|
const hackAction = decrypted.toString(CryptoJS.enc.Utf8) !== phoneNumbers;
|
||||||
if (hackAction) return { code: BizCode.ERROR, msg: 'fuck u' };
|
if (hackAction) return { code: BizCode.ERROR, msg: 'fuck u' };
|
||||||
|
// 防止接口调用 end
|
||||||
const code = Math.floor(Math.random() * 9000 + 1000);
|
const code = Math.floor(Math.random() * 9000 + 1000);
|
||||||
await this.redisService.set('' + phoneNumbers, code, 'EX', 60);
|
await this.redisService.set('' + phoneNumbers, code, 'EX', 60);
|
||||||
await this.smsService.send({ code, phoneNumbers });
|
await this.smsService.send({ code, phoneNumbers });
|
||||||
return { code: BizCode.OK };
|
return { code: BizCode.OK };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
|
||||||
this.ctx.logger.error(error);
|
this.ctx.logger.error(error);
|
||||||
return { code: BizCode.ERROR, msg: '[error] /web/sms error' };
|
return { code: BizCode.ERROR, msg: '[error] /web/sms error' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/admin/select/all')
|
||||||
|
async selectUser() {
|
||||||
|
const data = await this.userService.selectAll();
|
||||||
|
return { code: BizCode.OK, data };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/admin/update')
|
||||||
|
async updateUser(@Body() user: User) {
|
||||||
|
try {
|
||||||
|
await this.userService.update(user);
|
||||||
|
return { code: BizCode.OK };
|
||||||
|
} catch (error) {
|
||||||
|
this.ctx.logger.error(error);
|
||||||
|
return { code: BizCode.ERROR, msg: error };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,4 +25,7 @@ export class User {
|
||||||
|
|
||||||
@Column({ default: false })
|
@Column({ default: false })
|
||||||
user_sub?: boolean;
|
user_sub?: boolean;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
user_sub_expired?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@ export class UserService {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async selectAll(): Promise<User[]> {
|
||||||
|
return await this.userModel.find();
|
||||||
|
}
|
||||||
|
|
||||||
async createUser(user: User) {
|
async createUser(user: User) {
|
||||||
const h = hash('' + user.user_login);
|
const h = hash('' + user.user_login);
|
||||||
user.display_name = h.substring(0, 8);
|
user.display_name = h.substring(0, 8);
|
||||||
|
@ -24,4 +28,8 @@ export class UserService {
|
||||||
const result = await this.userModel.save(user);
|
const result = await this.userModel.save(user);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async update(user: User) {
|
||||||
|
this.userModel.save(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { removeLoginCookies } from "../util/cookie";
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
baseURL: "",
|
baseURL: "",
|
||||||
|
@ -38,6 +40,10 @@ instance.interceptors.response.use(
|
||||||
case 40000:
|
case 40000:
|
||||||
toast.error(msg);
|
toast.error(msg);
|
||||||
break;
|
break;
|
||||||
|
case 50000:
|
||||||
|
toast.error(msg);
|
||||||
|
removeLoginCookies();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// TODO ...
|
// TODO ...
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import Cookies from "js-cookie";
|
|
||||||
import { useUserStore } from "../store/user.store";
|
import { useUserStore } from "../store/user.store";
|
||||||
import Result from "../components/Result";
|
import Result from "../components/Result";
|
||||||
|
import { withLoginCookies } from "../util/cookie";
|
||||||
|
|
||||||
interface IGuardProps {
|
interface IGuardProps {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
|
@ -16,14 +16,13 @@ export const Guard = (props: IGuardProps) => {
|
||||||
const fetchUser = useUserStore((s: any) => s.fetchUser);
|
const fetchUser = useUserStore((s: any) => s.fetchUser);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const sign = Cookies.get("_sign_web");
|
|
||||||
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
|
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) fetchUser();
|
if (!user) fetchUser();
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
if (!sign && needAuth) return <Result code={405} />;
|
if (!withLoginCookies() && needAuth) return <Result code={405} />;
|
||||||
|
|
||||||
return props.children;
|
return props.children;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import Cookie from "js-cookie";
|
|
||||||
import { userState } from "../api";
|
import { userState } from "../api";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { removeLoginCookies, withLoginCookies } from "../util/cookie";
|
||||||
|
|
||||||
export const useUserStore = create((set) => {
|
export const useUserStore = create((set) => {
|
||||||
return {
|
return {
|
||||||
user: null,
|
user: null,
|
||||||
setUser: (user: any) => set({ user }),
|
setUser: (user: any) => set({ user }),
|
||||||
fetchUser: async () => {
|
fetchUser: async () => {
|
||||||
const sign = Cookie.get("_sign_web");
|
if (!withLoginCookies()) return set({ user: null });
|
||||||
if (!sign) return set({ user: null });
|
|
||||||
userState().then((res: any) => {
|
userState().then((res: any) => {
|
||||||
const { code, data } = res;
|
const { code, data } = res;
|
||||||
if (code === 10000) set({ user: data });
|
if (code === 10000) set({ user: data });
|
||||||
|
@ -17,8 +16,7 @@ export const useUserStore = create((set) => {
|
||||||
},
|
},
|
||||||
userExit: () => {
|
userExit: () => {
|
||||||
set({ user: null });
|
set({ user: null });
|
||||||
Cookie.remove("_sign_web");
|
removeLoginCookies();
|
||||||
Cookie.remove("_sign_web.sig");
|
|
||||||
toast.success("再见~!");
|
toast.success("再见~!");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
10
apps/web/src/util/cookie.ts
Normal file
10
apps/web/src/util/cookie.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import Cookie from "js-cookie";
|
||||||
|
|
||||||
|
export const withLoginCookies = () => {
|
||||||
|
return Cookie.get("_sign_web") && Cookie.get("_sign_web.sig");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeLoginCookies = () => {
|
||||||
|
Cookie.remove("_sign_web");
|
||||||
|
Cookie.remove("_sign_web.sig");
|
||||||
|
};
|
|
@ -80,44 +80,48 @@ function CourseDetail() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="course-detail">
|
<div className="course-detail">
|
||||||
{toc.length > 0 && (
|
{toc.length > 0 ? (
|
||||||
<aside className="table-of-content">
|
<>
|
||||||
<h2>{course.course_title}</h2>
|
<aside className="table-of-content">
|
||||||
<div>
|
<h2>{course.course_title}</h2>
|
||||||
<div style={{ color: "var(--color-text-3)" }}>
|
<div>
|
||||||
<span>{course.course_createtime}</span>
|
<div style={{ color: "var(--color-text-3)" }}>
|
||||||
|
<span>{course.course_createtime}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="toc">
|
||||||
<div className="toc">
|
{toc.map((i: any) => {
|
||||||
{toc.map((i: any) => {
|
if (i.level === 1) {
|
||||||
if (i.level === 1) {
|
return (
|
||||||
return (
|
<div className="level-1" key={i.title}>
|
||||||
<div className="level-1" key={i.title}>
|
{i.title}
|
||||||
{i.title}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (i.level === 2) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`level-2 ${i.active ? "active" : ""}`}
|
|
||||||
key={i.title}
|
|
||||||
onClick={() => onclickItem(i)}
|
|
||||||
>
|
|
||||||
<div className="bs ellipsis fc">
|
|
||||||
{i.icon}
|
|
||||||
<span>{i.title}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="time">{i.time}</span>
|
);
|
||||||
</div>
|
} else if (i.level === 2) {
|
||||||
);
|
return (
|
||||||
}
|
<div
|
||||||
})}
|
className={`level-2 ${i.active ? "active" : ""}`}
|
||||||
|
key={i.title}
|
||||||
|
onClick={() => onclickItem(i)}
|
||||||
|
>
|
||||||
|
<div className="bs ellipsis fc">
|
||||||
|
{i.icon}
|
||||||
|
<span>{i.title}</span>
|
||||||
|
</div>
|
||||||
|
<span className="time">{i.time}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<div className="content" style={{ left: 300 }}>
|
||||||
|
{view}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</>
|
||||||
|
) : (
|
||||||
|
<Result code={403} />
|
||||||
)}
|
)}
|
||||||
<div className="content" style={{ left: toc.length > 0 ? "300px" : 0 }}>
|
|
||||||
{view}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user