Pārlūkot izejas kodu

fix: router fix

王晓东 3 dienas atpakaļ
vecāks
revīzija
78bd5cb969
20 mainītis faili ar 757 papildinājumiem un 702 dzēšanām
  1. 0 44
      App.tsx
  2. 0 118
      CloneChatPage.tsx
  3. 0 54
      ContactPage.tsx
  4. 0 368
      EditPage.tsx
  5. 0 35
      LandingPage.tsx
  6. 2 2
      README.md
  7. 0 14
      index.html
  8. 1 1
      index.tsx
  9. 6 2
      package.json
  10. 619 5
      pnpm-lock.yaml
  11. 0 25
      routes.tsx
  12. 10 2
      src/App.tsx
  13. 14 0
      src/api/index.ts
  14. 35 1
      src/pages/Landing/LandingPage.tsx
  15. 33 0
      src/router/routes.tsx
  16. 0 25
      src/routes.tsx
  17. 5 0
      src/styles/index.scss
  18. 6 0
      src/styles/variables.scss
  19. 1 3
      tsconfig.json
  20. 25 3
      vite.config.ts

+ 0 - 44
App.tsx

@@ -1,44 +0,0 @@
-import React, { useState } from 'react';
-import AppRoutes from './routes';
-import type { ContactData } from './types';
-
-const App: React.FC = () => {
-  // Mock data for the contact, now managed by state
-  const [contactData, setContactData] = useState<ContactData>({
-    name: 'Alex Doe',
-    avatarUrl: 'https://picsum.photos/200',
-    virtualId: 'VID-2024-8A4B-C7D2-F1E0',
-    bio: 'Senior Frontend Engineer with a passion for creating beautiful and functional user interfaces. Expert in React, TypeScript, and modern web technologies. In my free time, I explore the intersection of AI and art.',
-    socials: {
-      linkedin: 'https://www.linkedin.com/in/alex-doe',
-      x: 'https://x.com/alex_doe',
-      github: 'https://github.com/alexdoe',
-      tiktok: 'https://www.tiktok.com/@alexdoe.dev',
-      instagram: 'https://www.instagram.com/alex.doe',
-      facebook: 'https://www.facebook.com/alex.doe',
-      discord: 'alex.doe#1234',
-    },
-    authenticityScore: {
-      videoVerification: 15, // max 15
-      socialBinding: 45, // max 45
-      cloneMaturity: 48, // max 60
-    },
-    videoIntroUrl: 'https://storage.googleapis.com/web-dev-assets/video-introduction-demo.mp4',
-    videoPosterUrl: 'https://picsum.photos/seed/video-poster/800/450',
-  });
-
-  const handleSave = (newData: ContactData) => {
-    setContactData(newData);
-  };
-
-  return (
-    <div className="min-h-screen bg-slate-50 font-sans text-slate-800">
-      <AppRoutes contactData={contactData} onSave={handleSave} />
-      <footer className="text-center p-4 text-slate-500 text-sm">
-        <p>&copy; 2024 Virtual Identity System. All rights reserved.</p>
-      </footer>
-    </div>
-  );
-};
-
-export default App;

+ 0 - 118
CloneChatPage.tsx

