提交 632a69a5 authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): migrate books audit module

上级 da1425d0
import { message } from "antd";
import request from "@/utils/request";
import type { AuditQuery } from "./query";
async function post(path: string, data: Record<string, unknown>) {
try {
const response = (await request.post(path, new URLSearchParams(data as Record<string, string>).toString())) as { data?: { code?: number; data?: any; message?: string } };
if (response.data?.code === 200) return { data: response.data.data, msg: response.data.message || "" };
message.error(response.data?.message || "数据获取失败!");
} catch {}
return false;
}
export const getAuditList = async (query: AuditQuery) => (await post("/book/teacher/getList/examine", query))?.data || false;
export const getExamineList = async (id: string | number) => (await post("/book/teacher/getExamineList", { id }))?.data || false;
export const addExamine = (data: Record<string, unknown>) => post("/book/examine/add", data);
import { Steps } from "antd";
import defaultUser from "@/assets/images/defaultUser.png";
import userStatus1 from "@/assets/images/user_status1.png";
import userStatus2 from "@/assets/images/user_status2.png";
import userStatus3 from "@/assets/images/user_status3.png";
export default function AuditHistory({ entries }: { entries: any[] }) {
return <Steps direction="vertical" items={entries.map((item, index) => { const status = item.audit_status === 1 ? "green" : item.audit_status === 2 ? "red" : "gray"; const icon = item.audit_status === 1 ? userStatus2 : item.audit_status === 2 ? userStatus3 : userStatus1; const isSubmit = item.audit_status === 0; return { icon: <div className="imgel"><div className="user" style={{ backgroundImage: `url(${isSubmit ? item.user_pic || defaultUser : item.examine_pic || defaultUser})` }}><img className="status" src={icon} /></div>{index < entries.length - 1 && <div className="connector" style={{ backgroundColor: status, borderColor: status }} />}</div>, description: <div className={isSubmit ? "" : "audit"}><strong>{isSubmit ? item.user_name : item.examine_name || ""}({isSubmit ? "提交人" : "审核人"})</strong><div className="date">{item.create_time}</div><div className="status" style={{ color: status }}>{isSubmit ? item.content : item.audit_status_name}</div><div className="infor">{isSubmit ? "提交信息" : "审核信息"}: {isSubmit ? item.release_mess || "" : item.content || ""}</div></div> }; })} />;
}
export { default } from "@/pages/books/audit/preview";
import type { Dayjs } from "dayjs";
export interface AuditFilters { name?: string; authors?: string; time?: [Dayjs | null, Dayjs | null] | null; }
export interface AuditQuery { page: number; page_size: number; audit_status: 2; name?: string; authors?: string; start_time?: number; end_time?: number; }
export function buildAuditQuery(page: number, pageSize: number, filters: AuditFilters): AuditQuery {
const query: AuditQuery = { page, page_size: pageSize, audit_status: 2 };
if (filters.name) query.name = filters.name;
if (filters.authors) query.authors = filters.authors;
if (filters.time?.[0] && filters.time[1]) { query.start_time = filters.time[0].startOf("day").unix(); query.end_time = filters.time[1].endOf("day").unix(); }
return query;
}
import { Navigate, Outlet, useRoutes } from "react-router";
import AuditDetailView from "./views/AuditDetailView";
import AuditListView from "./views/AuditListView";
import "@/pages/books/audit/index.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 /> }] }]); }
import { useCallback, useEffect, useRef, useState } from "react";
import { getAuditList } from "./api";
import { buildAuditQuery, type AuditFilters, type AuditQuery } from "./query";
export function useAuditList() {
const [filters, setFilters] = useState<AuditFilters>({}); const [applied, setApplied] = useState<AuditFilters>({}); 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<AuditQuery>(buildAuditQuery(1, 10, {}));
const load = useCallback(async (query: AuditQuery) => { last.current = query; setLoading(true); setError(null); try { const result: any = await getAuditList(query); if (!result) throw new Error("请求失败"); setRows(result.list || []); setTotal(result.total || 0); } catch (cause) { setError(cause instanceof Error ? cause : new Error("请求失败")); } finally { setLoading(false); } }, []);
useEffect(() => { void load(last.current); }, [load]);
const run = (nextPage: number, nextSize: number, nextFilters: AuditFilters) => void load(buildAuditQuery(nextPage, nextSize, nextFilters));
const search = () => { setApplied(filters); setPage(1); run(1, pageSize, filters); };
return { filters, setFilters, page, pageSize, rows, total, loading, error, search, reset: () => { setFilters({}); setApplied({}); setPage(1); run(1, pageSize, {}); }, refresh: () => { setApplied(filters); run(page, pageSize, filters); }, retry: () => void load(last.current), changePage: (nextPage: number) => { setPage(nextPage); run(nextPage, pageSize, applied); }, changePageSize: (nextSize: number) => { setPage(1); setPageSize(nextSize); run(1, nextSize, applied); } };
}
import { Button, Card, Form, Input, Modal, Select, Space, Spin, message } from "antd";
import { useEffect, useState } from "react";
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";
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); };
useEffect(() => { void loadHistory(); }, [id]);
const submit = async (values: any, force = false) => { if (!id || loading) return; setLoading(true); const result: any = await addExamine({ id, ...values, ...(force ? { check: 1 } : {}) }); setLoading(false); if (!result) return; if (result.data?.check === 1 && !force) { setForceMessage(result.msg); setForceOpen(true); return; } message.success(result.msg); navigate(-1); };
if (!id) return <div style={{ padding: 20 }}>缺少待审核书籍,请返回审核列表。</div>;
return <div style={{ padding: 20 }} className="auditdetail"><Modal mask={false} centered open={forceOpen} onOk={() => { setForceOpen(false); void submit(form.getFieldsValue(), true); }} onCancel={() => setForceOpen(false)} okText="确认"><p style={{ textAlign: "center", padding: "30px 0 18px", fontSize: 16 }}>{forceMessage}</p></Modal><Spin spinning={loading}><Form form={form} onFinish={(values) => void submit(values)} initialValues={{ audit_status: 1 }}><Form.Item label="审核状态" name="audit_status" rules={[{ required: true, message: "请选择审核状态" }]}><Select style={{ width: 260 }} options={[{ label: "通过", value: 1 }, { label: "不通过", value: 2 }]} /></Form.Item><Form.Item label="审核意见" name="content"><Input.TextArea placeholder="请输入审核意见" style={{ width: 700 }} autoSize={{ minRows: 6, maxRows: 8 }} /></Form.Item><Form.Item wrapperCol={{ offset: 4 }}><Space><Button onClick={() => form.resetFields()}>重置</Button><Button htmlType="submit" type="primary" disabled={loading}>提交</Button></Space></Form.Item></Form>{operationPermissionsList.includes("/books/audit/getExamineList") && <Card className="auditPanel" style={{ marginTop: 100, height: 350, overflow: "auto" }}><AuditHistory entries={history} /></Card>}</Spin></div>;
}
import { Button, DatePicker, Image, Input, Modal, Space } from "antd";
import type { ColumnsType } from "antd/es/table";
import { CloseOutlined } from "@ant-design/icons";
import { useState } from "react";
import { useNavigate } from "react-router";
import { useSelector } from "react-redux";
import { AppList, type AppListFilterItem } from "@/shared/components/AppList";
import PreviewModal from "../components/PreviewModal";
import type { AuditFilters } from "../query";
import { useAuditList } from "../useAuditList";
const { RangePicker } = DatePicker;
export default function AuditListView() {
const list = useAuditList(); const navigate = useNavigate(); const { operationPermissionsList = [] } = useSelector((state: any) => state.user); const [previewId, setPreviewId] = useState<any>();
const filters: AppListFilterItem<AuditFilters>[] = [{ name: "name", label: "书籍名称", render: (field) => <Input allowClear autoComplete="off" value={field.value || ""} onChange={(event) => field.onChange(event.target.value)} placeholder="请输入书籍名称" /> }, { name: "authors", label: "作者", render: (field) => <Input allowClear autoComplete="off" value={field.value || ""} onChange={(event) => field.onChange(event.target.value)} placeholder="请输入作者" /> }, { name: "time", label: "创建时间", render: (field) => <RangePicker value={field.value} onChange={(value) => field.onChange(value)} /> }];
const columns: ColumnsType<any> = [{ title: "ID", dataIndex: "id", align: "center" }, { title: "书籍图片", dataIndex: "img", align: "center", render: (src) => <Image width={80} height={80} src={src} /> }, { title: "书籍名称", dataIndex: "name", align: "center" }, { title: "作者", dataIndex: "authors", align: "center" }, { title: "创建时间", dataIndex: "create_time", align: "center" }, { title: "更新时间", dataIndex: "update_time", align: "center" }, { title: "操作", render: (_, row) => <Space>{operationPermissionsList.includes("/books/audit/getInfoByChapterId") && <Button onClick={(event) => { event.stopPropagation(); setPreviewId(row.id); }}>预览</Button>}{operationPermissionsList.includes("/books/audit/add") && <Button onClick={(event) => { event.stopPropagation(); navigate("../detail", { state: { id: row.id }, replace: true }); }}>审核</Button>}</Space> }];
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: filters, 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 }} onRetry={list.retry} tableProps={{ onRow: (row) => ({ onClick: () => setPreviewId(row.id) }) }} />
<Modal open={Boolean(previewId)} footer={null} destroyOnClose keyboard={false} closeIcon={<CloseOutlined style={{ fontSize: 20, color: "#fff" }} />} maskClosable={false} classNames={{ body: "phone-body", wrapper: "phone-wrapper" }} wrapClassName="wrap-phone-privew" width="494px" centered onCancel={() => setPreviewId(undefined)}><PreviewModal bookId={previewId} /></Modal>
</div>;
}
......@@ -7,9 +7,7 @@ const ManagementLayout = lazy(() => import("@/pages/books/management/index"));
const ManagementList = lazy(() => import("@/pages/books/management/list/index"));
const ManagementAddEdit = lazy(() => import("@/pages/books/management/addedit/index"));
const ChapterManagement = lazy(() => import("@/pages/books/section/index"));
const AuditLayout = lazy(() => import("@/pages/books/audit/index"));
const AuditTable = lazy(() => import("@/pages/books/audit/table"));
const AuditDetail = lazy(() => import("@/pages/books/audit/detail"));
const AuditRoutes = lazy(() => import("./audit/routes"));
const ClassifyRoutes = lazy(() => import("./classify/routes"));
const LabelRoutes = lazy(() => import("./label/routes"));
const SaleLayout = lazy(() => import("@/pages/books/sale/index"));
......@@ -40,15 +38,7 @@ export default function BooksRoutes() {
{ path: "chapter", Component: ChapterManagement },
],
},
{
path: "audit",
Component: AuditLayout,
children: [
{ index: true, Component: AuditTable },
{ path: "dataset", Component: AuditTable },
{ path: "detail", Component: AuditDetail },
],
},
{ path: "audit/*", Component: AuditRoutes },
{ path: "classify/*", Component: ClassifyRoutes },
{ path: "label/*", Component: LabelRoutes },
{
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论