TermList.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import { ActionType, ProTable } from "@ant-design/pro-components";
  2. import { useIntl } from "react-intl";
  3. import { Button, Space, Table, Dropdown, Modal, message } from "antd";
  4. import {
  5. ExclamationCircleOutlined,
  6. DeleteOutlined,
  7. ImportOutlined,
  8. PlusOutlined,
  9. } from "@ant-design/icons";
  10. import {
  11. ITermDeleteRequest,
  12. ITermListResponse,
  13. } from "../../components/api/Term";
  14. import { delete_2, get } from "../../request";
  15. import { IDeleteResponse } from "../../components/api/Article";
  16. import { useRef } from "react";
  17. import { IChannel } from "../channel/Channel";
  18. import TermExport from "./TermExport";
  19. import DataImport from "../admin/relation/DataImport";
  20. import TermModal from "./TermModal";
  21. interface IItem {
  22. sn: number;
  23. id: string;
  24. word: string;
  25. tag: string;
  26. channel?: IChannel;
  27. meaning: string;
  28. meaning2: string;
  29. note: string | null;
  30. createdAt: number;
  31. }
  32. interface IWidget {
  33. studioName?: string;
  34. channelId?: string;
  35. }
  36. const TermListWidget = ({ studioName, channelId }: IWidget) => {
  37. const intl = useIntl();
  38. const showDeleteConfirm = (id: string[], title: string) => {
  39. Modal.confirm({
  40. icon: <ExclamationCircleOutlined />,
  41. title:
  42. intl.formatMessage({
  43. id: "message.delete.sure",
  44. }) +
  45. intl.formatMessage({
  46. id: "message.irrevocable",
  47. }),
  48. content: title,
  49. okText: intl.formatMessage({
  50. id: "buttons.delete",
  51. }),
  52. okType: "danger",
  53. cancelText: intl.formatMessage({
  54. id: "buttons.no",
  55. }),
  56. onOk() {
  57. console.log("delete", id);
  58. return delete_2<ITermDeleteRequest, IDeleteResponse>(
  59. `/v2/terms/${id}`,
  60. {
  61. uuid: true,
  62. id: id,
  63. }
  64. )
  65. .then((json) => {
  66. if (json.ok) {
  67. message.success("删除成功");
  68. ref.current?.reload();
  69. } else {
  70. message.error(json.message);
  71. }
  72. })
  73. .catch((e) => console.log("Oops errors!", e));
  74. },
  75. });
  76. };
  77. const ref = useRef<ActionType>();
  78. return (
  79. <>
  80. <ProTable<IItem>
  81. actionRef={ref}
  82. columns={[
  83. {
  84. title: intl.formatMessage({
  85. id: "term.fields.sn.label",
  86. }),
  87. dataIndex: "sn",
  88. key: "sn",
  89. width: 80,
  90. search: false,
  91. },
  92. {
  93. title: intl.formatMessage({
  94. id: "term.fields.word.label",
  95. }),
  96. dataIndex: "word",
  97. key: "word",
  98. tip: "单词过长会自动收缩",
  99. ellipsis: true,
  100. },
  101. {
  102. title: intl.formatMessage({
  103. id: "term.fields.description.label",
  104. }),
  105. dataIndex: "tag",
  106. key: "tag",
  107. search: false,
  108. },
  109. {
  110. title: intl.formatMessage({
  111. id: "term.fields.channel.label",
  112. }),
  113. dataIndex: "channel",
  114. key: "channel",
  115. render(dom, entity, index, action, schema) {
  116. return entity.channel?.name;
  117. },
  118. },
  119. {
  120. title: intl.formatMessage({
  121. id: "term.fields.meaning.label",
  122. }),
  123. dataIndex: "meaning",
  124. key: "meaning",
  125. },
  126. {
  127. title: intl.formatMessage({
  128. id: "term.fields.meaning2.label",
  129. }),
  130. dataIndex: "meaning2",
  131. key: "meaning2",
  132. tip: "意思过长会自动收缩",
  133. ellipsis: true,
  134. },
  135. {
  136. title: intl.formatMessage({
  137. id: "term.fields.note.label",
  138. }),
  139. dataIndex: "note",
  140. key: "note",
  141. search: false,
  142. tip: "注释过长会自动收缩",
  143. ellipsis: true,
  144. },
  145. {
  146. title: intl.formatMessage({
  147. id: "forms.fields.created-at.label",
  148. }),
  149. key: "created-at",
  150. width: 200,
  151. search: false,
  152. dataIndex: "createdAt",
  153. valueType: "date",
  154. sorter: (a, b) => a.createdAt - b.createdAt,
  155. },
  156. {
  157. title: intl.formatMessage({ id: "buttons.option" }),
  158. key: "option",
  159. width: 120,
  160. valueType: "option",
  161. render: (text, row, index, action) => {
  162. return [
  163. <Dropdown.Button
  164. key={index}
  165. type="link"
  166. menu={{
  167. items: [
  168. {
  169. key: "remove",
  170. label: intl.formatMessage({
  171. id: "buttons.delete",
  172. }),
  173. icon: <DeleteOutlined />,
  174. danger: true,
  175. },
  176. ],
  177. onClick: (e) => {
  178. switch (e.key) {
  179. case "remove":
  180. showDeleteConfirm([row.id], row.word);
  181. break;
  182. default:
  183. break;
  184. }
  185. },
  186. }}
  187. >
  188. <TermModal
  189. trigger={"编辑"}
  190. id={row.id}
  191. studioName={studioName}
  192. channelId={channelId}
  193. onUpdate={() => ref.current?.reload()}
  194. />
  195. </Dropdown.Button>,
  196. ];
  197. },
  198. },
  199. ]}
  200. rowSelection={{
  201. // 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
  202. // 注释该行则默认不显示下拉选项
  203. selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
  204. }}
  205. tableAlertRender={({
  206. selectedRowKeys,
  207. selectedRows,
  208. onCleanSelected,
  209. }) => (
  210. <Space size={24}>
  211. <span>
  212. {intl.formatMessage({ id: "buttons.selected" })}
  213. {selectedRowKeys.length}
  214. <Button
  215. type="link"
  216. style={{ marginInlineStart: 8 }}
  217. onClick={onCleanSelected}
  218. >
  219. {intl.formatMessage({ id: "buttons.unselect" })}
  220. </Button>
  221. </span>
  222. </Space>
  223. )}
  224. tableAlertOptionRender={({
  225. intl,
  226. selectedRowKeys,
  227. selectedRows,
  228. onCleanSelected,
  229. }) => {
  230. return (
  231. <Space size={16}>
  232. <Button
  233. type="link"
  234. onClick={() => {
  235. console.log(selectedRowKeys);
  236. showDeleteConfirm(
  237. selectedRowKeys.map((item) => item.toString()),
  238. selectedRowKeys.length + "个单词"
  239. );
  240. onCleanSelected();
  241. }}
  242. >
  243. 批量删除
  244. </Button>
  245. </Space>
  246. );
  247. }}
  248. request={async (params = {}, sorter, filter) => {
  249. // TODO
  250. console.log(params, sorter, filter);
  251. const offset =
  252. ((params.current ? params.current : 1) - 1) *
  253. (params.pageSize ? params.pageSize : 20);
  254. let url = `/v2/terms?`;
  255. if (typeof channelId === "string") {
  256. url += `view=channel&id=${channelId}`;
  257. } else {
  258. url += `view=studio&name=${studioName}`;
  259. }
  260. url += `&limit=${params.pageSize}&offset=${offset}`;
  261. if (typeof params.keyword !== "undefined") {
  262. url += "&search=" + (params.keyword ? params.keyword : "");
  263. }
  264. const res = await get<ITermListResponse>(url);
  265. console.log(res);
  266. const items: IItem[] = res.data.rows.map((item, id) => {
  267. const date = new Date(item.updated_at);
  268. const id2 =
  269. ((params.current || 1) - 1) * (params.pageSize || 20) + id + 1;
  270. return {
  271. sn: id2,
  272. id: item.guid,
  273. word: item.word,
  274. tag: item.tag,
  275. channel: item.channel,
  276. meaning: item.meaning,
  277. meaning2: item.other_meaning,
  278. note: item.note,
  279. createdAt: date.getTime(),
  280. };
  281. });
  282. return {
  283. total: res.data.count,
  284. success: true,
  285. data: items,
  286. };
  287. }}
  288. rowKey="id"
  289. //bordered
  290. pagination={{
  291. showQuickJumper: true,
  292. showSizeChanger: true,
  293. }}
  294. toolBarRender={() => [
  295. <DataImport
  296. url="/v2/terms-import"
  297. urlExtra={
  298. channelId
  299. ? `view=channel&id=${channelId}`
  300. : `view=studio&name=${studioName}`
  301. }
  302. trigger={
  303. <Button icon={<ImportOutlined />}>
  304. {intl.formatMessage({ id: "buttons.import" })}
  305. </Button>
  306. }
  307. onSuccess={() => {
  308. ref.current?.reload();
  309. }}
  310. />,
  311. <TermExport channelId={channelId} studioName={studioName} />,
  312. <TermModal
  313. trigger={
  314. <Button key="button" icon={<PlusOutlined />} type="primary">
  315. {intl.formatMessage({ id: "buttons.create" })}
  316. </Button>
  317. }
  318. studioName={studioName}
  319. channelId={channelId}
  320. onUpdate={() => ref.current?.reload()}
  321. />,
  322. ]}
  323. search={false}
  324. options={{
  325. search: true,
  326. }}
  327. dateFormatter="string"
  328. />
  329. </>
  330. );
  331. };
  332. export default TermListWidget;