From d7948b5ed12715242f1ce04d7a0fcdc7aa77d67a Mon Sep 17 00:00:00 2001 From: mapan-nju <2246839805@qq.com> Date: Mon, 24 Mar 2025 15:36:12 +0800 Subject: [PATCH] Dependents page,versions page.search and sort functions in those pages --- .../[name]/[version]/dependencies/page.tsx | 2 +- .../[name]/[version]/dependents/page.tsx | 83 +++- .../[nsbehind]/[name]/[version]/layout.tsx | 2 +- .../[nsbehind]/[name]/[version]/page.tsx | 2 +- .../[name]/[version]/versions/page.tsx | 99 +++-- components/CrateNav.tsx | 2 +- components/DependentTable.tsx | 316 +++++++++++---- components/VersionsTable.tsx | 375 +++++++++++++++--- 8 files changed, 706 insertions(+), 175 deletions(-) diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx index 30122a8..1445d45 100644 --- a/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/dependencies/page.tsx @@ -49,7 +49,7 @@ const CratePage = () => { return (
-
+
diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx index 694f923..a6c78a3 100644 --- a/app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/dependents/page.tsx @@ -1,25 +1,23 @@ //Dependents页面 "use client"; import React, { useEffect, useState } from 'react'; - +import { useParams } from 'next/navigation'; +import Image from 'next/image'; import DependentTable from '@/components/DependentTable'; import { dependentsInfo } from '@/app/lib/all_interface'; -import { useParams } from 'next/navigation'; - - +import Link from 'next/link'; const CratePage = () => { const [results, setResults] = useState(null); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const params = useParams(); - - useEffect(() => { const fetchCrateData = async () => { try { + setError(null); const response = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/dependents`); if (!response.ok) { @@ -28,12 +26,10 @@ const CratePage = () => { const data = await response.json(); - setResults(data); // 设置获取的数据 - } catch (error) { - setError(null); console.log('Error fetching data:', error); + setError("Failed to fetch data."); } finally { setLoading(false); // 完成加载 } @@ -41,16 +37,67 @@ const CratePage = () => { fetchCrateData(); // 调用函数来获取数据 }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在 crateName 或 version 改变时重新获取数据 - if (loading) return
Loading...
; - if (error) return
Error: {error}
; - - + if (loading) return
Loading...
; + if (error) return
Error: {error}
; return ( -
- - - +
+
+
+ +
+
+ + {/* 页脚 */} +
+
+
+
+
+ CratesPro Logo +
+ +
+

Resources

+
    +
  • Documentation
  • +
  • About
  • +
  • Blog
  • +
  • FAQ
  • +
+
+ +
+

API

+
    +
  • API
  • +
  • BigQuery Dataset
  • +
  • GitHub
  • +
+
+ +
+

Legal

+
    +
  • Legal
  • +
  • Privacy
  • +
  • Terms
  • +
+
+
+ +
+

Copyright © 2023 jp21.com.cn All Rights Reserved(@ICPBH180237号)

+
+
+
+
); }; diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/layout.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/layout.tsx index 7ad3735..87dc666 100644 --- a/app/[nsfront]/[nsbehind]/[name]/[version]/layout.tsx +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/layout.tsx @@ -12,7 +12,7 @@ export default function Layout({ const params = useParams(); return ( -
+
{ return (
-
+
{/* 左侧内容区域 - 占据2列 */}
diff --git a/app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx b/app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx index b609d07..9b1495d 100644 --- a/app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx +++ b/app/[nsfront]/[nsbehind]/[name]/[version]/versions/page.tsx @@ -1,35 +1,30 @@ -//Dependents页面 +//Versions页面 "use client"; import React, { useEffect, useState } from 'react'; - -import VersionsTable from '@/components/VersionsTable'; -// import { VersionsInfo } from '@/app/lib/all_interface'; import { useParams } from 'next/navigation'; +import Image from 'next/image'; +import Link from 'next/link'; +import VersionsTable from '@/components/VersionsTable'; interface VersionInfo { version: string; - // publishDay: string; - dependents_number: number; + updated_at: string; + downloads: string; + dependents: number; } -// interface VersionsTableProps { -// data: VersionInfo[] | undefined; - -// } - const CratePage = () => { const [results, setResults] = useState(undefined); + console.log(results); const [loading, setLoading] = useState(true); - - const [error, setError] = useState(null); + const [error, setError] = useState(null); const params = useParams(); - - useEffect(() => { const fetchCrateData = async () => { try { + setError(null); const response = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/versions`); if (!response.ok) { @@ -37,30 +32,78 @@ const CratePage = () => { } const data = await response.json(); - - setResults(data); // 设置获取的数据 - } catch (error) { - setError(null); console.log('Error fetching data:', error); + setError("Failed to fetch data."); } finally { setLoading(false); // 完成加载 } }; fetchCrateData(); // 调用函数来获取数据 - }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在 crateName 或 version 改变时重新获取数据 - console.log('results in versionsssssss', results); - if (loading) return
Loading...
; - if (error) return
Error: {error}
; - + }, [params.name, params.version, params.nsfront, params.nsbehind]); // 依赖项数组,确保在参数改变时重新获取数据 + if (loading) return
Loading...
; + if (error) return
Error: {error}
; return ( -
- - - +
+
+
+ +
+
+ + {/* 页脚 */} +
+
+
+
+
+ CratesPro Logo +
+ +
+

Resources

+
    +
  • Documentation
  • +
  • About
  • +
  • Blog
  • +
  • FAQ
  • +
+
+ +
+

API

+
    +
  • API
  • +
  • BigQuery Dataset
  • +
  • GitHub
  • +
+
+ +
+

Legal

+
    +
  • Legal
  • +
  • Privacy
  • +
  • Terms
  • +
+
+
+ +
+

Copyright © 2023 jp21.com.cn All Rights Reserved(@ICPBH180237号)

+
+
+
+
); }; diff --git a/components/CrateNav.tsx b/components/CrateNav.tsx index 71e6dde..cc06285 100644 --- a/components/CrateNav.tsx +++ b/components/CrateNav.tsx @@ -65,7 +65,7 @@ const CrateNav: React.FC = ({ nsfront, nsbehind, name, version })
-
+
diff --git a/components/DependentTable.tsx b/components/DependentTable.tsx index e009b0e..5ff92fa 100644 --- a/components/DependentTable.tsx +++ b/components/DependentTable.tsx @@ -1,8 +1,8 @@ 'use client'; -import React, { useState, useMemo } from 'react'; -import { Table } from 'antd'; -// import { DownOutlined, UpOutlined } from '@ant-design/icons'; -// import { SorterResult } from 'antd/es/table/interface'; +import React, { useState } from 'react'; +import Link from 'next/link'; +import { useParams } from 'next/navigation'; +import Image from 'next/image'; interface DependentItem { crate_name: string; @@ -14,80 +14,254 @@ interface DependentTableProps { data: DependentItem[] | undefined; // 允许为 undefined } -const DependencyTable: React.FC = ({ data }) => { - const [sortColumn, setSortColumn] = useState(null); - const [sortDirection, setSortDirection] = useState(null); +const DependentTable: React.FC = ({ data }) => { + const params = useParams(); + const [searchQuery, setSearchQuery] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 15; // 每页显示条目数 + const [sortField, setSortField] = useState(null); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + // 处理搜索逻辑 + const handleSearch = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + setCurrentPage(1); // 搜索时重置到第一页 + }; - const y = 1; - if (y <= 0) { - setSortColumn(null); - setSortDirection(null); - console.log(data); - } + // 处理排序 + const handleSort = (field: string) => { + if (sortField === field) { + // 如果已经在按这个字段排序,则切换排序方向 + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + // 如果是新的排序字段,设置为升序 + setSortField(field); + setSortDirection('asc'); + } + setCurrentPage(1); // 排序时重置到第一页 + }; + + // 筛选数据 + const filteredData = data?.filter(item => + item.crate_name.toLowerCase().includes(searchQuery.toLowerCase()) || + item.version.toLowerCase().includes(searchQuery.toLowerCase()) || + item.relation.toLowerCase().includes(searchQuery.toLowerCase()) + ) || []; + + // 排序数据 + const sortedData = [...filteredData].sort((a, b) => { + if (!sortField) return 0; + let valueA, valueB; - const sortedData = useMemo(() => { - if (data === null) { - return []; + switch (sortField) { + case 'crate_name': + valueA = a.crate_name.toLowerCase(); + valueB = b.crate_name.toLowerCase(); + break; + case 'relation': + valueA = a.relation.toLowerCase(); + valueB = b.relation.toLowerCase(); + break; + default: + return 0; } - if (!data) { - return []; // 如果 data 为 undefined 或 null,返回空数组 + + if (valueA < valueB) return sortDirection === 'asc' ? -1 : 1; + if (valueA > valueB) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + + // 分页逻辑 + const totalPages = Math.ceil(sortedData.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentItems = sortedData.slice(startIndex, endIndex); + + // 分页控制函数 + const goToPage = (page: number) => { + if (page >= 1 && page <= totalPages) { + setCurrentPage(page); } - return sortColumn - ? data.sort((a: DependentItem, b: DependentItem) => { - if (a[sortColumn] < b[sortColumn]) return sortDirection === 'ascend' ? -1 : 1; - if (a[sortColumn] > b[sortColumn]) return sortDirection === 'ascend' ? 1 : -1; - return 0; - }) - : data; - }, [data, sortColumn, sortDirection]); - - const columns = [ - { - title: 'Crate', - dataIndex: 'crate_name', - key: 'Crate', - sorter: true, - sortDirection: sortDirection, - render: (text: string | number | bigint | boolean | React.ReactElement> | Iterable | React.ReactPortal | Promise | null | undefined) => {text}, - }, - { - title: 'Version', - dataIndex: 'version', - key: 'Version', - render: (text: string | number | bigint | boolean | React.ReactElement> | Iterable | React.ReactPortal | Promise | null | undefined) => {text}, - }, - { - title: 'Relation', - dataIndex: 'relation', - key: 'relation', - sorter: true, - sortDirection: sortDirection, - render: (text: string | number | bigint | boolean | React.ReactElement> | Iterable | React.ReactPortal | Promise | null | undefined) => {text}, - }, - - - ]; - - // const handleSort = (column: SorterResult | SorterResult[], direction: React.SetStateAction) => { - // setSortColumn(column.dataIndex); - // setSortDirection(direction); - // }; - const x = 1; - if (x <= 0) { - setSortColumn(null); - setSortDirection(null); - } + }; + + // 生成页码按钮 + const getPageButtons = () => { + const pageButtons = []; + const maxVisiblePages = 5; + + // 计算要显示的页码范围 + let startPage = Math.max(1, currentPage - 2); + const endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); + + // 调整起始页,确保显示足够的页码 + if (endPage - startPage + 1 < maxVisiblePages) { + startPage = Math.max(1, endPage - maxVisiblePages + 1); + } + + for (let i = startPage; i <= endPage; i++) { + pageButtons.push( + + ); + } + + return pageButtons; + }; + return ( - handleSort(sorter, sorter.order)} - //rowKey={(record) => record.Crate} - /> +
+ {/* 搜索区域 */} +
+
+
+
+ +
+ +
+
+
+ + {/* 表格 */} +
+
+
+
+ Crate + +
+
+ Version +
+
+ Relation + +
+
+
+
+ {currentItems.length > 0 ? ( + currentItems.map((item, index) => ( +
+
+ + {item.crate_name} + +
+
{item.version}
+
{item.relation}
+
+ )) + ) : ( +
+ {searchQuery ? 'No matching results found' : 'No data available'} +
+ )} +
+
+ + {/* 分页控制 */} + {sortedData.length > 0 && ( +
+ + + + {getPageButtons()} + + + +
+ )} +
); }; -export default DependencyTable; \ No newline at end of file +export default DependentTable; \ No newline at end of file diff --git a/components/VersionsTable.tsx b/components/VersionsTable.tsx index a2e6f31..28adddc 100644 --- a/components/VersionsTable.tsx +++ b/components/VersionsTable.tsx @@ -1,7 +1,8 @@ 'use client'; -import React, { useEffect, useState } from 'react'; -import { Table } from 'antd'; +import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; import { useParams } from 'next/navigation'; +import Image from 'next/image'; // 假设后端接口返回的类型 interface VersionInfo { @@ -9,20 +10,24 @@ interface VersionInfo { dependents: number; // 保持原始字段以便从API获取 updated_at: string; // 新增字段 downloads: string; - } // 新增 PublishDay 接口 -interface FormattedVersionInfo extends VersionInfo { - updated_at: string; // 新增字段 - downloads: string; -} +// interface FormattedVersionInfo extends VersionInfo { +// updated_at: string; // 新增字段 +// downloads: string; +// } const VersionsTable: React.FC = () => { - const [versionsData, setVersionsData] = useState([]); + const params = useParams(); + const [versionsData, setVersionsData] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const params = useParams(); + const [searchQuery, setSearchQuery] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 15; // 每页显示条目数 + const [sortField, setSortField] = useState(null); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); useEffect(() => { const fetchVersionsData = async () => { @@ -33,22 +38,14 @@ const VersionsTable: React.FC = () => { throw new Error(`HTTP error! status: ${response.status}`); } - const data: VersionInfo[] = await response.json(); + const data = await response.json(); // 检查数据是否有效 if (!Array.isArray(data)) { throw new Error('Invalid data format'); } - // 将 API 数据转换为 Table 需要的格式 - const formattedData = data.map((item) => ({ - version: item.version, - dependents: item.dependents, // 保留依赖数 - updated_at: item.updated_at, // 设置默认发布日为 N/A - downloads: item.downloads, // 设置默认下载数为 N/A - })); - - setVersionsData(formattedData); // 设置获取的数据 + setVersionsData(data); // 设置获取的数据 } catch (error) { setError(error instanceof Error ? error.message : 'An unknown error occurred'); // 改进错误处理 console.error('Error fetching data:', error); @@ -60,44 +57,314 @@ const VersionsTable: React.FC = () => { fetchVersionsData(); // 调用函数来获取数据 }, [params.nsfront, params.nsbehind, params.name, params.version]); // 依赖项数组 - const columns = [ - { - title: 'Version', - dataIndex: 'version', - key: 'version', - render: (text: string) => {text}, - }, - { - title: 'Updated_at', - dataIndex: 'updated_at', // 修改为使用 publishDay - key: 'updated_at', // 修改为使用 publishDay - render: (text: string) => {text}, - }, - { - title: 'Downloads', - dataIndex: 'downloads', // 修改为使用 publishDay - key: 'downloads', // 修改为使用 publishDay - render: (text: string) => {text}, - }, - { - title: 'Dependents', - dataIndex: 'dependents', - key: 'dependents', - render: (text: number) => {text}, - }, - ]; - - if (loading) return
Loading...
; + // 处理搜索逻辑 + const handleSearch = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + setCurrentPage(1); // 搜索时重置到第一页 + }; + + // 处理排序 + const handleSort = (field: string) => { + if (sortField === field) { + // 如果已经在按这个字段排序,则切换排序方向 + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + // 如果是新的排序字段,设置为升序 + setSortField(field); + setSortDirection('asc'); + } + setCurrentPage(1); // 排序时重置到第一页 + }; + + // 筛选数据 + const filteredData = versionsData.filter(item => + item.version.toLowerCase().includes(searchQuery.toLowerCase()) || + item.updated_at.toLowerCase().includes(searchQuery.toLowerCase()) || + item.downloads.toLowerCase().includes(searchQuery.toLowerCase()) || + String(item.dependents).includes(searchQuery) + ); + + // 辅助函数:将下载量字符串转换为数字 + const parseDownloadsToNumber = (downloadsStr: string): number => { + // 如果为空,返回0 + if (!downloadsStr) return 0; + + // 移除所有逗号 + const cleanStr = downloadsStr.replace(/,/g, ''); + + // 处理带单位的数字,如"1.2M"、"3.4K"等 + if (cleanStr.endsWith('K') || cleanStr.endsWith('k')) { + return parseFloat(cleanStr) * 1000; + } else if (cleanStr.endsWith('M') || cleanStr.endsWith('m')) { + return parseFloat(cleanStr) * 1000000; + } else if (cleanStr.endsWith('B') || cleanStr.endsWith('b')) { + return parseFloat(cleanStr) * 1000000000; + } + + // 尝试直接解析为数字 + return parseFloat(cleanStr) || 0; + }; + + // 辅助函数:比较版本号 + const compareVersions = (versionA: string, versionB: string): number => { + // 分割版本号为组件部分 + const partsA = versionA.split('.').map(part => parseInt(part) || 0); + const partsB = versionB.split('.').map(part => parseInt(part) || 0); + + // 确保两个版本号数组长度相同 + const maxLength = Math.max(partsA.length, partsB.length); + while (partsA.length < maxLength) partsA.push(0); + while (partsB.length < maxLength) partsB.push(0); + + // 逐部分比较 + for (let i = 0; i < maxLength; i++) { + if (partsA[i] > partsB[i]) return 1; + if (partsA[i] < partsB[i]) return -1; + } + + return 0; // 版本号相等 + }; + + // 排序数据 + const sortedData = [...filteredData].sort((a, b) => { + if (!sortField) return 0; + + let valueA, valueB; + + switch (sortField) { + case 'version': + // 使用版本号比较函数 + return sortDirection === 'asc' + ? compareVersions(a.version, b.version) + : compareVersions(b.version, a.version); + case 'downloads': + // 将下载量字符串转换为数字进行排序 + valueA = parseDownloadsToNumber(a.downloads); + valueB = parseDownloadsToNumber(b.downloads); + break; + case 'dependents': + valueA = a.dependents; + valueB = b.dependents; + break; + default: + return 0; + } + + if (valueA < valueB) return sortDirection === 'asc' ? -1 : 1; + if (valueA > valueB) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + + // 分页逻辑 + const totalPages = Math.ceil(sortedData.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentItems = sortedData.slice(startIndex, endIndex); + + // 分页控制函数 + const goToPage = (page: number) => { + if (page >= 1 && page <= totalPages) { + setCurrentPage(page); + } + }; + + // 生成页码按钮 + const getPageButtons = () => { + const pageButtons = []; + const maxVisiblePages = 5; + + // 计算要显示的页码范围 + let startPage = Math.max(1, currentPage - 2); + const endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); + + // 调整起始页,确保显示足够的页码 + if (endPage - startPage + 1 < maxVisiblePages) { + startPage = Math.max(1, endPage - maxVisiblePages + 1); + } + + for (let i = startPage; i <= endPage; i++) { + pageButtons.push( + + ); + } + + return pageButtons; + }; + + if (loading) return
Loading...
; if (error) return
Error: {error}
; return ( -
+
+ {/* 搜索区域 */} +
+
+
+
+ +
+ +
+
+
+ + {/* 表格 */} +
+
+
+
+ Version + +
+
+ Updated At +
+
+ Downloads + +
+
+ Dependents + +
+
+
+
+ {currentItems.length > 0 ? ( + currentItems.map((item, index) => ( +
+
+ + {item.version} + +
+
{item.updated_at}
+
{item.downloads}
+
{item.dependents}
+
+ )) + ) : ( +
+ {searchQuery ? 'No matching results found' : 'No data available'} +
+ )} +
+
+ + {/* 分页控制 */} + {sortedData.length > 0 && ( +
+ + + + {getPageButtons()} + + + +
+ )} +
); };