ChannelPickerTable.tsx 13 KB

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