ChannelPickerTable.tsx 13 KB

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