Преглед на файлове

英文显示bug修复、优化代码

chenhg преди 1 месец
родител
ревизия
ac36b74346
променени са 5 файла, в които са добавени 77 реда и са изтрити 60 реда
  1. 10 1
      src/components/Navbar.tsx
  2. 9 4
      src/components/Sidebar.tsx
  3. 15 15
      src/constants.ts
  4. 37 40
      src/pages/CruiseExperience.tsx
  5. 6 0
      src/types.ts

+ 10 - 1
src/components/Navbar.tsx

@@ -232,13 +232,16 @@ const Navbar: React.FC = () => {
                   onClick={() => setSearchOpen(true)} 
                   className={`transition-colors hover:text-vista-gold ${scrolled ? 'text-vista-darkblue' : 'text-white'}`}
                   aria-label="Search"
+                  type="button"
                >
                   <Search size={20} />
                </button>
 
                <button 
                   onClick={toggleLanguage} 
-                  className={`text-xs font-bold uppercase tracking-widest transition-colors ${scrolled ? 'text-vista-darkblue hover:text-vista-gold' : 'text-white hover:text-vista-gold'}`}
+                  className={`text-xs font-bold uppercase tracking-widest ${scrolled ? 'text-vista-darkblue hover:text-vista-gold' : 'text-white hover:text-vista-gold'}`}
+                  type="button"
+                  aria-label={`Switch to ${language === 'zh' ? 'English' : 'Chinese'}`}
                >
                   {language === 'zh' ? 'EN' : '中文'}
                </button>
@@ -274,6 +277,9 @@ const Navbar: React.FC = () => {
             <button 
               onClick={() => setMobileMenuOpen(true)} 
               className={`transition-colors ${scrolled ? 'text-vista-darkblue' : 'text-white'}`}
+              type="button"
+              aria-label="Open menu"
+              aria-expanded={mobileMenuOpen}
             >
               <Menu size={28} />
             </button>
@@ -301,6 +307,9 @@ const Navbar: React.FC = () => {
               <button 
                 onClick={() => setMobileMenuOpen(false)}
                 className="text-white hover:text-vista-gold transition-colors"
+                type="button"
+                aria-label="Close menu"
+                aria-expanded={mobileMenuOpen}
               >
                   <X size={28} />
               </button>

+ 9 - 4
src/components/Sidebar.tsx

@@ -23,7 +23,7 @@ const Sidebar: React.FC = () => {
           <div className="absolute right-12 bg-vista-darkblue text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none">
             24*7 预定电话:400-696-0666
           </div>
-          <button className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10">
+          <button className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10" type="button" aria-label="Contact customer service">
             <Phone size={20} />
           </button>
         </div>
@@ -44,7 +44,7 @@ const Sidebar: React.FC = () => {
 
           </div>
           <button
-              className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10">
+              className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10" type="button" aria-label="WeChat">
             <img
               src={WeChatIcon as string}
               alt="微信"
@@ -70,7 +70,7 @@ const Sidebar: React.FC = () => {
 
           </div>
           <button
-              className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10">
+              className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10" type="button" aria-label="Red Book">
             <img
               src={RedBook as string}
               alt="小红书"
@@ -95,7 +95,7 @@ const Sidebar: React.FC = () => {
             </div>
           </div>
           <button
-              className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10">
+              className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10" type="button" aria-label="TikTok">
             <img
                 src={TiktokIcon as string}
                 alt="抖音"
@@ -114,6 +114,9 @@ const Sidebar: React.FC = () => {
           <button
             onClick={() => setSettingsOpen(true)}
             className="w-12 h-12 bg-white rounded-full shadow-lg flex items-center justify-center text-vista-darkblue hover:bg-vista-gold hover:text-white transition-colors border border-vista-darkblue/10"
+            type="button"
+            aria-label="Customization settings"
+            aria-expanded={settingsOpen}
           >
             <Settings size={20} />
           </button>
@@ -123,6 +126,8 @@ const Sidebar: React.FC = () => {
         <button
           onClick={scrollToTop}
           className="w-12 h-12 bg-vista-darkblue text-white rounded-full shadow-lg flex items-center justify-center hover:bg-vista-gold transition-colors"
+          type="button"
+          aria-label="Back to top"
         >
           <ArrowUp size={20} />
         </button>

+ 15 - 15
src/constants.ts

@@ -507,22 +507,22 @@ export const CONTENT = {
             { title: "Schedules", link: "#/itineraries?section=schedules" },
           ]
         },
-        { 
-          title: "EXPERIENCE", 
-          link: "#experience",
+        {
+          title: "EXPERIENCE",
+          link: "#/experience",
           submenu: [
-            { title: "Service", link: "#exp-service" },
-            { title: "Dining", link: "#exp-dining" },
-            { title: "Activities", link: "#exp-activities" },
+            { title: "Service", link: "#/experience?section=service" },
+            { title: "Dining", link: "#/experience?section=dining" },
+            { title: "Activities", link: "#/experience?section=activities" },
           ]
         },
-        { 
-          title: "SPACES", 
-          link: "#spaces",
+        {
+          title: "SPACES",
+          link: "#/spaces",
           submenu: [
-            { title: "Facilities", link: "#space-facilities" },
-            { title: "Suites", link: "#space-rooms" },
-            { title: "Privileges", link: "#space-vip" },
+            { title: "Facilities", link: "#/spaces?section=facilities" },
+            { title: "Suites", link: "#/spaces?section=rooms" },
+            { title: "Privileges", link: "#/spaces?section=vip" },
           ]
         },
         { 
@@ -880,7 +880,7 @@ export const CONTENT = {
         id: "1",
         title: "Three Gorges Discovery",
         days: 4,
-        price: "Fr. ¥3,999",
+        price: "¥3,999",
         image: "https://picsum.photos/600/400?random=10",
         route: "Chongqing - Yichang"
       },
@@ -888,7 +888,7 @@ export const CONTENT = {
         id: "2",
         title: "Yangtze Heritage",
         days: 5,
-        price: "Fr. ¥4,599",
+        price: "¥4,599",
         image: "https://picsum.photos/600/400?random=11",
         route: "Yichang - Chongqing"
       },
@@ -896,7 +896,7 @@ export const CONTENT = {
         id: "3",
         title: "Grand River Voyage",
         days: 8,
-        price: "Fr. ¥8,888",
+        price: "¥8,888",
         image: "https://picsum.photos/600/400?random=12",
         route: "Chongqing - Shanghai"
       }

+ 37 - 40
src/pages/CruiseExperience.tsx

@@ -4,44 +4,41 @@ import Sidebar from '@/src/components/Sidebar.tsx';
 import Footer from '@/src/components/Footer.tsx';
 import { CONTENT } from '../constants.ts';
 import { useLanguage } from '@/src/contexts/LanguageContext.tsx';
-import { useTheme } from '@/src/contexts/ThemeContext.tsx';
 import { useLocation } from 'react-router-dom';
 import { useEffect } from 'react';
+import { ExperienceItem } from '../types';
 
 const CruiseExperience: React.FC = () => {
   const { language } = useLanguage();
-  const { itineraries } = useTheme();
   const location = useLocation();
   
   const t = CONTENT[language];
   
-  // Get section from query parameters
-  const section = new URLSearchParams(location.search).get('section');
-  
   // Scroll to section when location changes with better timing
   useEffect(() => {
     const params = new URLSearchParams(location.search);
     const section = params.get('section');
     
-    // Always scroll to top first if no section is specified
-    if (!section) {
-      window.scrollTo({ top: 0, behavior: 'smooth' });
-    } else {
-      // Use setTimeout to ensure DOM is fully rendered before attempting scroll
-      const timeoutId = setTimeout(() => {
+    const scrollToElement = () => {
+      // Always scroll to top first if no section is specified
+      if (!section) {
+        window.scrollTo({ top: 0, behavior: 'smooth' });
+      } else {
         const element = document.getElementById(`experience-${section}`);
         if (element) {
           element.scrollIntoView({ 
             behavior: 'smooth', 
-            block: 'start',
-            // inline: 'nearest'
+            block: 'start'
           });
         }
-      }, 300);
-      
-      // Cleanup timeout to prevent memory leaks
-      return () => clearTimeout(timeoutId);
-    }
+      }
+    };
+    
+    // Use requestAnimationFrame to ensure DOM is fully rendered
+    const animationFrameId = requestAnimationFrame(scrollToElement);
+    
+    // Cleanup animation frame to prevent memory leaks
+    return () => cancelAnimationFrame(animationFrameId);
   }, [location.search]);
 
   return (
@@ -52,15 +49,15 @@ const CruiseExperience: React.FC = () => {
       {/* Hero Section */}
       <section className="relative h-[60vh] md:h-[70vh] w-full overflow-hidden bg-vista-darkblue">
         <div className="absolute inset-0 bg-gradient-to-br from-vista-darkblue/95 to-vista-darkblue/60 z-10"></div>
-        <div className="absolute inset-0 bg-cover bg-center opacity-40" style={{ backgroundImage: `url(https://picsum.photos/1920/1080?random=25)` }}></div>
-        <div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNMTAgMTBoMjB2MjBIMTB6TTMwIDEwaDIwdjIwSDMweiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjAuNSIvPjwvcGF0dGVybj48L3N2Zz4=')] opacity-10 z-20"></div>
+        <div className="absolute inset-0 bg-cover bg-center opacity-40" style={{ backgroundImage: `url(https://picsum.photos/1920/1080?random=25)` }} aria-hidden="true"></div>
+        <div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXR0ZXJuIGlkPSJwYXR0ZXJuIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNMTAgMTBoMjB2MjBIMTB6TTMwIDEwaDIwdjIwSDMweiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjAuNSIvPjwvcGF0dGVybj48L3N2Zz4=')] opacity-10 z-20" aria-hidden="true"></div>
         <div className="relative z-30 h-full flex flex-col justify-center items-center text-center px-4 py-12">
           <span className="text-vista-gold uppercase tracking-widest text-xs sm:text-sm font-bold mb-4">{t.nav.menu[3]?.title || 'CRUISE EXPERIENCE'}</span>
           <h1 className="text-3xl sm:text-4xl md:text-5xl lg:text-7xl font-serif text-white mb-4 sm:mb-6 leading-tight">{t.nav.menu[3]?.title || 'Cruise Experience'}</h1>
           <p className="text-white/90 max-w-2xl sm:max-w-3xl text-base sm:text-lg md:text-xl mb-6 sm:mb-8 leading-relaxed">{language === 'zh' ? '享受豪华游轮带来的全方位优质体验' : 'Enjoy the comprehensive premium experience of luxury cruises'}</p>
-          <a href="#experience-service" className="px-8 sm:px-10 py-3 sm:py-4 bg-vista-gold text-white font-bold tracking-widest uppercase text-xs sm:text-sm hover:bg-opacity-90 transition-all duration-300 shadow-lg hover:shadow-xl">
-            {t.nav.book}
-          </a>
+          <a href="#experience-service" className="px-8 sm:px-10 py-3 sm:py-4 bg-vista-gold text-white font-bold tracking-widest uppercase text-xs sm:text-sm hover:bg-opacity-90 transition-all duration-300 shadow-lg hover:shadow-xl" aria-label={`${t.nav.book} - ${t.shipsPage.experience.service.title}`}>
+          {t.nav.book}
+        </a>
         </div>
       </section>
 
@@ -78,13 +75,13 @@ const CruiseExperience: React.FC = () => {
             </p>
           </div>
           
-          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
-            {t.shipsPage.experience.service.items.map((item: any, index: number) => (
-              <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2">
-                <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6">
+          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10" role="list">
+            {t.shipsPage.experience.service.items.map((item: ExperienceItem, index: number) => (
+              <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2" role="listitem" aria-labelledby={`service-item-title-${index}`}>
+                <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6" aria-hidden="true">
                   <span className="text-white text-2xl font-bold">{index + 1}</span>
                 </div>
-                <h3 className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
+                <h3 id={`service-item-title-${index}`} className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
                 <p className="text-vista-darkblue/70 leading-relaxed">
                   {item.desc}
                 </p>
@@ -108,14 +105,14 @@ const CruiseExperience: React.FC = () => {
             </p>
           </div>
           
-          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
-            {t.shipsPage.experience.dining.items.map((item: any, index: number) => (
-              <div key={index} className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2">
-                <div className="h-48 bg-gradient-to-br from-vista-darkblue to-vista-gold flex items-center justify-center">
+          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10" role="list">
+            {t.shipsPage.experience.dining.items.map((item: ExperienceItem, index: number) => (
+              <div key={index} className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-all duration-300 transform hover:-translate-y-2" role="listitem" aria-labelledby={`dining-item-title-${index}`}>
+                <div className="h-48 bg-gradient-to-br from-vista-darkblue to-vista-gold flex items-center justify-center" aria-hidden="true">
                   <span className="text-white text-4xl">🍽️</span>
                 </div>
                 <div className="p-8">
-                  <h3 className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
+                  <h3 id={`dining-item-title-${index}`} className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
                   <p className="text-vista-darkblue/70 leading-relaxed">
                     {item.desc}
                   </p>
@@ -140,13 +137,13 @@ const CruiseExperience: React.FC = () => {
             </p>
           </div>
           
-          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
-            {t.shipsPage.experience.activities.items.map((item: any, index: number) => (
-              <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2">
-                <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6">
-                  <span className="text-white text-2xl"></span>
+          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10" role="list">
+            {t.shipsPage.experience.activities.items.map((item: ExperienceItem, index: number) => (
+              <div key={index} className="bg-white rounded-lg p-8 shadow-md hover:shadow-xl transition-all duration-300 border border-gray-100 hover:border-vista-gold transform hover:-translate-y-2" role="listitem" aria-labelledby={`activity-item-title-${index}`}>
+                <div className="w-16 h-16 bg-vista-gold rounded-full flex items-center justify-center mb-6" aria-hidden="true">
+                  <span className="text-white text-2xl"></span>
                 </div>
-                <h3 className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
+                <h3 id={`activity-item-title-${index}`} className="text-2xl font-serif text-vista-darkblue mb-4 group-hover:text-vista-gold transition-colors">{item.title}</h3>
                 <p className="text-vista-darkblue/70 leading-relaxed">
                   {item.desc}
                 </p>
@@ -161,4 +158,4 @@ const CruiseExperience: React.FC = () => {
   );
 };
 
-export default CruiseExperience;
+export default React.memo(CruiseExperience);

+ 6 - 0
src/types.ts

@@ -63,4 +63,10 @@ export interface ShipsPageImages {
   aurora: string;
 }
 
+// Interface for Experience Items
+export interface ExperienceItem {
+  title: string;
+  desc: string;
+}
+
 export type Language = 'zh' | 'en';