CalculateListPage.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import {
  2. Avatar,
  3. List,
  4. message,
  5. Checkbox,
  6. Row,
  7. Col,
  8. Space,
  9. Button,
  10. Spin,
  11. Empty,
  12. } from "antd";
  13. import type { CheckboxProps } from "antd";
  14. import { useEffect, useState } from "react";
  15. import {
  16. del_file_by_id,
  17. get_fileInfo_by_id,
  18. searchDuplicateFile,
  19. } from "@/services";
  20. import { message as tauriMessage, save as dialogSave } from "@tauri-apps/api/dialog";
  21. import { appDataDir, join } from '@tauri-apps/api/path';
  22. import styles from "./CalculateListPage.module.less";
  23. import { useParams } from "react-router";
  24. import { insertSearchFilesPasamsType } from "@/types/files";
  25. import type { GetProp } from "antd";
  26. import File from "@/plugins/tauri-plugin-file/file";
  27. import { CopyText } from "@/components/Table/CopyText";
  28. export default function CalculateListPage() {
  29. let { fileId } = useParams();
  30. const [data, setData] = useState<FileItem[]>([]);
  31. const [loading, setLoading] = useState<boolean>(false);
  32. const [removeList, setRemoveList] = useState<string[]>([]);
  33. interface FileItem {
  34. sourceId: number;
  35. ids: string;
  36. hash: string;
  37. count: number;
  38. firstItem: insertSearchFilesPasamsType;
  39. otherItems: insertSearchFilesPasamsType[];
  40. }
  41. const appendData = async () => {
  42. const [isError, searchDuplicateFileRes] = await searchDuplicateFile({
  43. sourceId: `${fileId}`,
  44. });
  45. if (!isError) {
  46. typeof searchDuplicateFileRes === "string" &&
  47. (await tauriMessage(searchDuplicateFileRes, {
  48. title: "查询失败",
  49. type: "error",
  50. }));
  51. }
  52. if (Array.isArray(searchDuplicateFileRes)) {
  53. const newData: any[] = [];
  54. await searchDuplicateFileRes.reduce(
  55. async (prevPromise: any, currentFile: any) => {
  56. // 等待上一个 Promise 完成
  57. await prevPromise;
  58. const ids = currentFile.ids.split(",");
  59. const firstItem = await get_fileInfo_by_id(ids[0], `${fileId}`);
  60. const otherItems = await Promise.allSettled(
  61. ids
  62. .map((id: string) => {
  63. if (id === ids[0]) {
  64. return false;
  65. }
  66. return get_fileInfo_by_id(id, `${fileId}`);
  67. })
  68. .filter((elm: any) => elm)
  69. );
  70. newData.push({
  71. ...currentFile,
  72. firstItem: firstItem[0],
  73. otherItems: otherItems
  74. .map((elm) => {
  75. if (elm.status === "fulfilled" && !elm.value[1]) {
  76. setRemoveList([]);
  77. return elm.value[0];
  78. }
  79. return false;
  80. })
  81. .filter((elm: any) => elm),
  82. });
  83. return Promise.resolve(0);
  84. },
  85. Promise.resolve(0)
  86. );
  87. setData(newData);
  88. }
  89. };
  90. useEffect(() => {
  91. appendData();
  92. }, []);
  93. const onChange = (checkedValues: string[]) => {
  94. if (Array.isArray(checkedValues)) {
  95. setRemoveList(checkedValues);
  96. }
  97. };
  98. const CheckboxContent = (item: insertSearchFilesPasamsType) => (
  99. <div className={styles.CheckboxContent}>
  100. <div className={styles.modified_time}>
  101. <CopyText
  102. width="100px"
  103. color="#333"
  104. ellipsisLine={0}
  105. name={item.name || ""}
  106. ></CopyText>
  107. </div>
  108. <div className={styles.path}>
  109. <CopyText
  110. width="300px"
  111. color="#333"
  112. ellipsisLine={1}
  113. name={item.path || ""}
  114. ></CopyText>
  115. </div>
  116. <div className={styles.modified_time}>
  117. <CopyText
  118. width="100px"
  119. color="#333"
  120. name={item.modified_time || ""}
  121. ></CopyText>
  122. </div>
  123. <div className={styles.modified_time}>
  124. <CopyText
  125. width="100px"
  126. color="#333"
  127. name={item.file_size || ""}
  128. ></CopyText>
  129. </div>
  130. </div>
  131. );
  132. const waittime = (time = 100) => {
  133. return new Promise((resolve) => {
  134. setTimeout(() => {
  135. resolve(0);
  136. }, time);
  137. });
  138. };
  139. async function removeFilesByDB() {
  140. setLoading(true);
  141. const filesRes = await Promise.allSettled(
  142. removeList.map((path) => File.rmFile(path))
  143. );
  144. if (removeList.length === 1) {
  145. if (
  146. filesRes[0].status === "fulfilled" &&
  147. filesRes[0].value.code === 200
  148. ) {
  149. setRemoveList([]);
  150. del_file_by_id(removeList[0], `${fileId}`);
  151. message.success(`${removeList[0]} 删除成功!`);
  152. appendData();
  153. return;
  154. }
  155. await tauriMessage(removeList[0], {
  156. title: "删除失败",
  157. type: "error",
  158. });
  159. }
  160. const rmSuccess = filesRes.filter((res) => {
  161. return res.status === "fulfilled" && res.value.code === 200;
  162. });
  163. if (rmSuccess.length) {
  164. await rmSuccess.reduce(async (prevPromise: any, item: any) => {
  165. await prevPromise;
  166. return del_file_by_id(item.value.data, `${fileId}`);
  167. }, Promise.resolve());
  168. message.success(
  169. `${rmSuccess.length}个文件删除成功! ${filesRes.length - rmSuccess.length}个文件删除失败!`
  170. );
  171. appendData();
  172. await waittime(1500);
  173. setLoading(false);
  174. return;
  175. }
  176. await waittime(1500);
  177. setLoading(false);
  178. await tauriMessage("当前操作异常,请重新尝试!", {
  179. title: "删除失败",
  180. type: "error",
  181. });
  182. }
  183. async function openDialogSave() {
  184. // const appDataDir = await File.getAppDataDir();
  185. const appDataDirPath = await appDataDir();
  186. console.log(190, appDataDirPath);
  187. return;
  188. // dialogSave
  189. const filePath = await dialogSave({
  190. filters: [
  191. {
  192. name: "Image",
  193. extensions: ["png", "jpeg"],
  194. },
  195. ],
  196. });
  197. console.log(186, filePath);
  198. }
  199. return (
  200. <div className={styles.CalculateListPage}>
  201. <Spin spinning={loading}>
  202. <div
  203. style={{
  204. padding: "24px",
  205. }}
  206. >
  207. <Space>
  208. <Button type="primary" danger onClick={() => removeFilesByDB()}>
  209. 删除选中的文件
  210. </Button>
  211. <Button type="primary" onClick={() => openDialogSave()}>
  212. 统一移动到指定目录
  213. </Button>
  214. <Button type="primary">导出</Button>
  215. </Space>
  216. <div style={{ marginBottom: "12px" }}></div>
  217. <Checkbox.Group
  218. onChange={onChange}
  219. style={{ width: "100%" }}
  220. value={removeList}
  221. >
  222. <div style={{ width: "100%" }}>
  223. {data.map((item: FileItem) => (
  224. <div
  225. key={item.hash}
  226. style={{
  227. backgroundColor: "var(--color-2)",
  228. marginBottom: "24px",
  229. }}
  230. >
  231. <div className={styles.CheckboxGroup}>
  232. <Checkbox value={item.firstItem.path}>
  233. {CheckboxContent(item.firstItem)}
  234. </Checkbox>
  235. </div>
  236. <div
  237. style={{
  238. border: "1px solid var(--color-1)",
  239. padding: "12px 3px",
  240. }}
  241. className={styles.CheckboxGroup}
  242. >
  243. {item.otherItems.map((otherItem) => (
  244. <div key={otherItem.path}>
  245. <Checkbox value={otherItem.path}>
  246. {CheckboxContent(otherItem)}
  247. </Checkbox>
  248. </div>
  249. ))}
  250. </div>
  251. </div>
  252. ))}
  253. {!data.length && <div style={{
  254. padding: '48px 0',
  255. backgroundColor: '#fff'
  256. }}><Empty description={'当前目录没有找到重复的文件'}/></div>}
  257. </div>
  258. </Checkbox.Group>
  259. </div>
  260. </Spin>
  261. </div>
  262. );
  263. }