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

refactor(admin): migrate books label module

上级 21a3d404
import { message } from "antd";
import request from "@/utils/request";
import type { LabelQuery } from "./query";
async function post(path: string, data: Record<string, unknown>, notify = false) {
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) {
if (notify) message.success(response.data.message);
return response.data.data;
}
message.error(response.data?.message || "数据获取失败!");
} catch {}
return false;
}
export const getLabelList = (query: LabelQuery) => post("/book/label/getList", query);
export const getAllBooks = () => post("/book/label/getAllBooks", {});
export const getLabel = (id: string | number) => post("/book/label/getInfoById", { id });
export const addLabel = (data: Record<string, unknown>) => post("/book/label/add", data, true);
export const editLabel = (data: Record<string, unknown>) => post("/book/label/edit", data, true);
export const deleteLabel = (id: string | number) => post("/book/label/del", { id }, true);
import type { Dayjs } from "dayjs";
export interface LabelFilters {
name?: string;
time?: [Dayjs | null, Dayjs | null] | null;
}
export interface LabelQuery {
page: number;
page_size: number;
name?: string;
start_time?: number;
end_time?: number;
}
export function buildLabelQuery(page: number, pageSize: number, filters: LabelFilters): LabelQuery {
const query: LabelQuery = { page, page_size: pageSize };
if (filters.name) query.name = filters.name;
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, useRoutes } from "react-router";
import LabelView from "./views/LabelView";
export default function LabelRoutes() {
return useRoutes([
{ index: true, Component: LabelView },
{ path: "*", element: <Navigate to="/books/label" /> },
]);
}
import { useCallback, useEffect, useRef, useState } from "react";
import { getLabelList } from "./api";
import { buildLabelQuery, type LabelFilters, type LabelQuery } from "./query";
export function useLabelList() {
const [filters, setFilters] = useState<LabelFilters>({});
const [applied, setApplied] = useState<LabelFilters>({});
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<LabelQuery>(buildLabelQuery(1, 10, {}));
const load = useCallback(async (query: LabelQuery) => {
last.current = query;
setLoading(true);
setError(null);
try {
const result: any = await getLabelList(query);
if (!result) throw new Error("请求失败");
setRows(result.list || []);
setTotal(result.total || 0);
} catch (e) {
setError(e instanceof Error ? e : new Error("请求失败"));
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void load(last.current);
}, [load]);
const run = (p: number, s: number, f: LabelFilters) => void load(buildLabelQuery(p, s, f));
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),
reloadFirst: search,
changePage: (p: number) => {
setPage(p);
run(p, pageSize, applied);
},
changePageSize: (s: number) => {
setPage(1);
setPageSize(s);
run(1, s, applied);
},
};
}
import { App, Button, DatePicker, Drawer, Form, Input, Row, Space } 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 { addLabel, deleteLabel, editLabel, getAllBooks, getLabel } from "../api";
import type { LabelFilters } from "../query";
import { useLabelList } from "../useLabelList";
const { RangePicker } = DatePicker;
const items: AppListFilterItem<LabelFilters>[] = [
{
name: "name",
label: "标签名称",
render: (field) => (
<Input
allowClear
placeholder="请输入标签名称"
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
/>
),
},
{
name: "time",
label: "创建时间",
render: (field) => (
<RangePicker value={field.value} onChange={(value) => field.onChange(value)} />
),
},
];
export default function LabelView() {
const list = useLabelList();
const { modal } = App.useApp();
const { operationPermissionsList = [] } = useSelector((state: any) => state.user);
const [open, setOpen] = useState(false);
const [id, setId] = useState<any>("");
const [books, setBooks] = useState<any[]>([]);
const [bookIds, setBookIds] = useState<any[]>([]);
const [form] = Form.useForm();
useEffect(() => {
void getAllBooks().then((data: any) => data && setBooks(data));
}, []);
const edit = async (value: any) => {
setId(value);
const data: any = await getLabel(value);
if (data) {
form.setFieldsValue({ name: data.name });
setBookIds(data.books || []);
}
setOpen(true);
};
const submit = async (values: any) => {
const result = id
? await editLabel({ ...values, id, book_ids: bookIds })
: await addLabel({ ...values, book_ids: bookIds });
if (result) {
setOpen(false);
list.reloadFirst();
}
};
const toggle = (value: any) =>
setBookIds((old) =>
old.includes(value) ? old.filter((item) => item !== value) : [...old, value],
);
const columns: ColumnsType<any> = [
"ID:id",
"标签名称:name",
"创建时间:create_time",
"更新时间:update_time",
].map((item) => {
const [title, dataIndex] = item.split(":");
return { title, key: dataIndex, dataIndex, align: "center" };
});
columns.push({
title: "操作",
key: "handle",
render: (_, row) => (
<Space>
{operationPermissionsList.includes("/books/label/getInfoById") && (
<Button onClick={() => edit(row.id)}>编辑</Button>
)}
{operationPermissionsList.includes("/books/label/del") && (
<Button
onClick={() =>
modal.confirm({
title: "提示",
content: "标签将永久删除,是否继续?",
onOk: async () => {
if (await deleteLabel(row.id)) list.reloadFirst();
},
})
}
>
删除
</Button>
)}
</Space>
),
});
return (
<div className="label">
<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/label/add") ? (
<Button
type="primary"
ghost
onClick={() => {
setId("");
setBookIds([]);
form.resetFields();
setOpen(true);
}}
>
添加
</Button>
) : null
}
onRetry={list.retry}
/>
<Drawer placement="right" open={open} mask={false} onClose={() => setOpen(false)}>
<Form form={form} onFinish={submit} labelCol={{ span: 5 }}>
<Form.Item
label="标签名称"
name="name"
rules={[{ required: true, message: "请输入标签名称" }]}
>
<Input placeholder="请输入标签名称" />
</Form.Item>
<Form.Item label="书籍" rules={[{ required: true, message: "请选择书籍" }]}>
<Button
type="primary"
ghost
onClick={() =>
setBookIds(bookIds.length === books.length ? [] : books.map((book) => book.id))
}
>
全选
</Button>
<br />
<br />
<Row gutter={20}>
{books.map((book) => (
<Button
key={book.id}
type={bookIds.includes(book.id) ? "primary" : "default"}
ghost={bookIds.includes(book.id)}
onClick={() => toggle(book.id)}
>
{book.name}
</Button>
))}
</Row>
</Form.Item>
<Form.Item wrapperCol={{ offset: 5, span: 16 }}>
<Space size={20}>
<Button onClick={() => setOpen(false)}>取消</Button>
<Button type="primary" htmlType="submit">
提交
</Button>
</Space>
</Form.Item>
</Form>
</Drawer>
</div>
);
}
...@@ -11,7 +11,7 @@ const AuditLayout = lazy(() => import("@/pages/books/audit/index")); ...@@ -11,7 +11,7 @@ const AuditLayout = lazy(() => import("@/pages/books/audit/index"));
const AuditTable = lazy(() => import("@/pages/books/audit/table")); const AuditTable = lazy(() => import("@/pages/books/audit/table"));
const AuditDetail = lazy(() => import("@/pages/books/audit/detail")); const AuditDetail = lazy(() => import("@/pages/books/audit/detail"));
const ClassifyRoutes = lazy(() => import("./classify/routes")); const ClassifyRoutes = lazy(() => import("./classify/routes"));
const Label = lazy(() => import("@/pages/books/label/index")); const LabelRoutes = lazy(() => import("./label/routes"));
const SaleLayout = lazy(() => import("@/pages/books/sale/index")); const SaleLayout = lazy(() => import("@/pages/books/sale/index"));
const SaleTable = lazy(() => import("@/pages/books/sale/table")); const SaleTable = lazy(() => import("@/pages/books/sale/table"));
const SaleEdit = lazy(() => import("@/pages/books/sale/edit")); const SaleEdit = lazy(() => import("@/pages/books/sale/edit"));
...@@ -50,7 +50,7 @@ export default function BooksRoutes() { ...@@ -50,7 +50,7 @@ export default function BooksRoutes() {
], ],
}, },
{ path: "classify/*", Component: ClassifyRoutes }, { path: "classify/*", Component: ClassifyRoutes },
{ path: "label", Component: Label }, { path: "label/*", Component: LabelRoutes },
{ {
path: "sale", path: "sale",
Component: SaleLayout, Component: SaleLayout,
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论