ChannelPickerTable.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import { useEffect, useRef, useState } from "react";
  2. import { useIntl } from "react-intl";
  3. import { ActionType, ProList } from "@ant-design/pro-components";
  4. import { Alert, Button } from "antd";
  5. import { Badge, Dropdown, Space, Table, Typography } from "antd";
  6. import {
  7. GlobalOutlined,
  8. EditOutlined,
  9. MoreOutlined,
  10. CopyOutlined,
  11. ReloadOutlined,
  12. } from "@ant-design/icons";
  13. import { IApiResponseChannelList, IFinal, TChannelType } from "../api/Channel";
  14. import { post } from "../../request";
  15. import { LockIcon } from "../../assets/icon";
  16. import Studio, { IStudio } from "../auth/Studio";
  17. import ProgressSvg from "./ProgressSvg";
  18. import { IChannel } from "./Channel";
  19. import { ArticleType } from "../article/Article";
  20. import CopyToModal from "./CopyToModal";
  21. const { Link, Text } = Typography;
  22. interface IParams {
  23. owner?: string;
  24. }
  25. export interface IProgressRequest {
  26. sentence: string[];
  27. owner?: string;
  28. }
  29. export interface IItem {
  30. id: number;
  31. uid: string;
  32. title: string;
  33. summary: string;
  34. type: TChannelType;
  35. studio: IStudio;
  36. shareType: string;
  37. role?: string;
  38. publicity: number;
  39. final?: IFinal[];
  40. progress: number;
  41. createdAt: number;
  42. content_created_at?: string;
  43. content_updated_at?: string;
  44. }
  45. interface IWidget {
  46. type?: ArticleType | "editable";
  47. articleId?: string;
  48. multiSelect?: boolean /*是否支持多选*/;
  49. selectedKeys?: string[];
  50. reload?: boolean;
  51. disableChannelId?: string;
  52. defaultOwner?: string;
  53. onSelect?: Function;
  54. }
  55. const ChannelPickerTableWidget = ({
  56. type,
  57. articleId,
  58. multiSelect = true,
  59. selectedKeys = [],
  60. onSelect,
  61. disableChannelId,
  62. defaultOwner = "all",
  63. reload = false,
  64. }: IWidget) => {
  65. const intl = useIntl();
  66. const [selectedRowKeys, setSelectedRowKeys] =
  67. useState<React.Key[]>(selectedKeys);
  68. const [showCheckBox, setShowCheckBox] = useState<boolean>(false);
  69. const [copyChannel, setCopyChannel] = useState<IChannel>();
  70. const [copyOpen, setCopyOpen] = useState<boolean>(false);
  71. const [ownerChanged, setOwnerChanged] = useState<boolean>(false);
  72. const ref = useRef<ActionType>();
  73. useEffect(() => {
  74. if (reload) {
  75. ref.current?.reload();
  76. }
  77. }, [reload]);
  78. return (
  79. <Space direction="vertical" style={{ width: "100%" }}>
  80. {defaultOwner !== "all" && ownerChanged === false ? (
  81. <Alert
  82. message={
  83. <>
  84. {"目前仅显示了版本"}
  85. <Text keyboard>
  86. {intl.formatMessage({ id: `buttons.channel.${defaultOwner}` })}
  87. </Text>
  88. {"可以点"}
  89. <Text keyboard>{"版本筛选"}</Text>
  90. {"显示其他版本"}
  91. </>
  92. }
  93. type="success"
  94. closable
  95. action={
  96. <Button
  97. type="link"
  98. onClick={() => {
  99. if (typeof onSelect !== "undefined") {
  100. onSelect([]);
  101. }
  102. }}
  103. >
  104. 不选择
  105. </Button>
  106. }
  107. />
  108. ) : undefined}
  109. <ProList<IItem, IParams>
  110. actionRef={ref}
  111. rowSelection={
  112. showCheckBox
  113. ? {
  114. // 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
  115. // 注释该行则默认不显示下拉选项
  116. alwaysShowAlert: true,
  117. selectedRowKeys: selectedRowKeys,
  118. onChange: (selectedRowKeys: React.Key[]) => {
  119. setSelectedRowKeys(selectedRowKeys);
  120. },
  121. selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
  122. }
  123. : undefined
  124. }
  125. tableAlertRender={
  126. showCheckBox
  127. ? ({ selectedRowKeys, selectedRows, onCleanSelected }) => {
  128. return (
  129. <Space>
  130. {intl.formatMessage({ id: "buttons.selected" })}
  131. <Badge color="geekblue" count={selectedRowKeys.length} />
  132. <Link onClick={onCleanSelected}>
  133. {intl.formatMessage({ id: "buttons.empty" })}
  134. </Link>
  135. </Space>
  136. );
  137. }
  138. : undefined
  139. }
  140. tableAlertOptionRender={
  141. showCheckBox
  142. ? ({ selectedRowKeys, selectedRows, onCleanSelected }) => {
  143. return (
  144. <Space>
  145. <Link
  146. onClick={() => {
  147. if (typeof onSelect !== "undefined") {
  148. onSelect(
  149. selectedRows.map((item) => {
  150. return {
  151. id: item.uid,
  152. name: item.title,
  153. };
  154. })
  155. );
  156. setShowCheckBox(false);
  157. ref.current?.reload();
  158. }
  159. }}
  160. >
  161. {intl.formatMessage({
  162. id: "buttons.ok",
  163. })}
  164. </Link>
  165. <Link
  166. type="danger"
  167. onClick={() => {
  168. setShowCheckBox(false);
  169. }}
  170. >
  171. {intl.formatMessage({
  172. id: "buttons.cancel",
  173. })}
  174. </Link>
  175. </Space>
  176. );
  177. }
  178. : undefined
  179. }
  180. request={async (params = {}, sorter, filter) => {
  181. console.log(params, sorter, filter);
  182. const sentElement = document.querySelectorAll(".pcd_sent");
  183. let sentList: string[] = [];
  184. for (let index = 0; index < sentElement.length; index++) {
  185. const element = sentElement[index];
  186. const id = element.id.split("_")[1];
  187. sentList.push(id);
  188. }
  189. const currOwner = params.owner ? params.owner : defaultOwner;
  190. if (params.owner) {
  191. setOwnerChanged(true);
  192. }
  193. console.log("owner", currOwner);
  194. const res = await post<IProgressRequest, IApiResponseChannelList>(
  195. `/v2/channel-progress`,
  196. {
  197. sentence: sentList,
  198. owner: currOwner,
  199. }
  200. );
  201. console.debug("progress data", res.data.rows);
  202. const items: IItem[] = res.data.rows
  203. .filter((value) => value.name.substring(0, 4) !== "_Sys")
  204. .map((item, id) => {
  205. const date = new Date(item.created_at);
  206. let all: number = 0;
  207. let finished: number = 0;
  208. item.final?.forEach((value) => {
  209. all += value[0];
  210. finished += value[1] ? value[0] : 0;
  211. });
  212. const progress = finished / all;
  213. return {
  214. id: id,
  215. uid: item.uid,
  216. title: item.name,
  217. summary: item.summary,
  218. studio: item.studio,
  219. shareType: "my",
  220. role: item.role,
  221. type: item.type,
  222. publicity: item.status,
  223. createdAt: date.getTime(),
  224. final: item.final,
  225. progress: progress,
  226. };
  227. });
  228. //当前被选择的
  229. const currChannel = items.filter((value) =>
  230. selectedRowKeys.includes(value.uid)
  231. );
  232. let show = selectedRowKeys;
  233. //有进度的
  234. const progressing = items.filter(
  235. (value) => value.progress > 0 && !show.includes(value.uid)
  236. );
  237. show = [...show, ...progressing.map((item) => item.uid)];
  238. //我自己的
  239. const myChannel = items.filter(
  240. (value) => value.role === "owner" && !show.includes(value.uid)
  241. );
  242. show = [...show, ...myChannel.map((item) => item.uid)];
  243. //其他的
  244. const others = items.filter(
  245. (value) => !show.includes(value.uid) && value.role !== "member"
  246. );
  247. setSelectedRowKeys(selectedRowKeys);
  248. const channelData = [
  249. ...currChannel,
  250. ...progressing,
  251. ...myChannel,
  252. ...others,
  253. ];
  254. return {
  255. total: res.data.count,
  256. succcess: true,
  257. data: channelData,
  258. };
  259. }}
  260. rowKey="uid"
  261. bordered
  262. options={false}
  263. search={{
  264. filterType: "light",
  265. }}
  266. toolBarRender={() => [
  267. multiSelect ? (
  268. <Button
  269. onClick={() => {
  270. setShowCheckBox(true);
  271. }}
  272. >
  273. 选择
  274. </Button>
  275. ) : undefined,
  276. <Button
  277. type="link"
  278. onClick={() => {
  279. ref.current?.reload();
  280. }}
  281. icon={<ReloadOutlined />}
  282. />,
  283. ]}
  284. metas={{
  285. title: {
  286. render(dom, entity, index, action, schema) {
  287. let pIcon = <></>;
  288. switch (entity.publicity) {
  289. case 5:
  290. pIcon = <LockIcon />;
  291. break;
  292. case 10:
  293. pIcon = <LockIcon />;
  294. break;
  295. case 30:
  296. pIcon = <GlobalOutlined />;
  297. break;
  298. }
  299. return (
  300. <div
  301. key={index}
  302. style={{
  303. width: "100%",
  304. borderRadius: 5,
  305. padding: "0 5px",
  306. background:
  307. selectedKeys.includes(entity.uid) && !showCheckBox
  308. ? "linear-gradient(to left, rgb(63 255 165 / 54%), rgba(0, 0, 0, 0))"
  309. : undefined,
  310. }}
  311. >
  312. <div
  313. key="info"
  314. style={{ overflowX: "clip", display: "flex" }}
  315. >
  316. <Space>
  317. {pIcon}
  318. {entity.role !== "member" ? <EditOutlined /> : undefined}
  319. </Space>
  320. <Button
  321. type="link"
  322. disabled={disableChannelId === entity.uid}
  323. onClick={() => {
  324. if (typeof onSelect !== "undefined") {
  325. const e: IChannel = {
  326. name: entity.title,
  327. id: entity.uid,
  328. };
  329. onSelect([e]);
  330. }
  331. }}
  332. >
  333. <Space>
  334. <Studio data={entity.studio} hideName />
  335. {entity.title}
  336. </Space>
  337. </Button>
  338. </div>
  339. <div key="progress">
  340. <ProgressSvg data={entity.final} width={200} />
  341. </div>
  342. </div>
  343. );
  344. },
  345. search: false,
  346. },
  347. actions: {
  348. render: (dom, entity, index, action, schema) => {
  349. return (
  350. <Dropdown
  351. key={index}
  352. trigger={["click"]}
  353. menu={{
  354. items: [
  355. {
  356. key: "copy-to",
  357. label: intl.formatMessage({
  358. id: "buttons.copy.to",
  359. }),
  360. icon: <CopyOutlined />,
  361. },
  362. ],
  363. onClick: (e) => {
  364. switch (e.key) {
  365. case "copy-to":
  366. setCopyChannel({
  367. id: entity.uid,
  368. name: entity.title,
  369. type: entity.type,
  370. });
  371. setCopyOpen(true);
  372. break;
  373. default:
  374. break;
  375. }
  376. },
  377. }}
  378. placement="bottomRight"
  379. >
  380. <Button
  381. type="link"
  382. size="small"
  383. icon={<MoreOutlined />}
  384. ></Button>
  385. </Dropdown>
  386. );
  387. },
  388. },
  389. owner: {
  390. // 自己扩展的字段,主要用于筛选,不在列表中显示
  391. title: "版本筛选",
  392. valueType: "select",
  393. valueEnum: {
  394. all: { text: intl.formatMessage({ id: "buttons.channel.all" }) },
  395. my: {
  396. text: intl.formatMessage({ id: "buttons.channel.my" }),
  397. },
  398. collaborator: {
  399. text: intl.formatMessage({
  400. id: "buttons.channel.collaborator",
  401. }),
  402. },
  403. public: {
  404. text: intl.formatMessage({ id: "buttons.channel.public" }),
  405. },
  406. },
  407. },
  408. }}
  409. />
  410. <CopyToModal
  411. channel={copyChannel}
  412. open={copyOpen}
  413. onClose={() => setCopyOpen(false)}
  414. />
  415. </Space>
  416. );
  417. };
  418. export default ChannelPickerTableWidget;