瀏覽代碼

优化重复文件的筛选初始化体验

john 1 年之前
父節點
當前提交
81dae9fb90

+ 1 - 0
src/databases/createTableSql.ts

@@ -40,6 +40,7 @@ export const createSql = {
         hash TEXT,
         db_version TEXT,
         ids  TEXT,
+        idsNum INTEGER,
         UNIQUE (hash)
     );`,
 };

+ 2 - 0
src/pages/DuplicateFile/CalculateDuplicateFiles.tsx

@@ -21,6 +21,7 @@ import get_progress_by_sourceId, {
   get_fileInfo_by_path,
   get_list_by_sourceid,
   updateFileHsah,
+  duplicateFilesDBInit,
 } from "@/services/file-service";
 
 export default function CalculateDuplicateFiles() {
@@ -78,6 +79,7 @@ export default function CalculateDuplicateFiles() {
   async function pageInit() {
     if (fileId) {
       const [data, errorMsg] = await get_info_by_id(Number.parseInt(fileId));
+      await duplicateFilesDBInit(`${fileId}`);
       if (data && typeof data === "object") {
         setFileInfo(data);
       } else {

+ 140 - 125
src/pages/DuplicateFile/CalculateListPage.tsx

@@ -8,13 +8,14 @@ import {
   Table,
   Input,
   InputRef,
+  Spin,
 } from "antd";
 import React, { useEffect, useState, useRef } from "react";
 import { SearchOutlined, FolderOpenOutlined } from "@ant-design/icons";
 import {
   message as tauriMessage,
   save as dialogSave,
-  open as dialogopen,
+  open as dialogOpen,
 } from "@tauri-apps/api/dialog";
 import { insertSearchFilesPasamsType } from "@/types/files";
 import dayjs from "dayjs";
@@ -42,8 +43,7 @@ export default function CalculateListPage() {
   const location = useLocation();
   const searchInput = useRef<InputRef>(null as any);
   const [data, setData] = useState<FileItem[]>([]);
-  const [loading, setLoading] = useState<boolean>(false);
-  // const [tip, setTip] = useState<string>('');
+  const [loading, setLoading] = useState<boolean>(true);
   const [removeList, setRemoveList] = useState<string[]>([]);
   const [page, setPage] = useState(1);
   const [total, setTotal] = useState(0);
@@ -54,7 +54,9 @@ export default function CalculateListPage() {
   const [searchText, setSearchText] = useState<any>({});
   const [isCancelled, setIsCancelled] = useState(false); // 离开页面时终止正在执行的逻辑
   const [hasMounted, setHasMounted] = useState(false);
-
+  const [spinTip, setSpinTip] = useState("");
+  //
+  const [settimeStart, setSettimeStart] = useState<NodeJS.Timeout>();
   interface FileItem {
     sourceId: number;
     ids: string;
@@ -65,7 +67,43 @@ export default function CalculateListPage() {
     children: insertSearchFilesPasamsType[];
   }
 
+  useEffect(() => {
+    appendData();
+  }, []);
+
+  useEffect(() => {
+    if (hasMounted) {
+      getFileList();
+    } else {
+      const startTime: NodeJS.Timeout = setTimeout(() => {
+        getFileList();
+      }, 5000);
+      setSettimeStart(startTime);
+    }
+  }, [page, pageSize, sorterColumnKey, sorterOrder]);
+
+  useEffect(() => {
+    setTimeout(() => {
+      // 设置一个状态标志,表示组件已经挂载
+      setHasMounted(true);
+    }, 1000);
+    // 如果你需要在组件卸载时进行清理,可以在这里返回一个函数
+    // 当组件加载时,不做特殊操作
+    // 只在组件卸载时设置isCancelled为true
+    return () => {
+      if (hasMounted) {
+        console.log(47, " 当组件卸载时,设置isCancelled为true");
+        setIsCancelled(true);
+        stopStartFn();
+      }
+    };
+  }, [hasMounted]);
+
+  function stopStartFn() {
+    clearTimeout(settimeStart);
+  }
   async function getFileList() {
+    stopStartFn();
     setLoading(true);
     try {
       const sorters = Object.keys(sorterColumnKey).map((key) => ({
@@ -97,12 +135,15 @@ export default function CalculateListPage() {
       const newData = await processFiles(res.data, fileId);
 
       setTimeout(() => {
+        console.log(100, newData);
         setData(newData);
+        setSpinTip("");
         setLoading(false);
       }, 300);
     } catch (error) {
       console.error("Error loading file list:", error);
       setData([]);
+      setSpinTip("");
       setLoading(false);
     }
   }
@@ -111,13 +152,16 @@ export default function CalculateListPage() {
     data: string | any[],
     fileId: string | undefined,
   ) {
-    if (!Array.isArray(data) || !data.length) {
+    if (!data || !Array.isArray(data) || !data?.length) {
       return [];
     }
     const results = [];
     for (const file of data) {
       const otherItems = await Promise.allSettled(
-        file.ids.split(",").map((id: string) => get_fileInfo_by_id(id, fileId)),
+        file.ids
+          .split(",")
+          .filter((elm: any, index: any) => index)
+          .map((id: string) => get_fileInfo_by_id(id, fileId)),
       );
       results.push({
         ...file,
@@ -149,76 +193,60 @@ export default function CalculateListPage() {
           type: "error",
         }));
     }
+    if (
+      !searchDuplicateFileRes ||
+      !Array.isArray(searchDuplicateFileRes) ||
+      !searchDuplicateFileRes.length
+    ) {
+      // 执行可分页的接口数据请求
+      await getFileList();
+      setLoading(false);
+      return;
+    }
     /**
      * count: 2
      * hash: "fdd8051fcf884d8cc9a095cd77a58694e13b066aea68dc1fc353767ab0ebfe01"
      * ids: "25494,26393"
      * sourceId: 6
      * */
-    if (Array.isArray(searchDuplicateFileRes)) {
-      let index = -1;
-      await searchDuplicateFileRes.reduce(
-        async (prevPromise: any, currentFile: any) => {
-          // 等待上一个 Promise 完成
-          await prevPromise;
-          if (
-            isCancelled ||
-            window.location.href.indexOf(location.pathname) < 0
-          ) {
-            // @ts-ignore
-            throw "提前终止";
-            return Promise.resolve(0);
-          } // 如果设置了取消标志,则提前终止
-          index++;
-          const ids = currentFile.ids.split(",");
-          const firstItem = await get_fileInfo_by_id(ids[0], `${fileId}`);
-          try {
-            // 写到本地的数据库中
-            await setDuplicateFile(`${fileId}`, {
-              ...firstItem[0],
-              ids: currentFile.ids,
-            });
-          } catch (err) {
-            console.log(109, err);
-          }
-          // setTip(`正在统计中: ${Math.floor((index / searchDuplicateFileRes.length) * 100)}% : ${searchDuplicateFileRes.length - index}`);
+    setLoading(true);
+    let index = -1;
+    await searchDuplicateFileRes.reduce(
+      async (prevPromise: any, currentFile: any) => {
+        // 等待上一个 Promise 完成
+        await prevPromise;
+        if (
+          isCancelled ||
+          window.location.href.indexOf(location.pathname) < 0
+        ) {
+          // @ts-ignore
+          throw "提前终止";
           return Promise.resolve(0);
-        },
-        Promise.resolve(0),
-      );
-      // 执行可分页的接口数据请求
-      await getFileList();
-    } else {
-      setLoading(false);
-      // setTip('')
-    }
+        } // 如果设置了取消标志,则提前终止
+        index++;
+        const ids = currentFile.ids.split(",");
+        const firstItem = await get_fileInfo_by_id(ids[0], `${fileId}`);
+        try {
+          // 写到本地的数据库中
+          await setDuplicateFile(`${fileId}`, {
+            ...firstItem[0],
+            ids: currentFile.ids,
+            idsNum: ids.length,
+          });
+        } catch (err) {
+          console.log(199, err);
+        }
+        setSpinTip(
+          `进度: ${Math.floor((index / searchDuplicateFileRes.length) * 100)}% / 剩余 ${searchDuplicateFileRes.length - index} 个文件`,
+        );
+        return Promise.resolve(0);
+      },
+      Promise.resolve(0),
+    );
+    // 执行可分页的接口数据请求
+    await getFileList();
   };
 
-  useEffect(() => {
-    appendData();
-  }, []);
-
-  useEffect(() => {
-    setTimeout(() => {
-      // 设置一个状态标志,表示组件已经挂载
-      setHasMounted(true);
-    }, 300);
-    // 如果你需要在组件卸载时进行清理,可以在这里返回一个函数
-    // 当组件加载时,不做特殊操作
-    // 只在组件卸载时设置isCancelled为true
-    return () => {
-      if (hasMounted) {
-        console.log(47, " 当组件卸载时,设置isCancelled为true");
-        setIsCancelled(true);
-      }
-    };
-  }, [hasMounted]);
-
-  const onChange = (checkedValues: string[]) => {
-    if (Array.isArray(checkedValues)) {
-      setRemoveList(checkedValues);
-    }
-  };
   const openFileShowInExplorer = async (path: string) => {
     const res = await File.showFileInExplorer(path);
   };
@@ -289,7 +317,7 @@ export default function CalculateListPage() {
     // const appDataDirPath = await appDataDir();
     // console.log(190, appDataDirPath);
     // 打开本地的系统目录,暂时不支持多选
-    const selected = await dialogopen({
+    const selected = await dialogOpen({
       title: "请选择需要保存的目录",
       directory: true,
       multiple: false,
@@ -297,7 +325,7 @@ export default function CalculateListPage() {
     });
 
     await File.moveSpecificFiles(
-      ["/Users/honghaitao/Downloads/Xnip2023-05-31_21-39-11_副本.png"],
+      ["/Users/sysadmin/Downloads/垃圾分类统计表_副本.xlsx"],
       `${selected}`,
     );
     return;
@@ -312,10 +340,6 @@ export default function CalculateListPage() {
     });
   }
 
-  useEffect(() => {
-    getFileList();
-  }, [page, pageSize, sorterColumnKey, sorterOrder]);
-
   const handleSearchReset = (dataIndex: string) => {
     setTotal(0);
     setPage(1);
@@ -326,6 +350,7 @@ export default function CalculateListPage() {
     setSorterColumnKey({});
     getFileList();
   };
+
   const handleSearch = (dataIndex: string, value: string) => {
     setSearchText({
       ...searchText,
@@ -376,11 +401,7 @@ export default function CalculateListPage() {
         <Row>
           <Col span={2}>
             <FolderOpenOutlined
-              onClick={() =>
-                openFileShowInExplorer(
-                    `${record.path}`
-                )
-              }
+              onClick={() => openFileShowInExplorer(`${record.path}`)}
             />
           </Col>
           <Col span={22}>
@@ -409,6 +430,12 @@ export default function CalculateListPage() {
       ),
       ...getColumnSearchProps("path", "文件路径"),
     },
+    {
+      title: "重复数量",
+      dataIndex: "idsNum",
+      key: "idsNum",
+      sorter: true,
+    },
     {
       title: "文件大小",
       dataIndex: "file_size",
@@ -490,10 +517,6 @@ export default function CalculateListPage() {
     },
     selectedRowKeys: userSelectedRowKeys,
   };
-  const expandable = {
-    defaultExpandAllRows: true,
-  };
-  const [checkStrictly, setCheckStrictly] = useState(false);
 
   const onTableChange: any = (
     pagination: {
@@ -544,50 +567,42 @@ export default function CalculateListPage() {
   };
 
   return (
-    <div className={styles.CalculateListPage}>
-      <Space>
-        <Button type="primary" danger onClick={() => removeFilesByDB()}>
-          删除选中的文件
-        </Button>
-        <Button type="primary" onClick={() => openDialogSave()}>
-          统一移动到指定目录
-        </Button>
-        {/*<Button type="primary">导出</Button>*/}
-      </Space>
-      <Table
-        style={{
-          width: "calc(100% - 48px)",
-        }}
-        columns={columns as any}
-        loading={loading}
-        pagination={{
-          total,
-          pageSize,
-          current: page,
-          showQuickJumper: true,
-        }}
-        rowSelection={{ ...rowSelection }}
-        expandable={{
-          defaultExpandAllRows: false,
-        }}
-        scroll={{
-          scrollToFirstRowOnChange: true,
-          x: true,
-        }}
-        key={"id"}
-        dataSource={data}
-        onChange={onTableChange}
-      />
-      {!data.length && !loading && (
-        <div
+    <Spin spinning={loading} tip={spinTip}>
+      <div className={styles.CalculateListPage}>
+        <Space>
+          <Button type="primary" danger onClick={() => removeFilesByDB()}>
+            删除选中的文件
+          </Button>
+          <Button type="primary" onClick={() => openDialogSave()}>
+            统一移动到指定目录
+          </Button>
+          {!loading && spinTip && <div>{spinTip}</div>}
+          {/*<Button type="primary">导出</Button>*/}
+        </Space>
+        <Table
           style={{
-            padding: "48px 0",
-            backgroundColor: "#fff",
+            width: "calc(100% - 48px)",
           }}
-        >
-          <Empty description={"当前目录没有找到重复的文件"} />
-        </div>
-      )}
-    </div>
+          columns={columns as any}
+          pagination={{
+            total,
+            pageSize,
+            current: page,
+            showQuickJumper: true,
+          }}
+          rowSelection={{ ...rowSelection }}
+          expandable={{
+            defaultExpandAllRows: false,
+          }}
+          scroll={{
+            scrollToFirstRowOnChange: true,
+            x: true,
+          }}
+          key={"id"}
+          dataSource={data}
+          onChange={onTableChange}
+        />
+      </div>
+    </Spin>
   );
 }

+ 15 - 2
src/services/file-service.ts

@@ -515,6 +515,7 @@ export async function setDuplicateFile(
     modified_time,
     file_size,
     ids,
+    idsNum,
   }: insertSearchFilesPasamsType,
 ) {
   try {
@@ -524,9 +525,9 @@ export async function setDuplicateFile(
     await DB.execute(
       `
         INSERT into duplicate_files 
