Browse Source

1.文件优化
2.taiwind css改为本地

chenhg 1 month ago
parent
commit
71285d5709

+ 0 - 42
index.html

@@ -4,48 +4,6 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>长江行游轮 | Vista Cruises</title>
-    <script src="https://cdn.tailwindcss.com"></script>
-    <script>
-      tailwind.config = {
-        theme: {
-          extend: {
-            fontFamily: {
-              serif: ['Times New Roman', 'Times', 'serif'],
-              sans: ['Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],
-            },
-            colors: {
-              vista: {
-                black: '#1a1a1a',
-                gold: '#c5a059',
-                goldlight: '#e0c080',
-                gray: '#F9F8F6', 
-                darkblue: '#0a1f35',
-                teal: '#009ca6'
-              }
-            },
-            animation: {
-              'fade-in-up': 'fadeInUp 1s ease-out',
-              'slow-zoom': 'slowZoom 20s linear infinite alternate',
-              'slide-in-right': 'slideInRight 0.3s ease-out'
-            },
-            keyframes: {
-              fadeInUp: {
-                '0%': { opacity: '0', transform: 'translateY(20px)' },
-                '100%': { opacity: '1', transform: 'translateY(0)' },
-              },
-              slowZoom: {
-                '0%': { transform: 'scale(1)' },
-                '100%': { transform: 'scale(1.1)' },
-              },
-              slideInRight: {
-                '0%': { transform: 'translateX(100%)' },
-                '100%': { transform: 'translateX(0)' },
-              }
-            }
-          }
-        }
-      }
-    </script>
     <style>
       /* Hide scrollbar for Chrome, Safari and Opera */
       .no-scrollbar::-webkit-scrollbar {

File diff suppressed because it is too large
+ 2095 - 63
package-lock.json


+ 5 - 0
package.json

@@ -9,6 +9,7 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@tailwindcss/vite": "^0.0.0-experimental",
     "@types/qs": "^6.14.0",
     "axios": "^1.13.2",
     "lucide-react": "0.292.0",
@@ -18,8 +19,12 @@
     "react-router-dom": "6.28.0"
   },
   "devDependencies": {
+    "@tailwindcss/postcss": "^4.1.18",
     "@types/node": "^22.14.0",
     "@vitejs/plugin-react": "^5.0.0",
+    "autoprefixer": "^10.4.23",
+    "postcss": "^8.5.6",
+    "tailwindcss": "^3.4.19",
     "typescript": "~5.8.2",
     "vite": "^6.2.0"
   }

+ 6 - 0
postcss.config.js

@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}

+ 1 - 0
src/App.tsx

@@ -8,6 +8,7 @@ import AboutUs from '@/src/pages/AboutUs.tsx';
 import Guide from '@/src/pages/Guide.tsx';
 import { LanguageProvider } from '@/src/contexts/LanguageContext.tsx';
 import { ThemeProvider } from '@/src/contexts/ThemeContext.tsx';
