提交 8f59c494 authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): migrate books discussion module

上级 b2266adf
import { message } from "antd";
import request from "@/utils/request";
import type { DiscussionQuery } from "./query";
async function post(path: string, data: Record<string, unknown>, notify = false) {
try {
const r = (await request.post(
path,
new URLSearchParams(data as Record<string, string>).toString(),
)) as { data?: { code?: number; data?: any; message?: string } };
if (r.data?.code === 200) {
if (notify) message.success(r.data.message);
return r.data.data;
}
message.error(r.data?.message || "数据获取失败!");
} catch {}
return false;
}
export const getDiscussionList = (q: DiscussionQuery) =>
post("/comment/comment/getAllCommentList", q);
export const deleteComment = (id: any, book_id: any) =>
post("/comment/comment/delComment", { id, book_id }, true);
export const changeCommentStatus = (data: any) => post("/comment/comment/changeStatus", data, true);
export const getBooks = () => post("/book/book/getAllList", {});
export const getDiscussionSystem = () => post("/system/config/getSystem", { type: 3 });
export const editDiscussionSystem = (set_value: string) =>
post("/system/config/systemEdit", { type: 3, set_value }, true);
import type { Dayjs } from "dayjs";
export interface DiscussionFilters {
book_id?: any;
title?: string;
time?: [Dayjs | null, Dayjs | null] | null;
}
export interface DiscussionQuery {
page: number;
page_size: number;
book_id?: any;
title?: string;
start_time?: number;
end_time?: number;
}
export function buildDiscussionQuery(
page: number,
pageSize: number,
f: DiscussionFilters,
): DiscussionQuery {
const q: DiscussionQuery = { page, page_size: pageSize, book_id: f.book_id };
if (f.title) q.title = f.title;
if (f.time?.[0] && f.time[1]) {
q.start_time = f.time[0].startOf("day").unix();
q.end_time = f.time[1].endOf("day").unix();
}
return q;
}
import { Navigate, useRoutes } from "react-router";
import DiscussionView from "./views/DiscussionView";
export default function DiscussionRoutes() {
return useRoutes([
{ index: true, Component: DiscussionView },
{ path: "*", element: <Navigate to="/books/discussion" /> },
]);
}
import { useCallback, useEffect, useRef, useState } from "react";
import { getDiscussionList } from "./api";
import { buildDiscussionQuery, type DiscussionFilters, type DiscussionQuery } from "./query";
export function useDiscussionList(initialBook?: any) {
const [filters, setFilters] = useState<DiscussionFilters>({ book_id: initialBook });
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [rows, setRows] = useState<any[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const last = useRef<DiscussionQuery>(buildDiscussionQuery(1, 10, { book_id: initialBook }));
const load = useCallback(async (q: DiscussionQuery) => {
last.current = q;
setLoading(true);
try {
const r: any = await getDiscussionList(q);
if (!r) throw new Error("请求失败");
setRows(r.list || []);
setTotal(r.total || 0);
setError(null);
} catch (e) {
setError(e instanceof Error ? e : new Error("请求失败"));
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if (filters.book_id) void load(last.current);
}, [filters.book_id, load]);
const run = (p: number, s: number, f = filters) => void load(buildDiscussionQuery(p, s, f));
const search = () => {
setPage(1);
run(1, pageSize);
};
return {
filters,
setFilters,
page,
pageSize,
rows,
total,
loading,
error,
search,
reset: () => {
const f = { book_id: filters.book_id };
setFilters(f);
setPage(1);
run(1, pageSize, f);
},
refresh: () => run(page, pageSize),
retry: () => void load(last.current),
changePage: (p: number) => {
setPage(p);
run(p, pageSize);
},
changePageSize: (s: number) => {
setPage(1);
setPageSize(s);
run(1, s);
},
};
}
import {
App,
Button,
DatePicker,
Drawer,
Form,
Image,
Input,
Radio,
Select,
Space,
Switch,
} from "antd";
import type { ColumnsType } from "antd/es/table";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { AppList, type AppListFilterItem } from "@/shared/components/AppList";
import {
changeCommentStatus,
deleteComment,
editDiscussionSystem,
getBooks,
getDiscussionSystem,
} from "../api";
import type { DiscussionFilters } from "../query";
import { useDiscussionList } from "../useDiscussionList";
const { RangePicker } = DatePicker;
export default function DiscussionView() {
const [books, setBooks] = useState<any[]>([]);
const [systemOpen, setSystemOpen] = useState(false);
const [form] = Form.useForm();
const { modal } = App.useApp();
const { operationPermissionsList = [] } = useSelector((s: any) => s.user);
useEffect(() => {
void getBooks().then((b: any) => b && setBooks(b));
}, []);
const list = useDiscussionList(books[0]?.id);
useEffect(() => {
if (books[0] && !list.filters.book_id)
list.setFilters({ ...list.filters, book_id: books[0].id });
}, [books]);
const items: AppListFilterItem<DiscussionFilters>[] = [
{
name: "book_id",
label: "书籍名称",
render: (f) => (
<Select showSearch value={f.value} style={{ width: 160 }} onChange={f.onChange}>
{books.map((b) => (
<Select.Option key={b.id} value={b.id}>
{b.name}
</Select.Option>
))}
</Select>
),
},
{
name: "title",
label: "话题",
render: (f) => (
<Input
allowClear
value={f.value || ""}
onChange={(e) => f.onChange(e.target.value)}
placeholder="请输入话题"
/>
),
},
{
name: "time",
label: "讨论时间",
render: (f) => <RangePicker allowClear value={f.value} onChange={(v) => f.onChange(v)} />,
},
];
const columns: ColumnsType<any> = [
{ title: "ID", dataIndex: "id" },
{ title: "书籍名称", dataIndex: "name" },
{ title: "话题", dataIndex: "title", width: 200 },
{ title: "讨论人", dataIndex: "person_name" },
{ title: "讨论时间", dataIndex: "create_time" },
{
title: "讨论内容",
dataIndex: "content",
render: (content: any[]) =>
!content?.length
? "暂无内容"
: content.map((x, i) =>
x.types === "audio" ? (
<audio controls key={i}>
<source src={x.content} type="audio/mp3" />
</audio>
) : x.types === "image" ? (
<Image height={100} src={x.content} key={i} />
) : (
<span key={i}>{x.types === "text" ? x.content : "其他类型"}</span>
),
),
},
{
title: "审核",
dataIndex: "status",
render: (_, row) => (
<Switch
checked={row.status === 1}
onChange={async () => {
if (
await changeCommentStatus({
id: row.id,
status: row.status === 1 ? 2 : 1,
book_id: list.filters.book_id,
})
)
list.refresh();
}}
/>
),
},
{
title: "操作",
render: (_, row) =>
operationPermissionsList.includes("/books/discussion/delComment") && (
<Button
onClick={() =>
modal.confirm({
title: "提示",
content: "讨论将永久删除,是否继续?",
onOk: async () => {
if (await deleteComment(row.id, list.filters.book_id)) list.refresh();
},
})
}
>
删除
</Button>
),
},
];
return (
<div>
<AppList
rowKey="id"
columns={columns}
data={{ rows: list.rows, total: list.total, loading: list.loading, error: list.error }}
filters={{
value: list.filters,
items,
onChange: list.setFilters,
onSearch: list.search,
onReset: list.reset,
onRefresh: list.refresh,
}}
pagination={{
page: list.page,
pageSize: list.pageSize,
pageSizeOptions: [10, 20, 30, 40],
onChange: list.changePage,
onPageSizeChange: list.changePageSize,
}}
toolbar={
operationPermissionsList.includes("/books/discussion/getSystem") ? (
<Button
type="primary"
ghost
onClick={async () => {
const s: any = await getDiscussionSystem();
form.setFieldsValue({ set_value: s?.set_value });
setSystemOpen(true);
}}
>
审核设置
</Button>
) : null
}
onRetry={list.retry}
/>
<Drawer open={systemOpen} onClose={() => setSystemOpen(false)} footer={null} mask={false}>
<Form
form={form}
onFinish={async (v) => {
if (await editDiscussionSystem(v.set_value)) setSystemOpen(false);
}}
>
<Form.Item name="set_value" label="评论是否需要审核">
<Radio.Group>
<Radio value="1">是</Radio>
<Radio value="0">否</Radio>
</Radio.Group>
</Form.Item>
<Space>
<Button onClick={() => setSystemOpen(false)}>取消</Button>
<Button htmlType="submit" type="primary">
提交
</Button>
</Space>
</Form>
</Drawer>
</div>
);
}
...@@ -18,7 +18,7 @@ const SaleEdit = lazy(() => import("@/pages/books/sale/edit")); ...@@ -18,7 +18,7 @@ const SaleEdit = lazy(() => import("@/pages/books/sale/edit"));
const DiscussList = lazy(() => import("@/pages/books/sale/discussList/index")); const DiscussList = lazy(() => import("@/pages/books/sale/discussList/index"));
const DiscussDetail = lazy(() => import("@/pages/books/sale/discussDetail/index")); const DiscussDetail = lazy(() => import("@/pages/books/sale/discussDetail/index"));
const QuestionBank = lazy(() => import("@/pages/books/question-bank/index")); const QuestionBank = lazy(() => import("@/pages/books/question-bank/index"));
const Discussion = lazy(() => import("@/pages/books/discussion/index")); const DiscussionRoutes = lazy(() => import("./discussion/routes"));
const OrderManagementLayout = lazy(() => import("@/pages/books/order-management/index")); const OrderManagementLayout = lazy(() => import("@/pages/books/order-management/index"));
const OrderTable = lazy(() => import("@/pages/books/order-management/tab/table")); const OrderTable = lazy(() => import("@/pages/books/order-management/tab/table"));
const SaleStatistics = lazy(() => import("@/pages/books/order-management/tab/salesStatistics")); const SaleStatistics = lazy(() => import("@/pages/books/order-management/tab/salesStatistics"));
...@@ -63,7 +63,7 @@ export default function BooksRoutes() { ...@@ -63,7 +63,7 @@ export default function BooksRoutes() {
], ],
}, },
{ path: "question-bank", Component: QuestionBank }, { path: "question-bank", Component: QuestionBank },
{ path: "discussion", Component: Discussion }, { path: "discussion/*", Component: DiscussionRoutes },
{ {
path: "order-management", path: "order-management",
Component: OrderManagementLayout, Component: OrderManagementLayout,
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论