| 
 | 1 | +'use client';  | 
 | 2 | +import React, { useEffect, useState, useRef } from 'react';  | 
 | 3 | +import Link from 'next/link';  | 
 | 4 | +import { useParams } from 'next/navigation';  | 
 | 5 | +import Image from 'next/image';  | 
 | 6 | + | 
 | 7 | +interface MircheckerRes {  | 
 | 8 | +    run_state: boolean;  | 
 | 9 | +    exist: boolean;  | 
 | 10 | +    res: string;  | 
 | 11 | +}  | 
 | 12 | + | 
 | 13 | +const MircheckerPage = () => {  | 
 | 14 | +    const params = useParams();  | 
 | 15 | +    const [versionsList, setVersionsList] = useState<string[]>([]);  | 
 | 16 | +    const [mircheckerData, setMircheckerData] = useState<MircheckerRes | null>(null);  | 
 | 17 | +    const [loading, setLoading] = useState(true);  | 
 | 18 | +    const [error, setError] = useState<string | null>(null);  | 
 | 19 | +    const [searchTerm, setSearchTerm] = useState('');  | 
 | 20 | + | 
 | 21 | +    // 添加滚动条状态和引用  | 
 | 22 | +    const [isScrolling, setIsScrolling] = useState(false);  | 
 | 23 | +    const scrollTimeoutRef = useRef<NodeJS.Timeout>();  | 
 | 24 | +    const containerRef = useRef<HTMLDivElement>(null);  | 
 | 25 | + | 
 | 26 | +    // 添加滚动位置状态  | 
 | 27 | +    const [scrollPosition, setScrollPosition] = useState(0);  | 
 | 28 | + | 
 | 29 | +    // 获取版本列表和Mirchecker数据  | 
 | 30 | +    useEffect(() => {  | 
 | 31 | +        const fetchData = async () => {  | 
 | 32 | +            try {  | 
 | 33 | +                // 获取版本列表  | 
 | 34 | +                const versionsResponse = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}`);  | 
 | 35 | +                if (!versionsResponse.ok) {  | 
 | 36 | +                    throw new Error(`HTTP error! status: ${versionsResponse.status}`);  | 
 | 37 | +                }  | 
 | 38 | +                const versionsData = await versionsResponse.json();  | 
 | 39 | +                setVersionsList(versionsData.versions || []);  | 
 | 40 | + | 
 | 41 | +                // 获取 Mirchecker 数据  | 
 | 42 | +                const mircheckerResponse = await fetch(`/api/crates/${params.nsfront}/${params.nsbehind}/${params.name}/${params.version}/mirchecker`);  | 
 | 43 | +                if (!mircheckerResponse.ok) {  | 
 | 44 | +                    throw new Error(`HTTP error! status: ${mircheckerResponse.status}`);  | 
 | 45 | +                }  | 
 | 46 | +                const mircheckerData = await mircheckerResponse.json();  | 
 | 47 | +                setMircheckerData(mircheckerData);  | 
 | 48 | + | 
 | 49 | +                setLoading(false);  | 
 | 50 | +            } catch (error) {  | 
 | 51 | +                console.error('Error fetching data:', error);  | 
 | 52 | +                setError('获取数据时出错');  | 
 | 53 | +                setLoading(false);  | 
 | 54 | +            }  | 
 | 55 | +        };  | 
 | 56 | + | 
 | 57 | +        fetchData();  | 
 | 58 | +    }, [params.nsfront, params.nsbehind, params.name, params.version]);  | 
 | 59 | + | 
 | 60 | +    // 当前选中的版本  | 
 | 61 | +    const currentVersion = params.version as string;  | 
 | 62 | + | 
 | 63 | +    // 添加过滤后的版本列表计算  | 
 | 64 | +    const filteredVersions = versionsList.filter(version =>  | 
 | 65 | +        version.toLowerCase().includes(searchTerm.toLowerCase())  | 
 | 66 | +    );  | 
 | 67 | + | 
 | 68 | +    // 处理滚动事件  | 
 | 69 | +    const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {  | 
 | 70 | +        setScrollPosition(e.currentTarget.scrollTop);  | 
 | 71 | +        setIsScrolling(true);  | 
 | 72 | + | 
 | 73 | +        if (scrollTimeoutRef.current) {  | 
 | 74 | +            clearTimeout(scrollTimeoutRef.current);  | 
 | 75 | +        }  | 
 | 76 | + | 
 | 77 | +        scrollTimeoutRef.current = setTimeout(() => {  | 
 | 78 | +            setIsScrolling(false);  | 
 | 79 | +        }, 1500);  | 
 | 80 | +    };  | 
 | 81 | + | 
 | 82 | +    // 处理搜索输入  | 
 | 83 | +    const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {  | 
 | 84 | +        setSearchTerm(e.target.value);  | 
 | 85 | +        if (containerRef.current) {  | 
 | 86 | +            containerRef.current.scrollTop = 0;  | 
 | 87 | +            setScrollPosition(0);  | 
 | 88 | +        }  | 
 | 89 | +    };  | 
 | 90 | + | 
 | 91 | +    // 清理定时器  | 
 | 92 | +    useEffect(() => {  | 
 | 93 | +        return () => {  | 
 | 94 | +            if (scrollTimeoutRef.current) {  | 
 | 95 | +                clearTimeout(scrollTimeoutRef.current);  | 
 | 96 | +            }  | 
 | 97 | +        };  | 
 | 98 | +    }, []);  | 
 | 99 | + | 
 | 100 | +    if (loading) return (  | 
 | 101 | +        <div className="min-h-screen flex items-center justify-center">  | 
 | 102 | +            <p className="text-lg">加载中...</p>  | 
 | 103 | +        </div>  | 
 | 104 | +    );  | 
 | 105 | + | 
 | 106 | +    if (error) return (  | 
 | 107 | +        <div className="min-h-screen flex items-center justify-center">  | 
 | 108 | +            <p className="text-red-500 text-lg">错误: {error}</p>  | 
 | 109 | +        </div>  | 
 | 110 | +    );  | 
 | 111 | + | 
 | 112 | +    return (  | 
 | 113 | +        <div className="min-h-screen bg-[#F9F9F9] flex">  | 
 | 114 | +            {/* 左侧边栏 - 版本列表 */}  | 
 | 115 | +            <div className="w-[300px] h-screen sticky top-0 flex-shrink-0 bg-white shadow-[0_0_12px_0_#2b58dd17] backdrop-blur-[200px] flex flex-col">  | 
 | 116 | +                {/* Logo 和搜索框区域 - 固定不动 */}  | 
 | 117 | +                <div className="p-4 space-y-4 flex-shrink-0">  | 
 | 118 | +                    {/* Logo */}  | 
 | 119 | +                    <div className="flex justify-center">  | 
 | 120 | +                        <Image  | 
 | 121 | +                            src="/images/homepage/logo-top.png"  | 
 | 122 | +                            alt="CratesPro Logo"  | 
 | 123 | +                            width={150}  | 
 | 124 | +                            height={75}  | 
 | 125 | +                            className="mb-2"  | 
 | 126 | +                        />  | 
 | 127 | +                    </div>  | 
 | 128 | + | 
 | 129 | +                    {/* 搜索框 */}  | 
 | 130 | +                    <div className="relative">  | 
 | 131 | +                        <div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">  | 
 | 132 | +                            <Image  | 
 | 133 | +                                src="/images/homepage/senseleak-search.png"  | 
 | 134 | +                                alt="Search Icon"  | 
 | 135 | +                                width={16}  | 
 | 136 | +                                height={16}  | 
 | 137 | +                            />  | 
 | 138 | +                        </div>  | 
 | 139 | +                        <input  | 
 | 140 | +                            type="text"  | 
 | 141 | +                            placeholder="Search Files"  | 
 | 142 | +                            value={searchTerm}  | 
 | 143 | +                            onChange={handleSearch}  | 
 | 144 | +                            className="w-[250px] h-[36px] flex-shrink-0 pl-10 pr-4 rounded-[18px] border border-[#333333] bg-white shadow-[0_0_12px_0_#2b58dd17] text-[14px] font-['HarmonyOS_Sans_SC'] text-[#999999] focus:outline-none focus:ring-1 focus:ring-[#4B68FF] focus:border-[#4B68FF]"  | 
 | 145 | +                        />  | 
 | 146 | +                    </div>  | 
 | 147 | +                </div>  | 
 | 148 | + | 
 | 149 | +                {/* 版本列表区域 - 可滚动 */}  | 
 | 150 | +                <div  | 
 | 151 | +                    ref={containerRef}  | 
 | 152 | +                    className="flex-1 overflow-y-auto relative [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']"  | 
 | 153 | +                    onScroll={handleScroll}  | 
 | 154 | +                >  | 
 | 155 | +                    {/* 自定义滚动条 */}  | 
 | 156 | +                    <div  | 
 | 157 | +                        className={`absolute right-1 w-[4px] transition-opacity duration-150 ${isScrolling || containerRef.current?.scrollTop !== 0 ? 'opacity-100' : 'opacity-0'}`}  | 
 | 158 | +                        style={{  | 
 | 159 | +                            height: '60px',  | 
 | 160 | +                            top: containerRef.current  | 
 | 161 | +                                ? `${Math.min(  | 
 | 162 | +                                    (scrollPosition /  | 
 | 163 | +                                        (containerRef.current.scrollHeight - containerRef.current.clientHeight)) *  | 
 | 164 | +                                    (containerRef.current.clientHeight - 60),  | 
 | 165 | +                                    containerRef.current.clientHeight - 60  | 
 | 166 | +                                )}px`  | 
 | 167 | +                                : '0',  | 
 | 168 | +                            background: '#4B68FF',  | 
 | 169 | +                            borderRadius: '3px',  | 
 | 170 | +                            pointerEvents: 'none',  | 
 | 171 | +                            transition: 'top 0.1s linear, opacity 0.15s ease-in-out',  | 
 | 172 | +                        }}  | 
 | 173 | +                    />  | 
 | 174 | + | 
 | 175 | +                    <div className="p-4">  | 
 | 176 | +                        <div className="space-y-2">  | 
 | 177 | +                            {filteredVersions.map((version, index) => (  | 
 | 178 | +                                <Link  | 
 | 179 | +                                    key={index}  | 
 | 180 | +                                    href={`/${params.nsfront}/${params.nsbehind}/${params.name}/${version}/mirchecker`}  | 
 | 181 | +                                >  | 
 | 182 | +                                    <div className={`transition-colors cursor-pointer ${version === currentVersion  | 
 | 183 | +                                        ? 'bg-[#4b68ff] w-[278px] h-[37px] flex items-center text-white rounded-l-full rounded-r-none'  | 
 | 184 | +                                        : 'hover:bg-[#F5F7FF] text-[#333333] p-3'  | 
 | 185 | +                                        }`}>  | 
 | 186 | +                                        <div className={`flex items-center ${version === currentVersion ? 'pl-3' : ''}`}>  | 
 | 187 | +                                            <div className="mr-2">  | 
 | 188 | +                                                <Image  | 
 | 189 | +                                                    src="/images/homepage/senseleak-file.png"  | 
 | 190 | +                                                    alt="Version Icon"  | 
 | 191 | +                                                    width={16}  | 
 | 192 | +                                                    height={16}  | 
 | 193 | +                                                    className={version === currentVersion ? 'brightness-0 invert' : ''}  | 
 | 194 | +                                                />  | 
 | 195 | +                                            </div>  | 
 | 196 | +                                            <span className="font-['HarmonyOS_Sans_SC'] text-[16px]">Version-{version}</span>  | 
 | 197 | +                                        </div>  | 
 | 198 | +                                    </div>  | 
 | 199 | +                                </Link>  | 
 | 200 | +                            ))}  | 
 | 201 | +                            {filteredVersions.length === 0 && (  | 
 | 202 | +                                <div className="text-center py-4 text-[#999999] font-['HarmonyOS_Sans_SC']">  | 
 | 203 | +                                    没有找到匹配的版本  | 
 | 204 | +                                </div>  | 
 | 205 | +                            )}  | 
 | 206 | +                        </div>  | 
 | 207 | +                    </div>  | 
 | 208 | +                </div>  | 
 | 209 | +            </div>  | 
 | 210 | + | 
 | 211 | +            {/* 右侧内容区域 - Mirchecker数据 */}  | 
 | 212 | +            <div className="flex-1 py-8 px-12">  | 
 | 213 | +                <div className="max-w-[1500px] mx-auto">  | 
 | 214 | +                    <div className="mb-6 flex items-center gap-3">  | 
 | 215 | +                        <div className="w-[4px] h-[24px] flex-shrink-0 rounded-[2px] bg-[#4B68FF]"></div>  | 
 | 216 | +                        <h1 className="text-[24px] font-bold text-[#333333] tracking-[0.96px] font-['HarmonyOS_Sans_SC']">  | 
 | 217 | +                            Mirchecker Analysis: {params.name}/{params.version}  | 
 | 218 | +                        </h1>  | 
 | 219 | +                    </div>  | 
 | 220 | + | 
 | 221 | +                    <div className="bg-white rounded-2xl p-6 shadow-[0_0_12px_0_rgba(43,88,221,0.09)]">  | 
 | 222 | +                        {mircheckerData ? (  | 
 | 223 | +                            mircheckerData.run_state ? (  | 
 | 224 | +                                mircheckerData.exist ? (  | 
 | 225 | +                                    <pre className="whitespace-pre-wrap font-['HarmonyOS_Sans_SC'] text-[14px] leading-relaxed text-[#333333] p-4 bg-[#F8F9FC] rounded-lg overflow-x-auto">  | 
 | 226 | +                                        {mircheckerData.res}  | 
 | 227 | +                                    </pre>  | 
 | 228 | +                                ) : (  | 
 | 229 | +                                    <div className="flex flex-col items-center p-8">  | 
 | 230 | +                                        <Image  | 
 | 231 | +                                            src="/images/homepage/miss.png"  | 
 | 232 | +                                            alt="No data"  | 
 | 233 | +                                            width={140}  | 
 | 234 | +                                            height={140}  | 
 | 235 | +                                            className="mb-4"  | 
 | 236 | +                                        />  | 
 | 237 | +                                        <p className="text-[#C9D2FF] font-['HarmonyOS_Sans_SC'] text-[14px] font-normal leading-normal capitalize">  | 
 | 238 | +                                            no result  | 
 | 239 | +                                        </p>  | 
 | 240 | +                                    </div>  | 
 | 241 | +                                )  | 
 | 242 | +                            ) : (  | 
 | 243 | +                                <div className="flex flex-col items-center p-8">  | 
 | 244 | +                                    <Image  | 
 | 245 | +                                        src="/images/homepage/miss.png"  | 
 | 246 | +                                        alt="Run failed"  | 
 | 247 | +                                        width={140}  | 
 | 248 | +                                        height={140}  | 
 | 249 | +                                        className="mb-4"  | 
 | 250 | +                                    />  | 
 | 251 | +                                    <p className="text-[#C9D2FF] font-['HarmonyOS_Sans_SC'] text-[14px] font-normal leading-normal capitalize">  | 
 | 252 | +                                        run failed  | 
 | 253 | +                                    </p>  | 
 | 254 | +                                </div>  | 
 | 255 | +                            )  | 
 | 256 | +                        ) : (  | 
 | 257 | +                            <div className="flex flex-col items-center p-8">  | 
 | 258 | +                                <Image  | 
 | 259 | +                                    src="/images/homepage/miss.png"  | 
 | 260 | +                                    alt="No data"  | 
 | 261 | +                                    width={140}  | 
 | 262 | +                                    height={140}  | 
 | 263 | +                                    className="mb-4"  | 
 | 264 | +                                />  | 
 | 265 | +                                <p className="text-[#C9D2FF] font-['HarmonyOS_Sans_SC'] text-[14px] font-normal leading-normal capitalize">  | 
 | 266 | +                                    加载中...  | 
 | 267 | +                                </p>  | 
 | 268 | +                            </div>  | 
 | 269 | +                        )}  | 
 | 270 | +                    </div>  | 
 | 271 | +                </div>  | 
 | 272 | +            </div>  | 
 | 273 | +        </div>  | 
 | 274 | +    );  | 
 | 275 | +};  | 
 | 276 | + | 
 | 277 | +export default MircheckerPage;  | 
0 commit comments