CalculateListPage.tsx 8.8 KB

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