@@ -1,118 +0,0 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { useNavigate } from 'react-router-dom';
-
-interface Message {
-  id: number;
-  text: string;
-  sender: 'user' | 'clone';
-}
-
-const CloneChatPage: React.FC = () => {
-  const navigate = useNavigate();
-  const [messages, setMessages] = useState<Message[]>([
-    { id: 1, text: "Hello! You can start training me by talking to me. What's on your mind?", sender: 'clone' }
-  ]);
-  const [inputValue, setInputValue] = useState('');
-  const [isTyping, setIsTyping] = useState(false);
-  const messagesEndRef = useRef<HTMLDivElement>(null);
-
-  const scrollToBottom = () => {
-    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
-  };
-
-  useEffect(scrollToBottom, [messages, isTyping]);
-
-  const handleSendMessage = (e: React.FormEvent) => {
-    e.preventDefault();
-    if (inputValue.trim() === '') return;
-
-    const userMessage: Message = {
-      id: Date.now(),
-      text: inputValue,
-      sender: 'user',
-    };
-
-    setMessages(prev => [...prev, userMessage]);
-    setInputValue('');
-    setIsTyping(true);
-
-    // Simulate clone response
-    setTimeout(() => {
-      const cloneResponse: Message = {
-        id: Date.now() + 1,
-        text: `That's an interesting point. It reminds me of how you once said... (simulated response based on your persona).`,
-        sender: 'clone',
-      };
-      setIsTyping(false);
-      setMessages(prev => [...prev, cloneResponse]);
-    }, 1500 + Math.random() * 1000);
-  };
-
-  return (
-    <div className="flex flex-col h-[calc(100vh-60px)] max-w-3xl mx-auto bg-white rounded-2xl overflow-hidden border border-slate-200">
-        {/* Header */}
-        <header className="flex items-center justify-between p-4 border-b border-slate-200 bg-slate-50 flex-shrink-0">
-            <h1 className="text-xl font-bold text-slate-800">Train My Digital Clone</h1>
-            <button
-                onClick={() => navigate('/edit')}
-                className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-100"
-            >
-                Back to Edit
-            </button>
-        </header>
-
-        {/* Chat Area */}
-        <main className="flex-1 overflow-y-auto p-6 space-y-4">
-            {messages.map(msg => (
-                <div key={msg.id} className={`flex items-end gap-2 ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}>
-                    {msg.sender === 'clone' && (
-                        <div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex-shrink-0"></div>
-                    )}
-                    <div className={`max-w-xs md:max-w-md lg:max-w-lg px-4 py-2 rounded-2xl ${msg.sender === 'user' ? 'bg-blue-600 text-white rounded-br-none' : 'bg-slate-200 text-slate-800 rounded-bl-none'}`}>
-                       <p className="text-sm leading-relaxed">{msg.text}</p>
-                    </div>
-                </div>
-            ))}
-             {isTyping && (
-                <div className="flex items-end gap-2 justify-start">
-                    <div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex-shrink-0"></div>
-                    <div className="px-4 py-2 rounded-2xl bg-slate-200 rounded-bl-none">
-                        <div className="flex items-center justify-center space-x-1">
-                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce [animation-delay:-0.3s]"></span>
-                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce [animation-delay:-0.15s]"></span>
-                            <span className="w-1.5 h-1.5 bg-slate-500 rounded-full animate-bounce"></span>
-                        </div>
-                    </div>
-                </div>
-            )}
-            <div ref={messagesEndRef} />
-        </main>
-
-        {/* Input Form */}
-        <footer className="p-4 border-t border-slate-200 bg-slate-50 flex-shrink-0">
-            <form onSubmit={handleSendMessage} className="flex items-center gap-3">
-                <input
-                    type="text"
-                    value={inputValue}
-                    onChange={e => setInputValue(e.target.value)}
-                    placeholder="Talk to your clone..."
-                    className="flex-1 w-full px-4 py-2 bg-white border border-slate-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
-                    autoComplete="off"
-                />
-                <button
-                    type="submit"
-                    aria-label="Send message"
-                    className="w-10 h-10 flex-shrink-0 bg-blue-600 text-white rounded-full flex items-center justify-center hover:bg-blue-700 disabled:bg-blue-300 transition-colors"
-                    disabled={inputValue.trim() === ''}
-                >
-                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-5 h-5">
-                      <path d="M3.105 2.289a.75.75 0 00-.826.95l1.414 4.925A1.5 1.5 0 005.135 9.25h6.115a.75.75 0 010 1.5H5.135a1.5 1.5 0 00-1.442 1.086L2.279 16.76a.75.75 0 00.826.95l14.433-6.414a.75.75 0 000-1.392L3.105 2.289z" />
-                    </svg>
-                </button>
-            </form>
-        </footer>
-    </div>
-  );
-};
-
-export default CloneChatPage;

+ 0 - 54
ContactPage.tsx

@@ -1,54 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import VirtualIdCard from './components/VirtualIdCard';
-import AuthenticityScore from './components/AuthenticityScore';
-import Bio from './components/Bio';
-import SocialLinks from './components/SocialLinks';
-import VideoIntroduction from './components/VideoIntroduction';
-import type { ContactData } from './types';
-
-interface ContactPageProps {
-  data: ContactData;
-}
-
-const ContactPage: React.FC<ContactPageProps> = ({ data }) => {
-  const totalScore = data.authenticityScore.videoVerification + data.authenticityScore.socialBinding + data.authenticityScore.cloneMaturity;
-
-  return (
-    <main className="max-w-3xl mx-auto p-4 sm:p-6 md:p-8">
-      <div className="space-y-8">
-        <div className="flex justify-between items-center mb-4">
-            <h1 className="text-2xl font-bold text-slate-800">Contact Profile</h1>
-            <div>
-                <Link to="/" className="text-sm text-slate-600 hover:text-blue-600 mr-4">Home</Link>
-                <Link to="/edit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">Edit Page</Link>
-            </div>
-        </div>
-        <VirtualIdCard 
-          name={data.name}
-          avatarUrl={data.avatarUrl}
-          virtualId={data.virtualId}
-          totalScore={totalScore}
-        />
-        
-        <AuthenticityScore 
-          videoVerificationScore={data.authenticityScore.videoVerification}
-          socialBindingScore={data.authenticityScore.socialBinding}
-          cloneMaturityScore={data.authenticityScore.cloneMaturity}
-        />
-
-        <Bio text={data.bio} />
-
-        <VideoIntroduction
-          videoUrl={data.videoIntroUrl}
-          posterUrl={data.videoPosterUrl}
-        />
-
-        <SocialLinks links={data.socials} />
-
-      </div>
-    </main>
-  );
-};
-
-export default ContactPage;

+ 0 - 368
EditPage.tsx

@@ -1,368 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useNavigate } from 'react-router-dom';
-import type { ContactData, SocialLinksData } from './types';
-import LinkedInIcon from './components/icons/LinkedInIcon';
-import XIcon from './components/icons/XIcon';
-import GithubIcon from './components/icons/GithubIcon';
-import TiktokIcon from './components/icons/TiktokIcon';
-import InstagramIcon from './components/icons/InstagramIcon';
-import FacebookIcon from './components/icons/FacebookIcon';
-import DiscordIcon from './components/icons/DiscordIcon';
-import VerifiedIcon from './components/icons/VerifiedIcon';
-import VirtualIdCard from './components/VirtualIdCard';
-import UploadIcon from './components/icons/UploadIcon';
-import AuthenticityScore from './components/AuthenticityScore';
-import BrainCircuitIcon from './components/icons/BrainCircuitIcon';
-import ArrowRightIcon from './components/icons/ArrowRightIcon';
-
-interface EditPageProps {
-  initialData: ContactData;
-  onSave: (data: ContactData) => void;
-}
-
-// Reusable Section Component for editable fields
-const EditSection: React.FC<{title: string, children: React.ReactNode}> = ({ title, children }) => (
-    <div className="bg-white rounded-2xl border border-slate-200 p-6 space-y-4">
-        <h2 className="text-xl font-bold text-slate-800 mb-4">{title}</h2>
-        {children}
-    </div>
-);
-
-const socialPlatforms = [
-    { 
-        key: 'linkedin', name: 'LinkedIn', icon: <LinkedInIcon />,
-        brandClasses: {
-            iconBg: 'bg-gradient-to-br from-blue-500 to-sky-400',
-            button: 'bg-blue-600 hover:bg-blue-700',
-        }
-    },
-    { 
-        key: 'x', name: 'X (Twitter)', icon: <XIcon />,
-        brandClasses: {
-            iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
-            button: 'bg-slate-800 hover:bg-slate-900',
-        }
-    },
-    { 
-        key: 'github', name: 'GitHub', icon: <GithubIcon />,
-        brandClasses: {
-            iconBg: 'bg-gradient-to-br from-slate-900 to-slate-700',
-            button: 'bg-slate-800 hover:bg-slate-900',
-        }
-    },
-    { 
-        key: 'tiktok', name: 'TikTok', icon: <TiktokIcon />,
-        brandClasses: {
-            iconBg: 'bg-gradient-to-br from-black via-rose-500 to-cyan-400',
-            button: 'bg-black hover:bg-gray-800',
-        }
-    },
-    { 
-        key: 'instagram', name: 'Instagram', icon: <InstagramIcon />,
-        brandClasses: {
-            iconBg: 'bg-gradient-to-br from-purple-500 via-pink-500 to-yellow-500',
-            button: 'bg-pink-600 hover:bg-pink-700',
-        }
-    },
-    { 
-        key: 'facebook', name: 'Facebook', icon: <FacebookIcon />,
-        brandClasses: {
-            iconBg: 'bg-gradient-to-br from-blue-700 to-blue-500',
-            button: 'bg-blue-700 hover:bg-blue-800',
-        }
-    },
-    { 
-        key: 'discord', name: 'Discord', icon: <DiscordIcon />,
-        brandClasses: {
-            iconBg: 'bg-gradient-to-br from-indigo-600 to-purple-500',
-            button: 'bg-indigo-600 hover:bg-indigo-700',
-        }
-    },
-] as const;
-
-
-const EditPage: React.FC<EditPageProps> = ({ initialData, onSave }) => {
-    const navigate = useNavigate();
-    const [formData, setFormData] = useState<ContactData>(initialData);
-
-    useEffect(() => {
-        const handleOauthMessage = (event: MessageEvent) => {
-            if (event.origin !== window.location.origin) {
-                return;
-            }
-
-            const { type, platform, profileUrl, error } = event.data;
-
-            if (type === 'oauth-success' && platform && profileUrl) {
-                setFormData(prev => ({
-                    ...prev,
-                    socials: {
-                        ...prev.socials,
-                        [platform]: profileUrl
-                    }
-                }));
-            } else if (type === 'oauth-error') {
-                console.error(`OAuth Error for ${platform}:`, error);
-                alert(`Failed to connect ${platform}. Please try again.`);
-            }
-        };
-        
-        window.addEventListener('message', handleOauthMessage);
-
-        return () => {
-            window.removeEventListener('message', handleOauthMessage);
-        };
-    }, []);
-
-    useEffect(() => {
-        // Recalculate authenticity score when dependencies change
-        const connectedSocials = Object.values(formData.socials).filter(link => !!link).length;
-        
-        let socialScore = 0;
-        if (connectedSocials === 1) {
-            socialScore = 15;
-        } else if (connectedSocials === 2) {
-            socialScore = 30;
-        } else if (connectedSocials > 2) {
-            socialScore = 30 + (connectedSocials - 2) * 5;
-        }
-        
-        const finalSocialScore = Math.min(socialScore, 45);
-
-        const videoScore = formData.videoIntroUrl ? 15 : 0;
-        
-        // Only update if the scores have changed to prevent infinite loops
-        if (
-            finalSocialScore !== formData.authenticityScore.socialBinding ||
-            videoScore !== formData.authenticityScore.videoVerification
-        ) {
-            setFormData(prev => ({
-                ...prev,
-                authenticityScore: {
-                    ...prev.authenticityScore,
-                    socialBinding: finalSocialScore,
-                    videoVerification: videoScore,
-                }
-            }));
-        }
-
-    }, [formData.socials, formData.videoIntroUrl, formData.authenticityScore]);
-
-    const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
-        const { name, value } = e.target;
-        setFormData(prev => ({
-            ...prev,
-            [name]: value,
-        }));
-    };
-
-    const handleVideoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
-        const file = e.target.files?.[0];
-        if (file) {
-            const videoUrl = URL.createObjectURL(file);
-            setFormData(prev => ({
-                ...prev,
-                videoIntroUrl: videoUrl,
-                videoPosterUrl: '' // Reset poster for new video
-            }));
-        }
-    };
-
-    const handleSocialConnect = (platform: keyof SocialLinksData) => {
-        const isConnected = !!formData.socials[platform];
-
-        if (isConnected) {
-            // Disconnect logic
-            const newSocials = { ...formData.socials, [platform]: '' };
-            setFormData(prev => ({ ...prev, socials: newSocials }));
-            return;
-        }
-
-        // --- Real OAuth URLs ---
-        const redirectUri = `${window.location.origin}/oauth-callback.html`;
-        let oauthUrl = '';
-
-        switch (platform) {
-            case 'github':
-                oauthUrl = `https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=read:user&state=github`;
-                break;
-            case 'linkedin':
-                oauthUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=linkedin&scope=profile%20email%20openid`;
-                break;
-            case 'x':
-                 oauthUrl = `https://twitter.com/i/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=users.read%20tweet.read&state=x&code_challenge=challenge&code_challenge_method=plain`;
-                break;
-            case 'tiktok':
-                oauthUrl = `https://www.tiktok.com/v2/auth/authorize?client_key=YOUR_CLIENT_KEY&scope=user.info.basic&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=tiktok`;
-                break;
-            case 'instagram':
-                oauthUrl = `https://api.instagram.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&scope=user_profile,user_media&response_type=code&state=instagram`;
-                break;
-            case 'facebook':
-                oauthUrl = `https://www.facebook.com/v19.0/dialog/oauth?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&state=facebook&scope=public_profile,email`;
-                break;
-            case 'discord':
-                oauthUrl = `https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=identify%20email&state=discord`;
-                break;
-        }
-
-        // --- Popup Window Logic ---
-        const width = 600;
-        const height = 700;
-        const left = (window.innerWidth - width) / 2;
-        const top = (window.innerHeight - height) / 2;
-
-        window.open(
-            oauthUrl,
-            'socialLogin',
-            `width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes`
-        );
-    };
-
-    const handleSubmit = (e: React.FormEvent) => {
-        e.preventDefault();
-        onSave(formData);
-        navigate('/contact');
-    };
-
-    const totalScore = formData.authenticityScore.videoVerification + formData.authenticityScore.socialBinding + formData.authenticityScore.cloneMaturity;
-
-    return (
-        <main className="max-w-3xl mx-auto p-4 sm:p-6 md:p-8">
-            <form onSubmit={handleSubmit} className="space-y-8">
-                <div className="flex justify-between items-center mb-4">
-                    <h1 className="text-2xl font-bold text-slate-800">Certify My Homepage</h1>
-                    <div>
-                        <button type="button" onClick={() => navigate('/contact')} className="px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-50 mr-2">Cancel</button>
-                        <button type="submit" className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700">Save Changes</button>
-                    </div>
-                </div>
-
-                <VirtualIdCard
-                    name={formData.name}
-                    avatarUrl={formData.avatarUrl}
-                    virtualId={formData.virtualId}
-                    totalScore={totalScore}
-                />
-
-                <AuthenticityScore 
-                  videoVerificationScore={formData.authenticityScore.videoVerification}
-                  socialBindingScore={formData.authenticityScore.socialBinding}
-                  cloneMaturityScore={formData.authenticityScore.cloneMaturity}
-                />
-
-                <div className="bg-sky-50 border border-sky-200 text-sky-800 rounded-xl p-3 text-center text-sm -mt-4">
-                    <p>At your current score, you'll earn <span className="font-bold">100 tokens/minute</span> from calls.</p>
-                </div>
-
-                <div 
-                  onClick={() => navigate('/clone-chat')}
-                  className="bg-white rounded-2xl border border-slate-200 p-6 group cursor-pointer transition-colors duration-300 ease-in-out hover:bg-slate-50"
-                  role="button"
-                  aria-label="Train My Digital Clone"
-                >
-                  <div className="flex items-center gap-4">
-                      <div className="flex-shrink-0 w-12 h-12 rounded-2xl flex items-center justify-center bg-gradient-to-br from-indigo-500 to-purple-600 text-white group-hover:scale-110 transition-transform duration-300">
-                          <BrainCircuitIcon />
-                      </div>
-                      <div className="flex-grow">
-                          <h2 className="text-xl font-bold text-slate-800">Train My Digital Clone</h2>
-                          <p className="text-sm text-slate-600 mt-1">
-                              Improve your clone's quality score by continuously chatting with it.
-                          </p>
-                          <p className="text-sm text-indigo-600 font-medium mt-1">
-                              A higher score leads to greater call earnings.
-                          </p>
-                      </div>
-                      <div className="text-slate-400 group-hover:text-slate-800 transition-transform duration-300 group-hover:translate-x-1">
-                        <ArrowRightIcon className="w-5 h-5" />
-                      </div>
-                  </div>
-                </div>
-
-                <EditSection title="About Me">
-                    <label htmlFor="bio" className="sr-only">About Me</label>
-                    <textarea
-                        id="bio"
-                        name="bio"
-                        rows={5}
-                        className="w-full px-3 py-2 text-slate-700 bg-white border border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition"
-                        placeholder="Tell us about yourself..."
-                        value={formData.bio}
-                        onChange={handleInputChange}
-                    />
-                </EditSection>
-
-                <EditSection title="Video Introduction">
-                    <label htmlFor="video-upload" className="block aspect-video w-full rounded-lg bg-slate-200 relative overflow-hidden group cursor-pointer">
-                        <video key={formData.videoIntroUrl} poster={formData.videoPosterUrl} src={formData.videoIntroUrl} className="w-full h-full object-cover" />
-                        
-                        <div className="absolute inset-0 bg-black/60 flex flex-col items-center justify-center text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300">
-                            <UploadIcon />
-                            <span className="mt-2 block font-semibold">Upload New Video</span>
-                            <span className="text-xs text-slate-300">Click or drag & drop</span>
-                        </div>
-                         <input id="video-upload" type="file" onChange={handleVideoChange} accept="video/*" className="sr-only"/>
-                    </label>
-                </EditSection>
-                
-                <EditSection title="Verified Social Accounts">
-                    <p className="text-sm text-slate-600">Connect your social accounts to verify your identity. This enhances your Authenticity Score.</p>
-                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
-                        {socialPlatforms.map((platform) => {
-                            const isConnected = !!formData.socials[platform.key];
-                            
-                            if (!isConnected) {
-                                return (
-                                    <div key={platform.key} className="flex items-center p-4 rounded-xl border-2 border-dashed border-slate-200 bg-slate-50 transition-colors hover:border-slate-300 hover:bg-slate-100">
-                                        <div className="flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-slate-400 bg-slate-200">
-                                            {platform.icon}
-                                        </div>
-                                        <div className="ml-4 flex-grow">
-                                            <h3 className="font-bold text-slate-700 text-sm">{platform.name}</h3>
-                                            <p className="text-xs text-slate-500">Not Connected</p>
-                                        </div>
-                                        <button 
-                                            type="button" 
-                                            onClick={() => handleSocialConnect(platform.key)} 
-                                            className={`px-3 py-1.5 text-xs font-semibold text-white ${platform.brandClasses.button} rounded-md transition-transform transform hover:scale-105`}
-                                        >
-                                            Connect
-                                        </button>
-                                    </div>
-                                );
-                            }
-
-                            return (
-                                <div key={platform.key} className="flex items-center p-4 rounded-xl border border-slate-200 bg-white">
-                                  <div className={`flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center text-white ${platform.brandClasses.iconBg}`}>
-                                      {platform.icon}
-                                  </div>
-                                  <div className="ml-4 flex-grow overflow-hidden">
-                                      <div className="flex items-center">
-                                          <h3 className="font-bold text-slate-800 text-sm">{platform.name}</h3>
-                                          <div className="ml-2 flex items-center bg-green-100 text-green-800 text-xs font-medium px-2 py-0.5 rounded-full">
-                                            <VerifiedIcon className="w-3 h-3 mr-1" />
-                                            Verified
-                                          </div>
-                                      </div>
-                                      <p className="text-xs text-slate-500 font-mono truncate max-w-[120px] sm:max-w-xs">{formData.socials[platform.key].replace(/^https?:\/\/(www\.)?/, '')}</p>
-                                  </div>
-                                  <button 
-                                    type="button" 
-                                    onClick={() => handleSocialConnect(platform.key)} 
-                                    className="text-xs font-semibold text-red-600 hover:text-red-800 hover:bg-red-100 px-2 py-1 rounded-md transition-colors"
-                                  >
-                                    Disconnect
-                                  </button>
-                                </div>
-                            );
-                        })}
-                    </div>
-                </EditSection>
-            </form>
-        </main>
-    );
-};
-
-export default EditPage;

+ 0 - 35
LandingPage.tsx

@@ -1,35 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router-dom';
-
-const LandingPage: React.FC = () => {
-  const navigate = useNavigate();
-
-  return (
-    <div className="flex flex-col items-center justify-center min-h-[calc(100vh-60px)]">
-      <div className="text-center p-8">
-        <h1 className="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight">
-          Virtual Identity System
-        </h1>
-        <p className="mt-4 max-w-xl mx-auto text-lg text-slate-600">
-          Your unified, verifiable online presence.
-        </p>
-        <div className="mt-8 flex flex-col sm:flex-row justify-center gap-4">
-          <button
-            onClick={() => navigate('/contact')}
-            className="w-full sm:w-auto inline-flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-transform transform hover:scale-105"
-          >
-            Contact Homepage
-          </button>
-          <button
-            onClick={() => navigate('/edit')}
-            className="w-full sm:w-auto inline-flex items-center justify-center px-8 py-3 border border-slate-300 text-base font-medium rounded-md text-slate-700 bg-white hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-transform transform hover:scale-105"
-          >
-            Certify My Page
-          </button>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-export default LandingPage;

+ 2 - 2
README.md

@@ -14,7 +14,7 @@ View your app in AI Studio: https://ai.studio/apps/drive/19o8dRod9SJYV2EtKSNEon5
 
 
 1. Install dependencies:
-   `npm install`
+   `pnpm install`
 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
 3. Run the app:
-   `npm run dev`
+   `pnpm dev`

+ 0 - 14
index.html

@@ -5,18 +5,6 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>Contact Homepage</title>
     <script src="https://cdn.tailwindcss.com"></script>
-    <script type="importmap">
-{
-  "imports": {
-    "react": "https://esm.sh/react@18.2.0",
-    "react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
-    "react-router-dom": "https://esm.sh/react-router-dom@6",
-    "recharts": "https://esm.sh/recharts@2.12.7",
-    "react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
-    "react/": "https://aistudiocdn.com/react@^19.2.0/"
-  }
-}
-</script>
 <style>
    /* Holographic Gleam Effect */
   .holographic-gleam::before {
@@ -49,11 +37,9 @@
     to { transform: rotate(360deg); }
   }
 </style>
-<link rel="stylesheet" href="/index.css">
 </head>
   <body class="bg-slate-100">
     <div id="root"></div>
     <script type="module" src="/src/index.tsx"></script>
-  <script type="module" src="/index.tsx"></script>
 </body>
 </html>

+ 1 - 1
index.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import ReactDOM from 'react-dom/client';
 import { BrowserRouter } from 'react-router-dom';
-import App from './App';
+import App from './src/App';
 
 const rootElement = document.getElementById('root');
 if (!rootElement) {

+ 6 - 2
package.json

@@ -9,14 +9,18 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "alova": "^3.3.4",
+    "antd-mobile": "^5.41.1",
     "react": "^19.2.0",
+    "react-dom": "^19.2.0",
     "react-router-dom": "6",
-    "recharts": "2.12.7",
-    "react-dom": "^19.2.0"
+    "recharts": "2.12.7"
   },
   "devDependencies": {
     "@types/node": "^22.14.0",
+    "@types/react": "^19.2.2",
     "@vitejs/plugin-react": "^5.0.0",
+    "sass": "^1.93.3",
     "typescript": "~5.8.2",
     "vite": "^6.2.0"
   }

+ 619 - 5
pnpm-lock.yaml

@@ -8,6 +8,12 @@ importers:
 
   .:
     dependencies:
+      alova:
+        specifier: ^3.3.4
+        version: 3.3.4
+      antd-mobile:
+        specifier: ^5.41.1
+        version: 5.41.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
       react:
         specifier: ^19.2.0
         version: 19.2.0
@@ -24,18 +30,27 @@ importers:
       '@types/node':
         specifier: ^22.14.0
         version: 22.19.0
+      '@types/react':
+        specifier: ^19.2.2
+        version: 19.2.2
       '@vitejs/plugin-react':
         specifier: ^5.0.0
-        version: 5.1.0(vite@6.4.1(@types/node@22.19.0))
+        version: 5.1.0(vite@6.4.1(@types/node@22.19.0)(sass@1.93.3))
+      sass:
+        specifier: ^1.93.3
+        version: 1.93.3
       typescript:
         specifier: ~5.8.2
         version: 5.8.3
       vite:
         specifier: ^6.2.0
-        version: 6.4.1(@types/node@22.19.0)
+        version: 6.4.1(@types/node@22.19.0)(sass@1.93.3)
 
 packages:
 
+  '@alova/shared@1.3.1':
+    resolution: {integrity: sha512-ijSOaFLUFcVzMKSY3avoEE5C03/p9atjMDPBwvNkwnzaCrhv6/m4A121NdadF8YlHCRuifyYfz90IyEdMXTsJg==}
+
   '@babel/code-frame@7.27.1':
     resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
     engines: {node: '>=6.9.0'}
@@ -279,6 +294,15 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@floating-ui/core@1.7.3':
+    resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+  '@floating-ui/dom@1.7.4':
+    resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+  '@floating-ui/utils@0.2.10':
+    resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
   '@jridgewell/gen-mapping@0.3.13':
     resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
 
@@ -295,6 +319,125 @@ packages:
   '@jridgewell/trace-mapping@0.3.31':
     resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
 
+  '@parcel/watcher-android-arm64@2.5.1':
+    resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [android]
+
+  '@parcel/watcher-darwin-arm64@2.5.1':
+    resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@parcel/watcher-darwin-x64@2.5.1':
+    resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@parcel/watcher-freebsd-x64@2.5.1':
+    resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@parcel/watcher-linux-arm-glibc@2.5.1':
+    resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-arm-musl@2.5.1':
+    resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-linux-arm64-glibc@2.5.1':
+    resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-arm64-musl@2.5.1':
+    resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-linux-x64-glibc@2.5.1':
+    resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [glibc]
+
+  '@parcel/watcher-linux-x64-musl@2.5.1':
+    resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [linux]
+    libc: [musl]
+
+  '@parcel/watcher-win32-arm64@2.5.1':
+    resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@parcel/watcher-win32-ia32@2.5.1':
+    resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@parcel/watcher-win32-x64@2.5.1':
+    resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
+    engines: {node: '>= 10.0.0'}
+    cpu: [x64]
+    os: [win32]
+
+  '@parcel/watcher@2.5.1':
+    resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
+    engines: {node: '>= 10.0.0'}
+
+  '@rc-component/mini-decimal@1.1.0':
+    resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==}
+    engines: {node: '>=8.x'}
+
+  '@react-spring/animated@9.6.1':
+    resolution: {integrity: sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+  '@react-spring/core@9.6.1':
+    resolution: {integrity: sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+  '@react-spring/rafz@9.6.1':
+    resolution: {integrity: sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==}
+
+  '@react-spring/shared@9.6.1':
+    resolution: {integrity: sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+  '@react-spring/types@9.6.1':
+    resolution: {integrity: sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==}
+
+  '@react-spring/web@9.6.1':
+    resolution: {integrity: sha512-X2zR6q2Z+FjsWfGAmAXlQaoUHbPmfuCaXpuM6TcwXPpLE1ZD4A1eys/wpXboFQmDkjnrlTmKvpVna1MjWpZ5Hw==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+
   '@remix-run/router@1.23.0':
     resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
     engines: {node: '>=14.0.0'}
@@ -465,19 +608,62 @@ packages:
   '@types/estree@1.0.8':
     resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
 
+  '@types/js-cookie@3.0.6':
+    resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
+
   '@types/node@22.19.0':
     resolution: {integrity: sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==}
 
+  '@types/react@19.2.2':
+    resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
+
+  '@use-gesture/core@10.3.0':
+    resolution: {integrity: sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A==}
+
+  '@use-gesture/react@10.3.0':
+    resolution: {integrity: sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA==}
+    peerDependencies:
+      react: '>= 16.8.0'
+
   '@vitejs/plugin-react@5.1.0':
     resolution: {integrity: sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew==}
     engines: {node: ^20.19.0 || >=22.12.0}
     peerDependencies:
       vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
 
+  ahooks@3.9.6:
+    resolution: {integrity: sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  alova@3.3.4:
+    resolution: {integrity: sha512-UKKqXdvf8aQ4C7m3brO77YWe5CDz8N59PdAUz7M8gowKUUXTutbk0Vk5DRBrCe0hMUyyNMUhdCZ38llGxCViyQ==}
+    engines: {node: '>= 18.0.0'}
+
+  antd-mobile-icons@0.3.0:
+    resolution: {integrity: sha512-rqINQpJWZWrva9moCd1Ye695MZYWmqLPE+bY8d2xLRy7iSQwPsinCdZYjpUPp2zL/LnKYSyXxP2ut2A+DC+whQ==}
+
+  antd-mobile-v5-count@1.0.1:
+    resolution: {integrity: sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA==}
+
+  antd-mobile@5.41.1:
+    resolution: {integrity: sha512-fS5sTRLKHca5qryEYLGiPDLANK0rbhx8f8xk0Olu6ef00tLe0P9iqHQm0U3UtEBd8S454cilw5uv2J3I79Tbgg==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+  async-validator@4.2.5:
+    resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
+
   baseline-browser-mapping@2.8.23:
     resolution: {integrity: sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==}
     hasBin: true
 
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
   browserslist@4.27.0:
     resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -486,6 +672,13 @@ packages:
   caniuse-lite@1.0.30001753:
     resolution: {integrity: sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==}
 
+  chokidar@4.0.3:
+    resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+    engines: {node: '>= 14.16.0'}
+
+  classnames@2.5.1:
+    resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
   clsx@2.1.1:
     resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
     engines: {node: '>=6'}
@@ -540,6 +733,9 @@ packages:
     resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
     engines: {node: '>=12'}
 
+  dayjs@1.11.19:
+    resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
+
   debug@4.4.3:
     resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
     engines: {node: '>=6.0'}
@@ -552,6 +748,15 @@ packages:
   decimal.js-light@2.5.1:
     resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
 
+  deepmerge@4.3.1:
+    resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+    engines: {node: '>=0.10.0'}
+
+  detect-libc@1.0.3:
+    resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+    engines: {node: '>=0.10'}
+    hasBin: true
+
   dom-helpers@5.2.1:
     resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
 
@@ -583,6 +788,10 @@ packages:
       picomatch:
         optional: true
 
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
   fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -592,10 +801,32 @@ packages:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
 
+  immutable@5.1.4:
+    resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
+
   internmap@2.0.3:
     resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
     engines: {node: '>=12'}
 
+  intersection-observer@0.12.2:
+    resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  js-cookie@3.0.5:
+    resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
+    engines: {node: '>=14'}
+
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
@@ -619,14 +850,24 @@ packages:
   lru-cache@5.1.1:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
 
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+    engines: {node: '>=8.6'}
+
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
+  nano-memoize@3.0.16:
+    resolution: {integrity: sha512-JyK96AKVGAwVeMj3MoMhaSXaUNqgMbCRSQB3trUV8tYZfWEzqUBKdK1qJpfuNXgKeHOx1jv/IEYTM659ly7zUA==}
+
   nanoid@3.3.11:
     resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
+  node-addon-api@7.1.1:
+    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
   node-releases@2.0.27:
     resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
 
@@ -637,6 +878,10 @@ packages:
   picocolors@1.1.1:
     resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
 
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
   picomatch@4.0.3:
     resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
     engines: {node: '>=12'}
@@ -648,14 +893,48 @@ packages:
   prop-types@15.8.1:
     resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
 
+  rate-limiter-flexible@5.0.5:
+    resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==}
+
+  rc-field-form@1.44.0:
+    resolution: {integrity: sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA==}
+    engines: {node: '>=8.x'}
+    peerDependencies:
+      react: '>=16.9.0'
+      react-dom: '>=16.9.0'
+
+  rc-motion@2.9.5:
+    resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==}
+    peerDependencies:
+      react: '>=16.9.0'
+      react-dom: '>=16.9.0'
+
+  rc-segmented@2.4.1:
+    resolution: {integrity: sha512-KUi+JJFdKnumV9iXlm+BJ00O4NdVBp2TEexLCk6bK1x/RH83TvYKQMzIz/7m3UTRPD08RM/8VG/JNjWgWbd4cw==}
+    peerDependencies:
+      react: '>=16.0.0'
+      react-dom: '>=16.0.0'
+
+  rc-util@5.44.4:
+    resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==}
+    peerDependencies:
+      react: '>=16.9.0'
+      react-dom: '>=16.9.0'
+
   react-dom@19.2.0:
     resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
     peerDependencies:
       react: ^19.2.0
 
+  react-fast-compare@3.2.2:
+    resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
+
   react-is@16.13.1:
     resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
 
+  react-is@18.3.1:
+    resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
   react-refresh@0.18.0:
     resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
     engines: {node: '>=0.10.0'}
@@ -689,6 +968,10 @@ packages:
     resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
     engines: {node: '>=0.10.0'}
 
+  readdirp@4.1.2:
+    resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+    engines: {node: '>= 14.18.0'}
+
   recharts-scale@0.4.5:
     resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
 
@@ -699,14 +982,29 @@ packages:
       react: ^16.0.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
 
+  resize-observer-polyfill@1.5.1:
+    resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
+
   rollup@4.52.5:
     resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
+  runes2@1.1.4:
+    resolution: {integrity: sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==}
+
+  sass@1.93.3:
+    resolution: {integrity: sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
   scheduler@0.27.0:
     resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
 
+  screenfull@5.2.0:
+    resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
+    engines: {node: '>=0.10.0'}
+
   semver@6.3.1:
     resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
     hasBin: true
@@ -715,6 +1013,11 @@ packages:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}
 
+  staged-components@1.1.3:
+    resolution: {integrity: sha512-9EIswzDqjwlEu+ymkV09TTlJfzSbKgEnNteUnZSTxkpMgr5Wx2CzzA9WcMFWBNCldqVPsHVnRGGrApduq2Se5A==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
   tiny-invariant@1.3.3:
     resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
 
@@ -722,6 +1025,13 @@ packages:
     resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
     engines: {node: '>=12.0.0'}
 
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
+  tslib@2.8.1:
+    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
   typescript@5.8.3:
     resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
     engines: {node: '>=14.17'}
@@ -736,6 +1046,11 @@ packages:
     peerDependencies:
       browserslist: '>= 4.21.0'
 
+  use-sync-external-store@1.6.0:
+    resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
   victory-vendor@36.9.2:
     resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
 
@@ -784,6 +1099,8 @@ packages:
 
 snapshots:
 
+  '@alova/shared@1.3.1': {}
+
   '@babel/code-frame@7.27.1':
     dependencies:
       '@babel/helper-validator-identifier': 7.28.5
@@ -976,6 +1293,17 @@ snapshots:
   '@esbuild/win32-x64@0.25.12':
     optional: true
 
+  '@floating-ui/core@1.7.3':
+    dependencies:
+      '@floating-ui/utils': 0.2.10
+
+  '@floating-ui/dom@1.7.4':
+    dependencies:
+      '@floating-ui/core': 1.7.3
+      '@floating-ui/utils': 0.2.10
+
+  '@floating-ui/utils@0.2.10': {}
+
   '@jridgewell/gen-mapping@0.3.13':
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.5
@@ -995,6 +1323,104 @@ snapshots:
       '@jridgewell/resolve-uri': 3.1.2
       '@jridgewell/sourcemap-codec': 1.5.5
 
+  '@parcel/watcher-android-arm64@2.5.1':
+    optional: true
+
+  '@parcel/watcher-darwin-arm64@2.5.1':
+    optional: true
+
+  '@parcel/watcher-darwin-x64@2.5.1':
+    optional: true
+
+  '@parcel/watcher-freebsd-x64@2.5.1':
+    optional: true
+
+  '@parcel/watcher-linux-arm-glibc@2.5.1':
+    optional: true
+
+  '@parcel/watcher-linux-arm-musl@2.5.1':
+    optional: true
+
+  '@parcel/watcher-linux-arm64-glibc@2.5.1':
+    optional: true
+
+  '@parcel/watcher-linux-arm64-musl@2.5.1':
+    optional: true
+
+  '@parcel/watcher-linux-x64-glibc@2.5.1':
+    optional: true
+
+  '@parcel/watcher-linux-x64-musl@2.5.1':
+    optional: true
+
+  '@parcel/watcher-win32-arm64@2.5.1':
+    optional: true
+
+  '@parcel/watcher-win32-ia32@2.5.1':
+    optional: true
+
+  '@parcel/watcher-win32-x64@2.5.1':
+    optional: true
+
+  '@parcel/watcher@2.5.1':
+    dependencies:
+      detect-libc: 1.0.3
+      is-glob: 4.0.3
+      micromatch: 4.0.8
+      node-addon-api: 7.1.1
+    optionalDependencies:
+      '@parcel/watcher-android-arm64': 2.5.1
+      '@parcel/watcher-darwin-arm64': 2.5.1
+      '@parcel/watcher-darwin-x64': 2.5.1
+      '@parcel/watcher-freebsd-x64': 2.5.1
+      '@parcel/watcher-linux-arm-glibc': 2.5.1
+      '@parcel/watcher-linux-arm-musl': 2.5.1
+      '@parcel/watcher-linux-arm64-glibc': 2.5.1
+      '@parcel/watcher-linux-arm64-musl': 2.5.1
+      '@parcel/watcher-linux-x64-glibc': 2.5.1
+      '@parcel/watcher-linux-x64-musl': 2.5.1
+      '@parcel/watcher-win32-arm64': 2.5.1
+      '@parcel/watcher-win32-ia32': 2.5.1
+      '@parcel/watcher-win32-x64': 2.5.1
+    optional: true
+
+  '@rc-component/mini-decimal@1.1.0':
+    dependencies:
+      '@babel/runtime': 7.28.4
+
+  '@react-spring/animated@9.6.1(react@19.2.0)':
+    dependencies:
+      '@react-spring/shared': 9.6.1(react@19.2.0)
+      '@react-spring/types': 9.6.1
+      react: 19.2.0
+
+  '@react-spring/core@9.6.1(react@19.2.0)':
+    dependencies:
+      '@react-spring/animated': 9.6.1(react@19.2.0)
+      '@react-spring/rafz': 9.6.1
+      '@react-spring/shared': 9.6.1(react@19.2.0)
+      '@react-spring/types': 9.6.1
+      react: 19.2.0
+
+  '@react-spring/rafz@9.6.1': {}
+
+  '@react-spring/shared@9.6.1(react@19.2.0)':
+    dependencies:
+      '@react-spring/rafz': 9.6.1
+      '@react-spring/types': 9.6.1
+      react: 19.2.0
+
+  '@react-spring/types@9.6.1': {}
+
+  '@react-spring/web@9.6.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+    dependencies:
+      '@react-spring/animated': 9.6.1(react@19.2.0)
+      '@react-spring/core': 9.6.1(react@19.2.0)
+      '@react-spring/shared': 9.6.1(react@19.2.0)
+      '@react-spring/types': 9.6.1
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+
   '@remix-run/router@1.23.0': {}
 
   '@rolldown/pluginutils@1.0.0-beta.43': {}
@@ -1112,11 +1538,24 @@ snapshots:
 
   '@types/estree@1.0.8': {}
 
+  '@types/js-cookie@3.0.6': {}
+
   '@types/node@22.19.0':
     dependencies:
       undici-types: 6.21.0
 
-  '@vitejs/plugin-react@5.1.0(vite@6.4.1(@types/node@22.19.0))':
+  '@types/react@19.2.2':
+    dependencies:
+      csstype: 3.1.3
+
+  '@use-gesture/core@10.3.0': {}
+
+  '@use-gesture/react@10.3.0(react@19.2.0)':
+    dependencies:
+      '@use-gesture/core': 10.3.0
+      react: 19.2.0
+
+  '@vitejs/plugin-react@5.1.0(vite@6.4.1(@types/node@22.19.0)(sass@1.93.3))':
     dependencies:
       '@babel/core': 7.28.5
       '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
@@ -1124,12 +1563,68 @@ snapshots:
       '@rolldown/pluginutils': 1.0.0-beta.43
       '@types/babel__core': 7.20.5
       react-refresh: 0.18.0
-      vite: 6.4.1(@types/node@22.19.0)
+      vite: 6.4.1(@types/node@22.19.0)(sass@1.93.3)
     transitivePeerDependencies:
       - supports-color
 
+  ahooks@3.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@babel/runtime': 7.28.4
+      '@types/js-cookie': 3.0.6
+      dayjs: 1.11.19
+      intersection-observer: 0.12.2
+      js-cookie: 3.0.5
+      lodash: 4.17.21
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+      react-fast-compare: 3.2.2
+      resize-observer-polyfill: 1.5.1
+      screenfull: 5.2.0
+      tslib: 2.8.1
+
+  alova@3.3.4:
+    dependencies:
+      '@alova/shared': 1.3.1
+      rate-limiter-flexible: 5.0.5
+
+  antd-mobile-icons@0.3.0: {}
+
+  antd-mobile-v5-count@1.0.1: {}
+
+  antd-mobile@5.41.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@floating-ui/dom': 1.7.4
+      '@rc-component/mini-decimal': 1.1.0
+      '@react-spring/web': 9.6.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      '@use-gesture/react': 10.3.0(react@19.2.0)
+      ahooks: 3.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      antd-mobile-icons: 0.3.0
+      antd-mobile-v5-count: 1.0.1
+      classnames: 2.5.1
+      dayjs: 1.11.19
+      deepmerge: 4.3.1
+      nano-memoize: 3.0.16
+      rc-field-form: 1.44.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      rc-segmented: 2.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      rc-util: 5.44.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+      react-fast-compare: 3.2.2
+      react-is: 18.3.1
+      runes2: 1.1.4
+      staged-components: 1.1.3(react@19.2.0)
+      tslib: 2.8.1
+      use-sync-external-store: 1.6.0(react@19.2.0)
+
+  async-validator@4.2.5: {}
+
   baseline-browser-mapping@2.8.23: {}
 
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+    optional: true
+
   browserslist@4.27.0:
     dependencies:
       baseline-browser-mapping: 2.8.23
@@ -1140,6 +1635,12 @@ snapshots:
 
   caniuse-lite@1.0.30001753: {}
 
+  chokidar@4.0.3:
+    dependencies:
+      readdirp: 4.1.2
+
+  classnames@2.5.1: {}
+
   clsx@2.1.1: {}
 
   convert-source-map@2.0.0: {}
@@ -1184,12 +1685,19 @@ snapshots:
 
   d3-timer@3.0.1: {}
 
+  dayjs@1.11.19: {}
+
   debug@4.4.3:
     dependencies:
       ms: 2.1.3
 
   decimal.js-light@2.5.1: {}
 
+  deepmerge@4.3.1: {}
+
+  detect-libc@1.0.3:
+    optional: true
+
   dom-helpers@5.2.1:
     dependencies:
       '@babel/runtime': 7.28.4
@@ -1236,13 +1744,35 @@ snapshots:
     optionalDependencies:
       picomatch: 4.0.3
 
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+    optional: true
+
   fsevents@2.3.3:
     optional: true
 
   gensync@1.0.0-beta.2: {}
 
+  immutable@5.1.4: {}
+
   internmap@2.0.3: {}
 
+  intersection-observer@0.12.2: {}
+
+  is-extglob@2.1.1:
+    optional: true
+
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+    optional: true
+
+  is-number@7.0.0:
+    optional: true
+
+  js-cookie@3.0.5: {}
+
   js-tokens@4.0.0: {}
 
   jsesc@3.1.0: {}
@@ -1259,16 +1789,30 @@ snapshots:
     dependencies:
       yallist: 3.1.1
 
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
+      picomatch: 2.3.1
+    optional: true
+
   ms@2.1.3: {}
 
+  nano-memoize@3.0.16: {}
+
   nanoid@3.3.11: {}
 
+  node-addon-api@7.1.1:
+    optional: true
+
   node-releases@2.0.27: {}
 
   object-assign@4.1.1: {}
 
   picocolors@1.1.1: {}
 
+  picomatch@2.3.1:
+    optional: true
+
   picomatch@4.0.3: {}
 
   postcss@8.5.6:
@@ -1283,13 +1827,51 @@ snapshots:
       object-assign: 4.1.1
       react-is: 16.13.1
 
+  rate-limiter-flexible@5.0.5: {}
+
+  rc-field-form@1.44.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@babel/runtime': 7.28.4
+      async-validator: 4.2.5
+      rc-util: 5.44.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+
+  rc-motion@2.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@babel/runtime': 7.28.4
+      classnames: 2.5.1
+      rc-util: 5.44.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+
+  rc-segmented@2.4.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@babel/runtime': 7.28.4
+      classnames: 2.5.1
+      rc-motion: 2.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      rc-util: 5.44.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+
+  rc-util@5.44.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+    dependencies:
+      '@babel/runtime': 7.28.4
+      react: 19.2.0
+      react-dom: 19.2.0(react@19.2.0)
+      react-is: 18.3.1
+
   react-dom@19.2.0(react@19.2.0):
     dependencies:
       react: 19.2.0
       scheduler: 0.27.0
 
+  react-fast-compare@3.2.2: {}
+
   react-is@16.13.1: {}
 
+  react-is@18.3.1: {}
+
   react-refresh@0.18.0: {}
 
   react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
@@ -1323,6 +1905,8 @@ snapshots:
 
   react@19.2.0: {}
 
+  readdirp@4.1.2: {}
+
   recharts-scale@0.4.5:
     dependencies:
       decimal.js-light: 2.5.1
@@ -1340,6 +1924,8 @@ snapshots:
       tiny-invariant: 1.3.3
       victory-vendor: 36.9.2
 
+  resize-observer-polyfill@1.5.1: {}
+
   rollup@4.52.5:
     dependencies:
       '@types/estree': 1.0.8
@@ -1368,12 +1954,28 @@ snapshots:
       '@rollup/rollup-win32-x64-msvc': 4.52.5
       fsevents: 2.3.3
 
+  runes2@1.1.4: {}
+
+  sass@1.93.3:
+    dependencies:
+      chokidar: 4.0.3
+      immutable: 5.1.4
+      source-map-js: 1.2.1
+    optionalDependencies:
+      '@parcel/watcher': 2.5.1
+
   scheduler@0.27.0: {}
 
+  screenfull@5.2.0: {}
+
   semver@6.3.1: {}
 
   source-map-js@1.2.1: {}
 
+  staged-components@1.1.3(react@19.2.0):
+    dependencies:
+      react: 19.2.0
+
   tiny-invariant@1.3.3: {}
 
   tinyglobby@0.2.15:
@@ -1381,6 +1983,13 @@ snapshots:
       fdir: 6.5.0(picomatch@4.0.3)
       picomatch: 4.0.3
 
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+    optional: true
+
+  tslib@2.8.1: {}
+
   typescript@5.8.3: {}
 
   undici-types@6.21.0: {}
@@ -1391,6 +2000,10 @@ snapshots:
       escalade: 3.2.0
       picocolors: 1.1.1
 
+  use-sync-external-store@1.6.0(react@19.2.0):
+    dependencies:
+      react: 19.2.0
+
   victory-vendor@36.9.2:
     dependencies:
       '@types/d3-array': 3.2.2
@@ -1408,7 +2021,7 @@ snapshots:
       d3-time: 3.1.0
       d3-timer: 3.0.1
 
-  vite@6.4.1(@types/node@22.19.0):
+  vite@6.4.1(@types/node@22.19.0)(sass@1.93.3):
     dependencies:
       esbuild: 0.25.12
       fdir: 6.5.0(picomatch@4.0.3)
@@ -1419,5 +2032,6 @@ snapshots:
     optionalDependencies:
       '@types/node': 22.19.0
       fsevents: 2.3.3
+      sass: 1.93.3
 
   yallist@3.1.1: {}

+ 0 - 25
routes.tsx

@@ -1,25 +0,0 @@
-import React from 'react';
-import { Routes, Route } from 'react-router-dom';
-import LandingPage from './LandingPage';
-import ContactPage from './ContactPage';
-import EditPage from './EditPage';
-import CloneChatPage from './CloneChatPage';
-import type { ContactData } from './types';
-
-interface AppRoutesProps {
-  contactData: ContactData;
-  onSave: (data: ContactData) => void;
-}
-
-const AppRoutes: React.FC<AppRoutesProps> = ({ contactData, onSave }) => {
-  return (
-    <Routes>
-      <Route path="/" element={<LandingPage />} />
-      <Route path="/contact" element={<ContactPage data={contactData} />} />
-      <Route path="/edit" element={<EditPage initialData={contactData} onSave={onSave} />} />
-      <Route path="/clone-chat" element={<CloneChatPage />} />
-    </Routes>
-  );
-};
-
-export default AppRoutes;

+ 10 - 2
src/App.tsx

@@ -1,6 +1,8 @@
 import React, { useState } from 'react';
-import AppRoutes from './routes';
+import AppRoutes from '@/router/routes';
 import type { ContactData } from './types';
+import { Button } from 'antd-mobile'
+import '@/styles/index.scss'; 
 
 const App: React.FC = () => {
   // Mock data for the contact, now managed by state
@@ -31,12 +33,18 @@ const App: React.FC = () => {
     setContactData(newData);
   };
 
+  const handleClick = ()=> {
+    window.location.href = 'weixin://dl/business/?appid=wx7b649a5c1f27244d&path=pages/index/index&query=phone=18658870618&env_version=trial'
+  }
+
   return (
-    <div className="min-h-screen bg-slate-50 font-sans text-slate-800">
+    <div className="min-h-screen bg-slate-50 font-sans text-slate-800 app">
       <AppRoutes contactData={contactData} onSave={handleSave} />
       <footer className="text-center p-4 text-slate-500 text-sm">
+        <Button onClick={handleClick}>helloworld</Button>
         <p>&copy; 2024 Virtual Identity System. All rights reserved.</p>
       </footer>
+      
     </div>
   );
 };

+ 14 - 0
src/api/index.ts

@@ -0,0 +1,14 @@
+import { createAlova } from 'alova';
+import ReactHook from 'alova/react';
+import adapterFetch from 'alova/fetch';
+
+
+const alovaInstance = createAlova({
+  requestAdapter: adapterFetch(),
+  responded: response => response.json(),
+  statesHook: ReactHook
+});
+
+export {
+  alovaInstance,
+}

+ 35 - 1
src/pages/Landing/LandingPage.tsx

@@ -1,12 +1,46 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 import { useNavigate } from 'react-router-dom';
+import { alovaInstance } from '@/api/index'
+import { useRequest } from 'alova/client';
+import { DotLoading } from 'antd-mobile'
+
+type TItem = {
+    "userId": number,
+    "id": number,
+    "title": string,
+    "body": string
+  }
 
 const LandingPage: React.FC = () => {
   const navigate = useNavigate();
+  const { loading, data, error, send, update } = useRequest(
+    alovaInstance.Get<TItem[]>('https://jsonplaceholder.typicode.com/posts', {
+      cacheFor: 0
+    }),
+    {
+      initialData: [], 
+      immediate: false 
+    }
+  )
+  const test = async ()=> {
+    send()
+    // const response = await alovaInstance.Get('https://jsonplaceholder.typicode.com/posts');
+    // console.log(response,3333)
+  }
+  useEffect(()=> {
+    test()
+  }, [])
+  
+  if (loading) {
+    return <DotLoading />;
+  } else if (error) {
+    return <div>{error.message}</div>;
+  }
 
   return (
     <div className="flex flex-col items-center justify-center min-h-[calc(100vh-60px)]">
       <div className="text-center p-8">
+        <div>{data.map(item => item.title)}</div>
         <h1 className="text-4xl sm:text-5xl font-extrabold text-slate-900 tracking-tight">
           Virtual Identity System
         </h1>

+ 33 - 0
src/router/routes.tsx

@@ -0,0 +1,33 @@
+import React, { Suspense } from 'react';
+import { Routes, Route } from 'react-router-dom';
+import type { ContactData } from '@/types';
+import { DotLoading } from 'antd-mobile'
+
+// 懒加载组件
+const LandingPage = React.lazy(() => import('@/pages/Landing/LandingPage'));
+const ContactPage = React.lazy(() => import('@/pages/Contact/ContactPage'));
+const EditPage = React.lazy(() => import('@/pages/Edit/EditPage'));
+const CloneChatPage = React.lazy(() => import('@/pages/CloneChat/CloneChatPage'));
+
+// 加载中的备用 UI
+const LoadingFallback = () => <div><DotLoading /></div>;
+
+interface AppRoutesProps {
+  contactData: ContactData;
+  onSave: (data: ContactData) => void;
+}
+
+const AppRoutes: React.FC<AppRoutesProps> = ({ contactData, onSave }) => {
+  return (
+    <Suspense fallback={<LoadingFallback />}>
+      <Routes>
+        <Route path="/" element={<LandingPage />} />
+        <Route path="/contact" element={<ContactPage data={contactData} />} />
+        <Route path="/edit" element={<EditPage initialData={contactData} onSave={onSave} />} />
+        <Route path="/clone-chat" element={<CloneChatPage />} />
+      </Routes>
+    </Suspense>
+  );
+};
+
+export default AppRoutes;

+ 0 - 25
src/routes.tsx

@@ -1,25 +0,0 @@
-import React from 'react';
-import { Routes, Route } from 'react-router-dom';
-import LandingPage from './pages/Landing/LandingPage';
-import ContactPage from './pages/Contact/ContactPage';
-import EditPage from './pages/Edit/EditPage';
-import CloneChatPage from './pages/CloneChat/CloneChatPage';
-import type { ContactData } from './types';
-
-interface AppRoutesProps {
-  contactData: ContactData;
-  onSave: (data: ContactData) => void;
-}
-
-const AppRoutes: React.FC<AppRoutesProps> = ({ contactData, onSave }) => {
-  return (
-    <Routes>
-      <Route path="/" element={<LandingPage />} />
-      <Route path="/contact" element={<ContactPage data={contactData} />} />
-      <Route path="/edit" element={<EditPage initialData={contactData} onSave={onSave} />} />
-      <Route path="/clone-chat" element={<CloneChatPage />} />
-    </Routes>
-  );
-};
-
-export default AppRoutes;

+ 5 - 0
src/styles/index.scss

@@ -0,0 +1,5 @@
+@forward 'variables';
+
+.app{
+   background-color: $color-primary;
+}

+ 6 - 0
src/styles/variables.scss

@@ -0,0 +1,6 @@
+$color-white: #ffffff;
+$color-black: #000000;
+$color-primary: #006fff;
+$color-success: #03c988;
+$color-warning: #ff983f;
+$color-danger: #ff3d3d;

+ 1 - 3
tsconfig.json

@@ -19,9 +19,7 @@
     "allowJs": true,
     "jsx": "react-jsx",
     "paths": {
-      "@/*": [
-        "./*"
-      ]
+      "@/*": ["./src/*"]
     },
     "allowImportingTsExtensions": true,
     "noEmit": true

+ 25 - 3
vite.config.ts

@@ -16,8 +16,30 @@ export default defineConfig(({ mode }) => {
       },
       resolve: {
         alias: {
-          '@': path.resolve(__dirname, '.'),
+          // map @ to the src directory so imports like '@/router/routes' resolve correctly
+          '@': path.resolve(__dirname, 'src'),
         }
-      }
-    };
+      },
+      css: {
+        /**
+         * 如果启用了这个选项,那么 CSS 预处理器会尽可能在 worker 线程中运行;即通过多线程运行 CSS 预处理器,从而极大提高其处理速度
+         * https://cn.vitejs.dev/config/shared-options#css-preprocessormaxworkers
+         */
+        preprocessorMaxWorkers: true,
+        /**
+         * 建议只用来嵌入 SCSS 的变量声明文件,嵌入后全局可用
+         * 该选项可以用来为每一段样式内容添加额外的代码。但是要注意,如果你添加的是实际的样式而不仅仅是变量,那这些样式在最终的产物中会重复
+         * https://cn.vitejs.dev/config/shared-options.html#css-preprocessoroptions-extension-additionaldata
+         */
+        preprocessorOptions: {
+          scss: {
+            // 如果您的终端提示 legacy JS API Deprecation Warning, 您可以配置以下代码在 vite.config.ts 中
+            // 使用现代 CSS API,避免 legacy warning
+            api: 'modern-compiler',
+            // 全局引入变量文件,使用路径别名 @ 表示 src 目录
+            additionalData: `@use "@/styles/variables.scss" as *;`,
+          },
+        },
+      },
+  };
 });