diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/compare/index.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/compare/index.tsx new file mode 100644 index 000000000..6a3e4197e --- /dev/null +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/compare/index.tsx @@ -0,0 +1,1063 @@ +"use client"; +import React, { useEffect, useState } from 'react'; +import Head from 'next/head'; +import Image from 'next/image'; +import { useParams } from 'next/navigation'; +import { useRouter } from 'next/router'; +import { AppLayout } from '@/components/Layout/AppLayout'; +import AuthAppProviders from '@/components/Providers/AuthAppProviders'; +// import { ArrowsRightLeftIcon, ChevronDownIcon } from '@heroicons/react/24/outline'; +import CrateInfoLayout from '../layout'; + +interface VersionData { + version: string; + published: string; + description: string; + licenses: { + primary: string; + dependencies: string[]; + }; + securityAdvisories: { + inDependencies: Array<{ + id: string; + description: string; + severity?: string; + }>; + }; + dependencies: Array<{ + name: string; + version: string; + highlighted?: 'added' | 'removed' | 'updated'; + }>; +} + +const ComparePage = () => { + const params = useParams(); + const router = useRouter(); + const [leftVersion, setLeftVersion] = useState(null); + const [rightVersion, setRightVersion] = useState(null); + const selectedLeftVersion = '1.2.01'; + const selectedRightVersion = '1.2.01'; + const [isSwapped, setIsSwapped] = useState(false); + + // 从查询参数或URL参数中获取crate信息 + const crateName = (router.query.crateName as string) || params?.crateName as string || "tokio"; + + useEffect(() => { + // 模拟版本数据 + const getMockVersionData = (version: string, isLeftCard: boolean = false): VersionData => ({ + version, + published: version === '1.2.01' ? 'March7, 2025' : 'March7, 2025', + description: 'An event-driven,non-blocking I/O platform for writing asynchronous I/O backed applications.', + licenses: { + primary: 'MIT', + dependencies: [ + '0BSD OR Apache-2.0 OR MIT', + ...(version === '1.2.01' ? ['Apache-2.0', 'Apache-2.0 OR BSD-2-Clause OR MIT', 'Apache-2.0 OR BSL-1.0'] : []), + 'Apache-2.0 OR LGPL-2.1-or-later OR MIT', + 'Unicode-3.0 AND (Apache-2.0 OR MIT)' + ] + }, + securityAdvisories: { + inDependencies: [ + { + id: 'request', + description: 'Server-Side Request Forgery In Request', + severity: '2.88.2' + }, + { + id: 'GHSA-p8p7-x288-28g6', + description: 'Server-Side Request Forgery In Request' + }, + { + id: 'tough-cookie', + description: 'Server-Side Request Forgery In Request', + severity: '2.5.1' + }, + { + id: 'GHSA-p8p7-x288-28g6', + description: 'Server-Side Request Forgery In Request' + } + ] + }, + dependencies: [ + { name: 'abab', version: '2.0.6' }, + { name: 'acorn', version: '5.7.4', highlighted: (version === '1.2.01' && isLeftCard) ? 'added' : undefined }, + { name: 'acorn-globals', version: '6.4.2' }, + { name: 'ajv', version: '6.12.6' }, + { name: 'acorn', version: '5.7.4' }, + { name: 'abab', version: '2.0.6' }, + { name: 'browser-process-hrtime-addsvj', version: '6.4.2' } + ] + }); + + setLeftVersion(getMockVersionData(selectedLeftVersion, true)); + setRightVersion(getMockVersionData(selectedRightVersion, false)); + }, [selectedLeftVersion, selectedRightVersion]); + + const handleSwapVersions = () => { + setIsSwapped(!isSwapped); + }; + + const VersionSelector = ({ + version + }: { + version: string; + }) => ( +
+
+ + + +
+ + {version} + + + + +
+ ); + + // const ComparisonSection = ({ + // title, + // leftContent, + // rightContent + // }: { + // title: string; + // leftContent: React.ReactNode; + // rightContent: React.ReactNode; + // }) => ( + //
+ //

+ // {title} + //

