TermList.tsx 9.6 KB

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