-          (create_time, sourceId, name, type, path, hash, creation_time, modified_time, file_size, db_version, ids) 
+          (create_time, sourceId, name, type, path, hash, creation_time, modified_time, file_size, db_version, ids, idsNum) 
         VALUES 
-          ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
+          ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
       `,
       [
         new Date().getTime(),
@@ -540,6 +541,7 @@ export async function setDuplicateFile(
         file_size,
         "1",
         ids,
+        idsNum,
       ],
     );
     return Promise.resolve([true, ""]);
@@ -709,3 +711,14 @@ export async function getDuplicateFiles_v2({
     return { data: [], total: 0 };
   }
 }
+
+export async function duplicateFilesDBInit(sourceId: string) {
+  try {
+    const DB = await Database.load(`sqlite:files_${sourceId}.db`);
+    // 创建表
+    await DB.execute(createSql.search_files);
+    await DB.execute(createSql.duplicate_files);
+  } catch (error) {
+    console.log(error);
+  }
+}

+ 22 - 23
src/types/files.d.ts

@@ -1,4 +1,4 @@
-import {StepProps} from "antd/es/steps";
+import { StepProps } from "antd/es/steps";
 
 export interface FileInfoType {
   path?: string;
@@ -29,13 +29,14 @@ export type insertSearchFilesPasamsType = {
   file_path?: string;
   time?: string;
   // progress: number;
-  type: string,
-  name: string,
-  hash: string,
-  file_size: string,
-  creation_time?: string,
-  modified_time?: string,
-  ids?: string,
+  type: string;
+  name: string;
+  hash: string;
+  file_size: string;
+  creation_time?: string;
+  modified_time?: string;
+  ids?: string;
+  idsNum?: number;
 };
 
 export type historyListType = {
@@ -47,27 +48,25 @@ export type historyListType = {
   hash: string;
 };
 
-
 export type stepsStatusType = {
   scanDir: StepProps.status;
   fileOptions: StepProps.status;
   duplicateFiles: StepProps.status;
   done: StepProps.status;
-}
+};
 
 export type backFileInfoType = {
-    file_path: string,
-    file_name: string,
-    file_type: string,
-    file_size: string,
-    modified_time: string, // 时间戳形式
-    creation_time: string,
-}
-
+  file_path: string;
+  file_name: string;
+  file_type: string;
+  file_size: string;
+  modified_time: string; // 时间戳形式
+  creation_time: string;
+};
 
 export type fileInfoParamsType = {
-  path?: string,
-  checked_size_values?: string[],
-  types?: any[],
-  excluded_file_names?: number
-}
+  path?: string;
+  checked_size_values?: string[];
+  types?: any[];
+  excluded_file_names?: number;
+};