Commit 57d488f2 authored by fisherdaddy's avatar fisherdaddy

chore: update

parent c717bfff
...@@ -3,9 +3,11 @@ import '../styles/Timeline.css'; // 复用已有的Timeline样式 ...@@ -3,9 +3,11 @@ import '../styles/Timeline.css'; // 复用已有的Timeline样式
import events from '../data/anthropic-releases.json'; import events from '../data/anthropic-releases.json';
import SEO from '../components/SEO'; import SEO from '../components/SEO';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import { useScrollToTop } from '../hooks/useScrollToTop';
const AnthropicTimeline = () => { const AnthropicTimeline = () => {
const { t } = useTranslation(); const { t } = useTranslation();
useScrollToTop();
return ( return (
<> <>
......
...@@ -3,6 +3,7 @@ import { removeBackground } from "@imgly/background-removal"; ...@@ -3,6 +3,7 @@ import { removeBackground } from "@imgly/background-removal";
import styled from 'styled-components'; import styled from 'styled-components';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from './SEO'; import SEO from './SEO';
import { useScrollToTop } from '../hooks/useScrollToTop';
// Reuse container style // Reuse container style
const Container = styled.div` const Container = styled.div`
...@@ -157,6 +158,7 @@ const PrivacyNote = styled.div` ...@@ -157,6 +158,7 @@ const PrivacyNote = styled.div`
`; `;
function BackgroundRemover() { function BackgroundRemover() {
useScrollToTop();
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedImage, setSelectedImage] = useState(null); const [selectedImage, setSelectedImage] = useState(null);
const [removedBgImage, setRemovedBgImage] = useState(null); const [removedBgImage, setRemovedBgImage] = useState(null);
......
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
import '../styles/HandwriteGen.css'; import '../styles/HandwriteGen.css';
import html2canvas from 'html2canvas'; import html2canvas from 'html2canvas';
import { import {
...@@ -16,6 +18,7 @@ const { Option } = Select; ...@@ -16,6 +18,7 @@ const { Option } = Select;
const { Title } = Typography; const { Title } = Typography;
function HandwritingGenerator() { function HandwritingGenerator() {
const isLoading = usePageLoading();
const [text, setText] = useState(''); const [text, setText] = useState('');
const [font, setFont] = useState("'XINYE'"); const [font, setFont] = useState("'XINYE'");
const [paperType, setPaperType] = useState('Lined Paper'); // 默认值为横线纸 const [paperType, setPaperType] = useState('Lined Paper'); // 默认值为横线纸
...@@ -30,6 +33,15 @@ function HandwritingGenerator() { ...@@ -30,6 +33,15 @@ function HandwritingGenerator() {
const [lineSpacing, setLineSpacing] = useState(1.25); const [lineSpacing, setLineSpacing] = useState(1.25);
const [charSpacing, setCharSpacing] = useState(0); const [charSpacing, setCharSpacing] = useState(0);
useEffect(() => {
// Clear any loading states from previous navigation
const clearLoadingState = () => {
const event = new CustomEvent('clearLoadingState');
window.dispatchEvent(event);
};
clearLoadingState();
}, []);
const handleGenerate = () => { const handleGenerate = () => {
const previewElement = document.querySelector('.preview-area'); const previewElement = document.querySelector('.preview-area');
html2canvas(previewElement, { html2canvas(previewElement, {
...@@ -145,8 +157,9 @@ function HandwritingGenerator() { ...@@ -145,8 +157,9 @@ function HandwritingGenerator() {
const backgroundOffset = -(lineSpacing * fontSize - fontSize); const backgroundOffset = -(lineSpacing * fontSize - fontSize);
return ( return (
<div className="handwrite-container" style={{ paddingTop: '4rem' }}> <>
<Layout> {isLoading && <LoadingOverlay />}
<Layout className="min-h-screen pt-20">
<Sider width={300} className="site-layout-background"> <Sider width={300} className="site-layout-background">
<div className="settings-section"> <div className="settings-section">
<h2 className="title-label">手写字体生成器</h2> <h2 className="title-label">手写字体生成器</h2>
...@@ -314,7 +327,7 @@ function HandwritingGenerator() { ...@@ -314,7 +327,7 @@ function HandwritingGenerator() {
</Content> </Content>
</Layout> </Layout>
</Layout> </Layout>
</div> </>
); );
} }
......
This diff is collapsed.
...@@ -3,6 +3,8 @@ import styled from 'styled-components'; ...@@ -3,6 +3,8 @@ import styled from 'styled-components';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from './SEO'; import SEO from './SEO';
import imageCompression from 'browser-image-compression'; import imageCompression from 'browser-image-compression';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 复用 MarkdownToImage 的容器样式 // 复用 MarkdownToImage 的容器样式
const Container = styled.div` const Container = styled.div`
...@@ -402,6 +404,7 @@ const truncateFilename = (filename, maxLength = 10) => { ...@@ -402,6 +404,7 @@ const truncateFilename = (filename, maxLength = 10) => {
function ImageCompressor() { function ImageCompressor() {
const { t } = useTranslation(); const { t } = useTranslation();
const isLoading = usePageLoading();
const [images, setImages] = useState([]); // 修改为数组存储多张图片 const [images, setImages] = useState([]); // 修改为数组存储多张图片
const [compressedImages, setCompressedImages] = useState([]); const [compressedImages, setCompressedImages] = useState([]);
const [settings, setSettings] = useState({ const [settings, setSettings] = useState({
...@@ -523,6 +526,7 @@ function ImageCompressor() { ...@@ -523,6 +526,7 @@ function ImageCompressor() {
return ( return (
<> <>
{isLoading && <LoadingOverlay />}
<SEO <SEO
title={t('tools.imageCompressor.title')} title={t('tools.imageCompressor.title')}
description={t('tools.imageCompressor.description')} description={t('tools.imageCompressor.description')}
......
...@@ -2,6 +2,8 @@ import React, { useState, useRef, useCallback, useEffect } from 'react'; ...@@ -2,6 +2,8 @@ import React, { useState, useRef, useCallback, useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from './SEO'; import SEO from './SEO';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 复用 MarkdownToImage 的基础容器样式 // 复用 MarkdownToImage 的基础容器样式
const Container = styled.div` const Container = styled.div`
...@@ -164,6 +166,7 @@ const PrivacyNote = styled.div` ...@@ -164,6 +166,7 @@ const PrivacyNote = styled.div`
function ImageWatermark() { function ImageWatermark() {
const { t } = useTranslation(); const { t } = useTranslation();
const isLoading = usePageLoading();
const [image, setImage] = useState(null); const [image, setImage] = useState(null);
const [watermarkText, setWatermarkText] = useState(''); const [watermarkText, setWatermarkText] = useState('');
const [watermarkImage, setWatermarkImage] = useState(null); const [watermarkImage, setWatermarkImage] = useState(null);
...@@ -339,6 +342,7 @@ function ImageWatermark() { ...@@ -339,6 +342,7 @@ function ImageWatermark() {
return ( return (
<> <>
{isLoading && <LoadingOverlay />}
<SEO <SEO
title={t('tools.imageWatermark.title')} title={t('tools.imageWatermark.title')}
description={t('tools.imageWatermark.description')} description={t('tools.imageWatermark.description')}
......
...@@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react'; ...@@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from '../components/SEO'; import SEO from '../components/SEO';
import styled from 'styled-components'; import styled from 'styled-components';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
const Container = styled.div` const Container = styled.div`
min-height: 100vh; min-height: 100vh;
...@@ -48,6 +50,7 @@ function JsonFormatter() { ...@@ -48,6 +50,7 @@ function JsonFormatter() {
const [parsedJson, setParsedJson] = useState(null); const [parsedJson, setParsedJson] = useState(null);
const [isCopied, setIsCopied] = useState(false); const [isCopied, setIsCopied] = useState(false);
const [isCompressed, setIsCompressed] = useState(false); const [isCompressed, setIsCompressed] = useState(false);
const isLoading = usePageLoading();
useEffect(() => { useEffect(() => {
try { try {
...@@ -80,6 +83,7 @@ function JsonFormatter() { ...@@ -80,6 +83,7 @@ function JsonFormatter() {
return ( return (
<> <>
{isLoading && <LoadingOverlay />}
<SEO <SEO
title={t('tools.jsonFormatter.title')} title={t('tools.jsonFormatter.title')}
description={t('tools.jsonFormatter.description')} description={t('tools.jsonFormatter.description')}
......
...@@ -6,6 +6,8 @@ import DOMPurify from 'dompurify'; ...@@ -6,6 +6,8 @@ import DOMPurify from 'dompurify';
import SEO from './SEO'; import SEO from './SEO';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import html2canvas from 'html2canvas'; import html2canvas from 'html2canvas';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 容器样式 // 容器样式
const Container = styled.div` const Container = styled.div`
...@@ -144,6 +146,7 @@ const DownloadButton = styled.button` ...@@ -144,6 +146,7 @@ const DownloadButton = styled.button`
function HtmlPreview() { function HtmlPreview() {
const [html, setHtml] = useState(''); const [html, setHtml] = useState('');
const { t } = useTranslation(); const { t } = useTranslation();
const isLoading = usePageLoading();
// 处理 LaTeX 公式 // 处理 LaTeX 公式
const processLatex = (content) => { const processLatex = (content) => {
...@@ -269,6 +272,7 @@ function HtmlPreview() { ...@@ -269,6 +272,7 @@ function HtmlPreview() {
return ( return (
<> <>
{isLoading && <LoadingOverlay />}
<SEO <SEO
title={t('tools.latex2image.title')} title={t('tools.latex2image.title')}
description={t('tools.latex2image.description')} description={t('tools.latex2image.description')}
......
import React from 'react';
const LoadingOverlay = () => {
return (
<div className="fixed inset-0 bg-white/80 flex items-center justify-center z-50">
<div className="flex flex-col items-center gap-4">
<div className="w-12 h-12 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
<div className="text-indigo-600 font-medium">Loading...</div>
</div>
</div>
);
};
export default LoadingOverlay;
import React, { useState, useRef } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { marked } from 'marked'; import { marked } from 'marked';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from './SEO'; import SEO from './SEO';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 更新预设模板 // 更新预设模板
const templates = [ const templates = [
...@@ -303,11 +305,12 @@ const Preview = styled.div` ...@@ -303,11 +305,12 @@ const Preview = styled.div`
hyphens: auto; hyphens: auto;
`; `;
function TextToImage() { function MarkdownToImage() {
const { t } = useTranslation(); const { t } = useTranslation();
const [text, setText] = useState(''); const [text, setText] = useState('');
const [selectedTemplate, setSelectedTemplate] = useState(templates[0]); const [selectedTemplate, setSelectedTemplate] = useState(templates[0]);
const previewRef = useRef(null); const previewRef = useRef(null);
const isLoading = usePageLoading();
const formatText = (text) => { const formatText = (text) => {
return marked.parse(text, { return marked.parse(text, {
...@@ -419,6 +422,7 @@ function TextToImage() { ...@@ -419,6 +422,7 @@ function TextToImage() {
return ( return (
<> <>
{isLoading && <LoadingOverlay />}
<SEO <SEO
title={t('tools.markdown2image.title')} title={t('tools.markdown2image.title')}
description={t('tools.markdown2image.description')} description={t('tools.markdown2image.description')}
...@@ -473,4 +477,4 @@ function TextToImage() { ...@@ -473,4 +477,4 @@ function TextToImage() {
); );
} }
export default TextToImage; export default MarkdownToImage;
\ No newline at end of file \ No newline at end of file
import React from 'react'; import React from 'react';
import { useScrollToTop } from '../hooks/useScrollToTop';
import '../styles/Timeline.css'; import '../styles/Timeline.css';
import events from '../data/openai-releases.json'; import events from '../data/openai-releases.json';
import SEO from '../components/SEO'; import SEO from '../components/SEO';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
const Timeline = () => { const Timeline = () => {
useScrollToTop();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
......
// PricingChart.jsx // PricingChart.jsx
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useScrollToTop } from '../hooks/useScrollToTop';
import '../styles/PricingChart.css'; import '../styles/PricingChart.css';
const ChartLegend = ({ onLegendClick, highlightedBarTypes }) => { const ChartLegend = ({ onLegendClick, highlightedBarTypes }) => {
...@@ -99,6 +100,7 @@ const GridLines = () => ( ...@@ -99,6 +100,7 @@ const GridLines = () => (
); );
const PricingChart = ({ data }) => { const PricingChart = ({ data }) => {
useScrollToTop();
const [highlightedBarTypes, setHighlightedBarTypes] = useState({ const [highlightedBarTypes, setHighlightedBarTypes] = useState({
input: true, input: true,
output: true, output: true,
......
import React from 'react'; import React from 'react';
import { useScrollToTop } from '../hooks/useScrollToTop';
import PricingChart from '../components/PricingChart'; import PricingChart from '../components/PricingChart';
import OpenaiPricing from '../data/openai-pricing.json'; import OpenaiPricing from '../data/openai-pricing.json';
import LLMPricing from '../data/llm-pricing.json'; import LLMPricing from '../data/llm-pricing.json';
...@@ -6,6 +7,7 @@ import VisionPricing from '../data/vision-model-pricing.json'; ...@@ -6,6 +7,7 @@ import VisionPricing from '../data/vision-model-pricing.json';
import SEO from '../components/SEO'; import SEO from '../components/SEO';
const PricingCharts = () => { const PricingCharts = () => {
useScrollToTop();
const lastUpdateTime = '2024-11-06 21:30'; const lastUpdateTime = '2024-11-06 21:30';
return ( return (
......
...@@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react'; ...@@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import html2canvas from 'html2canvas'; import html2canvas from 'html2canvas';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import { useScrollToTop } from '../hooks/useScrollToTop';
// 更新中文字体数组,包含显示名称和 CSS 字体族名称 // 更新中文字体数组,包含显示名称和 CSS 字体族名称
const chineseFonts = [ const chineseFonts = [
...@@ -203,6 +204,7 @@ const DownloadButton = styled.button` ...@@ -203,6 +204,7 @@ const DownloadButton = styled.button`
`; `;
function QuoteCard() { function QuoteCard() {
useScrollToTop();
const { t } = useTranslation(); const { t } = useTranslation();
const [chineseText, setChineseText] = useState(''); const [chineseText, setChineseText] = useState('');
......
This diff is collapsed.
This diff is collapsed.
...@@ -2,6 +2,8 @@ import React, { useState } from 'react'; ...@@ -2,6 +2,8 @@ import React, { useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from './SEO'; import SEO from './SEO';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
const Container = styled.div` const Container = styled.div`
min-height: 100vh; min-height: 100vh;
...@@ -167,6 +169,7 @@ const DiffHeader = styled(TitleLabel)` ...@@ -167,6 +169,7 @@ const DiffHeader = styled(TitleLabel)`
function TextDiff() { function TextDiff() {
const { t } = useTranslation(); const { t } = useTranslation();
const isLoading = usePageLoading();
const [oldText, setOldText] = useState(''); const [oldText, setOldText] = useState('');
const [newText, setNewText] = useState(''); const [newText, setNewText] = useState('');
...@@ -180,6 +183,7 @@ function TextDiff() { ...@@ -180,6 +183,7 @@ function TextDiff() {
return ( return (
<> <>
{isLoading && <LoadingOverlay />}
<SEO <SEO
title={t('tools.textDiff.title')} title={t('tools.textDiff.title')}
description={t('tools.textDiff.description')} description={t('tools.textDiff.description')}
......
...@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react'; ...@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from './SEO'; import SEO from './SEO';
import styled from 'styled-components'; import styled from 'styled-components';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 复用相同的样式组件 // 复用相同的样式组件
const Container = styled.div` const Container = styled.div`
...@@ -45,6 +47,7 @@ const Title = styled.h2` ...@@ -45,6 +47,7 @@ const Title = styled.h2`
function UrlEncoderDecoder() { function UrlEncoderDecoder() {
const { t } = useTranslation(); const { t } = useTranslation();
const isLoading = usePageLoading();
const [input, setInput] = useState(''); const [input, setInput] = useState('');
const [resultText, setResultText] = useState(''); const [resultText, setResultText] = useState('');
const [isCopied, setIsCopied] = useState(false); const [isCopied, setIsCopied] = useState(false);
...@@ -81,6 +84,7 @@ function UrlEncoderDecoder() { ...@@ -81,6 +84,7 @@ function UrlEncoderDecoder() {
return ( return (
<> <>
{isLoading && <LoadingOverlay />}
<SEO <SEO
title={t('tools.urlEncodeDecode.title')} title={t('tools.urlEncodeDecode.title')}
description={t('tools.urlEncodeDecode.description')} description={t('tools.urlEncodeDecode.description')}
......
import { useState, useEffect } from 'react';
export const usePageLoading = () => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Set loading to false after a short delay to ensure content is ready
const timer = setTimeout(() => {
setIsLoading(false);
}, 500);
return () => clearTimeout(timer);
}, []);
return isLoading;
};
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export const useScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
};
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
"login": "Login", "login": "Login",
"loginSubtitle": "Welcome to AI Toolbox, please log in for the full experience", "loginSubtitle": "Welcome to AI Toolbox, please log in for the full experience",
"logout": "Logout", "logout": "Logout",
"copy": "Copy",
"copied": "Copied",
"dev-tools": { "dev-tools": {
"title": "Development Tools", "title": "Development Tools",
"description": "A collection of popular and efficient development tools, including JSON formatter, Base64 to image converter, online Diff tool, LaTeX renderer, and more. Designed to help developers handle everyday coding tasks quickly and boost development efficiency." "description": "A collection of popular and efficient development tools, including JSON formatter, Base64 to image converter, online Diff tool, LaTeX renderer, and more. Designed to help developers handle everyday coding tasks quickly and boost development efficiency."
......
...@@ -99,7 +99,8 @@ ...@@ -99,7 +99,8 @@
"download": "Download Image", "download": "Download Image",
"dragOrClick": "Drag and drop or click to upload", "dragOrClick": "Drag and drop or click to upload",
"fileName": "File name", "fileName": "File name",
"fileSize": "File size" "fileSize": "File size",
"preview": "Preview"
}, },
"fisherai": { "fisherai": {
"title": "FisherAI", "title": "FisherAI",
...@@ -117,7 +118,7 @@ ...@@ -117,7 +118,7 @@
"title": "Subtitle Generator", "title": "Subtitle Generator",
"description": "Quickly generate multi-line subtitle images with customizable styles", "description": "Quickly generate multi-line subtitle images with customizable styles",
"uploadImage": "Upload Background Image", "uploadImage": "Upload Background Image",
"dragOrClick": "Drag and drop or click to upload", "dropOrClick": "Drag and drop or click to upload",
"removeImage": "Remove Image", "removeImage": "Remove Image",
"globalSettings": "Global Settings", "globalSettings": "Global Settings",
"fontColor": "Font Color", "fontColor": "Font Color",
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
"login": "ログイン", "login": "ログイン",
"loginSubtitle": "AIツールボックスへようこそ。フル体験のためにログインしてください", "loginSubtitle": "AIツールボックスへようこそ。フル体験のためにログインしてください",
"logout": "ログアウト", "logout": "ログアウト",
"copy": "コピー",
"copied": "コピーされました",
"dev-tools": { "dev-tools": {
"title": "開発ツール", "title": "開発ツール",
"description": "JSONフォーマッター、Base64画像変換、オンラインDiffツール、LaTeXレンダリングなど、さまざまな人気で効率的な開発ツールを統合し、日常のコーディングタスクを迅速に処理し、開発効率を向上させます。" "description": "JSONフォーマッター、Base64画像変換、オンラインDiffツール、LaTeXレンダリングなど、さまざまな人気で効率的な開発ツールを統合し、日常のコーディングタスクを迅速に処理し、開発効率を向上させます。"
......
...@@ -99,7 +99,8 @@ ...@@ -99,7 +99,8 @@
"download": "画像をダウンロード", "download": "画像をダウンロード",
"dragOrClick": "画像をドラッグまたはクリックしてアップロード", "dragOrClick": "画像をドラッグまたはクリックしてアップロード",
"fileName": "ファイル名", "fileName": "ファイル名",
"fileSize": "ファイルサイズ" "fileSize": "ファイルサイズ",
"preview": "プレビュー"
}, },
"fisherai": { "fisherai": {
"title": "FisherAI", "title": "FisherAI",
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
"login": "로그인", "login": "로그인",
"loginSubtitle": "AI 툴��스에 오신 것을 환영합니다. 전체 경험을 위해 로그인해 주세요", "loginSubtitle": "AI 툴��스에 오신 것을 환영합니다. 전체 경험을 위해 로그인해 주세요",
"logout": "로그아웃", "logout": "로그아웃",
"copy": "복사",
"copied": "복사된 메시지",
"dev-tools": { "dev-tools": {
"title": "개발 도구", "title": "개발 도구",
"description": "JSON 포맷터, Base64 이미지 변환기, 온라인 Diff 도구, LaTeX 렌더러 등 인기 있고 효율적인 개발 도구를 통합하여, 일상적인 코딩 작업을 신속하게 처리하고 개발 효율성을 높입니다." "description": "JSON 포맷터, Base64 이미지 변환기, 온라인 Diff 도구, LaTeX 렌더러 등 인기 있고 효율적인 개발 도구를 통합하여, 일상적인 코딩 작업을 신속하게 처리하고 개발 효율성을 높입니다."
......
...@@ -100,7 +100,8 @@ ...@@ -100,7 +100,8 @@
"download": "이미지 다운로드", "download": "이미지 다운로드",
"dragOrClick": "드래그 앤 드롭 또는 클릭하여 업로드", "dragOrClick": "드래그 앤 드롭 또는 클릭하여 업로드",
"fileName": "파일 이름", "fileName": "파일 이름",
"fileSize": "파일 크기" "fileSize": "파일 크기",
"preview": "미리보기"
}, },
"fisherai": { "fisherai": {
"title": "FisherAI", "title": "FisherAI",
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
"login": "登录", "login": "登录",
"loginSubtitle": "欢迎使用 AI 工具箱,请登录以获得完整体验", "loginSubtitle": "欢迎使用 AI 工具箱,请登录以获得完整体验",
"logout": "退出登录", "logout": "退出登录",
"copy": "复制",
"copied": "已复制",
"dev-tools": { "dev-tools": {
"title": "开发工具", "title": "开发工具",
"description": "集成多种热门高效的开发工具,包括JSON格式化、Base64转图片、在线Diff对比、LaTeX渲染等,帮助开发者快速处理日常编码任务,提升开发效率。" "description": "集成多种热门高效的开发工具,包括JSON格式化、Base64转图片、在线Diff对比、LaTeX渲染等,帮助开发者快速处理日常编码任务,提升开发效率。"
......
...@@ -101,7 +101,8 @@ ...@@ -101,7 +101,8 @@
"download": "下载图片", "download": "下载图片",
"dragOrClick": "拖拽或点击上传图片", "dragOrClick": "拖拽或点击上传图片",
"fileName": "文件名", "fileName": "文件名",
"fileSize": "文件大小" "fileSize": "文件大小",
"preview": "预览"
}, },
"fisherai": { "fisherai": {
"title": "FisherAI", "title": "FisherAI",
...@@ -201,7 +202,7 @@ ...@@ -201,7 +202,7 @@
}, },
"textBehindImage": { "textBehindImage": {
"title": "文字穿越图片", "title": "文字穿越图片",
"description": "在图片主体与背景之间添加文字,创造3D效果", "description": "在图片主体与背景之间添加文字,3D效果",
"imageUpload": "图片上传", "imageUpload": "图片上传",
"uploadPrompt": "点击或拖拽上传图片", "uploadPrompt": "点击或拖拽上传图片",
"textSettings": "文字设置", "textSettings": "文字设置",
......
import React from 'react'; import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from '../js/i18n'; import { useTranslation } from '../js/i18n';
import SEO from '../components/SEO'; import SEO from '../components/SEO';
...@@ -25,10 +25,37 @@ const tools = [ ...@@ -25,10 +25,37 @@ const tools = [
const Home = () => { const Home = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState('');
const navigate = useNavigate();
useEffect(() => {
const handleClearLoading = () => {
setLoading('');
};
window.addEventListener('clearLoadingState', handleClearLoading);
return () => {
window.removeEventListener('clearLoadingState', handleClearLoading);
};
}, []);
const handleNavigate = (tool) => {
if (tool.external) {
window.open(tool.path, '_blank', 'noopener,noreferrer');
return;
}
setLoading(tool.id);
window.scrollTo(0, 0);
navigate(tool.path);
};
const renderToolLink = (tool) => { const renderToolLink = (tool) => {
const content = ( const content = (
<div className="group flex items-center gap-4 p-6 bg-white rounded-xl shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300"> <div className={`group flex items-center gap-4 p-6 bg-white rounded-xl shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300 relative ${loading === tool.id ? 'pointer-events-none' : ''}`}>
{loading === tool.id && (
<div className="absolute inset-0 bg-white/80 rounded-xl flex items-center justify-center z-10">
<div className="w-8 h-8 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
</div>
)}
<img <img
src={tool.icon} src={tool.icon}
alt={`${t(`tools.${tool.id}.title`)} icon`} alt={`${t(`tools.${tool.id}.title`)} icon`}
...@@ -48,17 +75,15 @@ const Home = () => { ...@@ -48,17 +75,15 @@ const Home = () => {
return tool.external ? ( return tool.external ? (
<a <a
href={tool.path} onClick={() => handleNavigate(tool)}
className="block" className="block cursor-pointer"
target="_blank"
rel="noopener noreferrer"
> >
{content} {content}
</a> </a>
) : ( ) : (
<Link to={tool.path} className="block"> <div onClick={() => handleNavigate(tool)} className="block cursor-pointer">
{content} {content}
</Link> </div>
); );
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment