ProjectList.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { useRef, useState } from "react";
  2. import { ActionType, ProList } from "@ant-design/pro-components";
  3. import { FormattedMessage, useIntl } from "react-intl";
  4. import { Link } from "react-router-dom";
  5. import { Button, message, Modal, Popover } from "antd";
  6. import { Dropdown } from "antd";
  7. import {
  8. ExclamationCircleOutlined,
  9. DeleteOutlined,
  10. PlusOutlined,
  11. } from "@ant-design/icons";
  12. import { delete_, get } from "../../request";
  13. import { PublicityValueEnum } from "../studio/table";
  14. import { IDeleteResponse } from "../api/Article";
  15. import { getSorterUrl } from "../../utils";
  16. import { TransferOutLinedIcon } from "../../assets/icon";
  17. import { IProjectData, IProjectListResponse, TProjectType } from "../api/task";
  18. import ProjectCreate from "./ProjectCreate";
  19. export const channelTypeFilter = {
  20. all: {
  21. text: <FormattedMessage id="channel.type.all.title" />,
  22. status: "Default",
  23. },
  24. translation: {
  25. text: <FormattedMessage id="channel.type.translation.label" />,
  26. status: "Success",
  27. },
  28. nissaya: {
  29. text: <FormattedMessage id="channel.type.nissaya.label" />,
  30. status: "Processing",
  31. },
  32. commentary: {
  33. text: <FormattedMessage id="channel.type.commentary.label" />,
  34. status: "Default",
  35. },
  36. original: {
  37. text: <FormattedMessage id="channel.type.original.label" />,
  38. status: "Default",
  39. },
  40. };
  41. export interface IResNumberResponse {
  42. ok: boolean;
  43. message: string;
  44. data: {
  45. my: number;
  46. collaboration: number;
  47. };
  48. }
  49. interface IWidget {
  50. studioName?: string;
  51. type?: TProjectType;
  52. readonly?: boolean;
  53. onSelect?: (data: IProjectData) => void;
  54. }
  55. const ProjectListWidget = ({
  56. studioName,
  57. type = "normal",
  58. readonly = false,
  59. onSelect,
  60. }: IWidget) => {
  61. const intl = useIntl();
  62. const [openCreate, setOpenCreate] = useState(false);
  63. const showDeleteConfirm = (id: string, title: string) => {
  64. Modal.confirm({
  65. icon: <ExclamationCircleOutlined />,
  66. title:
  67. intl.formatMessage({
  68. id: "message.delete.confirm",
  69. }) +
  70. intl.formatMessage({
  71. id: "message.irrevocable",
  72. }),
  73. content: title,
  74. okText: intl.formatMessage({
  75. id: "buttons.delete",
  76. }),
  77. okType: "danger",
  78. cancelText: intl.formatMessage({
  79. id: "buttons.no",
  80. }),
  81. onOk() {
  82. const url = `/v2/channel/${id}`;
  83. console.log("delete api request", url);
  84. return delete_<IDeleteResponse>(url)
  85. .then((json) => {
  86. console.info("api response", json);
  87. if (json.ok) {
  88. message.success("删除成功");
  89. ref.current?.reload();
  90. } else {
  91. message.error(json.message);
  92. }
  93. })
  94. .catch((e) => console.log("Oops errors!", e));
  95. },
  96. });
  97. };
  98. const ref = useRef<ActionType>();
  99. return (
  100. <>
  101. <ProList<IProjectData>
  102. actionRef={ref}
  103. metas={{
  104. title: {
  105. render: (text, row, index, action) => {
  106. return readonly ? (
  107. <>{row.title}</>
  108. ) : (
  109. <Link to={`/studio/${studioName}/task/project/${row.id}`}>
  110. {row.title}
  111. </Link>
  112. );
  113. },
  114. },
  115. description: {
  116. dataIndex: "summary",
  117. },
  118. subTitle: {
  119. render: (text, row, index, action) => {
  120. return <></>;
  121. },
  122. },
  123. }}
  124. onRow={(record) => {
  125. return {
  126. onClick: () => {
  127. console.info(`点击了行:${record.title}`);
  128. onSelect?.(record);
  129. },
  130. };
  131. }}
  132. columns={[
  133. {
  134. title: intl.formatMessage({
  135. id: "forms.fields.title.label",
  136. }),
  137. dataIndex: "title",
  138. width: 250,
  139. key: "title",
  140. tooltip: "过长会自动收缩",
  141. ellipsis: true,
  142. render(dom, entity, index, action, schema) {
  143. return (
  144. <Link to={`/studio/${studioName}/task/project/${entity.id}`}>
  145. {entity.title}
  146. </Link>
  147. );
  148. },
  149. },
  150. {
  151. title: intl.formatMessage({
  152. id: "forms.fields.executors.label",
  153. }),
  154. dataIndex: "executors",
  155. key: "executors",
  156. },
  157. {
  158. title: intl.formatMessage({
  159. id: "forms.fields.milestone.label",
  160. }),
  161. dataIndex: "milestone",
  162. key: "milestone",
  163. width: 80,
  164. search: false,
  165. },
  166. {
  167. title: intl.formatMessage({
  168. id: "forms.fields.status.label",
  169. }),
  170. dataIndex: "status",
  171. key: "status",
  172. width: 80,
  173. search: false,
  174. filters: true,
  175. onFilter: true,
  176. valueEnum: PublicityValueEnum(),
  177. },
  178. {
  179. title: intl.formatMessage({
  180. id: "forms.fields.updated-at.label",
  181. }),
  182. key: "updated_at",
  183. width: 100,
  184. search: false,
  185. dataIndex: "updated_at",
  186. valueType: "date",
  187. sorter: true,
  188. },
  189. {
  190. title: intl.formatMessage({ id: "buttons.option" }),
  191. key: "option",
  192. width: 100,
  193. valueType: "option",
  194. render: (text, row, index, action) => {
  195. return [
  196. <Dropdown.Button
  197. key={index}
  198. type="link"
  199. trigger={["click", "contextMenu"]}
  200. menu={{
  201. items: [
  202. {
  203. key: "transfer",
  204. label: intl.formatMessage({
  205. id: "columns.studio.transfer.title",
  206. }),
  207. icon: <TransferOutLinedIcon />,
  208. },
  209. {
  210. key: "remove",
  211. label: intl.formatMessage({
  212. id: "buttons.delete",
  213. }),
  214. icon: <DeleteOutlined />,
  215. danger: true,
  216. },
  217. ],
  218. onClick: (e) => {
  219. switch (e.key) {
  220. case "remove":
  221. showDeleteConfirm(row.id, row.title);
  222. break;
  223. default:
  224. break;
  225. }
  226. },
  227. }}
  228. >
  229. <Link to={`/studio/${studioName}/channel/${row.id}/setting`}>
  230. {intl.formatMessage({
  231. id: "buttons.setting",
  232. })}
  233. </Link>
  234. </Dropdown.Button>,
  235. ];
  236. },
  237. },
  238. ]}
  239. request={async (params = {}, sorter, filter) => {
  240. console.log(params, sorter, filter);
  241. let url = `/v2/project?view=studio&type=${type}`;
  242. const offset =
  243. ((params.current ? params.current : 1) - 1) *
  244. (params.pageSize ? params.pageSize : 20);
  245. url += `&limit=${params.pageSize}&offset=${offset}`;
  246. url += params.keyword ? "&keyword=" + params.keyword : "";
  247. url += getSorterUrl(sorter);
  248. console.log("project list api request", url);
  249. const res = await get<IProjectListResponse>(url);
  250. console.info("project list api response", res);
  251. return {
  252. total: res.data.count,
  253. succcess: res.ok,
  254. data: res.data.rows,
  255. };
  256. }}
  257. rowKey="id"
  258. bordered
  259. pagination={{
  260. showQuickJumper: true,
  261. showSizeChanger: true,
  262. }}
  263. search={false}
  264. options={{
  265. search: true,
  266. }}
  267. toolBarRender={() => [
  268. readonly ? (
  269. <></>
  270. ) : (
  271. <Popover
  272. content={
  273. <ProjectCreate
  274. studio={studioName}
  275. type={type}
  276. onCreate={() => {
  277. setOpenCreate(false);
  278. ref.current?.reload();
  279. }}
  280. />
  281. }
  282. placement="bottomRight"
  283. trigger="click"
  284. open={openCreate}
  285. onOpenChange={(open: boolean) => {
  286. setOpenCreate(open);
  287. }}
  288. >
  289. <Button key="button" icon={<PlusOutlined />} type="primary">
  290. {intl.formatMessage({ id: "buttons.create" })}
  291. </Button>
  292. </Popover>
  293. ),
  294. ]}
  295. />
  296. </>
  297. );
  298. };
  299. export default ProjectListWidget;