+import './styles.css';
 
 const App: React.FC = () => {
   return (

+ 3 - 3
src/components/FAQItem.tsx

@@ -7,11 +7,11 @@ export interface FAQItemProps {
 
 const FAQItem: React.FC<FAQItemProps> = ({ question, answer }) => {
   return (
-    <div className="bg-white rounded-lg shadow-xl p-6">
-      <h3 className="text-xl font-serif text-vista-darkblue mb-3">
+    <div className="bg-vista-gold rounded-lg shadow-xl p-6">
+      <h3 className="text-xl font-serif text-vista-black mb-3">
         {question}
       </h3>
-      <p className="text-vista-darkblue/70">
+      <p className="text-vista-black/70">
         {answer}
       </p>
     </div>

+ 1 - 1
src/components/Sidebar.tsx

@@ -1,5 +1,5 @@
 import React, { useState } from 'react';
-import { Phone, ArrowUp, Send, Settings } from 'lucide-react';
+import { Phone, ArrowUp, Settings } from 'lucide-react';
 import ThemeSettings from './ThemeSettings.tsx';
 import MiniProgramQRCode from '@/src/assets/QRPicture/wechat-QR.png';
 import TiktokQRCode from '@/src/assets/QRPicture/Tiktok-QR.png';

+ 1 - 1
src/components/VistaLogo.tsx

@@ -14,7 +14,7 @@ const VistaLogo: React.FC<VistaLogoProps> = ({ theme = 'dark', className = '', c
   // Default logic if color is not provided:
   // light theme (dark bg) -> white text
   // dark theme (light bg) -> gold text
-  const defaultColor = theme === 'light' ? '#ffffff' : '#c5a059';
+  const defaultColor = theme === 'light' ? '#ffffff' : '#b8882a';
 
   // Use explicit color if provided, otherwise fallback to theme logic
   const brandColor = color || defaultColor;

+ 29 - 0
src/contexts/ThemeContext.tsx

@@ -142,6 +142,35 @@ export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) =
     loadVideoContentFromApi();
   }, []); // Empty dependency array means this runs on every component mount (page refresh)
 
+  // Fetch cruise routes from API on every page refresh
+  useEffect(() => {
+    const loadCruiseRoutesFromApi = async () => {
+      try {
+        // Import the functions dynamically to avoid circular dependencies
+        const { fetchCruiseRoutes, extractCruiseRoutesFromArticles } = 
+            await import('../services/articleService.ts');
+
+        // Fetch cruise routes from the API (lang=1 for Chinese)
+        const cruiseRoutesData = await fetchCruiseRoutes(1);
+        
+        // Extract cruise routes from the articles data
+        const cruiseRoutes = extractCruiseRoutesFromArticles(cruiseRoutesData);
+        
+        // If we got cruise routes from the API, use them
+        if (cruiseRoutes.length > 0) {
+          setItineraries(cruiseRoutes);
+        }
+        // Otherwise, keep the default itineraries
+      } catch (error) {
+        console.error('Failed to fetch cruise routes from API:', error);
+        // Fallback to default itineraries if API fails
+        setItineraries(DEFAULT_ITINERARIES);
+      }
+    };
+
+    loadCruiseRoutesFromApi();
+  }, []); // Empty dependency array means this runs on every component mount (page refresh)
+
   // --- Content Sections ---
   const [itineraries, setItineraries] = useState<Itinerary[]>(() => {
     const saved = localStorage.getItem('vista_itineraries');

+ 0 - 1
src/pages/AboutUs.tsx

@@ -19,7 +19,6 @@ const AboutUs: React.FC = () => {
   const { language } = useLanguage();
   const { aboutHeroImage } = useTheme();
   const location = useLocation();
-  const t = CONTENT[language];
 
   useEffect(() => {
     const params = new URLSearchParams(location.search);

+ 103 - 0
src/services/articleService.ts

@@ -152,6 +152,39 @@ export const fetchVideoContent = async (lang: number): Promise<ApiResponse> => {
   }
 };
 
+// 获取游轮航线数据
+export const fetchCruiseRoutes = async (lang: number = 1): Promise<ApiResponse> => {
+  try {
+    const baseUrl = import.meta.env.VITE_SHIP_API_BASE_URL || 'http://localhost:48080';
+    const apiPath = import.meta.env.VITE_SHIP_API_PATH || '/ship-api';
+    const url = `${baseUrl}${apiPath}/cms/article/portal/page-list`;
+    const params = {
+      lang,
+      'type.id': '1955559122615775234'
+    };
+    
+    // 生成缓存键
+    const cacheKey = generateCacheKey(url, params);
+    
+    // 检查缓存
+    const cachedData = getCachedData(cacheKey);
+    if (cachedData) {
+      return cachedData;
+    }
+    
+    // 发起API请求
+    const response = await axios.get<ApiResponse>(url, { params });
+    
+    // 缓存响应数据
+    setCachedData(cacheKey, response.data);
+    
+    return response.data;
+  } catch (error) {
+    console.error('Failed to fetch cruise routes:', error);
+    throw error;
+  }
+};
+
 // 从文章数据中提取视频内容
 export const extractVideoContentFromArticles = (articlesData: ApiResponse): {
   title: string;
@@ -177,3 +210,73 @@ export const extractVideoContentFromArticles = (articlesData: ApiResponse): {
     video: videoArticle.video || undefined
   };
 };
+
+// 定义subContent条目的接口
+interface SubContentItem {
+  tid: number;
+  title: string;
+  content: string;
+}
+
+// 解析subContent字段
+export const parseSubContent = (subContent: string | null): Record<string, string> => {
+  if (!subContent) {
+    return {};
+  }
+  
+  try {
+    const parsedItems: SubContentItem[] = JSON.parse(subContent);
+    const result: Record<string, string> = {};
+    
+    // 从每个条目提取内容(去除HTML标签)
+    parsedItems.forEach(item => {
+      // 去除HTML标签并保留文本内容
+      const textContent = item.content.replace(/<[^>]*>/g, '').trim();
+      result[item.title] = textContent;
+    });
+    
+    return result;
+  } catch (error) {
+    console.error('Failed to parse subContent:', error);
+    return {};
+  }
+};
+
+// 从文章数据中提取游轮航线信息
+export const extractCruiseRoutesFromArticles = (articlesData: ApiResponse): any[] => {
+  // 检查API响应是否成功
+  if (articlesData.code !== 0 || !articlesData.data || !articlesData.data.list) {
+    return [];
+  }
+
+  // 转换文章数据为游轮航线格式
+  return articlesData.data.list.map(article => {
+    // 解析subContent获取详细信息
+    const subContentData = parseSubContent(article.subContent);
+    
+    // 提取天数信息(从title或summary中尝试提取)
+    let days = 0;
+    const daysMatch = article.title.match(/\d+天/) || article.summary.match(/\d+天/);
+    if (daysMatch) {
+      days = parseInt(daysMatch[0]);
+    }
+    
+    // 构建游轮航线对象
+    return {
+      id: article.id,
+      title: article.title || '',
+      days,
+      price: subContentData['价格'] || '价格待定',
+      image: article.coverUrl || '',
+      video: article.video || undefined,
+      route: subContentData['目的地'] || '',
+      description: article.summary || '',
+      highlights: [
+        // 从summary中提取亮点,或者使用默认值
+        ...(article.summary ? [article.summary] : [])
+      ],
+      itinerary: [], // 默认空行程,可能需要从content字段进一步解析
+      schedule: subContentData['航期'] || ''
+    };
+  });
+};

+ 3 - 0
src/styles.css

@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;

+ 45 - 0
tailwind.config.js

@@ -0,0 +1,45 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+  content: [
+    "./index.html",
+    "./src/**/*.{js,ts,jsx,tsx}",
+  ],
+  theme: {
+    extend: {
+      fontFamily: {
+        serif: ['Times New Roman', 'Times', 'serif'],
+        sans: ['Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],
+      },
+      colors: {
+        vista: {
+          black: '#1a1a1a',
+          gold: '#c5a059',
+          goldlight: '#e0c080',
+          gray: '#F9F8F6', 
+          darkblue: '#0a1f35',
+          teal: '#009ca6'
+        }
+      },
+      animation: {
+        'fade-in-up': 'fadeInUp 1s ease-out',
+        'slow-zoom': 'slowZoom 20s linear infinite alternate',
+        'slide-in-right': 'slideInRight 0.3s ease-out'
+      },
+      keyframes: {
+        fadeInUp: {
+          '0%': { opacity: '0', transform: 'translateY(20px)' },
+          '100%': { opacity: '1', transform: 'translateY(0)' },
+        },
+        slowZoom: {
+          '0%': { transform: 'scale(1)' },
+          '100%': { transform: 'scale(1.1)' },
+        },
+        slideInRight: {
+          '0%': { transform: 'translateX(100%)' },
+          '100%': { transform: 'translateX(0)' },
+        }
+      }
+    },
+  },
+  plugins: [],
+}

+ 2 - 1
vite.config.ts

@@ -1,6 +1,7 @@
 import path from 'path';
 import { defineConfig, loadEnv } from 'vite';
 import react from '@vitejs/plugin-react';
+import tailwind from '@tailwindcss/vite';
 
 export default defineConfig(({ mode }) => {
     const env = loadEnv(mode, '.', '');
@@ -9,7 +10,7 @@ export default defineConfig(({ mode }) => {
         port: 3000,
         host: '0.0.0.0',
       },
-      plugins: [react()],
+      plugins: [react(), tailwind()],
       define: {
         'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
         'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)