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

refactor(admin): use AppList for sensitive words

上级 da3f36f6
import { useState } from 'react' import { useCallback, useEffect, useRef, useState } from "react";
export function useSensitiveList() { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); return { page, pageSize, setPage, setPageSize } } import { getList } from "./api";
import { buildSensitiveQuery } from "./query";
export function useSensitiveList() {
const [filters, setFilters] = useState<{ word_name?: string }>({});
const [applied, setApplied] = useState<{ word_name?: string }>({});
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(buildSensitiveQuery({ page: 1, pageSize: 10 }));
const load = useCallback(async (query: any) => {
last.current = query; setLoading(true); setError(null);
try { const result: any = await getList(query); if (!result || result instanceof Error) 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, nextPageSize: number, nextFilters: typeof filters) => void load(buildSensitiveQuery({ page: nextPage, pageSize: nextPageSize, filters: 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), reloadFirst: search,
changePage: (nextPage: number) => { setPage(nextPage); run(nextPage, pageSize, applied); },
changePageSize: (nextPageSize: number) => { setPage(1); setPageSize(nextPageSize); run(1, nextPageSize, applied); },
};
}
import React, { useEffect, useState, useMemo } from "react"; import { App, Button, Drawer, Form, Input, Result, Space, Upload, message } from "antd";
import { import { useEffect, useState } from "react";
Table, import { AppList } from "@/shared/components/AppList";
Input, import { addWords, delWords, editWords, importWords } from "../api";
Result, import { useSensitiveList } from "../useSensitiveList";
Button,
Row, export default function SensitiveListView() {
Col, const list = useSensitiveList(); const { modal } = App.useApp(); const [form] = Form.useForm();
Space, const [open, setOpen] = useState(false); const [id, setId] = useState(); const [file, setFile] = useState();
DatePicker, const [importOpen, setImportOpen] = useState(false); const [importResult, setImportResult] = useState();
Upload, useEffect(() => { if (!open) { form.resetFields(); setId(undefined); } }, [open, form]);
Drawer, const save = async (values) => { const success = id ? await editWords({ ...values, id }) : await addWords(values); if (success) { setOpen(false); list.reloadFirst(); } };
Form, const upload = async () => { if (!file) return; const data = new FormData(); data.append("file", file); const result = await importWords(data); if (result) setImportResult(result); };
Modal, const closeImport = () => { setImportOpen(false); setImportResult(undefined); setFile(undefined); };
message,
Image,
} from "antd";
const { RangePicker } = DatePicker;
const { TextArea } = Input;
import PaginationCom from "@/common/Pagination";
import { getList, addWords, importWords, editWords, delWords, infoWords } from "../api";
import Add from "@/assets/images/icon/add.png";
import reload from "@/assets/images/icon/reload.png";
import reset from "@/assets/images/icon/reset.png";
import filter from "@/assets/images/icon/filter.png";
import imports from "@/assets/images/icon/import.png";
import success from "@/assets/images/success.png";
import err from "@/assets/images/err.png";
import TableCom from "@/common/TableCom/index";
import _ from "lodash-es";
const Sensitive = () => {
const [data, setData] = useState([]);
const [page_size, setpage_size] = useState(10);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [delMoadal, setDelModal] = useState(false);
// 添加/编辑弹框
const [showModal, setShowModal] = useState(false);
const [form] = Form.useForm();
// 上传弹框
const [uploadModal, setUploadModal] = useState(false);
// 操作某一项的id
const [id, setid] = useState(0);
// 是否添加
const [isadd, setisadd] = useState(true);
const [word_name, setword_name] = useState("");
const [file, setFile] = useState("");
const handleEdit = (obj) => {
setid(obj.id);
form.setFieldsValue(obj);
setisadd(false);
setShowModal(true);
};
const init = async (obj = {}) => {
setLoading(true);
const { total, list } = await getList({ page, page_size, ...obj });
setTotal(total);
setData(list);
setLoading(false);
};
const changeWord = (ev) => {
let val = ev.target.value;
setword_name(val);
if (val == "") {
init();
}
};
const handleFilter = async () => {
init({ word_name });
};
const submitForm = async (obj) => {
console.log("点解了编辑的提交表单");
let bool;
if (isadd) {
bool = await addWords(obj);
} else {
bool = await editWords({
...obj,
id,
});
}
bool && init();
setShowModal(false);
};
const afterClose = () => {
form.setFieldsValue({ word_name: "" });
};
const delSuccess = async () => {
const bool = await delWords({ id });
console.log(bool);
if (bool) message.success("删除成功");
init();
setDelModal(false);
};
const UploadProps = {
name: "file",
accept: ".xlsx",
showUploadList: false,
beforeUpload(file) {
const suffixArr = ["xlsx"];
const suffix = file.name.substring(file.name.lastIndexOf(".") + 1);
if (!suffixArr.includes(suffix)) {
message.error(`请上传以${suffixArr.join("、")}的敏感词文件`);
return false;
}
setFile(file);
return false;
},
};
const [importRes, setimportRes] = useState(false);
const [importMsg, setimportMsg] = useState(false);
const [importFlag, setimportFlag] = useState(false);
const handleUpload = async () => {
const formData = new FormData();
formData.append("file", file);
const { err, msg } = await importWords(formData);
if (err == 0) {
setimportRes(true);
} else {
setimportRes(false);
}
setimportMsg(msg);
setimportFlag(true);
};
const handleReset = () => {
setword_name("");
init();
};
// 下载模板
const handleDownload = async () => {
window.open(import.meta.env.VITE_API_URL + "/敏感词导入示例.xlsx");
};
useEffect(() => {
if (!showModal) {
form.resetFields();
form.setFieldsValue({ word_name: "" });
}
if (!uploadModal) {
setFile("");
setimportFlag(false);
}
}, [showModal, uploadModal]);
const columns = [ const columns = [
{ { title: "ID", dataIndex: "id", align: "center" }, { title: "敏感词", dataIndex: "word_name", align: "center" }, { title: "创建时间", dataIndex: "create_time", align: "center" },
title: "ID", { title: "操作", align: "center", render: (_, row) => <Space><Button onClick={() => { setId(row.id); form.setFieldsValue(row); setOpen(true); }}>编辑</Button><Button onClick={() => modal.confirm({ title: "提示", content: "敏感词将永久删除,是否继续?", onOk: async () => { if (await delWords({ id: row.id })) { message.success("删除成功"); list.refresh(); } } })}>删除</Button></Space> },
key: "id",
dataIndex: "id",
align: "center",
},
{
title: "敏感词",
key: "word_name",
dataIndex: "word_name",
align: "center",
},
{
title: "创建时间",
key: "create_time",
dataIndex: "create_time",
align: "center",
},
{
title: "操作",
key: "handle",
dataIndex: "handle",
align: "center",
render: (_, record) => {
return (
<Space>
<Button
onClick={() => {
handleEdit(record);
}}
>
编辑
</Button>
<Button
onClick={() => {
setid(record.id);
setDelModal(true);
}}
>
删除
</Button>
</Space>
);
},
},
]; ];
return ( return <div className="classify"><AppList rowKey="id" columns={columns} data={{ rows: list.rows, total: list.total, loading: list.loading, error: list.error }} filters={{ value: list.filters, items: [{ name: "word_name", label: "敏感词名称", render: (field) => <Input autoComplete="off" allowClear value={field.value || ""} onChange={(event) => field.onChange(event.target.value)} placeholder="请输入敏感词名称" /> }], 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={<Space><Button type="primary" ghost onClick={() => setImportOpen(true)}>导入</Button><Button type="primary" ghost onClick={() => setOpen(true)}>添加</Button></Space>} onRetry={list.retry} />
<div className="classify"> <Drawer placement="right" open={open} mask={false} footer={null} onClose={() => setOpen(false)}><Form form={form} onFinish={save} labelCol={{ span: 5 }}><Form.Item label="敏感词" name="word_name" rules={[{ required: true, message: "请输入敏感词" }]}><Input autoComplete="off" showCount maxLength={10} placeholder="请输入敏感词" /></Form.Item><Form.Item wrapperCol={{ offset: 5 }}><Space><Button onClick={() => setOpen(false)}>取消</Button><Button type="primary" htmlType="submit">提交</Button></Space></Form.Item></Form></Drawer>
<Row justify={"space-between"}> <Drawer open={importOpen} mask={false} footer={null} onClose={closeImport}>{importResult ? <Result status={importResult.err === 0 ? "success" : "error"} subTitle={importResult.msg} extra={<Space>{importResult.err !== 0 && <Button type="primary" ghost onClick={() => window.open(`${import.meta.env.VITE_API_URL}/敏感词导入失败.xlsx`)}>下载导入失败的数据</Button>}<Button type="primary" onClick={closeImport}>完成</Button></Space>} /> : <Form onFinish={upload}><Form.Item label="下载模板"><Button type="primary" ghost onClick={() => window.open(`${import.meta.env.VITE_API_URL}/敏感词导入示例.xlsx`)}>下载模板</Button></Form.Item><p>1.请勿修改模板表头,防止导入失败;2.敏感词最大长度支持10个汉字;</p><Form.Item label="导入模板"><Upload accept=".xlsx" showUploadList={false} beforeUpload={(value) => { if (!value.name.endsWith(".xlsx")) return Upload.LIST_IGNORE; setFile(value); return false; }}><Button type="primary" ghost>选择文件</Button></Upload><p>{file?.name}</p></Form.Item><Space><Button onClick={closeImport}>取消</Button><Button type="primary" htmlType="submit" disabled={!file}>确认</Button></Space></Form>}</Drawer>
<Col> </div>;
<Form layout="inline"> }
<Form.Item label="敏感词名称">
<Input
autoComplete="off"
value={word_name}
onChange={changeWord}
allowClear
placeholder="请输入敏感词名称"
id="name"
></Input>
</Form.Item>
<Space>
<Button
type="primary"
ghost
icon={
<span
style={{
display: "inline-block",
width: "11px",
height: "10px",
pointerEvents: "none",
}}
>
<Image src={reset} />
</span>
}
onClick={handleReset}
>
重置
</Button>
<Button
type="primary"
icon={
<span
style={{
display: "inline-block",
width: "13px",
height: "12px",
pointerEvents: "none",
}}
>
<Image src={filter} />
</span>
}
ghost
onClick={() => {
setPage(1);
Object.values(filterObj).some((item) => item) ? init(filterObj) : init();
}}
>
筛选
</Button>
<Button
type="primary"
ghost
icon={
<span
style={{
display: "inline-block",
width: "10px",
height: "12px",
pointerEvents: "none",
}}
>
<Image src={reload} />
</span>
}
onClick={() =>
Object.values(filterObj).some((item) => item) ? init(filterObj) : init()
}
>
刷新
</Button>
</Space>
</Form>
</Col>
<Col>
<Space>
<Button
type="primary"
ghost
icon={
<span
style={{
display: "inline-block",
width: "11px",
height: "12px",
pointerEvents: "none",
}}
>
<Image src={imports} />
</span>
}
onClick={() => setUploadModal(true)}
>
导入
</Button>
<Button
type="primary"
ghost
onClick={() => {
setisadd(true);
setShowModal(true);
}}
icon={
<span
style={{
display: "inline-block",
width: "13px",
height: "12px",
pointerEvents: "none",
}}
>
<Image src={Add} />
</span>
}
>
添加
</Button>
</Space>
</Col>
</Row>
<br />
{TableCom({ columns, loading, data })}
<Modal
mask={false}
centered
open={delMoadal}
onOk={delSuccess}
onCancel={() => setDelModal(false)}
footer={(_, { OkBtn, CancelBtn }) => {
return (
<Row justify={"center"} className="delModal" style={{ marginBottom: "30px" }}>
<Space size={20}>
<CancelBtn />
<OkBtn />
</Space>
</Row>
);
}}
okText="确认"
>
<p style={{ textAlign: "center", padding: "30px 0 18px", fontSize: 16 }}>
敏感词将永久删除,是否继续?
</p>
</Modal>
<br />
<PaginationCom
total={total}
page_size={page_size}
setpage_size={setpage_size}
page={page}
setPage={setPage}
init={init}
filterObj={{ word_name }}
/>
<Modal
mask={false}
centered
footer={null}
open={uploadModal}
onOk={delSuccess}
onCancel={() => setUploadModal(false)}
>
{!importFlag ? (
<>
<br />
<br />
<Form onFinish={handleUpload}>
<div style={{ paddingLeft: 75 }}>
<Form.Item label="下载模板" style={{ marginBottom: 10 }}>
<Button ghost type="primary" onClick={handleDownload}>
下载模板
</Button>
</Form.Item>
<p
className="linetext"
style={{ fontWeight: 400, color: "#999999", fontSize: 12, marginBottom: 30 }}
>
1.请勿修改模板表头,防止导入失败;2.敏感词最大长度支持10个汉字;
</p>
<Form.Item label="导入模板">
<Upload {...UploadProps}>
<Button ghost type="primary">
选择文件
</Button>
</Upload>
<p style={{ fontSize: 12, marginTop: 8 }}>{file.name}</p>
</Form.Item>
</div>
<Form.Item>
<Row gutter={30} justify={"center"}>
<Col>
<Button size="large" onClick={() => setUploadModal(false)}>
取消
</Button>
</Col>
<Col>
<Button size="large" type="primary" htmlType="submit">
确认
</Button>
</Col>
</Row>
</Form.Item>
</Form>
</>
) : (
<Result
status={importRes ? "success" : "error"}
icon={<img width={60} src={importRes ? success : err} />}
subTitle={<p style={{ color: importRes ? "#029b00" : "#aa1941" }}>{importMsg}</p>}
extra={[
!importRes && (
<Button
type="primary"
key="err"
ghost
onClick={() => {
window.open(import.meta.env.VITE_API_URL + "/敏感词导入失败.xlsx");
}}
>
下载导入失败的数据
</Button>
),
<Button
type="primary"
key="finsh"
onClick={() => {
setUploadModal(false);
}}
>
完成
</Button>,
]}
/>
)}
</Modal>
<Drawer
placement="right"
onClose={() => {
setShowModal(false);
afterClose();
}}
open={showModal}
mask={false}
footer={null}
>
<br />
<Form form={form} onFinish={submitForm} labelCol={{ span: 5 }}>
<Form.Item
label="敏感词"
name="word_name"
rules={[{ required: true, message: "请输入敏感词" }]}
>
<Input autoComplete="off" showCount maxLength={10} placeholder="请输入敏感词"></Input>
</Form.Item>
<Form.Item wrapperCol={{ offset: 5, span: 16 }} style={{ textAlign: "center" }}>
<Space size={20}>
<Button onClick={() => setShowModal(false)}>取消</Button>
<Button type="primary" htmlType="submit">
提交
</Button>
</Space>
</Form.Item>
</Form>
</Drawer>
</div>
);
};
export default Sensitive;
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论