+ //
+ //
{leftContent}
+ //
{rightContent}
+ //
+ //
+ // ); + + const getHighlightColor = (type?: 'added' | 'removed' | 'updated') => { + switch (type) { + case 'added': return 'rgb(234,141,143)'; // 粉色(红色),acorn使用 + case 'removed': return 'rgb(234,141,143)'; // 红色 + case 'updated': return 'rgb(232,192,97)'; // 橙色 + default: return 'transparent'; + } + }; + + if (!leftVersion || !rightVersion) { + return
Loading...
; + } + + return ( + <> + + Compare - {crateName} + + +
+
+ {/* 左右两个卡片 */} +
+ {/* 交换按钮 - 位于顶部中间 */} +
+ +
+ + {/* 左侧卡片 */} +
+ {/* 版本选择器 */} +
+ +
+ {/* Published */} +
+

+ Published +

+
+
+ {leftVersion.published} +
+
+
+ + {/* Description */} +
+

+ Description +

+
+ {leftVersion.description} +
+
+ + {/* Licenses */} +
+ {/* 分割线 */} +
+

+ Licenses +

+
+
+
+ LICENSES +
+
+ {leftVersion.licenses.primary} +
+
+
+
+ DEPENDENCY LICENSES +
+
+ {leftVersion.licenses.dependencies.map((license) => ( +
+ {license} + {license === 'Apache-2.0' && selectedLeftVersion === '1.2.01' && ( + + + + )} +
+ ))} +
+
+
+
+ + {/* Security Advisories */} +
+

+ Security Advisories +

+
+
+ IN THE DEPENDENCIES +
+
+ {leftVersion.securityAdvisories.inDependencies.map((advisory, index) => ( +
+
+
+ {advisory.id} +
+ {advisory.severity && ( +
+ {advisory.severity} +
+ )} + {advisory.id !== 'request' && advisory.id !== 'tough-cookie' && ( +
+ {advisory.description} +
+ )} +
+ {advisory.id === 'GHSA-p8p7-x288-28g6' && index === 1 && ( + + + + )} +
+ ))} +
+
+
+ + {/* Dependencies */} +
+

+ Dependencies +

+
+ {leftVersion.dependencies.map((dep) => ( +
+
+ + {dep.name} + + + {dep.version} + +
+ {dep.highlighted && ( + + + + )} +
+ ))} +
+
+
+ + {/* 右侧卡片 */} +
+ {/* 版本选择器 */} +
+ +
+ {/* Published */} +
+

+ Published +

+
+
+ {rightVersion.published} +
+ + + +
+
+ + {/* Description */} +
+

+ Description +

+
+ {rightVersion.description} +
+
+ + {/* Licenses */} +
+ {/* 分割线 */} +
+

+ Licenses +

+
+
+
+ LICENSES +
+
+ {rightVersion.licenses.primary} +
+
+
+
+ DEPENDENCY LICENSES +
+
+ {rightVersion.licenses.dependencies.map((license) => ( +
+ {license} + {license === 'Apache-2.0 OR BSL-1.0' && selectedRightVersion === '1.2.01' && ( + + + + )} +
+ ))} +
+
+
+
+ + {/* Security Advisories */} +
+

+ Security Advisories +

+
+
+ IN THE DEPENDENCIES +
+
+ {rightVersion.securityAdvisories.inDependencies.map((advisory) => ( +
+
+
+ {advisory.id} +
+ {advisory.severity && ( +
+ {advisory.severity} +
+ )} + {advisory.id !== 'request' && advisory.id !== 'tough-cookie' && ( +
+ {advisory.description} +
+ )} +
+
+ ))} +
+
+
+ + {/* Dependencies */} +
+

+ Dependencies +

+
+ {rightVersion.dependencies.map((dep) => ( +
+
+ + {dep.name} + + + {dep.version} + +
+
+ ))} +
+
+
+
+
+
+ + + ); +}; + +// 添加 getProviders 方法以适配新的项目结构 +ComparePage.getProviders = (page: any, pageProps: any) => { + return ( + + {page} + + ); +}; + +export default ComparePage; diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/dependencies/index.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/dependencies/index.tsx new file mode 100644 index 000000000..797c9efdf --- /dev/null +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/dependencies/index.tsx @@ -0,0 +1,529 @@ +"use client"; +import React, { useEffect, useState } from 'react'; +import Head from 'next/head'; +import { useParams } from 'next/navigation'; +import { useRouter } from 'next/router'; +import { AppLayout } from '@/components/Layout/AppLayout'; +import AuthAppProviders from '@/components/Providers/AuthAppProviders'; +import { MagnifyingGlassIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +import CrateInfoLayout from '../layout'; +import Image from 'next/image'; + +interface Dependency { + id: string; + name: string; + version: string; + relation: 'Direct' | 'Indirect'; + license: string; + dependencies: number; + expanded?: boolean; + description?: string; + published?: string; +} + +const DependenciesPage = () => { + const params = useParams(); + const router = useRouter(); + const [dependencies, setDependencies] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [viewMode, setViewMode] = useState<'table' | 'graph'>('table'); + const [searchTerm, setSearchTerm] = useState(''); + + // 从查询参数或URL参数中获取crate信息 + const crateName = (router.query.crateName as string) || params?.crateName as string || "tokio"; + const version = (router.query.version as string) || params?.version as string || "1.2.01"; + // const nsfront = params?.nsfront as string || router.query.org as string; + // const nsbehind = params?.nsbehind as string || "rust/rust-ecosystem/crate-info"; + + useEffect(() => { + // 模拟依赖数据 + const mockDependencies: Dependency[] = [ + { + id: '1', + name: 'cheerio', + version: '1.1.1.0', + relation: 'Direct', + license: 'MIT', + dependencies: 21, + expanded: false + }, + { + id: '2', + name: 'Text', + version: '1.1.1.0', + relation: 'Direct', + license: 'MIT', + dependencies: 216, + expanded: true, + description: 'The fast, flexible & elegant library for parsing and manipulating HTML and XML.', + published: 'June 8, 2025' + }, + { + id: '3', + name: 'Text', + version: 'Subtitle', + relation: 'Direct', + license: 'MIT', + dependencies: 68, + expanded: false + }, + { + id: '4', + name: 'Text', + version: 'Subtitle', + relation: 'Direct', + license: 'MIT', + dependencies: 23, + expanded: false + }, + { + id: '5', + name: 'Text', + version: 'Subtitle', + relation: 'Direct', + license: 'MIT', + dependencies: 1299, + expanded: false + }, + { + id: '6', + name: 'Text', + version: 'Subtitle', + relation: 'Direct', + license: 'MIT', + dependencies: 99, + expanded: false + }, + { + id: '7', + name: 'Text', + version: 'Subtitle', + relation: 'Direct', + license: 'MIT', + dependencies: 66, + expanded: false + }, + { + id: '8', + name: 'Text', + version: 'Subtitle', + relation: 'Direct', + license: 'MIT', + dependencies: 666, + expanded: false + } + ]; + + // 直接设置数据,不使用加载延迟 + setDependencies(mockDependencies); + }, [crateName, version]); + + const toggleExpanded = (id: string) => { + setDependencies(prev => + prev.map(dep => + dep.id === id ? { ...dep, expanded: !dep.expanded } : dep + ) + ); + }; + + const filteredDependencies = dependencies.filter(dep => + dep.name.toLowerCase().includes(searchTerm.toLowerCase()) || + dep.version.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return ( + <> + + Dependencies - {crateName} + + + {/* 主要内容区域 */} +
+
+ {/* 统一的白色面板 */} +
+ {/* 搜索和视图切换 - 在面板内部 */} +
+
+
+
+ +
+ setSearchTerm(e.target.value)} + style={{ + display: 'flex', + height: 'var(--Spacing-8, 36px)', + padding: '0 var(--Spacing-1, 4px)', + alignItems: 'center', + alignSelf: 'stretch', + borderRadius: 'var(--Radius-2-max, 4px)', + border: '1px solid var(--Colors-Neutral-Neutral-Alpha-5, #0009321f)', + background: 'var(--Tokens-Colors-surface, #ffffffe6)', + paddingLeft: '40px', + width: '100%' + }} + /> +
+
+
+ + +
+
+ + {/* 表格 - 在面板内部 */} +
+ + + + + + + + + + + + {filteredDependencies.map((dependency) => ( + + + + + + + + + {dependency.expanded && dependency.description && ( + + + + )} + + ))} + +
+ Package + + Notes + +
+ Relation + sort +
+
+ License + + Dependencies +
+
+ +
+
+ {dependency.name} +
+
+ {dependency.version} +
+
+
+
+ + + + {dependency.relation} + + + {dependency.license} + + {dependency.dependencies} +
+
+ + + + + + + + + + + + + + + +
+ Version: + + {dependency.version} +
+ Published: + + {dependency.published} +
+ Description: + + {dependency.description} +
+
+
+
+
+ + {/* 分页功能区 */} +
+
+
+ {/* Previous 按钮 */} + + + {/* 当前页码 */} + {currentPage} + + {/* Next 按钮 */} + +
+
+
+
+
+
+ + ); +}; + +// 添加 getProviders 方法以适配新的项目结构 +DependenciesPage.getProviders = (page: any, pageProps: any) => { + return ( + + {page} + + ); +}; + +export default DependenciesPage; diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/dependents/index.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/dependents/index.tsx new file mode 100644 index 000000000..292b03142 --- /dev/null +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/dependents/index.tsx @@ -0,0 +1,363 @@ +"use client"; +import React, { useEffect, useState } from 'react'; +import Head from 'next/head'; +import { useParams } from 'next/navigation'; +import { useRouter } from 'next/router'; +import { AppLayout } from '@/components/Layout/AppLayout'; +import AuthAppProviders from '@/components/Providers/AuthAppProviders'; +// import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +import CrateInfoLayout from '../layout'; + +interface Dependent { + id: string; + name: string; + version: string; + relation: 'Direct' | 'Indirect'; + license?: string; + dependencies?: number; + expanded?: boolean; + description?: string; + published?: string; +} + +const DependentsPage = () => { + const params = useParams(); + const router = useRouter(); + const [dependents, setDependents] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + // const [viewMode, setViewMode] = useState<'table' | 'graph'>('table'); + const searchTerm = ''; + + // 从查询参数或URL参数中获取crate信息 + const crateName = (router.query.crateName as string) || params?.crateName as string || "tokio"; + const version = (router.query.version as string) || params?.version as string || "1.2.01"; + + useEffect(() => { + // 模拟dependents数据 - 显示使用当前包的其他包 + const mockDependents: Dependent[] = [ + { + id: '1', + name: 'github-random-star', + version: '1.0.2', + relation: 'Direct', + expanded: false + }, + { + id: '2', + name: 'github-random-star', + version: '1.0.1', + relation: 'Direct', + expanded: false + } + ]; + + // 直接设置数据,不使用加载延迟 + setDependents(mockDependents); + }, [crateName, version]); + + const filteredDependents = dependents.filter(dep => + dep.name.toLowerCase().includes(searchTerm.toLowerCase()) || + dep.version.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + return ( + <> + + Dependents - {crateName} + + + {/* 主要内容区域 */} +
+
+ {/* 统一的白色面板 */} +
+ + + {/* 数据统计显示 - 在面板内部 */} +
+
+
+ Direct + Indirect +
+ +
+ + 26 + + + 12 + +
+ + {/* 进度条 */} +
+
+
+
+
+
+
+
+
+
+ + {/* 表格 - 在面板内部 */} +
+ + + + + + + + + + {filteredDependents.map((dependent) => ( + + + + + + + {dependent.expanded && dependent.description && ( + + + + )} + + ))} + +
+ Package + + Version + + Relation +
+
+ {dependent.name} +
+
+ + {dependent.version} + + + + {dependent.relation} + +
+
+
+ + Version: {dependent.version} + + + Published: {dependent.published} + +
+

