瀏覽代碼

重复文件表格的文件大小排序

john 1 年之前
父節點
當前提交
79158dd5a9

+ 2 - 0
src/pages/DuplicateFile/CalculateListPage.module.less

@@ -1,5 +1,7 @@
 .CalculateListPage {
   background-color: #99999908;
+  padding: 24px;
+  width: 100%;
   .CheckboxGroup {
     margin-bottom: 12px;
     width: 100%;

+ 203 - 145
src/pages/DuplicateFile/CalculateListPage.tsx

@@ -9,6 +9,7 @@ import {
   Button,
   Spin,
   Empty,
+  Table,
 } from "antd";
 import type { CheckboxProps } from "antd";
 import { useEffect, useState } from "react";
@@ -17,7 +18,8 @@ import {
   get_fileInfo_by_id,
   searchDuplicateFile,
   setDuplicateFile,
-  getDuplicateFile
+  getDuplicateFile,
+  getDuplicateFiles_v2
 } from "@/services";
 import {
   message as tauriMessage,
@@ -44,6 +46,12 @@ export default function CalculateListPage() {
   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 [tableKey, setTableKey] = useState('123456')
+  const [sorterOrder, setSorterOrder] = useState('')
+  const [sorterColumnKey, setSorterColumnKey] = useState({})
   interface FileItem {
     sourceId: number;
     ids: string;
@@ -51,18 +59,33 @@ export default function CalculateListPage() {
     count: number;
     firstItem: insertSearchFilesPasamsType;
     otherItems: insertSearchFilesPasamsType[];
+    children: insertSearchFilesPasamsType[];
   }
 
   async function getFileList() {
     setLoading(true);
-    setTip('加载数据中~')
-    const res = await getDuplicateFile({
-      sourceId: `${fileId}`,
-       page,
-      pageSize: 10
+    setTip('加载数据中~');
+    const sorters = [];
+    Object.keys(sorterColumnKey).forEach(key => {
+      sorters.push({
+        column: key,
+        order: sorterColumnKey[key]
+      })
+    })
+    const res = await getDuplicateFiles_v2({
+      searchParams: {
+        sourceId: `${fileId}`,
+        keywords: {},
+        sorters: sorters
+      },
+      page,
+      pageSize
     })
+    console.log(69696969, sorterColumnKey);
     const newData: any[] = [];
-    if(Array.isArray(res.data)) {
+    const allids = []
+    setTotal(res.total)
+    if(Array.isArray(res.data) && res.data.length) {
       await res.data.reduce(
         async (prevPromise: any, currentFile: any) => {
           const ids = currentFile.ids.split(",");
@@ -76,35 +99,41 @@ export default function CalculateListPage() {
               })
               .filter((elm: any) => elm)
           );
+          const children = otherItems
+            .map((elm) => {
+              if (elm.status === "fulfilled" && !elm.value[1]) {
+                allids.push(`${ids[0]}_${elm.value[0].id}`)
+                return {
+                  id: `${ids[0]}_${elm.value[0].id}`,
+                  key: `${ids[0]}_${elm.value[0].id}`,
+                  ...elm.value[0]
+                };
+              }
+              return false;
+            })
+            .filter((elm: any) => elm)
+          allids.push(ids[0]);
           newData.push({
             ...currentFile,
-            otherItems: otherItems
-              .map((elm) => {
-                if (elm.status === "fulfilled" && !elm.value[1]) {
-                  return elm.value[0];
-                }
-                return false;
-              })
-              .filter((elm: any) => elm),
+            id: ids[0],
+            key: ids[0],
+            children: children,
+            otherItems: children,
           });
           return Promise.resolve(0)
         }
       ), Promise.resolve(0)
-      const allFilesHash = data.map(elm => elm.hash);
-      const noDuplicateData = newData.filter(elm => {
-        if(allFilesHash.indexOf(elm.hash) > -1) {
-          return false
-        }
-        return elm
-      })
-      setData([
-        ...data,
-        ...noDuplicateData
-      ]);
+      setData(newData);
+      setTableKey(`${new Date().getTime()}`);
       setTimeout(() => {
         setLoading(false);
         setTip('');
       }, 300)
+    } else {
+      setData([]);
+      setTableKey(`${new Date().getTime()}`);
+      setLoading(false);
+      setTip('');
     }
   }
   const appendData = async () => {
@@ -171,43 +200,6 @@ export default function CalculateListPage() {
     const res = await File.showFileInExplorer(path);
   };
 
-  const CheckboxContent = (item: insertSearchFilesPasamsType) => (
-    <div className={styles.CheckboxContent}>
-      <div>
-        <FolderOpenOutlined onClick={() => openFileShowInExplorer(item.path)} />
-      </div>
-      <div className={styles.modified_time}>
-        <CopyText
-          width="300px"
-          color="#333"
-          ellipsisLine={0}
-          name={item.name || ""}
-        ></CopyText>
-      </div>
-      <div className={styles.path}>
-        <CopyText
-          width="300px"
-          color="#333"
-          ellipsisLine={1}
-          name={item.path || ""}
-        ></CopyText>
-      </div>
-      <div className={styles.modified_time}>
-        <CopyText
-          width="150px"
-          color="#333"
-          name={item.modified_time ? dayjs.unix(item.modified_time).format('YYYY-MM-DD HH:mm:ss') : ""}
-        ></CopyText>
-      </div>
-      <div className={styles.modified_time}>
-        <CopyText
-          width="100px"
-          color="#333"
-          name={item.file_size ? formatFileSize(item.file_size) : ''}
-        ></CopyText>
-      </div>
-    </div>
-  );
   const waittime = (time = 100) => {
     return new Promise((resolve) => {
       setTimeout(() => {
@@ -215,6 +207,7 @@ export default function CalculateListPage() {
       }, time);
     });
   };
+
   async function removeFilesByDB() {
     setLoading(true);
     const filesRes = await Promise.allSettled(
@@ -259,6 +252,7 @@ export default function CalculateListPage() {
       type: "error",
     });
   }
+
   async function openDialogSave() {
     // const appDataDir = await File.getAppDataDir();
     // const appDataDirPath = await appDataDir();
@@ -283,98 +277,162 @@ export default function CalculateListPage() {
       ],
     });
   }
+
   useEffect(() => {
-    if(data.length) {
-      getFileList();  
+    getFileList();
+  }, [page, pageSize, sorterColumnKey, sorterOrder])
+
+  const columns: ColumnsType<DataType> = [
+    {
+      title: '文件名称',
+      dataIndex: 'name',
+      key: 'name',
+      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}>
+            {record.name}
+          </Col>
+        </Row>
+      )
+    },
+    {
+      title: '文件路径',
+      dataIndex: 'path',
+      key: 'path',
+      render: (text: string, record: { path?: string }) => (
+        <Row>
+          
+          <CopyText
+            width="300px"
+            ellipsisLine={1}
+            color="#333"
+            name={record.path || ""}
+          ></CopyText>
+        </Row>
+        ),
+    },
+    {
+      title: '文件大小',
+      dataIndex: 'file_size',
+      key: 'file_size',
+      sorter: true,
+      render: (text: string, record: { file_size?: string }) => (
+          <CopyText
+            width="150px"
+            ellipsisLine={1}
+            color="#333"
+            name={formatFileSize(record.file_size)}
+          ></CopyText>
+        ),
+    },
+    {
+      title: '修改时间',
+      dataIndex: 'modified_time',
+      key: 'modified_time',
+      render: (text: string, record: { modified_time?: string }) => (
+          <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: TableRowSelection<DataType> = {
+    onChange: (selectedRowKeys, selectedRows) => {
+      console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+      setUserSelectedRowKeys(selectedRowKeys);
+    },
+    onSelect: (record, selected, selectedRows) => {
+      console.log('onSelect', record, selected, selectedRows);
+    },
+    onSelectAll: (selected, selectedRows, changeRows) => {
+      console.log('onSelectAll', selected, selectedRows, changeRows);
+    },
+    selectedRowKeys: userSelectedRowKeys
+  };
+  const expandable = {
+    defaultExpandAllRows: true
+  }
+  const [checkStrictly, setCheckStrictly] = useState(false);
+
+  function onTableChange(pagination,filters, sorter,extra) {
+    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
+        })
+      }
     }
-  }, [page])
-  function getNext() {
-    setPage(page + 1);
   }
+
   return (
     <div className={styles.CalculateListPage}>
-      <Spin spinning={loading} tip={tip}>
+      <Space>
+        <Button type="primary" danger onClick={() => removeFilesByDB()}>
+          删除选中的文件
+        </Button>
+        <Button type="primary" onClick={() => openDialogSave()}>
+          统一移动到指定目录
+        </Button>
+        <Button type="primary">导出</Button>
+      </Space>
+      <Table
+        columns={columns}
+        loading={loading}
+        pagination={{
+          total,
+          pageSize,
+          current: page,
+          showQuickJumper: true
+        }}
+        rowSelection={{ ...rowSelection}}
+        expandable={{
+          defaultExpandAllRows: false
+        }}
+        dataIndex="id"
+        dataSource={data}
+        onChange={onTableChange}
+      />
+      {!data.length && !loading && (
         <div
           style={{
-            padding: "24px",
-            minHeight: '50vh'
+            padding: "48px 0",
+            backgroundColor: "#fff",
           }}
         >
-          <Space>
-            <Button type="primary" danger onClick={() => removeFilesByDB()}>
-              删除选中的文件
-            </Button>
-            <Button type="primary" onClick={() => openDialogSave()}>
-              统一移动到指定目录
-            </Button>
-            <Button type="primary">导出</Button>
-          </Space>
-          <div style={{ marginBottom: "12px" }}></div>
-          <Checkbox.Group
-            onChange={onChange}
-            style={{ width: "100%" }}
-            value={removeList}
-          >
-            <div style={{ width: "100%" }}>
-              <List>
-                <VirtualList
-                    data={data}
-                    itemKey="hash"
-                >
-                {/*height={ContainerHeight}*/}
-                  {(item: any) => (
-                    <List.Item key={item.hash}>
-                      <div
-                        key={item.hash}
-                        style={{
-                          backgroundColor: "var(--color-2)",
-                          marginBottom: "24px",
-                          width: '100%'
-                        }}
-                      >
-                        <div className={styles.CheckboxGroup}>
-                          <Checkbox value={item.path}>
-                            {CheckboxContent(item as any)}
-                            {/*item.path*/}
-                          </Checkbox>
-                        </div>
-                        <div
-                          style={{
-                            border: "1px solid var(--color-1)",
-                            padding: "12px 3px",
-                          }}
-                          className={styles.CheckboxGroup}
-                        >
-                          {item.otherItems.map((otherItem) => (
-                            <div key={otherItem.path}>
-                              <Checkbox value={otherItem.path}>
-                                {CheckboxContent(otherItem)}
-                              </Checkbox>
-                            </div>
-                          ))}
-                        </div>
-                      </div>
-                    </List.Item>
-                  )}
-                </VirtualList>
-              </List>
-            </div>
-          </Checkbox.Group>
-          <Button type="primary" onClick={() => getNext()}>
-            加载更多
-          </Button>
-          {!data.length && !loading && (
-            <div
-              style={{
-                padding: "48px 0",
-                backgroundColor: "#fff",
-              }}
-            >
-              <Empty description={"当前目录没有找到重复的文件"} />
-            </div>
-          )}
+          <Empty description={"当前目录没有找到重复的文件"} />
         </div>
-      </Spin>
+      )}
     </div>
   );
 }

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

@@ -577,7 +577,9 @@ export async function getDuplicateFile(
   {
     page: number,
     pageSize: number,
-    sourceId: string
+    sourceId: string,
+    sorterOrder?: string,
+    sorterColumnKey?: string
   }
 ): Promise<{
   [x: string]: any;
@@ -594,7 +596,7 @@ export async function getDuplicateFile(
     );
     const total = Array.isArray(totalResult) && totalResult[0].total; // 获取总记录数
     // 计算分页偏移量
-    const offset = (page || 1 - 1) * (pageSize || 10);
+    const offset = (page - 1 || 1 - 1) * (pageSize || 10);
     // 获取当前页的数据
     const data = await DB.select(
       "SELECT * FROM duplicate_files LIMIT ? OFFSET ?",
@@ -608,3 +610,126 @@ export async function getDuplicateFile(
 
 
 
+/*
+代码解释:
+多字段搜索:通过遍历 searchParams.keywords 对象,为每个指定的字段添加模糊搜索条件。
+多字段排序:searchParams.sorters 是一个包含多个排序规则的数组,这些规则被转换为 SQL ORDER BY 子句的一部分。
+参数化查询:继续使用参数化查询以防止 SQL 注入,并确保查询的安全性和效率。
+这种方式为你的应用提供了更灵活的数据检索方式,使其能够根据多个字段进行搜索和排序。
+
+
+以下是一个调用上述 `getDuplicateFiles` 函数的示例,该示例演示如何根据多个字段进行搜索和排序。在这个示例中,我们假设你正在查找包含特定关键字的文件名称或路径,并希望结果先按文件大小降序排列,然后按创建时间升序排列。
+
+### 示例调用
+
+假设你正在编写一个前端界面或服务端处理请求的脚本,这里是如何设置参数并调用函数的:
+
+```javascript
+async function fetchData() {
+  // 设置分页参数
+  const page = 1;
+  const pageSize = 10;
+
+  // 设置搜索和排序参数
+  const searchParams = {
+    sourceId: '123', // 假设的 sourceId
+    keywords: {
+      name: 'report', // 搜索文件名包含 'report'
+      path: '2024'    // 搜索路径包含 '2024'
+    },
+    sorters: [
+      { column: 'file_size', order: 'DESC' }, // 按文件大小降序
+      { column: 'create_time', order: 'ASC' } // 按创建时间升序
+    ]
+  };
+
+  // 调用函数获取数据
+  try {
+    const result = await getDuplicateFiles({ page, pageSize, searchParams });
+    console.log('Fetched Data:', result.data);
+    console.log('Total Records:', result.total);
+  } catch (error) {
+    console.error('Error fetching duplicate files:', error);
+  }
+}
+
+// 执行函数
+fetchData();
+```
+
+### 函数行为说明:
+
+- **分页**:查询第一页数据,每页显示10条记录。
+- **搜索条件**:
+  - 在 `name` 字段中搜索包含 "report" 的记录。
+  - 在 `path` 字段中搜索包含 "2024" 的记录。
+- **排序条件**:
+  - 首先按 `file_size` 字段降序排列,确保较大的文件先显示。
+  - 然后按 `create_time` 字段升序排列,较早创建的文件在相同大小的情况下先显示。
+
+这个示例充分展示了如何在实际应用中使用灵活的查询功能,满足复杂的数据检索需求。
+
+*/
+type SearchParam = {
+    sourceId: string,
+    keywords: { [key: string]: string }, // key: 字段名称, value: 搜索关键词
+    sorters: { column: string, order: 'ASC' | 'DESC' }[] // 排序数组
+};
+
+interface FetchParams {
+  page: number,
+  pageSize: number,
+  searchParams: SearchParam
+}
+
+export async function getDuplicateFiles_v2({
+  page,
+  pageSize,
+  searchParams,
+}: FetchParams): Promise<{
+  data: FileInfoType[];
+  total: number;
+}> {
+  try {
+    const DB = await Database.load(`sqlite:files_${searchParams.sourceId}.db`);
+    // 创建表
+    await DB.execute(createSql.duplicate_files);
+    // 动态构建查询条件
+    const conditions = [];
+    const params = [];
+
+    // 处理多字段搜索
+    Object.keys(searchParams.keywords).forEach(field => {
+      if (searchParams.keywords[field]) {
+        conditions.push(`${field} LIKE ?`);
+        params.push(`%${searchParams.keywords[field]}%`);
+      }
+    });
+
+    // 计算分页偏移量
+    const offset = (page - 1) * pageSize;
+
+    // 动态构建排序条件
+    const orderByClauses = searchParams.sorters.map(sorter => `${sorter.column} ${sorter.order}`).join(', ');
+    const orderBy = orderByClauses ? `ORDER BY ${orderByClauses}` : '';
+
+    // 查询总记录数(考虑搜索条件)
+    const totalQuery = `SELECT COUNT(*) AS total FROM duplicate_files ${conditions.length ? 'WHERE ' + conditions.join(' AND ') : ''}`;
+    const totalResult = await DB.select(totalQuery, params);
+    const total = Array.isArray(totalResult) && totalResult[0].total; // 获取总记录数
+
+    // 获取当前页的数据
+    const dataQuery = `SELECT * FROM duplicate_files ${conditions.length ? 'WHERE ' + conditions.join(' AND ') : ''} ${orderBy} LIMIT ? OFFSET ?`;
+    params.push(pageSize, offset);
+    const data = await DB.select(dataQuery, params);
+
+    return { data: Array.isArray(data) ? data : [], total }; // 返回包含数据和总记录数的对象
+  } catch (error) {
+    console.error('Error fetching data:', error);
+    return { data: [], total: 0 };
+  }
+}
+
+
+
+