Explorar o código

增加所有文件的管理页面

john hai 1 ano
pai
achega
6a8366a9db
Modificáronse 1 ficheiros con 599 adicións e 0 borrados
  1. 599 0
      src/pages/DuplicateFile/FilesManage.tsx

+ 599 - 0
src/pages/DuplicateFile/FilesManage.tsx

@@ -0,0 +1,599 @@
+import {
+  Avatar,
+  List,
+  message,
+  Checkbox,
+  Row,
+  Col,
+  Space,
+  Button,
+  Spin,
+  Empty,
+  Table,
+  Input,
+  InputRef,
+} from "antd";
+import React, { useEffect, useState, useRef } from "react";
+import { SearchOutlined } from "@ant-design/icons";
+import {
+  del_file_by_id,
+  get_fileInfo_by_id,
+  searchDuplicateFile,
+  setDuplicateFile,
+  getDuplicateFiles_v2,
+} from "@/services";
+import {
+  message as tauriMessage,
+  save as dialogSave,
+  open as dialogopen,
+} from "@tauri-apps/api/dialog";
+import { homeDir } from "@tauri-apps/api/path";
+import styles from "./CalculateListPage.module.less";
+import { useParams } from "react-router";
+import { insertSearchFilesPasamsType } from "@/types/files";
+import File from "@/plugins/tauri-plugin-file/file";
+import { CopyText } from "@/components/Table/CopyText";
+import { FolderOpenOutlined } from "@ant-design/icons";
+import dayjs from "dayjs";
+import { formatFileSize } from "@/utils";
+import { useLocation } from "react-router-dom";
+
+type KeywordsType<T> = {
+  [key: string]: T;
+};
+
+export default function FilesManage() {
+  let { fileId } = useParams();
+  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 [removeList, setRemoveList] = useState<string[]>([]);
+  const [page, setPage] = useState(1);
+  const [total, setTotal] = useState(0);
+  const [pageSize, setPageSize] = useState(10);
+  const [userSelectedRowKeys, setUserSelectedRowKeys] = useState<string[]>([]);
+  const [sorterOrder, setSorterOrder] = useState({});
+  const [sorterColumnKey, setSorterColumnKey] = useState<any>({});
+  const [searchText, setSearchText] = useState<any>({});
+  const [isCancelled, setIsCancelled] = useState(false); // 离开页面时终止正在执行的逻辑
+  const [hasMounted, setHasMounted] = useState(false);
+
+  interface FileItem {
+    sourceId: number;
+    ids: string;
+    hash: string;
+    count: number;
+    firstItem: insertSearchFilesPasamsType;
+    otherItems: insertSearchFilesPasamsType[];
+    children: insertSearchFilesPasamsType[];
+  }
+
+  async function getFileList() {
+    setLoading(true);
+    try {
+      const sorters = Object.keys(sorterColumnKey).map((key) => ({
+        column: key,
+        order: sorterColumnKey[key],
+      }));
+      const keywords: KeywordsType<string> = {};
+
+      if (searchText) {
+        Object.keys(searchText).forEach((key) => {
+          if (searchText[key]) {
+            keywords[key] = searchText[key];
+          }
+        });
+      }
+
+      const res = await getDuplicateFiles_v2({
+        searchParams: {
+          sourceId: `${fileId}`,
+          keywords: keywords,
+          sorters: sorters,
+        },
+        page: page - 1,
+        pageSize,
+        table_name: "search_files",
+      });
+
+      setTotal(res.total);
+
+      const newData = await processFiles(res.data, fileId);
+
+      setTimeout(() => {
+        setData(newData);
+        setLoading(false);
+      }, 300);
+    } catch (error) {
+      console.error("Error loading file list:", error);
+      setData([]);
+      setLoading(false);
+    }
+  }
+
+  async function processFiles(
+    data: string | any[],
+    fileId: string | undefined,
+  ) {
+    if (!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)),
+      // );
+      results.push({
+        ...file,
+        key: file.id,
+        // children: (otherItems as any[])
+        //   .filter(
+        //     (result: any) => result.status === "fulfilled" && !result.value[1],
+        //   )
+        //   .map((result) => ({
+        //     id: `${file.id}_${result.value[0].id}`,
+        //     key: `${file.id}_${result.value[0].id}`,
+        //     ...result.value[0],
+        //   })),
+      });
+    }
+    return results;
+  }
+
+  const appendData = async () => {
+    setLoading(true);
+    setRemoveList([]);
+    const [isError, searchDuplicateFileRes] = await searchDuplicateFile({
+      sourceId: `${fileId}`,
+    });
+    if (!isError) {
+      typeof searchDuplicateFileRes === "string" &&
+        (await tauriMessage(searchDuplicateFileRes, {
+          title: "查询失败",
+          type: "error",
+        }));
+    }
+    /**
+     * 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}`);
+          return Promise.resolve(0);
+        },
+        Promise.resolve(0),
+      );
+      // 执行可分页的接口数据请求
+      await getFileList();
+    } else {
+      setLoading(false);
+      // setTip('')
+    }
+  };
+
+  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);
+  };
+
+  const waittime = (time = 100) => {
+    return new Promise((resolve) => {
+      setTimeout(() => {
+        resolve(0);
+      }, time);
+    });
+  };
+
+  async function removeFilesByDB() {
+    setLoading(true);
+    const filesRes = await Promise.allSettled(
+      removeList.map((path) => File.rmFile(path)),
+    );
+    if (removeList.length === 1) {
+      if (
+        filesRes[0].status === "fulfilled" &&
+        (filesRes[0] as any).value.code === 200
+      ) {
+        setRemoveList([]);
+        await del_file_by_id(removeList[0], `${fileId}`);
+        message.success(`${removeList[0]} 删除成功!`);
+        await appendData();
+        return;
+      }
+      await tauriMessage(removeList[0], {
+        title: "删除失败",
+        type: "error",
+      });
+    }
+    const rmSuccess = filesRes.filter((res) => {
+      return res.status === "fulfilled" && res.value.code === 200;
+    });
+    if (rmSuccess.length) {
+      await rmSuccess.reduce(async (prevPromise: any, item: any) => {
+        await prevPromise;
+        if (
+          isCancelled ||
+          window.location.href.indexOf(location.pathname) < 0
+        ) {
+          // @ts-ignore
+          throw "提前终止";
+          return Promise.resolve(0);
+        } // 如果设置了取消标志,则提前终止
+        return del_file_by_id(item.value.data, `${fileId}`);
+      }, Promise.resolve());
+      message.success(
+        `${rmSuccess.length}个文件删除成功! ${filesRes.length - rmSuccess.length}个文件删除失败!`,
+      );
+      await appendData();
+      await waittime(1500);
+      setLoading(false);
+      return;
+    }
+    await waittime(1500);
+    setLoading(false);
+    await tauriMessage("当前操作异常,请重新尝试!", {
+      title: "删除失败",
+      type: "error",
+    });
+  }
+
+  async function openDialogSave() {
+    // const appDataDir = await File.getAppDataDir();
+    // const appDataDirPath = await appDataDir();
+    // console.log(190, appDataDirPath);
+    // 打开本地的系统目录,暂时不支持多选
+    const selected = await dialogopen({
+      title: "请选择需要保存的目录",
+      directory: true,
+      multiple: false,
+      defaultPath: await homeDir(),
+    });
+
+    await File.moveSpecificFiles(
+      ["/Users/honghaitao/Downloads/Xnip2023-05-31_21-39-11_副本.png"],
+      `${selected}`,
+    );
+    return;
+    // dialogSave
+    const filePath = await dialogSave({
+      filters: [
+        {
+          name: "Image",
+          extensions: ["png", "jpeg"],
+        },
+      ],
+    });
+  }
+
+  useEffect(() => {
+    getFileList();
+  }, [page, pageSize, sorterColumnKey, sorterOrder]);
+
+  const handleSearchReset = (dataIndex: string) => {
+    setTotal(0);
+    setPage(1);
+    setSearchText({
+      ...searchText,
+      [dataIndex]: "",
+    });
+    setSorterColumnKey({});
+    getFileList();
+  };
+  const handleSearch = (dataIndex: string, value: string) => {
+    setSearchText({
+      ...searchText,
+      [dataIndex]: value,
+    });
+  };
+
+  const getColumnSearchProps = (dataIndex: string, dataIndexName: string) => ({
+    filterDropdown: () => (
+      <div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
+        <Input
+          ref={searchInput}
+          placeholder={`搜索${dataIndexName}`}
+          value={searchText[dataIndex]}
+          onChange={(e) => handleSearch(dataIndex, e.target.value)}
+          onPressEnter={() => getFileList()}
+          style={{ marginBottom: 8, display: "block" }}
+        />
+        <Space>
+          <Button
+            type="primary"
+            icon={<SearchOutlined />}
+            size="small"
+            onClick={() => getFileList()}
+            style={{ width: 90 }}
+          >
+            搜索
+          </Button>
+          <Button
+            size="small"
+            onClick={() => handleSearchReset(dataIndex)}
+            style={{ width: 90 }}
+          >
+            重置
+          </Button>
+        </Space>
+      </div>
+    ),
+  });
+
+  const columns = [
+    {
+      title: "文件名称",
+      dataIndex: "name",
+      key: "name",
+      fixed: "left",
+      render: (text: string, record: { name?: string; path?: string }) => (
+        <Row>
+          <Col span={2}>
+            <FolderOpenOutlined
+              onClick={() =>
+                openFileShowInExplorer(
+                  "/Users/sysadmin/Library/Application Support/com.hht.com/files_3.db",
+                )
+              }
+            />
+          </Col>
+          <Col span={22}>
+            <CopyText
+              width="300px"
+              ellipsisLine={1}
+              color="#333"
+              name={record.name || ""}
+            ></CopyText>
+          </Col>
+        </Row>
+      ),
+      ...getColumnSearchProps("name", "文件名称"),
+    },
+    {
+      title: "文件路径",
+      dataIndex: "path",
+      key: "path",
+      render: (text: string, record: { path?: string }) => (
+        <CopyText
+          width="300px"
+          ellipsisLine={1}
+          color="#333"
+          name={record.path || ""}
+        ></CopyText>
+      ),
+      ...getColumnSearchProps("path", "文件路径"),
+    },
+    {
+      title: "文件大小",
+      dataIndex: "file_size",
+      key: "file_size",
+      sorter: true,
+      render: (text: string, record: { file_size: number }) => (
+        <CopyText
+          width="150px"
+          ellipsisLine={1}
+          color="#333"
+          name={formatFileSize(record.file_size)}
+        ></CopyText>
+      ),
+    },
+    {
+      title: "生成时间",
+      dataIndex: "create_time",
+      key: "create_time",
+      sorter: true,
+      render: (text: string, record: { create_time: number }) => (
+        <CopyText
+          width="160px"
+          ellipsisLine={1}
+          color="#333"
+          name={dayjs
+            .unix(record.create_time / 1000)
+            .format("YYYY-MM-DD HH:mm:ss")}
+        ></CopyText>
+      ),
+    },
+    {
+      title: "创建时间",
+      dataIndex: "creation_time",
+      key: "creation_time",
+      sorter: true,
+      render: (text: string, record: { creation_time: number }) => (
+        <CopyText
+          width="160px"
+          ellipsisLine={1}
+          color="#333"
+          name={dayjs.unix(record.creation_time).format("YYYY-MM-DD HH:mm:ss")}
+        ></CopyText>
+      ),
+    },
+    {
+      title: "修改时间",
+      dataIndex: "modified_time",
+      key: "modified_time",
+      sorter: true,
+      render: (text: string, record: { modified_time: number }) => (
+        <CopyText
+          width="160px"
+          ellipsisLine={1}
+          color="#333"
+          name={dayjs.unix(record.modified_time).format("YYYY-MM-DD HH:mm:ss")}
+        ></CopyText>
+      ),
+    },
+  ];
+
+  // rowSelection objects indicates the need for row selection
+  const rowSelection: any = {
+    onChange: (
+      selectedRowKeys: React.SetStateAction<string[]>,
+      selectedRows: any,
+    ) => {
+      console.log(
+        `selectedRowKeys: ${selectedRowKeys}`,
+        "selectedRows: ",
+        selectedRows,
+      );
+      setUserSelectedRowKeys(selectedRowKeys);
+    },
+    onSelect: (record: any, selected: any, selectedRows: any) => {
+      console.log("onSelect", record, selected, selectedRows);
+    },
+    onSelectAll: (selected: any, selectedRows: any, changeRows: any) => {
+      console.log("onSelectAll", selected, selectedRows, changeRows);
+    },
+    selectedRowKeys: userSelectedRowKeys,
+  };
+  const expandable = {
+    defaultExpandAllRows: true,
+  };
+  const [checkStrictly, setCheckStrictly] = useState(false);
+
+  const onTableChange: any = (
+    pagination: {
+      current: React.SetStateAction<number>;
+      pageSize: React.SetStateAction<number>;
+    },
+    filters: any,
+    sorter: {
+      order: string;
+      columnKey: any;
+    },
+    extra: any,
+  ) => {
+    console.log("pagination", pagination);
+    console.log("sorter", sorter);
+    if (pagination.current !== page) {
+      setTotal(0);
+      setPage(pagination.current);
+      return;
+    }
+    if (pagination.pageSize !== pageSize) {
+      setTotal(0);
+      setPage(1);
+      setPageSize(pagination.pageSize);
+      return;
+    }
+    // 筛选排序
+    if (
+      sorter &&
+      JSON.stringify(sorter) !== "{}" &&
+      sorter.order !== sorterOrder
+    ) {
+      setTotal(0);
+      setPage(1);
+      if (sorter.order) {
+        setSorterColumnKey({
+          // ...sorterColumnKey,
+          [sorter.columnKey]: sorter.order === "ascend" ? "ASC" : "DESC",
+        });
+      } else {
+        const _sorterColumnKey = { ...sorterColumnKey };
+        delete _sorterColumnKey[sorter.columnKey];
+        setSorterColumnKey({
+          ..._sorterColumnKey,
+        });
+      }
+    }
+  };
+
+  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
+          style={{
+            padding: "48px 0",
+            backgroundColor: "#fff",
+          }}
+        >
+          <Empty description={"当前目录没有找到重复的文件"} />
+        </div>
+      )}
+    </div>
+  );
+}