+ {dependent.description} +

+
+
+
+
+ + {/* 分页功能区 */} +
+
+
+ {/* Previous 按钮 */} + + + {/* 当前页码 */} + {currentPage} + + {/* Next 按钮 */} + +
+
+
+
+
+ + + ); +}; + +// 添加 getProviders 方法以适配新的项目结构 +DependentsPage.getProviders = (page: any, pageProps: any) => { + return ( + + {page} + + ); +}; + +export default DependentsPage; diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/search/crate-info/index.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/index.tsx similarity index 87% rename from moon/apps/web/pages/[org]/rust/rust-ecosystem/search/crate-info/index.tsx rename to moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/index.tsx index 0d113e1a1..cf2cda4ca 100644 --- a/moon/apps/web/pages/[org]/rust/rust-ecosystem/search/crate-info/index.tsx +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/index.tsx @@ -8,7 +8,8 @@ import { useRouter } from 'next/router'; // import Image from 'next/image'; import { AppLayout } from '@/components/Layout/AppLayout'; import AuthAppProviders from '@/components/Providers/AuthAppProviders'; -import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +// import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; +import CrateInfoLayout from './layout'; // interface CVE { // subtitle?: string; @@ -62,7 +63,6 @@ const CratePage = () => { const params = useParams(); const router = useRouter(); const [results, setResults] = useState(null); - const [loading, setLoading] = useState(true); const [error, _setError] = useState(null); const [_packageCurrentPage, _setPackageCurrentPage] = useState(1); const [_depCurrentPage, _setDepCurrentPage] = useState(1); @@ -73,7 +73,7 @@ const CratePage = () => { const crateName = (router.query.crateName as string) || params?.crateName as string || "example-crate"; const version = (router.query.version as string) || params?.version as string || "1.0.0"; const nsfront = params?.nsfront as string || router.query.org as string; - const nsbehind = params?.nsbehind as string || "rust/rust-ecosystem/search/crate-info"; + const nsbehind = params?.nsbehind as string || "rust/rust-ecosystem/crate-info"; const name = params?.name as string || crateName; // const basePath = `/${nsfront}/${nsbehind}/${name}/${version}`; @@ -122,15 +122,11 @@ const CratePage = () => { versions: ["1.0.0", "1.1.0", "1.2.0", "2.0.0"] }; - // 模拟加载延迟 - setTimeout(() => { - setResults(mockData); - _setVersions(mockData.versions); - setLoading(false); - }, 500); + // 直接设置数据,不使用加载延迟 + setResults(mockData); + _setVersions(mockData.versions); }, [crateName]); - if (loading) return

