UserDictTable.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. import { useIntl } from "react-intl";
  2. import {
  3. Button,
  4. Space,
  5. Table,
  6. Dropdown,
  7. Drawer,
  8. message,
  9. Modal,
  10. Typography,
  11. } from "antd";
  12. import {
  13. PlusOutlined,
  14. ExclamationCircleOutlined,
  15. DeleteOutlined,
  16. } from "@ant-design/icons";
  17. import { type ActionType, ProTable } from "@ant-design/pro-components";
  18. import DictCreate from "./DictCreate";
  19. import type {
  20. IApiResponseDictList,
  21. IDictInfo,
  22. IUserDictDeleteRequest,
  23. } from "../../api/Dict";
  24. import { delete_2, get } from "../../request";
  25. import { useRef, useState } from "react";
  26. import DictEdit from "./DictEdit";
  27. import type { IDeleteResponse } from "../../api/Article";
  28. import TimeShow from "../general/TimeShow";
  29. import { getSorterUrl } from "../../utils";
  30. const { Link } = Typography;
  31. export interface IWord {
  32. sn: number;
  33. wordId: string;
  34. word: string;
  35. type?: string | null;
  36. grammar?: string | null;
  37. parent?: string | null;
  38. meaning?: string | null;
  39. note?: string | null;
  40. factors?: string | null;
  41. dict?: IDictInfo;
  42. updated_at?: string;
  43. created_at?: string;
  44. }
  45. interface IParams {
  46. word?: string;
  47. parent?: string;
  48. dict?: string;
  49. }
  50. interface IWidget {
  51. studioName?: string;
  52. view?: "studio" | "all";
  53. dictName?: string;
  54. }
  55. const UserDictTableWidget = ({
  56. studioName,
  57. view = "studio",
  58. dictName,
  59. }: IWidget) => {
  60. const intl = useIntl();
  61. const [isEditOpen, setIsEditOpen] = useState(false);
  62. const [isCreateOpen, setIsCreateOpen] = useState(false);
  63. const [wordId, setWordId] = useState<string>();
  64. const [drawerTitle, setDrawerTitle] = useState("New Word");
  65. const showDeleteConfirm = (id: string[], title: string) => {
  66. Modal.confirm({
  67. icon: <ExclamationCircleOutlined />,
  68. title:
  69. intl.formatMessage({
  70. id: "message.delete.confirm",
  71. }) +
  72. intl.formatMessage({
  73. id: "message.irrevocable",
  74. }),
  75. content: title,
  76. okText: intl.formatMessage({
  77. id: "buttons.delete",
  78. }),
  79. okType: "danger",
  80. cancelText: intl.formatMessage({
  81. id: "buttons.no",
  82. }),
  83. onOk() {
  84. console.log("delete", id);
  85. return delete_2<IUserDictDeleteRequest, IDeleteResponse>(
  86. `/v2/userdict/${id}`,
  87. {
  88. id: JSON.stringify(id),
  89. }
  90. )
  91. .then((json: unknown) => {
  92. if (json.ok) {
  93. message.success("删除成功");
  94. ref.current?.reload();
  95. } else {
  96. message.error(json.message);
  97. }
  98. })
  99. .catch((e: unknown) => console.log("Oops errors!", e));
  100. },
  101. });
  102. };
  103. const ref = useRef<ActionType | null>(null);
  104. return (
  105. <>
  106. <ProTable<IWord, IParams>
  107. actionRef={ref}
  108. columns={[
  109. {
  110. title: intl.formatMessage({
  111. id: "dict.fields.sn.label",
  112. }),
  113. dataIndex: "sn",
  114. key: "sn",
  115. width: 80,
  116. search: false,
  117. },
  118. {
  119. title: intl.formatMessage({
  120. id: "dict.fields.word.label",
  121. }),
  122. dataIndex: "word",
  123. key: "word",
  124. tooltip: "单词过长会自动收缩",
  125. ellipsis: true,
  126. },
  127. {
  128. title: intl.formatMessage({
  129. id: "dict.fields.type.label",
  130. }),
  131. dataIndex: "type",
  132. key: "type",
  133. search: false,
  134. filters: true,
  135. onFilter: true,
  136. valueEnum: {
  137. all: { text: "全部", status: "Default" },
  138. n: { text: "名词", status: "Default" },
  139. ti: { text: "三性", status: "Processing" },
  140. v: { text: "动词", status: "Success" },
  141. ind: { text: "不变词", status: "Success" },
  142. },
  143. },
  144. {
  145. title: intl.formatMessage({
  146. id: "dict.fields.grammar.label",
  147. }),
  148. dataIndex: "grammar",
  149. key: "grammar",
  150. search: false,
  151. },
  152. {
  153. title: intl.formatMessage({
  154. id: "dict.fields.parent.label",
  155. }),
  156. dataIndex: "parent",
  157. key: "parent",
  158. },
  159. {
  160. title: intl.formatMessage({
  161. id: "dict.fields.meaning.label",
  162. }),
  163. dataIndex: "meaning",
  164. key: "meaning",
  165. tooltip: "意思过长会自动收缩",
  166. ellipsis: true,
  167. search: false,
  168. },
  169. {
  170. title: intl.formatMessage({
  171. id: "dict.fields.note.label",
  172. }),
  173. dataIndex: "note",
  174. key: "note",
  175. search: false,
  176. tooltip: "注释过长会自动收缩",
  177. ellipsis: true,
  178. },
  179. {
  180. title: intl.formatMessage({
  181. id: "dict.fields.factors.label",
  182. }),
  183. dataIndex: "factors",
  184. key: "factors",
  185. search: false,
  186. },
  187. {
  188. title: intl.formatMessage({
  189. id: "forms.fields.dict.shortname.label",
  190. }),
  191. dataIndex: "dict",
  192. key: "dict",
  193. hideInTable: view !== "all",
  194. search: view !== "all" ? false : undefined,
  195. render: (_text, row, _index, _action) => {
  196. return row.dict?.shortname;
  197. },
  198. },
  199. {
  200. title: intl.formatMessage({
  201. id: "forms.fields.updated-at.label",
  202. }),
  203. key: "updated_at",
  204. width: 200,
  205. search: false,
  206. dataIndex: "updated_at",
  207. valueType: "date",
  208. sorter: true,
  209. render: (_text, row, _index, _action) => {
  210. return (
  211. <TimeShow
  212. updatedAt={row.updated_at}
  213. showIcon={false}
  214. showLabel={false}
  215. />
  216. );
  217. },
  218. },
  219. {
  220. title: intl.formatMessage({ id: "buttons.option" }),
  221. key: "option",
  222. hideInTable: view === "all",
  223. width: 120,
  224. valueType: "option",
  225. render: (_text, row, index, _action) => {
  226. return [
  227. <Dropdown.Button
  228. key={index}
  229. type="link"
  230. menu={{
  231. items: [
  232. {
  233. key: "remove",
  234. label: intl.formatMessage({
  235. id: "buttons.delete",
  236. }),
  237. icon: <DeleteOutlined />,
  238. danger: true,
  239. },
  240. ],
  241. onClick: (e) => {
  242. switch (e.key) {
  243. case "share":
  244. break;
  245. case "remove":
  246. showDeleteConfirm([row.wordId], row.word);
  247. break;
  248. default:
  249. break;
  250. }
  251. },
  252. }}
  253. >
  254. <Link
  255. onClick={() => {
  256. setWordId(row.wordId);
  257. setDrawerTitle(row.word);
  258. setIsEditOpen(true);
  259. }}
  260. >
  261. {intl.formatMessage({
  262. id: "buttons.edit",
  263. })}
  264. </Link>
  265. </Dropdown.Button>,
  266. ];
  267. },
  268. },
  269. ]}
  270. rowSelection={
  271. view === "all"
  272. ? undefined
  273. : {
  274. // 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
  275. // 注释该行则默认不显示下拉选项
  276. selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
  277. }
  278. }
  279. tableAlertRender={
  280. view === "all"
  281. ? undefined
  282. : ({ selectedRowKeys, ___selectedRows, onCleanSelected }) => (
  283. <Space size={24}>
  284. <span>
  285. {intl.formatMessage({ id: "buttons.selected" })}
  286. {selectedRowKeys.length}
  287. <Button
  288. type="link"
  289. style={{ marginInlineStart: 8 }}
  290. onClick={onCleanSelected}
  291. >
  292. {intl.formatMessage({ id: "buttons.unselect" })}
  293. </Button>
  294. </span>
  295. </Space>
  296. )
  297. }
  298. tableAlertOptionRender={
  299. view === "all"
  300. ? undefined
  301. : ({
  302. ___intl,
  303. selectedRowKeys,
  304. ___selectedRows,
  305. onCleanSelected,
  306. }) => {
  307. return (
  308. <Space size={16}>
  309. <Button
  310. type="link"
  311. onClick={() => {
  312. console.log(selectedRowKeys);
  313. showDeleteConfirm(
  314. selectedRowKeys.map((item) => item.toString()),
  315. selectedRowKeys.length + "个单词"
  316. );
  317. onCleanSelected();
  318. }}
  319. >
  320. 批量删除
  321. </Button>
  322. </Space>
  323. );
  324. }
  325. }
  326. request={async (params = {}, sorter, filter) => {
  327. console.log(params, sorter, filter);
  328. const offset =
  329. ((params.current ? params.current : 1) - 1) *
  330. (params.pageSize ? params.pageSize : 20);
  331. let url = "/v2/userdict?";
  332. switch (view) {
  333. case "studio":
  334. url += `view=studio&name=${studioName}`;
  335. break;
  336. case "all":
  337. url += `view=all`;
  338. break;
  339. default:
  340. break;
  341. }
  342. url += `&limit=${params.pageSize}&offset=${offset}`;
  343. url += params.keyword ? "&search=" + params.keyword : "";
  344. url += params.word ? `&word=${params.word}` : "";
  345. url += params.parent ? `&parent=${params.parent}` : "";
  346. url += params.dict ? `&dict=${params.dict}` : "";
  347. url += dictName
  348. ? dictName !== "all"
  349. ? `&dict=${dictName}`
  350. : ""
  351. : "";
  352. url += getSorterUrl(sorter);
  353. console.log(url);
  354. const res = await get<IApiResponseDictList>(url);
  355. const items: IWord[] = res.data.rows.map((item, id) => {
  356. const id2 =
  357. ((params.current || 1) - 1) * (params.pageSize || 20) + id + 1;
  358. return {
  359. sn: id2,
  360. wordId: item.id,
  361. word: item.word,
  362. type: item.type,
  363. grammar: item.grammar,
  364. parent: item.parent,
  365. meaning: item.mean,
  366. note: item.note,
  367. factors: item.factors,
  368. dict: item.dict,
  369. updated_at: item.updated_at,
  370. };
  371. });
  372. return {
  373. total: res.data.count,
  374. success: true,
  375. data: items,
  376. };
  377. }}
  378. rowKey="wordId"
  379. bordered
  380. pagination={{
  381. showQuickJumper: true,
  382. showSizeChanger: true,
  383. }}
  384. search={{
  385. filterType: "light",
  386. }}
  387. options={{
  388. search: true,
  389. }}
  390. headerTitle=""
  391. toolBarRender={
  392. view === "all"
  393. ? undefined
  394. : () => [
  395. <Button
  396. key="button"
  397. icon={<PlusOutlined />}
  398. type="primary"
  399. onClick={() => {
  400. setDrawerTitle("New word");
  401. setIsCreateOpen(true);
  402. }}
  403. disabled={true}
  404. >
  405. {intl.formatMessage({ id: "buttons.create" })}
  406. </Button>,
  407. ]
  408. }
  409. />
  410. <Drawer
  411. title={drawerTitle}
  412. placement="right"
  413. open={isCreateOpen}
  414. onClose={() => {
  415. setIsCreateOpen(false);
  416. }}
  417. key="create"
  418. style={{ maxWidth: "100%" }}
  419. contentWrapperStyle={{ overflowY: "auto" }}
  420. footer={null}
  421. >
  422. <DictCreate studio={studioName ? studioName : ""} />
  423. </Drawer>
  424. <Drawer
  425. title={drawerTitle}
  426. placement="right"
  427. open={isEditOpen}
  428. onClose={() => {
  429. setIsEditOpen(false);
  430. }}
  431. key="edit"
  432. style={{ maxWidth: "100%" }}
  433. contentWrapperStyle={{ overflowY: "auto" }}
  434. footer={null}
  435. >
  436. <DictEdit wordId={wordId} />
  437. </Drawer>
  438. </>
  439. );
  440. };
  441. export default UserDictTableWidget;