Loading...

; if (error) return

Error: {error}

; // const _getCurrentPageItems = (items: CVE[], currentPage: number) => { @@ -147,132 +143,7 @@ const CratePage = () => { Crate Info - {crateName || 'Crate'} -
- {/* 搜索栏 */} -
-
-
-
-
- -
- -
-
-
-
-
- - {/* 分类标签和版本选择区域 */} -
-
- {/* Crate信息 */} -
-
-
-
Cargo crate
-

- {crateName} -

-
-
-
- - - -
- {version} - - - -
-
-
- - {/* 导航标签 */} -
- {[ - { id: 'overview', label: 'overview' }, - { id: 'dependencis', label: 'dependencis' }, - { id: 'depentes', label: 'depentes' }, - { id: 'compare', label: 'compare' }, - { id: 'versions', label: 'versions' } - ].map((tab) => ( - - ))} -
-
-
- +
@@ -515,7 +386,7 @@ const CratePage = () => { > ISC
-
22
+
22
{
- + View all dependencies ({results.dependencies.direct + results.dependencies.indirect}) @@ -898,7 +769,7 @@ const CratePage = () => {
-
+
); }; diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/layout.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/layout.tsx new file mode 100644 index 000000000..1c1af6ef3 --- /dev/null +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/layout.tsx @@ -0,0 +1,214 @@ +"use client"; +import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import { useRouter } from 'next/router'; +import { useParams } from 'next/navigation'; +import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; + +interface CrateInfoLayoutProps { + children: React.ReactNode; +} + +const CrateInfoLayoutComponent = ({ children }: CrateInfoLayoutProps) => { + const router = useRouter(); + const params = useParams(); + + // 从查询参数或URL参数中获取crate信息 + const crateName = useMemo(() => + (router.query.crateName as string) || params?.crateName as string || "example-crate", + [router.query.crateName, params?.crateName] + ); + const version = useMemo(() => + (router.query.version as string) || params?.version as string || "1.0.0", + [router.query.version, params?.version] + ); + const nsfront = useMemo(() => + params?.nsfront as string || router.query.org as string, + [params?.nsfront, router.query.org] + ); + + // 根据当前路径确定activeTab + const [activeTab, setActiveTab] = useState<'overview' | 'dependencies' | 'dependents' | 'compare' | 'versions'>('overview'); + + useEffect(() => { + const path = router.asPath; + + if (path.includes('/dependencies')) { + setActiveTab('dependencies'); + } else if (path.includes('/dependents')) { + setActiveTab('dependents'); + } else if (path.includes('/compare')) { + setActiveTab('compare'); + } else if (path.includes('/versions')) { + setActiveTab('versions'); + } else { + setActiveTab('overview'); + } + }, [router.asPath]); + + const handleTabClick = useCallback((href: string) => { + router.push(href, undefined, { shallow: true }); + }, [router]); + + const navigationTabs = useMemo(() => [ + { id: 'overview', label: 'overview', href: `/${nsfront}/rust/rust-ecosystem/crate-info/${crateName}?crateName=${crateName}&version=${version}` }, + { id: 'dependencies', label: 'dependencies', href: `/${nsfront}/rust/rust-ecosystem/crate-info/${crateName}/dependencies?crateName=${crateName}&version=${version}` }, + { id: 'dependents', label: 'dependents', href: `/${nsfront}/rust/rust-ecosystem/crate-info/${crateName}/dependents?crateName=${crateName}&version=${version}` }, + { id: 'compare', label: 'compare', href: `/${nsfront}/rust/rust-ecosystem/crate-info/${crateName}/compare?crateName=${crateName}&version=${version}` }, + { id: 'versions', label: 'versions', href: `/${nsfront}/rust/rust-ecosystem/crate-info/${crateName}/versions?crateName=${crateName}&version=${version}` } + ], [nsfront, crateName, version]); + + return ( +
+ {/* 搜索栏 */} +
+
+
+
+
+ +
+ +
+
+
+
+
+ + {/* 分类标签和版本选择区域 */} +
+
+ {/* Crate信息 */} +
+
+
+
Cargo crate
+

+ {crateName} +

+
+
+
+ + + +
+ {version} + + + +
+
+
+ + {/* 导航标签 */} +
+ {navigationTabs.map((tab) => ( + tab.href ? ( + + ) : ( + + ) + ))} +
+
+
+ + {/* 主要内容区域 */} + {children} +
+ ); +}; + +const CrateInfoLayout = React.memo(CrateInfoLayoutComponent); + +CrateInfoLayout.displayName = 'CrateInfoLayout'; + +export default CrateInfoLayout; \ No newline at end of file diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/versions/index.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/versions/index.tsx new file mode 100644 index 000000000..2f5243427 --- /dev/null +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/crate-info/[crateName]/versions/index.tsx @@ -0,0 +1,339 @@ +"use client"; +import React, { useEffect, useState } from 'react'; +import Head from 'next/head'; +import { useParams } from 'next/navigation'; +import { useRouter } from 'next/router'; +import { AppLayout } from '@/components/Layout/AppLayout'; +import AuthAppProviders from '@/components/Providers/AuthAppProviders'; +// import { ChevronUpDownIcon } from '@heroicons/react/24/outline'; +import CrateInfoLayout from '../layout'; +import Image from 'next/image'; + +interface VersionInfo { + id: string; + version: string; + published: string; + dependents: number; +} + +const VersionsPage = () => { + const params = useParams(); + const router = useRouter(); + const [versions, setVersions] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + const itemsPerPage = 10; + + // 从查询参数或URL参数中获取crate信息 + const crateName = (router.query.crateName as string) || params?.crateName as string || "tokio"; + + useEffect(() => { + // 模拟版本数据 + const mockVersions: VersionInfo[] = [ + { + id: '1', + version: '1.45.1', + published: 'May 24, 2025', + dependents: 19934 + }, + { + id: '2', + version: '1.45.0', + published: 'May 6, 2025', + dependents: 9921 + }, + { + id: '3', + version: '1.44.2', + published: 'April 6, 2025', + dependents: 3 + }, + { + id: '4', + version: '1.44.1', + published: 'April 6, 2025', + dependents: 6 + }, + { + id: '5', + version: '1.44.0', + published: 'April 5, 2025', + dependents: 0 + }, + { + id: '6', + version: '1.43.1', + published: 'March 13, 2025', + dependents: 82 + }, + { + id: '7', + version: '1.43.0', + published: 'March 10, 2025', + dependents: 992 + }, + { + id: '8', + version: '1.42.1', + published: 'March 3, 2025', + dependents: 666 + }, + { + id: '9', + version: '1.42.0', + published: 'January 13, 2025', + dependents: 0 + }, + { + id: '10', + version: '1.41.1', + published: 'November 7, 2024', + dependents: 0 + } + ]; + + // 排序版本 + const sortedVersions = [...mockVersions].sort((a, b) => { + const aVersion = a.version.split('.').map(Number); + const bVersion = b.version.split('.').map(Number); + + for (let i = 0; i < Math.max(aVersion.length, bVersion.length); i++) { + const aPart = aVersion[i] || 0; + const bPart = bVersion[i] || 0; + + if (aPart !== bPart) { + return sortOrder === 'desc' ? bPart - aPart : aPart - bPart; + } + } + return 0; + }); + + setVersions(sortedVersions); + }, [sortOrder]); + + const handleSort = () => { + setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc'); + }; + + const handleVersionClick = (_version: string) => { + // 可以导航到该版本的详情页 + // TODO: 实现版本导航功能 + }; + + // 分页逻辑 + const totalPages = Math.ceil(versions.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentVersions = versions.slice(startIndex, endIndex); + + const handlePreviousPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + const handleNextPage = () => { + if (currentPage < totalPages) { + setCurrentPage(currentPage + 1); + } + }; + + return ( + <> + + Versions - {crateName} + + +
+
+ {/* 表格 */} +
+
+ + + + + + + + + + {currentVersions.map((versionInfo) => ( + + + + + + ))} + +
+ + + Published + + Dependents +
+ + + + {versionInfo.published} + + + + {versionInfo.dependents.toLocaleString()} + +
+
+
+ + {/* 分页功能区 */} +
+
+
+ {/* Previous 按钮 */} + + + {/* 当前页码 */} + {currentPage} + + {/* Next 按钮 */} + +
+
+
+
+
+
+ + ); +}; + +// 添加 getProviders 方法以适配新的项目结构 +VersionsPage.getProviders = (page: any, pageProps: any) => { + return ( + + {page} + + ); +}; + +export default VersionsPage; diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/ecosystem-cve/cve-info/[cve-id]/index.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/ecosystem-cve/cve-info/[cve-id]/index.tsx index 779162ca4..ced05db66 100644 --- a/moon/apps/web/pages/[org]/rust/rust-ecosystem/ecosystem-cve/cve-info/[cve-id]/index.tsx +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/ecosystem-cve/cve-info/[cve-id]/index.tsx @@ -1,5 +1,5 @@ import Head from 'next/head' -// import Image from 'next/image' +import Image from 'next/image' import { AppLayout } from '@/components/Layout/AppLayout' import AuthAppProviders from '@/components/Providers/AuthAppProviders' // import { useState } from 'react' @@ -109,10 +109,12 @@ export default function CVEInfoPage() { )}
diff --git a/moon/apps/web/pages/[org]/rust/rust-ecosystem/search/index.tsx b/moon/apps/web/pages/[org]/rust/rust-ecosystem/search/index.tsx index 4ea0c6344..6387dcf41 100644 --- a/moon/apps/web/pages/[org]/rust/rust-ecosystem/search/index.tsx +++ b/moon/apps/web/pages/[org]/rust/rust-ecosystem/search/index.tsx @@ -123,15 +123,10 @@ export default function SearchResultsPage() { const [search, setSearch] = useState('') const [activeTab, setActiveTab] = useState('All') const [searchResults, setSearchResults] = useState([]) - const [loading, setLoading] = useState(false) const router = useRouter() const performSearch = useCallback(async (_query: string) => { - setLoading(true) - setTimeout(() => { - setSearchResults(mockSearchResults) - setLoading(false) - }, 500) + setSearchResults(mockSearchResults) }, []) useEffect(() => { @@ -165,23 +160,23 @@ export default function SearchResultsPage() { Search Results - Rust Ecosystem -
- {/* 搜索栏 */} -
-
+
+ {/* 搜索栏 */} +
+
@@ -211,36 +206,36 @@ export default function SearchResultsPage() { />
- {/* 分类标签和搜索结果区域 */} -
-
- {/* 分类标签 */} -
- {tabs.map((tab) => ( - - ))} -
+ {/* 分类标签和搜索结果区域 */} +
+
+ {/* 分类标签 */} +
+ {tabs.map((tab) => ( + + ))} +
- {/* 搜索结果列表容器 */} + {/* 搜索结果列表容器 */}
- {loading ? ( -
-
-
- ) : ( -
- {searchResults.map((item, index) => ( +
+ {searchResults.map((item, index) => (
{ e.currentTarget.style.background = '#EBEBEB' }} @@ -317,53 +307,52 @@ export default function SearchResultsPage() { e.currentTarget.style.background = '#ffffff00' }} > -
- - {item.type} - -

router.push({ - pathname: `/${router.query.org}/rust/rust-ecosystem/search/crate-info`, - query: { - crateName: item.name, - version: item.version || '1.0.0' - } - })} - > - {item.name} -

-

- {item.details} -

-
-
- ))} -
- )} +
+ + {item.type} + +

router.push({ + pathname: `/${router.query.org}/rust/rust-ecosystem/crate-info/${item.name}`, + query: { + crateName: item.name, + version: item.version || '1.0.0' + } + })} + > + {item.name} +

+

+ {item.details} +

+
+
+ ))} +
- {/* 分页 */} -
- - 1 - -
-
-
-
-
- - ) + {/* 分页 */} +
+ + 1 + +
+
+
+
+
+ + ) } SearchResultsPage.getProviders = (page: any, pageProps: any) => { diff --git a/moon/apps/web/public/rust/rust-ecosystem/crate-info/Frame.png b/moon/apps/web/public/rust/rust-ecosystem/crate-info/Frame.png new file mode 100644 index 000000000..e1eb21ed1 Binary files /dev/null and b/moon/apps/web/public/rust/rust-ecosystem/crate-info/Frame.png differ diff --git a/moon/apps/web/public/rust/rust-ecosystem/crate-info/compare/exchange.png b/moon/apps/web/public/rust/rust-ecosystem/crate-info/compare/exchange.png new file mode 100644 index 000000000..e1eb21ed1 Binary files /dev/null and b/moon/apps/web/public/rust/rust-ecosystem/crate-info/compare/exchange.png differ diff --git a/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/Components - table.png b/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/Components - table.png new file mode 100644 index 000000000..2bc01784a Binary files /dev/null and b/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/Components - table.png differ diff --git "a/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/crate-\350\257\246\346\203\205-dependencis.png" "b/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/crate-\350\257\246\346\203\205-dependencis.png" new file mode 100644 index 000000000..b6559ca1f Binary files /dev/null and "b/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/crate-\350\257\246\346\203\205-dependencis.png" differ diff --git a/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/double-arrow-up.png b/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/double-arrow-up.png new file mode 100644 index 000000000..531be8d1e Binary files /dev/null and b/moon/apps/web/public/rust/rust-ecosystem/crate-info/dependencies/double-arrow-up.png differ