From 157f055acb36fd9d1d525fb3c0fc5966ae233e09 Mon Sep 17 00:00:00 2001 From: iHeyTang Date: Sun, 28 Sep 2025 11:16:43 +0800 Subject: [PATCH 1/6] feat(i18n): implement internationalization support across the application - Added a new i18n hook for managing language settings and translations. - Introduced a LanguageSwitcher component for dynamic language switching. - Updated existing components and pages to utilize the i18n system for text translations. - Enhanced app configuration to support runtime language updates for tabBar and navigation titles. - Added language resources for English, Japanese, Korean, and simplified Chinese. - Improved loading and error messages to be language-aware. - Refactored existing code to ensure compatibility with the new i18n structure. --- src/app.config.ts | 10 +- src/app.tsx | 14 +- src/components/LanguageSwitcher/index.css | 76 ++++++++++ src/components/LanguageSwitcher/index.tsx | 89 ++++++++++++ src/components/LoadingOverlay/index.tsx | 7 +- src/components/TemplateCard/index.tsx | 6 +- src/hooks/index.ts | 3 +- src/hooks/useI18n.ts | 84 +++++++++++ src/i18n/example.tsx | 135 +++++++++++++++++ src/i18n/index.ts | 122 ++++++++++++++++ src/i18n/locales/en-US.ts | 94 ++++++++++++ src/i18n/locales/index.ts | 18 +++ src/i18n/locales/ja-JP.ts | 94 ++++++++++++ src/i18n/locales/ko-KR.ts | 94 ++++++++++++ src/i18n/locales/zh-CN.ts | 94 ++++++++++++ src/i18n/manager.ts | 110 ++++++++++++++ src/i18n/utils.ts | 168 ++++++++++++++++++++++ src/pages/friends-photo/index.config.ts | 2 +- src/pages/friends-photo/index.tsx | 76 +++++----- src/pages/history/index.css | 12 +- src/pages/history/index.tsx | 33 +++-- src/pages/home/index.config.ts | 2 +- src/pages/home/index.css | 15 ++ src/pages/home/index.tsx | 42 ++++-- 24 files changed, 1326 insertions(+), 74 deletions(-) create mode 100644 src/components/LanguageSwitcher/index.css create mode 100644 src/components/LanguageSwitcher/index.tsx create mode 100644 src/hooks/useI18n.ts create mode 100644 src/i18n/example.tsx create mode 100644 src/i18n/index.ts create mode 100644 src/i18n/locales/en-US.ts create mode 100644 src/i18n/locales/index.ts create mode 100644 src/i18n/locales/ja-JP.ts create mode 100644 src/i18n/locales/ko-KR.ts create mode 100644 src/i18n/locales/zh-CN.ts create mode 100644 src/i18n/manager.ts create mode 100644 src/i18n/utils.ts diff --git a/src/app.config.ts b/src/app.config.ts index 588cbe9..afaa24f 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,3 +1,5 @@ +// 注意:由于小程序的限制,tabBar文本和页面标题需要在运行时动态设置 +// 这里保留中文作为默认值,实际的国际化会在运行时处理 export default defineAppConfig({ pages: [ 'pages/home/index', // 新的模板卡片首页 @@ -13,13 +15,13 @@ export default defineAppConfig({ list: [ { pagePath: 'pages/home/index', - text: '游乐场', + text: '游乐场', // 运行时会被替换为对应语言 iconPath: './assets/icons/playground.png', selectedIconPath: './assets/icons/playground-selected.png', }, { pagePath: 'pages/history/index', - text: '我的', + text: '我的', // 运行时会被替换为对应语言 iconPath: './assets/icons/user.png', selectedIconPath: './assets/icons/user-selected.png', }, @@ -28,12 +30,12 @@ export default defineAppConfig({ window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', - navigationBarTitleText: '图生视频', + navigationBarTitleText: '图生视频', // 运行时会被替换为对应语言 navigationBarTextStyle: 'black', }, permission: { 'scope.album': { - desc: '用于保存生成的图片和视频到相册', + desc: '用于保存生成的图片和视频到相册', // 运行时会被替换为对应语言 }, }, }); diff --git a/src/app.tsx b/src/app.tsx index 2038276..a7cd419 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -5,13 +5,23 @@ import configStore from './store' import './app.css' import { createPlatformFactory } from './platforms' +import { initLanguageFromStorage } from './i18n/utils' +import { i18nManager } from './i18n/manager' const store = configStore() function App({ children }: PropsWithChildren) { useLaunch(async () => { + // 初始化国际化 + try { + await initLanguageFromStorage() + await i18nManager.initializeApp() + } catch (error) { + console.error('i18n初始化失败:', error) + } + const authorize = createPlatformFactory().createAuthorize() - + try { // 检查登录状态,包括OAuth 2.0回调处理 const isLoggedIn = await authorize.checkLogin() @@ -23,7 +33,7 @@ function App({ children }: PropsWithChildren) { console.error('登录检查失败:', error) } }) - + return ( {children} diff --git a/src/components/LanguageSwitcher/index.css b/src/components/LanguageSwitcher/index.css new file mode 100644 index 0000000..c080040 --- /dev/null +++ b/src/components/LanguageSwitcher/index.css @@ -0,0 +1,76 @@ +/* 语言切换组件样式 */ +.language-switcher { + display: flex; + align-items: center; + justify-content: center; + padding: 8px 16px; + background-color: #f8f9fa; + border-radius: 20px; + border: 1px solid #e9ecef; + cursor: pointer; + transition: all 0.2s ease; + user-select: none; +} + +.language-switcher:hover { + background-color: #e9ecef; + border-color: #dee2e6; +} + +.language-switcher:active { + background-color: #dee2e6; + transform: scale(0.98); +} + +.language-switcher-content { + display: flex; + align-items: center; + gap: 6px; +} + +.language-icon { + font-size: 16px; +} + +.language-text { + font-size: 14px; + color: #495057; + font-weight: 500; +} + +.language-arrow { + font-size: 10px; + color: #6c757d; + transition: transform 0.2s ease; +} + +.language-switcher:active .language-arrow { + transform: rotate(180deg); +} + +/* 小尺寸变体 */ +.language-switcher.small { + padding: 6px 12px; +} + +.language-switcher.small .language-text { + font-size: 12px; +} + +.language-switcher.small .language-icon { + font-size: 14px; +} + +/* 紧凑变体 */ +.language-switcher.compact { + padding: 4px 8px; + border-radius: 12px; +} + +.language-switcher.compact .language-text { + display: none; +} + +.language-switcher.compact .language-arrow { + display: none; +} diff --git a/src/components/LanguageSwitcher/index.tsx b/src/components/LanguageSwitcher/index.tsx new file mode 100644 index 0000000..a18c8a7 --- /dev/null +++ b/src/components/LanguageSwitcher/index.tsx @@ -0,0 +1,89 @@ +/** + * 语言切换组件 + */ + +import { View, Text } from '@tarojs/components'; +import { useState } from 'react'; +import Taro from '@tarojs/taro'; +import { useI18n } from '../../hooks/useI18n'; +import { Language } from '../../i18n'; +import { i18nManager } from '../../i18n/manager'; +import './index.css'; + +interface LanguageSwitcherProps { + className?: string; +} + +const LanguageSwitcher: React.FC = ({ className = '' }) => { + const { currentLanguage, changeLanguage, supportedLanguages, languageNames, t } = useI18n(); + const [switching, setSwitching] = useState(false); + + const handleLanguageSwitch = async () => { + if (switching) return; + + try { + setSwitching(true); + + // 显示语言选择菜单 + const languageOptions = supportedLanguages.map(lang => languageNames[lang]); + const currentIndex = supportedLanguages.indexOf(currentLanguage); + + const result = await Taro.showActionSheet({ + itemList: languageOptions, + }); + + const selectedLanguage = supportedLanguages[result.tapIndex]; + + if (selectedLanguage && selectedLanguage !== currentLanguage) { + // 显示切换中的提示 + Taro.showLoading({ + title: t('common.loading'), + mask: true, + }); + + // 切换语言 + await changeLanguage(selectedLanguage); + + // 更新应用级别的i18n设置 + await i18nManager.onLanguageChange(); + + Taro.hideLoading(); + + // 显示成功提示 + Taro.showToast({ + title: t('common.operationSuccess'), + icon: 'success', + duration: 1500, + }); + } + } catch (error: any) { + Taro.hideLoading(); + + // 用户取消选择不显示错误 + if (error.errMsg && error.errMsg.includes('cancel')) { + return; + } + + console.error('Language switch failed:', error); + Taro.showToast({ + title: t('common.operationFailed'), + icon: 'error', + duration: 2000, + }); + } finally { + setSwitching(false); + } + }; + + return ( + + + 🌐 + {languageNames[currentLanguage]} + + + + ); +}; + +export default LanguageSwitcher; diff --git a/src/components/LoadingOverlay/index.tsx b/src/components/LoadingOverlay/index.tsx index 91ec81c..8696f06 100644 --- a/src/components/LoadingOverlay/index.tsx +++ b/src/components/LoadingOverlay/index.tsx @@ -1,4 +1,5 @@ import { View, Text } from '@tarojs/components'; +import { useI18n } from '../../hooks/useI18n'; import './index.css'; interface LoadingOverlayProps { @@ -6,13 +7,15 @@ interface LoadingOverlayProps { } const LoadingOverlay: React.FC = ({ children }) => { + const { t } = useI18n(); + return ( - AI正在生成中... - 请耐心等待,正在为您精心制作 + {t('loading.aiGenerating')} + {t('loading.pleaseWaitDesc')} {children} diff --git a/src/components/TemplateCard/index.tsx b/src/components/TemplateCard/index.tsx index f921966..8aa2df1 100644 --- a/src/components/TemplateCard/index.tsx +++ b/src/components/TemplateCard/index.tsx @@ -2,6 +2,7 @@ import { View, Text, Image, Video } from '@tarojs/components'; import { useState, useRef, useMemo } from 'react'; import Taro from '@tarojs/taro'; import { Template } from '../../store/types'; +import { useI18n } from '../../hooks/useI18n'; import './index.css'; interface TemplateCardProps { @@ -10,6 +11,7 @@ interface TemplateCardProps { } export default function TemplateCard({ template, onClick }: TemplateCardProps) { + const { t } = useI18n(); const [splitPosition, setSplitPosition] = useState(50); // 分割线位置百分比 const [isDragging, setIsDragging] = useState(false); const [containerInfo, setContainerInfo] = useState(null); @@ -104,7 +106,7 @@ export default function TemplateCard({ template, onClick }: TemplateCardProps) { ✨{template.name} - 内容由AI生成 + {t('templates.aiGeneratedContent')} ) : ( @@ -159,7 +161,7 @@ export default function TemplateCard({ template, onClick }: TemplateCardProps) { ✨{template.name} - 内容由AI生成 + {t('templates.aiGeneratedContent')} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index d96ff5b..4b40f09 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -2,4 +2,5 @@ export { useAd } from './useAd' export { useSdk, useServerSdk } from './useSdk' -export { useUploadVideo } from './useUploadVideo' \ No newline at end of file +export { useUploadVideo } from './useUploadVideo' +export { useI18n } from './useI18n' diff --git a/src/hooks/useI18n.ts b/src/hooks/useI18n.ts new file mode 100644 index 0000000..8c315b6 --- /dev/null +++ b/src/hooks/useI18n.ts @@ -0,0 +1,84 @@ +/** + * i18n React Hook + */ + +import { useState, useEffect, useCallback } from 'react'; +import { Language, languageNames } from '../i18n'; +import { + getCurrentLanguage, + setCurrentLanguage, + initLanguageFromStorage, + detectAndSetSystemLanguage, + t, + formatNumber, + formatDate +} from '../i18n/utils'; + +export interface UseI18nReturn { + /** 当前语言 */ + currentLanguage: Language; + /** 翻译函数 */ + t: typeof t; + /** 切换语言 */ + changeLanguage: (language: Language) => Promise; + /** 支持的语言列表 */ + supportedLanguages: Language[]; + /** 语言显示名称 */ + languageNames: typeof languageNames; + /** 格式化数字 */ + formatNumber: typeof formatNumber; + /** 格式化日期 */ + formatDate: typeof formatDate; + /** 是否正在加载 */ + loading: boolean; +} + +/** + * i18n Hook + */ +export const useI18n = (): UseI18nReturn => { + const [currentLanguage, setCurrentLanguageState] = useState(getCurrentLanguage()); + const [loading, setLoading] = useState(true); + + // 初始化语言设置 + useEffect(() => { + const initLanguage = async () => { + try { + // 首先尝试从本地存储加载 + await initLanguageFromStorage(); + + // 如果没有保存的语言,检测系统语言 + const language = await detectAndSetSystemLanguage(); + setCurrentLanguageState(language); + } catch (error) { + console.error('Failed to initialize language:', error); + } finally { + setLoading(false); + } + }; + + initLanguage(); + }, []); + + // 切换语言 + const changeLanguage = useCallback(async (language: Language) => { + try { + await setCurrentLanguage(language); + setCurrentLanguageState(language); + } catch (error) { + console.error('Failed to change language:', error); + throw error; + } + }, []); + + return { + currentLanguage, + t, + changeLanguage, + supportedLanguages: ['zh-CN', 'en-US', 'ja-JP', 'ko-KR'], + languageNames, + formatNumber, + formatDate, + loading, + }; +}; diff --git a/src/i18n/example.tsx b/src/i18n/example.tsx new file mode 100644 index 0000000..6e94b50 --- /dev/null +++ b/src/i18n/example.tsx @@ -0,0 +1,135 @@ +/** + * i18n使用示例 + * 这个文件展示了如何在不同场景下使用国际化功能 + */ + +import { View, Text, Button } from '@tarojs/components'; +import { useState } from 'react'; +import { useI18n } from '../hooks/useI18n'; +import LanguageSwitcher from '../components/LanguageSwitcher'; + +// 示例1: 基本使用 +function BasicExample() { + const { t } = useI18n(); + + return ( + + {t('common.confirm')} + {t('home.title')} + {t('navigation.playground')} + + ); +} + +// 示例2: 带参数的翻译 +function ParameterExample() { + const { t } = useI18n(); + const userName = '张三'; + const count = 42; + + return ( + + {/* 注意:需要在语言文件中定义支持参数的翻译键 */} + {t('welcome.message', { name: userName })} + {t('notification.count', { count: count.toString() })} + + ); +} + +// 示例3: 语言切换 +function LanguageSwitchExample() { + const { currentLanguage, changeLanguage, supportedLanguages, languageNames } = useI18n(); + + const handleLanguageChange = async (language: string) => { + try { + await changeLanguage(language as any); + } catch (error) { + console.error('Language change failed:', error); + } + }; + + return ( + + 当前语言: {languageNames[currentLanguage]} + + {/* 方式1: 使用内置的语言切换组件 */} + + + {/* 方式2: 自定义语言切换按钮 */} + {supportedLanguages.map(lang => ( + + ))} + + ); +} + +// 示例4: 数字和日期格式化 +function FormattingExample() { + const { formatNumber, formatDate } = useI18n(); + + return ( + + 数字: {formatNumber(1234.56)} + 日期: {formatDate(new Date())} + 自定义日期: {formatDate(new Date(), { + year: 'numeric', + month: 'long', + day: 'numeric' + })} + + ); +} + +// 示例5: 条件渲染基于语言 +function ConditionalExample() { + const { currentLanguage, t } = useI18n(); + + return ( + + {t('common.loading')} + + {/* 根据语言显示不同内容 */} + {currentLanguage === 'zh-CN' && ( + 这是中文特有的内容 + )} + + {currentLanguage === 'en-US' && ( + This is English-specific content + )} + + ); +} + +// 示例6: 在类组件中使用(如果需要) +import { Component } from 'react'; +import { getCurrentLanguage, t } from '../i18n/utils'; + +class ClassComponentExample extends Component { + state = { + currentLanguage: getCurrentLanguage(), + }; + + render() { + return ( + + {t('common.confirm')} + 当前语言: {this.state.currentLanguage} + + ); + } +} + +export { + BasicExample, + ParameterExample, + LanguageSwitchExample, + FormattingExample, + ConditionalExample, + ClassComponentExample, +}; diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..4ddb811 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,122 @@ +/** + * 国际化配置文件 + * 支持多语言切换和动态加载 + */ + +export type Language = 'zh-CN' | 'en-US' | 'ja-JP' | 'ko-KR'; + +export interface I18nConfig { + defaultLanguage: Language; + fallbackLanguage: Language; + supportedLanguages: Language[]; + storageKey: string; +} + +export const i18nConfig: I18nConfig = { + defaultLanguage: 'zh-CN', + fallbackLanguage: 'zh-CN', + supportedLanguages: ['zh-CN', 'en-US', 'ja-JP', 'ko-KR'], + storageKey: 'app_language', +}; + +// 语言显示名称映射 +export const languageNames: Record = { + 'zh-CN': '简体中文', + 'en-US': 'English', + 'ja-JP': '日本語', + 'ko-KR': '한국어', +}; + +// 语言资源类型定义 +export interface LanguageResources { + common: { + confirm: string; + cancel: string; + ok: string; + loading: string; + retry: string; + success: string; + error: string; + warning: string; + tips: string; + pleaseWait: string; + networkError: string; + unknownError: string; + operationSuccess: string; + operationFailed: string; + }; + navigation: { + playground: string; + mine: string; + history: string; + home: string; + }; + home: { + title: string; + loadTemplatesFailed: string; + refreshSuccess: string; + refreshFailed: string; + imageAuditFailed: string; + imageAuditNotPassed: string; + imageContentNotCompliant: string; + imageNeedsManualReview: string; + imageAuditUncertain: string; + imageAuditPassed: string; + imageAuditFailedRetry: string; + waitForTaskCompletion: string; + userConfirmedAuditFailure: string; + }; + friendsPhoto: { + title: string; + usageTips: string; + usageTipsContent: string; + iKnow: string; + selectingImage: string; + uploading: string; + uploadSuccess: string; + uploadFailed: string; + needAlbumPermission: string; + uploadTimeout: string; + networkConnectionFailed: string; + uploadFailedRetry: string; + uploadTwoImages: string; + aiGenerating: string; + waitForTaskCompletion: string; + firstPhoto: string; + selectYourPhoto: string; + secondPhoto: string; + selectFriendPhoto: string; + clickToChange: string; + clickToUpload: string; + friendsPhotoGeneration: string; + uploadTwoPhotosDesc: string; + photoPreview: string; + confirmAndGenerate: string; + photo1: string; + photo2: string; + generating: string; + startGenerating: string; + }; + templates: { + aiGeneratedContent: string; + }; + loading: { + aiGenerating: string; + pleaseWaitDesc: string; + }; + audit: { + imageContentNotCompliant: string; + imageNeedsManualReview: string; + imageAuditUncertain: string; + imageAuditPassed: string; + imageAuditFailed: string; + imageAuditNotPassed: string; + iKnow: string; + }; + history: { + title: string; + noRecords: string; + loadFailed: string; + processingFailed: string; + }; +} diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts new file mode 100644 index 0000000..e14fea9 --- /dev/null +++ b/src/i18n/locales/en-US.ts @@ -0,0 +1,94 @@ +import { LanguageResources } from '../index'; + +export const enUS: LanguageResources = { + common: { + confirm: 'Confirm', + cancel: 'Cancel', + ok: 'OK', + loading: 'Loading...', + retry: 'Retry', + success: 'Success', + error: 'Error', + warning: 'Warning', + tips: 'Tips', + pleaseWait: 'Please wait...', + networkError: 'Network connection failed', + unknownError: 'Unknown error', + operationSuccess: 'Operation successful', + operationFailed: 'Operation failed', + }, + navigation: { + playground: 'Playground', + mine: 'Mine', + history: 'History', + home: 'Home', + }, + home: { + title: 'Image to Video', + loadTemplatesFailed: 'Failed to load templates', + refreshSuccess: 'Refresh successful', + refreshFailed: 'Refresh failed', + imageAuditFailed: 'Image audit failed, please retry', + imageAuditNotPassed: 'Image audit not passed', + imageContentNotCompliant: 'Image content does not comply with regulations, please select a compliant image', + imageNeedsManualReview: 'Image needs manual review, please try again later or select another image', + imageAuditUncertain: 'Image audit result is uncertain, please select another image', + imageAuditPassed: 'Image audit passed', + imageAuditFailedRetry: 'Image audit failed, please select another image', + waitForTaskCompletion: 'Please wait for the current task to complete before trying again', + userConfirmedAuditFailure: 'User confirmed audit failure information', + }, + friendsPhoto: { + title: 'Friends Photo', + usageTips: 'Usage Tips', + usageTipsContent: '• Clear face photos work better\n• Choose photos with good lighting\n• Supports JPG, PNG formats', + iKnow: 'I Know', + selectingImage: 'Selecting image...', + uploading: 'Uploading...', + uploadSuccess: 'Upload successful', + uploadFailed: 'Image upload failed', + needAlbumPermission: 'Need permission to access album', + uploadTimeout: 'Upload timeout, please retry', + networkConnectionFailed: 'Network connection failed', + uploadFailedRetry: 'Image upload failed, please retry', + uploadTwoImages: 'Please upload two images first', + aiGenerating: 'AI is generating...', + waitForTaskCompletion: 'Please wait for the current task to complete before trying again', + firstPhoto: 'First Photo', + selectYourPhoto: 'Select your photo', + secondPhoto: 'Second Photo', + selectFriendPhoto: 'Select friend\'s photo', + clickToChange: 'Click to change', + clickToUpload: 'Click to upload', + friendsPhotoGeneration: 'Friends Photo Generation', + uploadTwoPhotosDesc: 'Upload two photos, AI will generate an amazing photo video for you', + photoPreview: 'Photo Preview', + confirmAndGenerate: 'Confirm and start generation', + photo1: 'Photo 1', + photo2: 'Photo 2', + generating: 'Generating...', + startGenerating: 'Start Generating Photo', + }, + templates: { + aiGeneratedContent: 'Content generated by AI', + }, + loading: { + aiGenerating: 'AI is generating...', + pleaseWaitDesc: 'Please be patient, we are crafting something special for you', + }, + audit: { + imageContentNotCompliant: 'Image content does not comply with regulations, please select a compliant image', + imageNeedsManualReview: 'Image needs manual review, please try again later or select another image', + imageAuditUncertain: 'Image audit result is uncertain, please select another image', + imageAuditPassed: 'Image audit passed', + imageAuditFailed: 'Image audit failed, please select another image', + imageAuditNotPassed: 'Image audit not passed', + iKnow: 'I Know', + }, + history: { + title: 'My Works', + noRecords: 'No creation history, go create', + loadFailed: 'Failed to load records', + processingFailed: 'Processing failed', + }, +}; diff --git a/src/i18n/locales/index.ts b/src/i18n/locales/index.ts new file mode 100644 index 0000000..eedc29d --- /dev/null +++ b/src/i18n/locales/index.ts @@ -0,0 +1,18 @@ +/** + * 语言资源导出文件 + */ + +import { Language, LanguageResources } from '../index'; +import { zhCN } from './zh-CN'; +import { enUS } from './en-US'; +import { jaJP } from './ja-JP'; +import { koKR } from './ko-KR'; + +export const locales: Record = { + 'zh-CN': zhCN, + 'en-US': enUS, + 'ja-JP': jaJP, + 'ko-KR': koKR, +}; + +export { zhCN, enUS, jaJP, koKR }; diff --git a/src/i18n/locales/ja-JP.ts b/src/i18n/locales/ja-JP.ts new file mode 100644 index 0000000..15315bb --- /dev/null +++ b/src/i18n/locales/ja-JP.ts @@ -0,0 +1,94 @@ +import { LanguageResources } from '../index'; + +export const jaJP: LanguageResources = { + common: { + confirm: '確認', + cancel: 'キャンセル', + ok: 'わかりました', + loading: '読み込み中...', + retry: '再試行', + success: '成功', + error: 'エラー', + warning: '警告', + tips: 'ヒント', + pleaseWait: 'しばらくお待ちください...', + networkError: 'ネットワーク接続に失敗しました', + unknownError: '不明なエラー', + operationSuccess: '操作が成功しました', + operationFailed: '操作に失敗しました', + }, + navigation: { + playground: 'プレイグラウンド', + mine: 'マイページ', + history: '履歴', + home: 'ホーム', + }, + home: { + title: '画像から動画', + loadTemplatesFailed: 'テンプレートの読み込みに失敗しました', + refreshSuccess: '更新が成功しました', + refreshFailed: '更新に失敗しました', + imageAuditFailed: '画像審査に失敗しました。再試行してください', + imageAuditNotPassed: '画像審査に通りませんでした', + imageContentNotCompliant: '画像内容が規範に適合していません。規範に適合した画像を再選択してください', + imageNeedsManualReview: '画像は人工審査が必要です。しばらく後に再試行するか、他の画像に変更してください', + imageAuditUncertain: '画像審査結果が不確定です。画像を再選択してください', + imageAuditPassed: '画像審査に通りました', + imageAuditFailedRetry: '画像審査に失敗しました。画像を再選択してください', + waitForTaskCompletion: '生成中のタスクが完了するまでお待ちください', + userConfirmedAuditFailure: 'ユーザーが審査失敗情報を確認しました', + }, + friendsPhoto: { + title: '友達の写真', + usageTips: '使用のヒント', + usageTipsContent: '• 鮮明な顔写真の方が効果的です\n• 明るい写真を選択することをお勧めします\n• JPG、PNG形式をサポートしています', + iKnow: 'わかりました', + selectingImage: '画像を選択中...', + uploading: 'アップロード中...', + uploadSuccess: 'アップロード成功', + uploadFailed: '画像のアップロードに失敗しました', + needAlbumPermission: 'アルバムへのアクセス許可が必要です', + uploadTimeout: 'アップロードがタイムアウトしました。再試行してください', + networkConnectionFailed: 'ネットワーク接続に失敗しました', + uploadFailedRetry: '画像のアップロードに失敗しました。再試行してください', + uploadTwoImages: 'まず2枚の画像をアップロードしてください', + aiGenerating: 'AIが生成中...', + waitForTaskCompletion: '生成中のタスクが完了するまでお待ちください', + firstPhoto: '1枚目の写真', + selectYourPhoto: 'あなたの写真を選択', + secondPhoto: '2枚目の写真', + selectFriendPhoto: '友達の写真を選択', + clickToChange: 'クリックして変更', + clickToUpload: 'クリックしてアップロード', + friendsPhotoGeneration: '友達の写真生成', + uploadTwoPhotosDesc: '2枚の写真をアップロードすると、AIが素晴らしい合成動画を生成します', + photoPreview: '写真プレビュー', + confirmAndGenerate: '確認後、生成を開始できます', + photo1: '写真 1', + photo2: '写真 2', + generating: '生成中...', + startGenerating: '写真生成を開始', + }, + templates: { + aiGeneratedContent: 'AI生成コンテンツ', + }, + loading: { + aiGenerating: 'AIが生成中...', + pleaseWaitDesc: 'お待ちください。特別なものを作成しています', + }, + audit: { + imageContentNotCompliant: '画像内容が規範に適合していません。規範に適合した画像を再選択してください', + imageNeedsManualReview: '画像は人工審査が必要です。しばらく後に再試行するか、他の画像に変更してください', + imageAuditUncertain: '画像審査結果が不確定です。画像を再選択してください', + imageAuditPassed: '画像審査に通りました', + imageAuditFailed: '画像審査に失敗しました。画像を再選択してください', + imageAuditNotPassed: '画像審査に通りませんでした', + iKnow: 'わかりました', + }, + history: { + title: '私の作品', + noRecords: '作成履歴がありません。作成しに行きましょう', + loadFailed: 'レコードの読み込みに失敗しました', + processingFailed: '処理に失敗しました', + }, +}; diff --git a/src/i18n/locales/ko-KR.ts b/src/i18n/locales/ko-KR.ts new file mode 100644 index 0000000..4049801 --- /dev/null +++ b/src/i18n/locales/ko-KR.ts @@ -0,0 +1,94 @@ +import { LanguageResources } from '../index'; + +export const koKR: LanguageResources = { + common: { + confirm: '확인', + cancel: '취소', + ok: '알겠습니다', + loading: '로딩 중...', + retry: '재시도', + success: '성공', + error: '오류', + warning: '경고', + tips: '팁', + pleaseWait: '잠시만 기다려주세요...', + networkError: '네트워크 연결 실패', + unknownError: '알 수 없는 오류', + operationSuccess: '작업 성공', + operationFailed: '작업 실패', + }, + navigation: { + playground: '플레이그라운드', + mine: '내 정보', + history: '기록', + home: '홈', + }, + home: { + title: '이미지에서 비디오로', + loadTemplatesFailed: '템플릿 로드 실패', + refreshSuccess: '새로고침 성공', + refreshFailed: '새로고침 실패', + imageAuditFailed: '이미지 심사 실패, 다시 시도해주세요', + imageAuditNotPassed: '이미지 심사 통과하지 못함', + imageContentNotCompliant: '이미지 내용이 규정에 맞지 않습니다. 규정에 맞는 이미지를 다시 선택해주세요', + imageNeedsManualReview: '이미지는 수동 심사가 필요합니다. 나중에 다시 시도하거나 다른 이미지로 변경해주세요', + imageAuditUncertain: '이미지 심사 결과가 불확실합니다. 이미지를 다시 선택해주세요', + imageAuditPassed: '이미지 심사 통과', + imageAuditFailedRetry: '이미지 심사 실패, 이미지를 다시 선택해주세요', + waitForTaskCompletion: '생성 중인 작업이 완료될 때까지 기다려주세요', + userConfirmedAuditFailure: '사용자가 심사 실패 정보를 확인했습니다', + }, + friendsPhoto: { + title: '친구 사진', + usageTips: '사용 팁', + usageTipsContent: '• 선명한 얼굴 사진이 더 좋은 효과를 냅니다\n• 밝은 사진을 선택하는 것을 권장합니다\n• JPG, PNG 형식을 지원합니다', + iKnow: '알겠습니다', + selectingImage: '이미지 선택 중...', + uploading: '업로드 중...', + uploadSuccess: '업로드 성공', + uploadFailed: '이미지 업로드 실패', + needAlbumPermission: '앨범 접근 권한이 필요합니다', + uploadTimeout: '업로드 시간 초과, 다시 시도해주세요', + networkConnectionFailed: '네트워크 연결 실패', + uploadFailedRetry: '이미지 업로드 실패, 다시 시도해주세요', + uploadTwoImages: '먼저 두 장의 이미지를 업로드해주세요', + aiGenerating: 'AI가 생성 중...', + waitForTaskCompletion: '생성 중인 작업이 완료될 때까지 기다려주세요', + firstPhoto: '첫 번째 사진', + selectYourPhoto: '당신의 사진을 선택하세요', + secondPhoto: '두 번째 사진', + selectFriendPhoto: '친구의 사진을 선택하세요', + clickToChange: '클릭하여 변경', + clickToUpload: '클릭하여 업로드', + friendsPhotoGeneration: '친구 사진 생성', + uploadTwoPhotosDesc: '두 장의 사진을 업로드하면 AI가 멋진 합성 비디오를 생성합니다', + photoPreview: '사진 미리보기', + confirmAndGenerate: '확인 후 생성을 시작할 수 있습니다', + photo1: '사진 1', + photo2: '사진 2', + generating: '생성 중...', + startGenerating: '사진 생성 시작', + }, + templates: { + aiGeneratedContent: 'AI 생성 콘텐츠', + }, + loading: { + aiGenerating: 'AI가 생성 중...', + pleaseWaitDesc: '잠시만 기다려주세요. 특별한 것을 만들고 있습니다', + }, + audit: { + imageContentNotCompliant: '이미지 내용이 규정에 맞지 않습니다. 규정에 맞는 이미지를 다시 선택해주세요', + imageNeedsManualReview: '이미지는 수동 심사가 필요합니다. 나중에 다시 시도하거나 다른 이미지로 변경해주세요', + imageAuditUncertain: '이미지 심사 결과가 불확실합니다. 이미지를 다시 선택해주세요', + imageAuditPassed: '이미지 심사 통과', + imageAuditFailed: '이미지 심사 실패, 이미지를 다시 선택해주세요', + imageAuditNotPassed: '이미지 심사 통과하지 못함', + iKnow: '알겠습니다', + }, + history: { + title: '내 작품', + noRecords: '생성 기록이 없습니다. 만들어 보세요', + loadFailed: '레코드 로드 실패', + processingFailed: '처리 실패', + }, +}; diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts new file mode 100644 index 0000000..90de63f --- /dev/null +++ b/src/i18n/locales/zh-CN.ts @@ -0,0 +1,94 @@ +import { LanguageResources } from '../index'; + +export const zhCN: LanguageResources = { + common: { + confirm: '确定', + cancel: '取消', + ok: '我知道了', + loading: '加载中...', + retry: '重试', + success: '成功', + error: '错误', + warning: '警告', + tips: '提示', + pleaseWait: '请稍后...', + networkError: '网络连接失败', + unknownError: '未知错误', + operationSuccess: '操作成功', + operationFailed: '操作失败', + }, + navigation: { + playground: '游乐场', + mine: '我的', + history: '历史记录', + home: '首页', + }, + home: { + title: '图生视频', + loadTemplatesFailed: '加载模板失败', + refreshSuccess: '刷新成功', + refreshFailed: '刷新失败', + imageAuditFailed: '图片审核失败,请重试', + imageAuditNotPassed: '图片审核未通过', + imageContentNotCompliant: '图片内容不符合规范,请重新选择符合规范的图片', + imageNeedsManualReview: '图片需要人工审核,请稍后重试或更换其他图片', + imageAuditUncertain: '图片审核结果不确定,请重新选择图片', + imageAuditPassed: '图片审核通过', + imageAuditFailedRetry: '图片审核失败,请重新选择图片', + waitForTaskCompletion: '请等待生成中的任务完成后再尝试', + userConfirmedAuditFailure: '用户确认审核失败信息', + }, + friendsPhoto: { + title: '好友合照', + usageTips: '使用小贴士', + usageTipsContent: '• 选择清晰的人脸照片效果更佳\n• 建议选择光线充足的照片\n• 支持JPG、PNG格式', + iKnow: '我知道了', + selectingImage: '选择图片中...', + uploading: '正在上传...', + uploadSuccess: '上传成功', + uploadFailed: '图片上传失败', + needAlbumPermission: '需要授权访问相册', + uploadTimeout: '上传超时,请重试', + networkConnectionFailed: '网络连接失败', + uploadFailedRetry: '图片上传失败,请重试', + uploadTwoImages: '请先上传两张图片', + aiGenerating: 'AI正在生成中...', + waitForTaskCompletion: '请等待生成中的任务完成后再尝试', + firstPhoto: '第一张照片', + selectYourPhoto: '选择你的照片', + secondPhoto: '第二张照片', + selectFriendPhoto: '选择朋友的照片', + clickToChange: '点击更换', + clickToUpload: '点击上传', + friendsPhotoGeneration: '好友合照生成', + uploadTwoPhotosDesc: '上传两张照片,AI将为你们生成精彩的合照视频', + photoPreview: '照片预览', + confirmAndGenerate: '确认无误后即可开始生成', + photo1: '照片 1', + photo2: '照片 2', + generating: '生成中...', + startGenerating: '开始生成合照', + }, + templates: { + aiGeneratedContent: '内容由AI生成', + }, + loading: { + aiGenerating: 'AI正在生成中...', + pleaseWaitDesc: '请耐心等待,正在为您精心制作', + }, + audit: { + imageContentNotCompliant: '图片内容不符合规范,请重新选择符合规范的图片', + imageNeedsManualReview: '图片需要人工审核,请稍后重试或更换其他图片', + imageAuditUncertain: '图片审核结果不确定,请重新选择图片', + imageAuditPassed: '图片审核通过', + imageAuditFailed: '图片审核失败,请重新选择图片', + imageAuditNotPassed: '图片审核未通过', + iKnow: '我知道了', + }, + history: { + title: '我的作品', + noRecords: '暂无创作历史,去发布', + loadFailed: '加载记录失败', + processingFailed: '处理失败', + }, +}; diff --git a/src/i18n/manager.ts b/src/i18n/manager.ts new file mode 100644 index 0000000..280d493 --- /dev/null +++ b/src/i18n/manager.ts @@ -0,0 +1,110 @@ +/** + * i18n管理器 + * 处理应用级别的国际化设置,包括tabBar、导航栏标题等 + */ + +import Taro from '@tarojs/taro'; +import { Language } from './index'; +import { t } from './utils'; + +export class I18nManager { + private static instance: I18nManager; + + public static getInstance(): I18nManager { + if (!I18nManager.instance) { + I18nManager.instance = new I18nManager(); + } + return I18nManager.instance; + } + + /** + * 更新TabBar文本 + */ + public async updateTabBarTexts(): Promise { + try { + // 更新第一个tab(游乐场) + await Taro.setTabBarItem({ + index: 0, + text: t('navigation.playground'), + }); + + // 更新第二个tab(我的) + await Taro.setTabBarItem({ + index: 1, + text: t('navigation.mine'), + }); + } catch (error) { + console.warn('Failed to update tabBar texts:', error); + } + } + + /** + * 更新导航栏标题 + * @param pageKey 页面键名 + */ + public async updateNavigationBarTitle(pageKey?: string): Promise { + try { + let title = ''; + + // 根据页面设置不同的标题 + switch (pageKey) { + case 'home': + title = t('home.title'); + break; + case 'friends-photo': + title = t('friendsPhoto.title'); + break; + case 'history': + title = t('navigation.history'); + break; + default: + title = t('home.title'); // 默认标题 + } + + await Taro.setNavigationBarTitle({ title }); + } catch (error) { + console.warn('Failed to update navigation bar title:', error); + } + } + + /** + * 初始化应用国际化设置 + */ + public async initializeApp(): Promise { + try { + // 更新TabBar文本 + await this.updateTabBarTexts(); + + // 更新当前页面标题 + const pages = Taro.getCurrentPages(); + const currentPage = pages[pages.length - 1]; + + if (currentPage) { + const route = currentPage.route; + let pageKey = ''; + + if (route?.includes('home')) { + pageKey = 'home'; + } else if (route?.includes('friends-photo')) { + pageKey = 'friends-photo'; + } else if (route?.includes('history')) { + pageKey = 'history'; + } + + await this.updateNavigationBarTitle(pageKey); + } + } catch (error) { + console.error('Failed to initialize app i18n:', error); + } + } + + /** + * 语言切换时的处理 + */ + public async onLanguageChange(): Promise { + await this.initializeApp(); + } +} + +// 导出单例实例 +export const i18nManager = I18nManager.getInstance(); diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts new file mode 100644 index 0000000..480a215 --- /dev/null +++ b/src/i18n/utils.ts @@ -0,0 +1,168 @@ +/** + * 国际化工具函数 + */ + +import Taro from '@tarojs/taro'; +import { Language, i18nConfig } from './index'; +import { locales } from './locales'; + +// 当前语言状态 +let currentLanguage: Language = i18nConfig.defaultLanguage; + +/** + * 获取当前语言 + */ +export const getCurrentLanguage = (): Language => { + return currentLanguage; +}; + +/** + * 设置当前语言 + */ +export const setCurrentLanguage = async (language: Language): Promise => { + if (!i18nConfig.supportedLanguages.includes(language)) { + console.warn(`Unsupported language: ${language}, fallback to ${i18nConfig.fallbackLanguage}`); + language = i18nConfig.fallbackLanguage; + } + + currentLanguage = language; + + // 保存到本地存储 + try { + await Taro.setStorageSync(i18nConfig.storageKey, language); + } catch (error) { + console.error('Failed to save language to storage:', error); + } +}; + +/** + * 从本地存储初始化语言 + */ +export const initLanguageFromStorage = async (): Promise => { + try { + const savedLanguage = await Taro.getStorageSync(i18nConfig.storageKey); + if (savedLanguage && i18nConfig.supportedLanguages.includes(savedLanguage)) { + currentLanguage = savedLanguage; + } + } catch (error) { + console.error('Failed to load language from storage:', error); + } + + return currentLanguage; +}; + +/** + * 获取嵌套对象的值 + */ +const getNestedValue = (obj: any, path: string): string => { + return path.split('.').reduce((current, key) => { + return current && current[key] !== undefined ? current[key] : undefined; + }, obj); +}; + +/** + * 翻译函数 + * @param key 翻译键,支持嵌套路径,如 'common.confirm' + * @param params 参数对象,用于字符串插值 + * @returns 翻译后的文本 + */ +export const t = (key: string, params?: Record): string => { + const currentLocale = locales[currentLanguage]; + const fallbackLocale = locales[i18nConfig.fallbackLanguage]; + + // 尝试从当前语言获取翻译 + let translation = getNestedValue(currentLocale, key); + + // 如果当前语言没有找到,尝试从回退语言获取 + if (translation === undefined && currentLanguage !== i18nConfig.fallbackLanguage) { + translation = getNestedValue(fallbackLocale, key); + } + + // 如果仍然没有找到,返回键名 + if (translation === undefined) { + console.warn(`Translation not found for key: ${key}`); + return key; + } + + // 处理参数插值 + if (params && typeof translation === 'string') { + return translation.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => { + return params[paramKey] !== undefined ? String(params[paramKey]) : match; + }); + } + + return translation; +}; + +/** + * 检测系统语言并设置 + */ +export const detectAndSetSystemLanguage = async (): Promise => { + try { + const systemInfo = await Taro.getSystemInfo(); + const systemLanguage = systemInfo.language; + + // 映射系统语言到支持的语言 + const languageMap: Record = { + 'zh-CN': 'zh-CN', + 'zh-Hans': 'zh-CN', + 'zh': 'zh-CN', + 'en-US': 'en-US', + 'en': 'en-US', + 'ja-JP': 'ja-JP', + 'ja': 'ja-JP', + 'ko-KR': 'ko-KR', + 'ko': 'ko-KR', + }; + + const detectedLanguage = languageMap[systemLanguage] || i18nConfig.defaultLanguage; + + // 如果本地存储中没有语言设置,使用检测到的语言 + const savedLanguage = await Taro.getStorageSync(i18nConfig.storageKey); + if (!savedLanguage) { + await setCurrentLanguage(detectedLanguage); + return detectedLanguage; + } + + return getCurrentLanguage(); + } catch (error) { + console.error('Failed to detect system language:', error); + return i18nConfig.defaultLanguage; + } +}; + +/** + * 格式化数字(根据语言环境) + */ +export const formatNumber = (num: number): string => { + try { + const locale = currentLanguage === 'zh-CN' ? 'zh-CN' : + currentLanguage === 'ja-JP' ? 'ja-JP' : + currentLanguage === 'ko-KR' ? 'ko-KR' : 'en-US'; + + return new Intl.NumberFormat(locale).format(num); + } catch (error) { + return num.toString(); + } +}; + +/** + * 格式化日期(根据语言环境) + */ +export const formatDate = (date: Date, options?: Intl.DateTimeFormatOptions): string => { + try { + const locale = currentLanguage === 'zh-CN' ? 'zh-CN' : + currentLanguage === 'ja-JP' ? 'ja-JP' : + currentLanguage === 'ko-KR' ? 'ko-KR' : 'en-US'; + + const defaultOptions: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'short', + day: 'numeric', + }; + + return new Intl.DateTimeFormat(locale, options || defaultOptions).format(date); + } catch (error) { + return date.toLocaleDateString(); + } +}; diff --git a/src/pages/friends-photo/index.config.ts b/src/pages/friends-photo/index.config.ts index c2b46d0..532396c 100644 --- a/src/pages/friends-photo/index.config.ts +++ b/src/pages/friends-photo/index.config.ts @@ -1,5 +1,5 @@ export default definePageConfig({ - navigationBarTitleText: '好友合照', + navigationBarTitleText: '好友合照', // 默认标题,运行时会被i18n替换 enableShareAppMessage: true, enableShareTimeline: true, }); diff --git a/src/pages/friends-photo/index.tsx b/src/pages/friends-photo/index.tsx index cef9172..0091857 100644 --- a/src/pages/friends-photo/index.tsx +++ b/src/pages/friends-photo/index.tsx @@ -3,6 +3,8 @@ import { useState, useEffect } from 'react'; import Taro, { navigateTo } from '@tarojs/taro'; import { useSdk, useServerSdk } from '../../hooks/index'; import { useImageDetectionTaskManager, ImageAuditResult, AuditConclusion } from '../../hooks/useImageDetectionTaskManager'; +import { useI18n } from '../../hooks/useI18n'; +import { i18nManager } from '../../i18n/manager'; import './index.css'; @@ -10,6 +12,7 @@ import './index.css'; const FRIENDS_PHOTO_TEMPLATE_CODE = 'friends_photo'; export default function FriendsPhoto() { + const { t } = useI18n(); const sdk = useSdk(); const serverSdk = useServerSdk(); const { submitAuditTask } = useImageDetectionTaskManager(); @@ -19,15 +22,20 @@ export default function FriendsPhoto() { const [loading, setLoading] = useState(false); const [showGuide, setShowGuide] = useState(true); + // 设置页面标题 + useEffect(() => { + i18nManager.updateNavigationBarTitle('friends-photo'); + }, []); + // 显示使用指南 useEffect(() => { if (!showGuide) return; const timer = setTimeout(() => { Taro.showModal({ - title: '使用小贴士', - content: '• 选择清晰的人脸照片效果更佳\n• 建议选择光线充足的照片\n• 支持JPG、PNG格式', - confirmText: '我知道了', + title: t('friendsPhoto.usageTips'), + content: t('friendsPhoto.usageTipsContent'), + confirmText: t('friendsPhoto.iKnow'), showCancel: false, success: () => { setShowGuide(false); @@ -41,18 +49,18 @@ export default function FriendsPhoto() { // 处理审核失败 const handleAuditFailure = (auditResult: ImageAuditResult) => { const messages: Record = { - [AuditConclusion.REJECT]: '图片内容不符合规范,请重新选择符合规范的图片', - [AuditConclusion.REVIEW]: '图片需要人工审核,请稍后重试或更换其他图片', - [AuditConclusion.UNCERTAIN]: '图片审核结果不确定,请重新选择图片', - [AuditConclusion.PASS]: '图片审核通过', + [AuditConclusion.REJECT]: t('audit.imageContentNotCompliant'), + [AuditConclusion.REVIEW]: t('audit.imageNeedsManualReview'), + [AuditConclusion.UNCERTAIN]: t('audit.imageAuditUncertain'), + [AuditConclusion.PASS]: t('audit.imageAuditPassed'), }; - const message = auditResult.conclusion ? messages[auditResult.conclusion] : '图片审核失败,请重新选择图片'; + const message = auditResult.conclusion ? messages[auditResult.conclusion] : t('audit.imageAuditFailed'); Taro.showModal({ - title: '图片审核未通过', + title: t('audit.imageAuditNotPassed'), content: message, - confirmText: '我知道了', + confirmText: t('audit.iKnow'), showCancel: false, }); }; @@ -64,7 +72,7 @@ export default function FriendsPhoto() { Taro.vibrateShort(); Taro.showLoading({ - title: '选择图片中...', + title: t('friendsPhoto.selectingImage'), mask: true, }); @@ -72,13 +80,13 @@ export default function FriendsPhoto() { count: 1, onImageSelected: () => { Taro.showLoading({ - title: '正在上传...', + title: t('friendsPhoto.uploading'), mask: true, }); }, onProgress: () => { Taro.showLoading({ - title: '上传中...', + title: t('friendsPhoto.uploading'), mask: true, }); }, @@ -120,7 +128,7 @@ export default function FriendsPhoto() { // 成功反馈 Taro.vibrateShort(); Taro.showToast({ - title: `图片${imageIndex}上传成功`, + title: `${t('friendsPhoto.uploadSuccess')} ${imageIndex}`, icon: 'success', duration: 1500, }); @@ -136,18 +144,18 @@ export default function FriendsPhoto() { } // 提供更详细的错误提示 - let errorTitle = '图片上传失败'; + let errorTitle = t('friendsPhoto.uploadFailed'); if (error?.message) { errorTitle = error.message; } else if (error?.errMsg) { if (error.errMsg.includes('chooseImage:fail auth')) { - errorTitle = '需要授权访问相册'; + errorTitle = t('friendsPhoto.needAlbumPermission'); } else if (error.errMsg.includes('timeout')) { - errorTitle = '上传超时,请重试'; + errorTitle = t('friendsPhoto.uploadTimeout'); } else if (error.errMsg.includes('network')) { - errorTitle = '网络连接失败'; + errorTitle = t('friendsPhoto.networkConnectionFailed'); } else { - errorTitle = '图片上传失败,请重试'; + errorTitle = t('friendsPhoto.uploadFailedRetry'); } } @@ -166,7 +174,7 @@ export default function FriendsPhoto() { if (!image1 || !image2) { Taro.vibrateShort(); Taro.showToast({ - title: '请先上传两张图片', + title: t('friendsPhoto.uploadTwoImages'), icon: 'none', duration: 2000, }); @@ -179,7 +187,7 @@ export default function FriendsPhoto() { try { Taro.showLoading({ - title: 'AI正在生成中...', + title: t('friendsPhoto.aiGenerating'), mask: true, }); @@ -202,7 +210,7 @@ export default function FriendsPhoto() { console.error('提交失败:', error); Taro.showToast({ - title: '请等待生成中的任务完成后再尝试', + title: t('friendsPhoto.waitForTaskCompletion'), icon: 'none', duration: 2000, }); @@ -228,14 +236,14 @@ export default function FriendsPhoto() { 🔄 - 点击更换 + {t('friendsPhoto.clickToChange')} ) : ( 📷 - 点击上传 + {t('friendsPhoto.clickToUpload')} )} @@ -258,9 +266,9 @@ export default function FriendsPhoto() { 👫 - 好友合照生成 + {t('friendsPhoto.friendsPhotoGeneration')} - 上传两张照片,AI将为你们生成精彩的合照视频 + {t('friendsPhoto.uploadTwoPhotosDesc')} @@ -268,26 +276,26 @@ export default function FriendsPhoto() { {/* 上传区域 */} - {renderUploadCard(1, image1, "第一张照片", "选择你的照片")} - {renderUploadCard(2, image2, "第二张照片", "选择朋友的照片")} + {renderUploadCard(1, image1, t('friendsPhoto.firstPhoto'), t('friendsPhoto.selectYourPhoto'))} + {renderUploadCard(2, image2, t('friendsPhoto.secondPhoto'), t('friendsPhoto.selectFriendPhoto'))} {/* 预览区域 */} {image1 && image2 && ( - 📸 照片预览 - 确认无误后即可开始生成 + 📸 {t('friendsPhoto.photoPreview')} + {t('friendsPhoto.confirmAndGenerate')} - 照片 1 + {t('friendsPhoto.photo1')} + - 照片 2 + {t('friendsPhoto.photo2')} @@ -307,12 +315,12 @@ export default function FriendsPhoto() { {loading ? ( <> - 生成中... + {t('friendsPhoto.generating')} ) : ( <> - 开始生成合照 + {t('friendsPhoto.startGenerating')} )} diff --git a/src/pages/history/index.css b/src/pages/history/index.css index 31e0dbd..34afc12 100644 --- a/src/pages/history/index.css +++ b/src/pages/history/index.css @@ -9,11 +9,21 @@ padding: 32px 0; } +.header-content { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 32px; +} + .history-title { font-size: 48px; font-weight: 500; color: #1d1f22; - padding: 0 32px; +} + +.language-switcher-compact { + flex-shrink: 0; } .history-list { diff --git a/src/pages/history/index.tsx b/src/pages/history/index.tsx index ffa215f..9a17d51 100644 --- a/src/pages/history/index.tsx +++ b/src/pages/history/index.tsx @@ -2,8 +2,12 @@ import { Image, ScrollView, Text, View } from '@tarojs/components'; import Taro, { navigateTo } from '@tarojs/taro'; import { useEffect, useState } from 'react'; import { useServerSdk } from '../../hooks/index'; +import { useI18n } from '../../hooks/useI18n'; +import { i18nManager } from '../../i18n/manager'; +import LanguageSwitcher from '../../components/LanguageSwitcher'; import './index.css'; export default function History() { + const { t } = useI18n(); const [records, setRecords] = useState([]); const [refreshing, setRefreshing] = useState(false); const [loading, setLoading] = useState(true); @@ -33,7 +37,7 @@ export default function History() { } catch (error) { console.error('加载记录失败:', error); Taro.showToast({ - title: '加载记录失败', + title: t('common.operationFailed'), icon: 'error', duration: 2000, }); @@ -44,6 +48,8 @@ export default function History() { useEffect(() => { loadRecords(); + // 设置页面标题 + i18nManager.updateNavigationBarTitle('history'); }, []); // 定时更新生成中任务的进度 @@ -65,14 +71,14 @@ export default function History() { try { await loadRecords(); Taro.showToast({ - title: '刷新成功', + title: t('home.refreshSuccess'), icon: 'success', duration: 1500, }); } catch (error) { console.error('刷新失败:', error); Taro.showToast({ - title: '刷新失败', + title: t('home.refreshFailed'), icon: 'error', duration: 1500, }); @@ -91,8 +97,8 @@ export default function History() { } else if (record.status === 'failed') { // 显示错误信息 Taro.showModal({ - title: '处理失败', - content: record.errorMessage || '图片处理失败,请重试', + title: t('common.operationFailed'), + content: record.errorMessage || t('common.operationFailed'), showCancel: false, }); } else if (record.status === 'processing') { @@ -117,9 +123,14 @@ export default function History() { return ( - {/* - 我的作品 - */} + {/* 设置区域 */} + + + {t('navigation.mine')} + + + + {loading ? ( - 加载中... + {t('common.loading')} ) : records.length === 0 ? ( - 暂无创作历史,去发布 + {t('history.noRecords')} ) : ( records.map(record => { @@ -157,7 +168,7 @@ export default function History() { {/* 加载loading 转圈 */} - + )} diff --git a/src/pages/home/index.config.ts b/src/pages/home/index.config.ts index b9491a7..e2b22c2 100644 --- a/src/pages/home/index.config.ts +++ b/src/pages/home/index.config.ts @@ -1,5 +1,5 @@ export default definePageConfig({ - navigationBarTitleText: '游乐场', + navigationBarTitleText: '游乐场', // 默认标题,运行时会被i18n替换 navigationBarBackgroundColor: '#F5F6F8', navigationBarTextStyle: 'black', backgroundColorTop: '#F5F6F8', diff --git a/src/pages/home/index.css b/src/pages/home/index.css index 85f35be..7cb9007 100644 --- a/src/pages/home/index.css +++ b/src/pages/home/index.css @@ -4,6 +4,21 @@ height: 100vh; display: flex; flex-direction: column; + position: relative; +} + +/* 浮动语言切换器 */ +.language-switcher-float { + position: fixed; + top: 20px; + right: 20px; + z-index: 1000; + opacity: 0.8; + transition: opacity 0.2s ease; +} + +.language-switcher-float:hover { + opacity: 1; } /* ScrollView 容器样式 */ diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 056f53b..50caaae 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -8,6 +8,9 @@ import { Template } from '../../store/types'; import TemplateCard from '../../components/TemplateCard'; import { useSdk, useServerSdk } from '../../hooks/index'; import { useImageDetectionTaskManager, ImageAuditResult, AuditConclusion } from '../../hooks/useImageDetectionTaskManager'; +import { useI18n } from '../../hooks/useI18n'; +import { i18nManager } from '../../i18n/manager'; +import LanguageSwitcher from '../../components/LanguageSwitcher'; import './index.css'; @@ -15,6 +18,7 @@ import './index.css'; type LoadingState = false | 'uploading' | 'auditing' | 'processing'; export default function Home() { + const { t } = useI18n(); const dispatch = useAppDispatch(); const templates = useAppSelector(selectTemplates); const sdk = useSdk(); @@ -30,7 +34,7 @@ export default function Home() { } catch (error) { console.error('加载模板失败:', error); Taro.showToast({ - title: '加载模板失败', + title: t('home.loadTemplatesFailed'), icon: 'error', duration: 2000, }); @@ -43,12 +47,12 @@ export default function Home() { try { await loadTemplates(); Taro.showToast({ - title: '刷新成功', + title: t('home.refreshSuccess'), icon: 'success', duration: 1500, }); } catch (error) { - console.error('刷新失败:', error); + console.error(t('home.refreshFailed'), error); } finally { setRefreshing(false); } @@ -57,34 +61,37 @@ export default function Home() { useEffect(() => { // 需要先检查是否有模板配置文件 loadTemplates(); + + // 设置页面标题 + i18nManager.updateNavigationBarTitle('home'); }, [dispatch]); // 获取加载状态的显示文本 const getLoadingText = (state: LoadingState): string => { switch (state) { default: - return '请稍后...'; + return t('common.pleaseWait'); } }; // 处理审核失败 const handleAuditFailure = (auditResult: ImageAuditResult) => { const messages: Record = { - [AuditConclusion.REJECT]: '图片内容不符合规范,请重新选择符合规范的图片', - [AuditConclusion.REVIEW]: '图片需要人工审核,请稍后重试或更换其他图片', - [AuditConclusion.UNCERTAIN]: '图片审核结果不确定,请重新选择图片', - [AuditConclusion.PASS]: '图片审核通过', // 虽然不会用到,但为了类型完整性 + [AuditConclusion.REJECT]: t('audit.imageContentNotCompliant'), + [AuditConclusion.REVIEW]: t('audit.imageNeedsManualReview'), + [AuditConclusion.UNCERTAIN]: t('audit.imageAuditUncertain'), + [AuditConclusion.PASS]: t('audit.imageAuditPassed'), // 虽然不会用到,但为了类型完整性 }; - const message = auditResult.conclusion ? messages[auditResult.conclusion] : '图片审核失败,请重新选择图片'; + const message = auditResult.conclusion ? messages[auditResult.conclusion] : t('audit.imageAuditFailed'); Taro.showModal({ - title: '图片审核未通过', + title: t('audit.imageAuditNotPassed'), content: message, - confirmText: '我知道了', + confirmText: t('audit.iKnow'), showCancel: false, success: () => { - console.log('用户确认审核失败信息'); + console.log(t('home.userConfirmedAuditFailure')); }, }); }; @@ -109,7 +116,7 @@ export default function Home() { }, onProgress: () => { Taro.showLoading({ - title: `请稍后...`, + title: t('common.pleaseWait'), mask: true, }); }, @@ -157,7 +164,7 @@ export default function Home() { // 业务处理失败 Taro.hideLoading(); Taro.showToast({ - title: '请等待生成中的任务完成后再尝试', + title: t('home.waitForTaskCompletion'), icon: 'none', duration: 2000, }); @@ -173,7 +180,7 @@ export default function Home() { // 统一错误处理 Taro.hideLoading(); Taro.showToast({ - title: '图片审核失败,请重试', + title: t('home.imageAuditFailed'), icon: 'error', duration: 2000, }); @@ -183,6 +190,11 @@ export default function Home() { return ( + {/* 语言切换按钮 - 右上角浮动 */} + + + + Date: Sun, 28 Sep 2025 11:33:31 +0800 Subject: [PATCH 2/6] refactor: modularize components and improve image upload handling --- .../components/IntroCard/index.css | 63 +++ .../components/IntroCard/index.tsx | 23 ++ .../components/SubmitButton/index.css | 102 +++++ .../components/SubmitButton/index.tsx | 32 ++ .../components/UploadCard/index.css | 141 +++++++ .../components/UploadCard/index.tsx | 44 ++ src/pages/friends-photo/components/index.ts | 3 + src/pages/friends-photo/hooks/index.ts | 2 + .../friends-photo/hooks/useImageUpload.ts | 144 +++++++ .../friends-photo/hooks/useUsageGuide.ts | 31 ++ src/pages/friends-photo/index.css | 382 ------------------ src/pages/friends-photo/index.tsx | 288 ++----------- src/pages/home/index.tsx | 91 +---- 13 files changed, 626 insertions(+), 720 deletions(-) create mode 100644 src/pages/friends-photo/components/IntroCard/index.css create mode 100644 src/pages/friends-photo/components/IntroCard/index.tsx create mode 100644 src/pages/friends-photo/components/SubmitButton/index.css create mode 100644 src/pages/friends-photo/components/SubmitButton/index.tsx create mode 100644 src/pages/friends-photo/components/UploadCard/index.css create mode 100644 src/pages/friends-photo/components/UploadCard/index.tsx create mode 100644 src/pages/friends-photo/components/index.ts create mode 100644 src/pages/friends-photo/hooks/index.ts create mode 100644 src/pages/friends-photo/hooks/useImageUpload.ts create mode 100644 src/pages/friends-photo/hooks/useUsageGuide.ts diff --git a/src/pages/friends-photo/components/IntroCard/index.css b/src/pages/friends-photo/components/IntroCard/index.css new file mode 100644 index 0000000..b038e96 --- /dev/null +++ b/src/pages/friends-photo/components/IntroCard/index.css @@ -0,0 +1,63 @@ +/* 介绍卡片 */ +.intro-card { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + margin: 20px; + border-radius: 20px; + padding: 24px; + box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3); + position: relative; + overflow: hidden; +} + +.intro-card::before { + content: ''; + position: absolute; + top: -50%; + right: -50%; + width: 100%; + height: 100%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); + border-radius: 50%; +} + +.intro-content { + display: flex; + align-items: center; + gap: 16px; + position: relative; + z-index: 1; +} + +.intro-icon { + font-size: 36px; + background: rgba(255, 255, 255, 0.2); + border-radius: 16px; + padding: 12px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.intro-text { + flex: 1; +} + +.intro-title { + font-size: 20px; + font-weight: 600; + color: white; + margin-bottom: 4px; +} + +.intro-subtitle { + font-size: 14px; + color: rgba(255, 255, 255, 0.9); + line-height: 1.4; +} + +/* 响应式适配 */ +@media (max-width: 375px) { + .intro-card { + margin: 16px; + padding: 20px; + } +} diff --git a/src/pages/friends-photo/components/IntroCard/index.tsx b/src/pages/friends-photo/components/IntroCard/index.tsx new file mode 100644 index 0000000..88bb5cd --- /dev/null +++ b/src/pages/friends-photo/components/IntroCard/index.tsx @@ -0,0 +1,23 @@ +import { View } from '@tarojs/components'; +import { useI18n } from '../../../../hooks/useI18n'; +import './index.css'; + +interface IntroCardProps { + className?: string; +} + +export default function IntroCard({ className = '' }: IntroCardProps) { + const { t } = useI18n(); + + return ( + + + 👫 + + {t('friendsPhoto.friendsPhotoGeneration')} + {t('friendsPhoto.uploadTwoPhotosDesc')} + + + + ); +} diff --git a/src/pages/friends-photo/components/SubmitButton/index.css b/src/pages/friends-photo/components/SubmitButton/index.css new file mode 100644 index 0000000..f669741 --- /dev/null +++ b/src/pages/friends-photo/components/SubmitButton/index.css @@ -0,0 +1,102 @@ +/* 提交区域 */ +.submit-section { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 20px; + background: white; + border-top: 1px solid #e5e7eb; + box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08); + backdrop-filter: blur(10px); + z-index: 10; +} + +/* 提交按钮 */ +.submit-button { + width: 100%; + height: 56px; + background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); + border-radius: 28px; + border: none; + color: white; + font-size: 16px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.3s ease; + box-shadow: 0 6px 24px rgba(59, 130, 246, 0.4); + position: relative; + overflow: hidden; +} + +.submit-button::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transform: rotate(45deg); + transition: transform 0.6s ease; +} + +.submit-button:not(.disabled):active::before { + transform: rotate(45deg) translateX(100%); +} + +.submit-button.disabled { + background: #d1d5db; + box-shadow: none; + cursor: not-allowed; +} + +.submit-button:not(.disabled):active { + transform: translateY(2px); + box-shadow: 0 3px 16px rgba(59, 130, 246, 0.4); +} + +.submit-icon { + font-size: 18px; +} + +.submit-text { + font-weight: 600; +} + +/* 加载动画 */ +.loading-spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top: 2px solid white; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* 响应式适配 */ +@media (max-width: 375px) { + .submit-section { + padding: 16px; + } +} + +/* 深色模式适配 */ +@media (prefers-color-scheme: dark) { + .submit-section { + background: #2a2a2a; + border-color: #333; + } +} diff --git a/src/pages/friends-photo/components/SubmitButton/index.tsx b/src/pages/friends-photo/components/SubmitButton/index.tsx new file mode 100644 index 0000000..b2356fd --- /dev/null +++ b/src/pages/friends-photo/components/SubmitButton/index.tsx @@ -0,0 +1,32 @@ +import { View, Button } from '@tarojs/components'; +import { useI18n } from '../../../../hooks/useI18n'; +import './index.css'; + +interface SubmitButtonProps { + disabled: boolean; + loading: boolean; + onSubmit: () => void; + className?: string; +} + +export default function SubmitButton({ disabled, loading, onSubmit, className = '' }: SubmitButtonProps) { + const { t } = useI18n(); + + return ( + + + + ); +} diff --git a/src/pages/friends-photo/components/UploadCard/index.css b/src/pages/friends-photo/components/UploadCard/index.css new file mode 100644 index 0000000..f2b181f --- /dev/null +++ b/src/pages/friends-photo/components/UploadCard/index.css @@ -0,0 +1,141 @@ +/* 上传卡片 */ +.upload-card { + background: white; + border-radius: 16px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + overflow: hidden; + transition: all 0.3s ease; +} + +.upload-card:active { + transform: scale(0.98); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12); +} + +.upload-card-content { + padding: 20px; +} + +/* 上传信息 */ +.upload-info { + margin-bottom: 16px; +} + +.upload-title { + font-size: 16px; + font-weight: 600; + color: #1d1f22; + margin-bottom: 4px; +} + +.upload-description { + font-size: 14px; + color: #8e9bae; +} + +/* 上传区域 */ +.upload-area { + width: 100%; + height: 160px; + border: 2px dashed #e5e7eb; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + background: #fafbfc; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.upload-area:active { + transform: scale(0.98); +} + +.upload-area.has-image { + border: 2px solid #3b82f6; + background: transparent; +} + +/* 上传占位符 */ +.upload-placeholder { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.upload-icon { + font-size: 32px; + color: #8e9bae; +} + +.upload-hint { + font-size: 14px; + color: #8e9bae; + font-weight: 500; +} + +/* 上传的图片 */ +.upload-image { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 10px; +} + +/* 图片覆盖层 */ +.image-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.3s ease; + backdrop-filter: blur(4px); +} + +.upload-area.has-image:active .image-overlay { + opacity: 1; +} + +.overlay-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.change-icon { + font-size: 24px; + color: white; +} + +.change-text { + color: white; + font-size: 14px; + font-weight: 500; +} + +/* 深色模式适配 */ +@media (prefers-color-scheme: dark) { + .upload-card { + background: #2a2a2a; + border: 1px solid #333; + } + + .upload-title { + color: #fff; + } + + .upload-description { + color: #999; + } + + .upload-area { + background: #333; + border-color: #444; + } +} diff --git a/src/pages/friends-photo/components/UploadCard/index.tsx b/src/pages/friends-photo/components/UploadCard/index.tsx new file mode 100644 index 0000000..16b08a3 --- /dev/null +++ b/src/pages/friends-photo/components/UploadCard/index.tsx @@ -0,0 +1,44 @@ +import { View, Image } from '@tarojs/components'; +import { useI18n } from '../../../../hooks/useI18n'; +import './index.css'; + +interface UploadCardProps { + imageUrl: string; + title: string; + description: string; + onUpload: () => void; + className?: string; +} + +export default function UploadCard({ imageUrl, title, description, onUpload, className = '' }: UploadCardProps) { + const { t } = useI18n(); + + return ( + + + + {title} + {description} + + + {imageUrl ? ( + <> + + + + 🔄 + {t('friendsPhoto.clickToChange')} + + + + ) : ( + + 📷 + {t('friendsPhoto.clickToUpload')} + + )} + + + + ); +} diff --git a/src/pages/friends-photo/components/index.ts b/src/pages/friends-photo/components/index.ts new file mode 100644 index 0000000..cbafda3 --- /dev/null +++ b/src/pages/friends-photo/components/index.ts @@ -0,0 +1,3 @@ +export { default as IntroCard } from './IntroCard'; +export { default as UploadCard } from './UploadCard'; +export { default as SubmitButton } from './SubmitButton'; diff --git a/src/pages/friends-photo/hooks/index.ts b/src/pages/friends-photo/hooks/index.ts new file mode 100644 index 0000000..acbd67d --- /dev/null +++ b/src/pages/friends-photo/hooks/index.ts @@ -0,0 +1,2 @@ +export { useUsageGuide } from './useUsageGuide'; +export { useImageUpload } from './useImageUpload'; diff --git a/src/pages/friends-photo/hooks/useImageUpload.ts b/src/pages/friends-photo/hooks/useImageUpload.ts new file mode 100644 index 0000000..adb31e3 --- /dev/null +++ b/src/pages/friends-photo/hooks/useImageUpload.ts @@ -0,0 +1,144 @@ +import { useState } from 'react'; +import Taro from '@tarojs/taro'; +import { useSdk } from '../../../hooks/index'; +import { useImageDetectionTaskManager, ImageAuditResult, AuditConclusion } from '../../../hooks/useImageDetectionTaskManager'; +import { useI18n } from '../../../hooks/useI18n'; + +export function useImageUpload() { + const [image1, setImage1] = useState(''); + const [image2, setImage2] = useState(''); + const sdk = useSdk(); + const { submitAuditTask } = useImageDetectionTaskManager(); + const { t } = useI18n(); + + // 处理审核失败 + const handleAuditFailure = (auditResult: ImageAuditResult) => { + const messages: Record = { + [AuditConclusion.REJECT]: t('audit.imageContentNotCompliant'), + [AuditConclusion.REVIEW]: t('audit.imageNeedsManualReview'), + [AuditConclusion.UNCERTAIN]: t('audit.imageAuditUncertain'), + [AuditConclusion.PASS]: t('audit.imageAuditPassed'), + }; + + const message = auditResult.conclusion ? messages[auditResult.conclusion] : t('audit.imageAuditFailed'); + + Taro.showModal({ + title: t('audit.imageAuditNotPassed'), + content: message, + confirmText: t('audit.iKnow'), + showCancel: false, + }); + }; + + // 上传单张图片 + const uploadSingleImage = async (imageIndex: 1 | 2) => { + try { + // 添加触觉反馈 + Taro.vibrateShort(); + + Taro.showLoading({ + title: t('friendsPhoto.selectingImage'), + mask: true, + }); + + const imageUrl = await sdk.chooseAndUploadImage({ + count: 1, + onImageSelected: () => { + Taro.showLoading({ + title: t('friendsPhoto.uploading'), + mask: true, + }); + }, + onProgress: () => { + Taro.showLoading({ + title: t('friendsPhoto.uploading'), + mask: true, + }); + }, + }); + + // 审核图片 + const auditResult = await new Promise((resolve, reject) => { + submitAuditTask( + { + imageUrl, + options: { + enableCache: true, + timeout: 30000, + }, + }, + (status, progress) => { + console.log('审核进度:', status, progress); + }, + result => { + console.log('审核成功:', result); + resolve(result); + }, + error => { + console.error('审核失败:', error); + reject(error); + } + ); + }); + + Taro.hideLoading(); + + if (auditResult.conclusion === AuditConclusion.PASS) { + if (imageIndex === 1) { + setImage1(imageUrl); + } else { + setImage2(imageUrl); + } + + // 成功反馈 + Taro.vibrateShort(); + Taro.showToast({ + title: `${t('friendsPhoto.uploadSuccess')} ${imageIndex}`, + icon: 'success', + duration: 1500, + }); + } else { + handleAuditFailure(auditResult); + } + } catch (error: any) { + Taro.hideLoading(); + + // 用户取消选择图片,不显示错误提示 + if (error.errMsg && error.errMsg.includes('chooseImage:fail cancel')) { + return; + } + + // 提供更详细的错误提示 + let errorTitle = t('friendsPhoto.uploadFailed'); + if (error?.message) { + errorTitle = error.message; + } else if (error?.errMsg) { + if (error.errMsg.includes('chooseImage:fail auth')) { + errorTitle = t('friendsPhoto.needAlbumPermission'); + } else if (error.errMsg.includes('timeout')) { + errorTitle = t('friendsPhoto.uploadTimeout'); + } else if (error.errMsg.includes('network')) { + errorTitle = t('friendsPhoto.networkConnectionFailed'); + } else { + errorTitle = t('friendsPhoto.uploadFailedRetry'); + } + } + + Taro.showToast({ + title: errorTitle, + icon: 'error', + duration: 3000, + }); + + console.error('图片上传详细错误:', error); + } + }; + + return { + image1, + image2, + uploadSingleImage, + setImage1, + setImage2, + }; +} diff --git a/src/pages/friends-photo/hooks/useUsageGuide.ts b/src/pages/friends-photo/hooks/useUsageGuide.ts new file mode 100644 index 0000000..1b97eb0 --- /dev/null +++ b/src/pages/friends-photo/hooks/useUsageGuide.ts @@ -0,0 +1,31 @@ +import { useState, useEffect } from 'react'; +import Taro from '@tarojs/taro'; +import { useI18n } from '../../../hooks/useI18n'; + +export function useUsageGuide() { + const [showGuide, setShowGuide] = useState(true); + const { t } = useI18n(); + + useEffect(() => { + if (!showGuide) return; + + const timer = setTimeout(() => { + Taro.showModal({ + title: t('friendsPhoto.usageTips'), + content: t('friendsPhoto.usageTipsContent'), + confirmText: t('friendsPhoto.iKnow'), + showCancel: false, + success: () => { + setShowGuide(false); + }, + }); + }, 1000); + + return () => clearTimeout(timer); + }, [showGuide, t]); + + return { + showGuide, + setShowGuide, + }; +} diff --git a/src/pages/friends-photo/index.css b/src/pages/friends-photo/index.css index a3e2fd5..6fbed15 100644 --- a/src/pages/friends-photo/index.css +++ b/src/pages/friends-photo/index.css @@ -14,61 +14,6 @@ padding-bottom: 100px; /* 为固定底部按钮留空间 */ } -/* 介绍卡片 */ -.intro-card { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - margin: 20px; - border-radius: 20px; - padding: 24px; - box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3); - position: relative; - overflow: hidden; -} - -.intro-card::before { - content: ''; - position: absolute; - top: -50%; - right: -50%; - width: 100%; - height: 100%; - background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); - border-radius: 50%; -} - -.intro-content { - display: flex; - align-items: center; - gap: 16px; - position: relative; - z-index: 1; -} - -.intro-icon { - font-size: 36px; - background: rgba(255, 255, 255, 0.2); - border-radius: 16px; - padding: 12px; - backdrop-filter: blur(10px); - border: 1px solid rgba(255, 255, 255, 0.3); -} - -.intro-text { - flex: 1; -} - -.intro-title { - font-size: 20px; - font-weight: 600; - color: white; - margin-bottom: 4px; -} - -.intro-subtitle { - font-size: 14px; - color: rgba(255, 255, 255, 0.9); - line-height: 1.4; -} /* 上传区域 */ .upload-section { @@ -78,321 +23,18 @@ gap: 16px; } -/* 上传卡片 */ -.upload-card { - background: white; - border-radius: 16px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - overflow: hidden; - transition: all 0.3s ease; -} - -.upload-card:active { - transform: scale(0.98); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12); -} - -.upload-card-content { - padding: 20px; -} - -/* 上传信息 */ -.upload-info { - margin-bottom: 16px; -} - -.upload-title { - font-size: 16px; - font-weight: 600; - color: #1d1f22; - margin-bottom: 4px; -} - -.upload-description { - font-size: 14px; - color: #8e9bae; -} - -/* 上传区域 */ -.upload-area { - width: 100%; - height: 160px; - border: 2px dashed #e5e7eb; - border-radius: 12px; - display: flex; - align-items: center; - justify-content: center; - background: #fafbfc; - transition: all 0.3s ease; - position: relative; - overflow: hidden; -} - -.upload-area:active { - transform: scale(0.98); -} - -.upload-area.has-image { - border: 2px solid #3b82f6; - background: transparent; -} - -/* 上传占位符 */ -.upload-placeholder { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.upload-icon { - font-size: 32px; - color: #8e9bae; -} - -.upload-hint { - font-size: 14px; - color: #8e9bae; - font-weight: 500; -} - -/* 上传的图片 */ -.upload-image { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 10px; -} - -/* 图片覆盖层 */ -.image-overlay { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: center; - justify-content: center; - opacity: 0; - transition: opacity 0.3s ease; - backdrop-filter: blur(4px); -} - -.upload-area.has-image:active .image-overlay { - opacity: 1; -} - -.overlay-content { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.change-icon { - font-size: 24px; - color: white; -} - -.change-text { - color: white; - font-size: 14px; - font-weight: 500; -} - -/* 预览卡片 */ -.preview-card { - background: white; - margin: 24px 20px 0; - border-radius: 16px; - padding: 20px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - animation: slideUp 0.3s ease-out; -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.preview-header { - margin-bottom: 16px; -} - -.preview-title { - font-size: 16px; - font-weight: 600; - color: #1d1f22; - margin-bottom: 4px; -} - -.preview-subtitle { - font-size: 14px; - color: #8e9bae; -} - -/* 预览网格 */ -.preview-grid { - display: flex; - align-items: center; - gap: 16px; -} - -.preview-item { - flex: 1; - position: relative; -} - -.preview-image { - width: 100%; - aspect-ratio: 1; - object-fit: cover; - border-radius: 12px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); -} - -.preview-label { - position: absolute; - bottom: 8px; - left: 8px; - background: rgba(0, 0, 0, 0.7); - color: white; - font-size: 12px; - font-weight: 500; - padding: 4px 8px; - border-radius: 6px; - backdrop-filter: blur(4px); -} - -.preview-connector { - font-size: 20px; - font-weight: bold; - color: #3b82f6; - background: rgba(59, 130, 246, 0.1); - width: 32px; - height: 32px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; -} /* 底部间距 */ .bottom-spacer { height: 20px; } -/* 提交区域 */ -.submit-section { - position: fixed; - bottom: 0; - left: 0; - right: 0; - padding: 20px; - background: white; - border-top: 1px solid #e5e7eb; - box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08); - backdrop-filter: blur(10px); - z-index: 10; -} - -/* 提交按钮 */ -.submit-button { - width: 100%; - height: 56px; - background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); - border-radius: 28px; - border: none; - color: white; - font-size: 16px; - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - transition: all 0.3s ease; - box-shadow: 0 6px 24px rgba(59, 130, 246, 0.4); - position: relative; - overflow: hidden; -} - -.submit-button::before { - content: ''; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.2), transparent); - transform: rotate(45deg); - transition: transform 0.6s ease; -} - -.submit-button:not(.disabled):active::before { - transform: rotate(45deg) translateX(100%); -} - -.submit-button.disabled { - background: #d1d5db; - box-shadow: none; - cursor: not-allowed; -} - -.submit-button:not(.disabled):active { - transform: translateY(2px); - box-shadow: 0 3px 16px rgba(59, 130, 246, 0.4); -} - -.submit-icon { - font-size: 18px; -} - -.submit-text { - font-weight: 600; -} - -/* 加载动画 */ -.loading-spinner { - width: 16px; - height: 16px; - border: 2px solid rgba(255, 255, 255, 0.3); - border-top: 2px solid white; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} /* 响应式适配 */ @media (max-width: 375px) { - .intro-card { - margin: 16px; - padding: 20px; - } - .upload-section { padding: 0 16px; } - - .preview-card { - margin: 20px 16px 0; - } - - .submit-section { - padding: 16px; - } } /* 深色模式适配 */ @@ -400,28 +42,4 @@ .friends-photo { background: #1a1a1a; } - - .upload-card, - .preview-card { - background: #2a2a2a; - border: 1px solid #333; - } - - .upload-title { - color: #fff; - } - - .upload-description { - color: #999; - } - - .upload-area { - background: #333; - border-color: #444; - } - - .submit-section { - background: #2a2a2a; - border-color: #333; - } } diff --git a/src/pages/friends-photo/index.tsx b/src/pages/friends-photo/index.tsx index 0091857..0501f67 100644 --- a/src/pages/friends-photo/index.tsx +++ b/src/pages/friends-photo/index.tsx @@ -1,176 +1,44 @@ -import { View, Image, Button, ScrollView } from '@tarojs/components'; +import { View, ScrollView } from '@tarojs/components'; import { useState, useEffect } from 'react'; -import Taro, { navigateTo } from '@tarojs/taro'; -import { useSdk, useServerSdk } from '../../hooks/index'; -import { useImageDetectionTaskManager, ImageAuditResult, AuditConclusion } from '../../hooks/useImageDetectionTaskManager'; +import Taro, { navigateTo, useRouter } from '@tarojs/taro'; +import { useServerSdk } from '../../hooks/index'; import { useI18n } from '../../hooks/useI18n'; import { i18nManager } from '../../i18n/manager'; +// 导入组件 +import { IntroCard, UploadCard, SubmitButton } from './components'; + +// 导入hooks +import { useUsageGuide, useImageUpload } from './hooks'; + import './index.css'; -// 好友合照模板的固定代码 -const FRIENDS_PHOTO_TEMPLATE_CODE = 'friends_photo'; - export default function FriendsPhoto() { + const { templateCode } = useRouter().params; const { t } = useI18n(); - const sdk = useSdk(); const serverSdk = useServerSdk(); - const { submitAuditTask } = useImageDetectionTaskManager(); - const [image1, setImage1] = useState(''); - const [image2, setImage2] = useState(''); const [loading, setLoading] = useState(false); - const [showGuide, setShowGuide] = useState(true); + + // 使用自定义hooks + useUsageGuide(); // 自动处理使用指南显示 + const { image1, image2, uploadSingleImage } = useImageUpload(); // 设置页面标题 useEffect(() => { i18nManager.updateNavigationBarTitle('friends-photo'); }, []); - // 显示使用指南 - useEffect(() => { - if (!showGuide) return; - - const timer = setTimeout(() => { - Taro.showModal({ - title: t('friendsPhoto.usageTips'), - content: t('friendsPhoto.usageTipsContent'), - confirmText: t('friendsPhoto.iKnow'), - showCancel: false, - success: () => { - setShowGuide(false); - } - }); - }, 1000); - - return () => clearTimeout(timer); - }, [showGuide]); - - // 处理审核失败 - const handleAuditFailure = (auditResult: ImageAuditResult) => { - const messages: Record = { - [AuditConclusion.REJECT]: t('audit.imageContentNotCompliant'), - [AuditConclusion.REVIEW]: t('audit.imageNeedsManualReview'), - [AuditConclusion.UNCERTAIN]: t('audit.imageAuditUncertain'), - [AuditConclusion.PASS]: t('audit.imageAuditPassed'), - }; - - const message = auditResult.conclusion ? messages[auditResult.conclusion] : t('audit.imageAuditFailed'); - - Taro.showModal({ - title: t('audit.imageAuditNotPassed'), - content: message, - confirmText: t('audit.iKnow'), - showCancel: false, - }); - }; - - // 上传单张图片 - const uploadSingleImage = async (imageIndex: 1 | 2) => { - try { - // 添加触觉反馈 - Taro.vibrateShort(); - - Taro.showLoading({ - title: t('friendsPhoto.selectingImage'), - mask: true, - }); - - const imageUrl = await sdk.chooseAndUploadImage({ - count: 1, - onImageSelected: () => { - Taro.showLoading({ - title: t('friendsPhoto.uploading'), - mask: true, - }); - }, - onProgress: () => { - Taro.showLoading({ - title: t('friendsPhoto.uploading'), - mask: true, - }); - }, - }); - - // 审核图片 - const auditResult = await new Promise((resolve, reject) => { - submitAuditTask( - { - imageUrl, - options: { - enableCache: true, - timeout: 30000, - }, - }, - (status, progress) => { - console.log('审核进度:', status, progress); - }, - result => { - console.log('审核成功:', result); - resolve(result); - }, - error => { - console.error('审核失败:', error); - reject(error); - } - ); - }); - - Taro.hideLoading(); - - if (auditResult.conclusion === AuditConclusion.PASS) { - if (imageIndex === 1) { - setImage1(imageUrl); - } else { - setImage2(imageUrl); - } - - // 成功反馈 - Taro.vibrateShort(); - Taro.showToast({ - title: `${t('friendsPhoto.uploadSuccess')} ${imageIndex}`, - icon: 'success', - duration: 1500, - }); - } else { - handleAuditFailure(auditResult); - } - } catch (error: any) { - Taro.hideLoading(); - - // 用户取消选择图片,不显示错误提示 - if (error.errMsg && error.errMsg.includes('chooseImage:fail cancel')) { - return; - } - - // 提供更详细的错误提示 - let errorTitle = t('friendsPhoto.uploadFailed'); - if (error?.message) { - errorTitle = error.message; - } else if (error?.errMsg) { - if (error.errMsg.includes('chooseImage:fail auth')) { - errorTitle = t('friendsPhoto.needAlbumPermission'); - } else if (error.errMsg.includes('timeout')) { - errorTitle = t('friendsPhoto.uploadTimeout'); - } else if (error.errMsg.includes('network')) { - errorTitle = t('friendsPhoto.networkConnectionFailed'); - } else { - errorTitle = t('friendsPhoto.uploadFailedRetry'); - } - } - - Taro.showToast({ - title: errorTitle, - icon: 'error', - duration: 3000, - }); - - console.error('图片上传详细错误:', error); - } - }; - // 提交处理 const handleSubmit = async () => { + if (!templateCode) { + Taro.showToast({ + title: t('friendsPhoto.templateCodeNotSet'), + icon: 'error', + duration: 2000, + }); + return; + } if (!image1 || !image2) { Taro.vibrateShort(); Taro.showToast({ @@ -196,14 +64,14 @@ export default function FriendsPhoto() { const taskId = await serverSdk.executeTemplate({ imageUrl: combinedImageUrl, - templateCode: FRIENDS_PHOTO_TEMPLATE_CODE, + templateCode: templateCode, }); Taro.hideLoading(); // 跳转到结果页面 navigateTo({ - url: `/pages/result/index?taskId=${taskId}&templateCode=${FRIENDS_PHOTO_TEMPLATE_CODE}`, + url: `/pages/result/index?taskId=${taskId}&templateCode=${templateCode}`, }); } catch (error) { Taro.hideLoading(); @@ -219,112 +87,34 @@ export default function FriendsPhoto() { } }; - const renderUploadCard = (imageIndex: 1 | 2, imageUrl: string, title: string, description: string) => ( - - - - {title} - {description} - - uploadSingleImage(imageIndex)} - > - {imageUrl ? ( - <> - - - - 🔄 - {t('friendsPhoto.clickToChange')} - - - - ) : ( - - 📷 - {t('friendsPhoto.clickToUpload')} - - )} - - - - ); - return ( - + {/* 顶部介绍卡片 */} - - - 👫 - - {t('friendsPhoto.friendsPhotoGeneration')} - - {t('friendsPhoto.uploadTwoPhotosDesc')} - - - - + {/* 上传区域 */} - {renderUploadCard(1, image1, t('friendsPhoto.firstPhoto'), t('friendsPhoto.selectYourPhoto'))} - {renderUploadCard(2, image2, t('friendsPhoto.secondPhoto'), t('friendsPhoto.selectFriendPhoto'))} + uploadSingleImage(1)} + /> + uploadSingleImage(2)} + /> - {/* 预览区域 */} - {image1 && image2 && ( - - - 📸 {t('friendsPhoto.photoPreview')} - {t('friendsPhoto.confirmAndGenerate')} - - - - - {t('friendsPhoto.photo1')} - - + - - - {t('friendsPhoto.photo2')} - - - - )} - {/* 底部间距 */} {/* 固定底部按钮 */} - - - + ); } diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 50caaae..6858f37 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -97,95 +97,8 @@ export default function Home() { }; const handleTemplateClick = async (template: Template) => { - // 如果是好友合照,跳转到专门的页面 - if (template.name === '好友合照') { - navigateTo({ url: `/pages/friends-photo/index` }); - return; - } - - try { - // 第一步:选择并上传图片 - const imageCount = template.code === '2image-to-video' ? 2 : 1; - const imageUrl = await sdk.chooseAndUploadImage({ - count: imageCount, - onImageSelected: () => { - Taro.showLoading({ - title: getLoadingText('uploading'), - mask: true, - }); - }, - onProgress: () => { - Taro.showLoading({ - title: t('common.pleaseWait'), - mask: true, - }); - }, - }); - const auditResult = await new Promise((resolve, reject) => { - submitAuditTask( - { - imageUrl, - options: { - enableCache: true, - timeout: 30000, - }, - }, - (status, progress) => { - console.log('审核进度:', status, progress); - }, - result => { - console.log('审核成功:', result); - resolve(result); - }, - error => { - console.error('审核失败:', error); - reject(error); - } - ); - }); - - // 第三步:检查审核结果 - if (auditResult.conclusion === AuditConclusion.PASS) { - // 短暂延迟后继续业务处理 - await new Promise(resolve => setTimeout(resolve, 1000)); - // 第四步:执行业务逻辑 - try { - const taskId = await serverSdk.executeTemplate({ - imageUrl, - templateCode: template.code, - }); - // 隐藏loading - Taro.hideLoading(); - // 跳转到生成页面 - navigateTo({ - url: `/pages/result/index?taskId=${taskId.taskId}&templateCode=${template.code}`, - }); - } catch (businessError) { - // 业务处理失败 - Taro.hideLoading(); - Taro.showToast({ - title: t('home.waitForTaskCompletion'), - icon: 'none', - duration: 2000, - }); - } - } else { - // 审核不通过 - handleAuditFailure(auditResult); - } - } catch (error: any) { - if (error.errMsg.includes('chooseImage:fail cancel')) { - return; - } - // 统一错误处理 - Taro.hideLoading(); - Taro.showToast({ - title: t('home.imageAuditFailed'), - icon: 'error', - duration: 2000, - }); - } finally { - } + navigateTo({ url: `/pages/friends-photo/index?templateCode=${template.code}` }); + return; }; return ( From c855cfca82c0c08b079cf1183d456f1c1906de95 Mon Sep 17 00:00:00 2001 From: iHeyTang Date: Sun, 28 Sep 2025 11:49:08 +0800 Subject: [PATCH 3/6] refactor(i18n): simplify internationalization to support only English - Removed language switching functionality and related hooks. - Updated i18n configuration to default to English and support only English. - Adjusted components to hide language switcher and related features. - Ensured all language-related functions and formatting are fixed to English. --- src/hooks/useI18n.ts | 47 ++++----------------- src/i18n/index.ts | 6 +-- src/i18n/utils.ts | 83 ++++++++----------------------------- src/pages/history/index.tsx | 4 +- src/pages/home/index.tsx | 7 +--- 5 files changed, 34 insertions(+), 113 deletions(-) diff --git a/src/hooks/useI18n.ts b/src/hooks/useI18n.ts index 8c315b6..47f65a5 100644 --- a/src/hooks/useI18n.ts +++ b/src/hooks/useI18n.ts @@ -2,13 +2,9 @@ * i18n React Hook */ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useCallback } from 'react'; import { Language, languageNames } from '../i18n'; import { - getCurrentLanguage, - setCurrentLanguage, - initLanguageFromStorage, - detectAndSetSystemLanguage, t, formatNumber, formatDate @@ -34,48 +30,23 @@ export interface UseI18nReturn { } /** - * i18n Hook + * i18n Hook - 固定为英语 */ export const useI18n = (): UseI18nReturn => { - const [currentLanguage, setCurrentLanguageState] = useState(getCurrentLanguage()); - const [loading, setLoading] = useState(true); + const [currentLanguage] = useState('en-US'); + const [loading] = useState(false); // 不需要加载,直接设为false - // 初始化语言设置 - useEffect(() => { - const initLanguage = async () => { - try { - // 首先尝试从本地存储加载 - await initLanguageFromStorage(); - - // 如果没有保存的语言,检测系统语言 - const language = await detectAndSetSystemLanguage(); - setCurrentLanguageState(language); - } catch (error) { - console.error('Failed to initialize language:', error); - } finally { - setLoading(false); - } - }; - - initLanguage(); - }, []); - - // 切换语言 - const changeLanguage = useCallback(async (language: Language) => { - try { - await setCurrentLanguage(language); - setCurrentLanguageState(language); - } catch (error) { - console.error('Failed to change language:', error); - throw error; - } + // 切换语言 - 空实现,因为只支持英语 + const changeLanguage = useCallback(async () => { + // 不执行任何操作,因为只支持英语 + console.log('Language switching is disabled, only English is supported'); }, []); return { currentLanguage, t, changeLanguage, - supportedLanguages: ['zh-CN', 'en-US', 'ja-JP', 'ko-KR'], + supportedLanguages: ['en-US'], // 只支持英语 languageNames, formatNumber, formatDate, diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 4ddb811..3cf7de3 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -13,9 +13,9 @@ export interface I18nConfig { } export const i18nConfig: I18nConfig = { - defaultLanguage: 'zh-CN', - fallbackLanguage: 'zh-CN', - supportedLanguages: ['zh-CN', 'en-US', 'ja-JP', 'ko-KR'], + defaultLanguage: 'en-US', + fallbackLanguage: 'en-US', + supportedLanguages: ['en-US'], storageKey: 'app_language', }; diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts index 480a215..ea3eb1f 100644 --- a/src/i18n/utils.ts +++ b/src/i18n/utils.ts @@ -6,8 +6,8 @@ import Taro from '@tarojs/taro'; import { Language, i18nConfig } from './index'; import { locales } from './locales'; -// 当前语言状态 -let currentLanguage: Language = i18nConfig.defaultLanguage; +// 当前语言状态 - 固定为英语 +let currentLanguage: Language = 'en-US'; /** * 获取当前语言 @@ -17,37 +17,26 @@ export const getCurrentLanguage = (): Language => { }; /** - * 设置当前语言 + * 设置当前语言 - 固定为英语 */ -export const setCurrentLanguage = async (language: Language): Promise => { - if (!i18nConfig.supportedLanguages.includes(language)) { - console.warn(`Unsupported language: ${language}, fallback to ${i18nConfig.fallbackLanguage}`); - language = i18nConfig.fallbackLanguage; - } - - currentLanguage = language; +export const setCurrentLanguage = async (): Promise => { + // 始终使用英语 + currentLanguage = 'en-US'; // 保存到本地存储 try { - await Taro.setStorageSync(i18nConfig.storageKey, language); + await Taro.setStorageSync(i18nConfig.storageKey, 'en-US'); } catch (error) { console.error('Failed to save language to storage:', error); } }; /** - * 从本地存储初始化语言 + * 从本地存储初始化语言 - 固定为英语 */ export const initLanguageFromStorage = async (): Promise => { - try { - const savedLanguage = await Taro.getStorageSync(i18nConfig.storageKey); - if (savedLanguage && i18nConfig.supportedLanguages.includes(savedLanguage)) { - currentLanguage = savedLanguage; - } - } catch (error) { - console.error('Failed to load language from storage:', error); - } - + // 始终返回英语,忽略本地存储 + currentLanguage = 'en-US'; return currentLanguage; }; @@ -95,73 +84,37 @@ export const t = (key: string, params?: Record): string }; /** - * 检测系统语言并设置 + * 检测系统语言并设置 - 固定为英语 */ export const detectAndSetSystemLanguage = async (): Promise => { - try { - const systemInfo = await Taro.getSystemInfo(); - const systemLanguage = systemInfo.language; - - // 映射系统语言到支持的语言 - const languageMap: Record = { - 'zh-CN': 'zh-CN', - 'zh-Hans': 'zh-CN', - 'zh': 'zh-CN', - 'en-US': 'en-US', - 'en': 'en-US', - 'ja-JP': 'ja-JP', - 'ja': 'ja-JP', - 'ko-KR': 'ko-KR', - 'ko': 'ko-KR', - }; - - const detectedLanguage = languageMap[systemLanguage] || i18nConfig.defaultLanguage; - - // 如果本地存储中没有语言设置,使用检测到的语言 - const savedLanguage = await Taro.getStorageSync(i18nConfig.storageKey); - if (!savedLanguage) { - await setCurrentLanguage(detectedLanguage); - return detectedLanguage; - } - - return getCurrentLanguage(); - } catch (error) { - console.error('Failed to detect system language:', error); - return i18nConfig.defaultLanguage; - } + // 始终返回英语,忽略系统语言检测 + currentLanguage = 'en-US'; + return 'en-US'; }; /** - * 格式化数字(根据语言环境) + * 格式化数字(根据语言环境) - 固定为英语 */ export const formatNumber = (num: number): string => { try { - const locale = currentLanguage === 'zh-CN' ? 'zh-CN' : - currentLanguage === 'ja-JP' ? 'ja-JP' : - currentLanguage === 'ko-KR' ? 'ko-KR' : 'en-US'; - - return new Intl.NumberFormat(locale).format(num); + return new Intl.NumberFormat('en-US').format(num); } catch (error) { return num.toString(); } }; /** - * 格式化日期(根据语言环境) + * 格式化日期(根据语言环境) - 固定为英语 */ export const formatDate = (date: Date, options?: Intl.DateTimeFormatOptions): string => { try { - const locale = currentLanguage === 'zh-CN' ? 'zh-CN' : - currentLanguage === 'ja-JP' ? 'ja-JP' : - currentLanguage === 'ko-KR' ? 'ko-KR' : 'en-US'; - const defaultOptions: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric', }; - return new Intl.DateTimeFormat(locale, options || defaultOptions).format(date); + return new Intl.DateTimeFormat('en-US', options || defaultOptions).format(date); } catch (error) { return date.toLocaleDateString(); } diff --git a/src/pages/history/index.tsx b/src/pages/history/index.tsx index 9a17d51..809f39a 100644 --- a/src/pages/history/index.tsx +++ b/src/pages/history/index.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import { useServerSdk } from '../../hooks/index'; import { useI18n } from '../../hooks/useI18n'; import { i18nManager } from '../../i18n/manager'; -import LanguageSwitcher from '../../components/LanguageSwitcher'; +// import LanguageSwitcher from '../../components/LanguageSwitcher'; // 已隐藏 - 只支持英语 import './index.css'; export default function History() { const { t } = useI18n(); @@ -127,7 +127,7 @@ export default function History() { {t('navigation.mine')} - + {/* 语言切换器已隐藏 - 只支持英语 */} diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 6858f37..7ca0513 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -10,7 +10,7 @@ import { useSdk, useServerSdk } from '../../hooks/index'; import { useImageDetectionTaskManager, ImageAuditResult, AuditConclusion } from '../../hooks/useImageDetectionTaskManager'; import { useI18n } from '../../hooks/useI18n'; import { i18nManager } from '../../i18n/manager'; -import LanguageSwitcher from '../../components/LanguageSwitcher'; +// import LanguageSwitcher from '../../components/LanguageSwitcher'; // 已隐藏 - 只支持英语 import './index.css'; @@ -103,10 +103,7 @@ export default function Home() { return ( - {/* 语言切换按钮 - 右上角浮动 */} - - - + {/* 语言切换按钮已隐藏 - 只支持英语 */} Date: Sun, 28 Sep 2025 12:58:45 +0800 Subject: [PATCH 4/6] refactor(friends-photo): streamline Friends Photo page components and styles --- src/app.config.ts | 2 +- src/app.tsx | 6 +- src/assets/icons/photo.png | Bin 0 -> 14769 bytes .../components/IntroCard/index.css | 63 --------- .../components/IntroCard/index.tsx | 23 --- .../components/SubmitButton/index.css | 80 +++-------- .../components/SubmitButton/index.tsx | 5 +- .../components/UploadCard/index.css | 131 ++++-------------- .../components/UploadCard/index.tsx | 40 ++---- src/pages/friends-photo/components/index.ts | 1 - src/pages/friends-photo/hooks/index.ts | 1 - .../friends-photo/hooks/useUsageGuide.ts | 31 ----- src/pages/friends-photo/index.css | 41 ++---- src/pages/friends-photo/index.tsx | 44 +++--- src/platforms/h5/payment.ts | 2 +- 15 files changed, 84 insertions(+), 386 deletions(-) create mode 100644 src/assets/icons/photo.png delete mode 100644 src/pages/friends-photo/components/IntroCard/index.css delete mode 100644 src/pages/friends-photo/components/IntroCard/index.tsx delete mode 100644 src/pages/friends-photo/hooks/useUsageGuide.ts diff --git a/src/app.config.ts b/src/app.config.ts index afaa24f..8103ce5 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -4,8 +4,8 @@ export default defineAppConfig({ pages: [ 'pages/home/index', // 新的模板卡片首页 'pages/history/index', // 历史记录页面 - 'pages/result/index', 'pages/friends-photo/index', // 好友合照页面 + 'pages/result/index', ], tabBar: { color: '#8E9BAE', diff --git a/src/app.tsx b/src/app.tsx index f7140fd..520e61c 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -21,7 +21,7 @@ function App({ children }: PropsWithChildren) { } const authorize = createPlatformFactory().createAuthorize() - const payment = createPlatformFactory().createPayment() + // const payment = createPlatformFactory().createPayment() try { // 检查登录状态,包括OAuth 2.0回调处理 const isLoggedIn = await authorize.checkLogin() @@ -29,8 +29,8 @@ function App({ children }: PropsWithChildren) { // 可以根据需要决定是否自动跳转登录 await authorize.login() } - const result = await payment.pay(`character_figurine_v1`, ``) - console.log({ result }) + // const result = await payment.pay(`character_figurine_v1`, ``) + // console.log({ result }) } catch (error) { console.error('登录检查失败:', error) } diff --git a/src/assets/icons/photo.png b/src/assets/icons/photo.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf8b34c9b1f9c68635c5311f2aaebb4c0e99a34 GIT binary patch literal 14769 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 - - 👫 - - {t('friendsPhoto.friendsPhotoGeneration')} - {t('friendsPhoto.uploadTwoPhotosDesc')} - - - - ); -} diff --git a/src/pages/friends-photo/components/SubmitButton/index.css b/src/pages/friends-photo/components/SubmitButton/index.css index f669741..18b81cc 100644 --- a/src/pages/friends-photo/components/SubmitButton/index.css +++ b/src/pages/friends-photo/components/SubmitButton/index.css @@ -4,74 +4,44 @@ bottom: 0; left: 0; right: 0; - padding: 20px; - background: white; - border-top: 1px solid #e5e7eb; - box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08); - backdrop-filter: blur(10px); + padding: 20px 14px; + padding-bottom: calc(20px + env(safe-area-inset-bottom)); z-index: 10; } /* 提交按钮 */ .submit-button { width: 100%; - height: 56px; - background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); - border-radius: 28px; + height: 96px; + background: #000; + border-radius: 24px; border: none; - color: white; - font-size: 16px; - font-weight: 600; + outline: none; display: flex; align-items: center; justify-content: center; gap: 8px; - transition: all 0.3s ease; - box-shadow: 0 6px 24px rgba(59, 130, 246, 0.4); - position: relative; - overflow: hidden; -} - -.submit-button::before { - content: ''; - position: absolute; - top: -50%; - left: -50%; - width: 200%; - height: 200%; - background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.2), transparent); - transform: rotate(45deg); - transition: transform 0.6s ease; -} - -.submit-button:not(.disabled):active::before { - transform: rotate(45deg) translateX(100%); -} - -.submit-button.disabled { - background: #d1d5db; - box-shadow: none; - cursor: not-allowed; -} - -.submit-button:not(.disabled):active { - transform: translateY(2px); - box-shadow: 0 3px 16px rgba(59, 130, 246, 0.4); -} - -.submit-icon { - font-size: 18px; + transition: all 0.2s ease; } .submit-text { + color: white; + font-size: 24px; font-weight: 600; } +.submit-button[disabled] { + background-color: #ccc; + cursor: not-allowed; + border: none; + outline: none; +} + /* 加载动画 */ .loading-spinner { width: 16px; height: 16px; - border: 2px solid rgba(255, 255, 255, 0.3); + border: 2px solid rgb(255 255 255 / 30%); border-top: 2px solid white; border-radius: 50%; animation: spin 1s linear infinite; @@ -81,22 +51,8 @@ 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } - -/* 响应式适配 */ -@media (max-width: 375px) { - .submit-section { - padding: 16px; - } -} - -/* 深色模式适配 */ -@media (prefers-color-scheme: dark) { - .submit-section { - background: #2a2a2a; - border-color: #333; - } -} diff --git a/src/pages/friends-photo/components/SubmitButton/index.tsx b/src/pages/friends-photo/components/SubmitButton/index.tsx index b2356fd..5668270 100644 --- a/src/pages/friends-photo/components/SubmitButton/index.tsx +++ b/src/pages/friends-photo/components/SubmitButton/index.tsx @@ -21,10 +21,7 @@ export default function SubmitButton({ disabled, loading, onSubmit, className = {t('friendsPhoto.generating')} ) : ( - <> - - {t('friendsPhoto.startGenerating')} - + {t('friendsPhoto.startGenerating')} )} diff --git a/src/pages/friends-photo/components/UploadCard/index.css b/src/pages/friends-photo/components/UploadCard/index.css index f2b181f..17061ad 100644 --- a/src/pages/friends-photo/components/UploadCard/index.css +++ b/src/pages/friends-photo/components/UploadCard/index.css @@ -1,60 +1,29 @@ /* 上传卡片 */ .upload-card { - background: white; - border-radius: 16px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); - overflow: hidden; - transition: all 0.3s ease; + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + transition: all 0.2s ease; } .upload-card:active { transform: scale(0.98); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12); } -.upload-card-content { - padding: 20px; -} - -/* 上传信息 */ -.upload-info { - margin-bottom: 16px; -} - -.upload-title { - font-size: 16px; - font-weight: 600; - color: #1d1f22; - margin-bottom: 4px; -} - -.upload-description { - font-size: 14px; - color: #8e9bae; -} - -/* 上传区域 */ -.upload-area { +/* 上传内容区域 */ +.upload-content { width: 100%; - height: 160px; - border: 2px dashed #e5e7eb; - border-radius: 12px; + aspect-ratio: 3/4; + background: #f6f7f9; + border-radius: 16px; display: flex; align-items: center; justify-content: center; - background: #fafbfc; - transition: all 0.3s ease; - position: relative; overflow: hidden; -} - -.upload-area:active { - transform: scale(0.98); -} - -.upload-area.has-image { - border: 2px solid #3b82f6; - background: transparent; + position: relative; } /* 上传占位符 */ @@ -62,18 +31,16 @@ display: flex; flex-direction: column; align-items: center; - gap: 8px; + justify-content: center; + width: 100%; + height: 100%; + gap: 24px; } .upload-icon { - font-size: 32px; - color: #8e9bae; -} - -.upload-hint { - font-size: 14px; - color: #8e9bae; - font-weight: 500; + width: 25%; + aspect-ratio: 1; + height: auto; } /* 上传的图片 */ @@ -81,61 +48,13 @@ width: 100%; height: 100%; object-fit: cover; - border-radius: 10px; } -/* 图片覆盖层 */ -.image-overlay { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: center; - justify-content: center; - opacity: 0; - transition: opacity 0.3s ease; - backdrop-filter: blur(4px); -} - -.upload-area.has-image:active .image-overlay { - opacity: 1; -} - -.overlay-content { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.change-icon { - font-size: 24px; - color: white; -} - -.change-text { - color: white; +/* 标题 */ +.upload-title { font-size: 14px; font-weight: 500; -} - -/* 深色模式适配 */ -@media (prefers-color-scheme: dark) { - .upload-card { - background: #2a2a2a; - border: 1px solid #333; - } - - .upload-title { - color: #fff; - } - - .upload-description { - color: #999; - } - - .upload-area { - background: #333; - border-color: #444; - } + color: #666; + text-align: center; + line-height: 1.2; } diff --git a/src/pages/friends-photo/components/UploadCard/index.tsx b/src/pages/friends-photo/components/UploadCard/index.tsx index 16b08a3..db89c94 100644 --- a/src/pages/friends-photo/components/UploadCard/index.tsx +++ b/src/pages/friends-photo/components/UploadCard/index.tsx @@ -1,43 +1,25 @@ import { View, Image } from '@tarojs/components'; -import { useI18n } from '../../../../hooks/useI18n'; import './index.css'; interface UploadCardProps { imageUrl: string; title: string; - description: string; onUpload: () => void; className?: string; } -export default function UploadCard({ imageUrl, title, description, onUpload, className = '' }: UploadCardProps) { - const { t } = useI18n(); - +export default function UploadCard({ imageUrl, title, onUpload, className = '' }: UploadCardProps) { return ( - - - - {title} - {description} - - - {imageUrl ? ( - <> - - - - 🔄 - {t('friendsPhoto.clickToChange')} - - - - ) : ( - - 📷 - {t('friendsPhoto.clickToUpload')} - - )} - + + + {imageUrl ? ( + + ) : ( + + + {title} + + )} ); diff --git a/src/pages/friends-photo/components/index.ts b/src/pages/friends-photo/components/index.ts index cbafda3..044d379 100644 --- a/src/pages/friends-photo/components/index.ts +++ b/src/pages/friends-photo/components/index.ts @@ -1,3 +1,2 @@ -export { default as IntroCard } from './IntroCard'; export { default as UploadCard } from './UploadCard'; export { default as SubmitButton } from './SubmitButton'; diff --git a/src/pages/friends-photo/hooks/index.ts b/src/pages/friends-photo/hooks/index.ts index acbd67d..8513c14 100644 --- a/src/pages/friends-photo/hooks/index.ts +++ b/src/pages/friends-photo/hooks/index.ts @@ -1,2 +1 @@ -export { useUsageGuide } from './useUsageGuide'; export { useImageUpload } from './useImageUpload'; diff --git a/src/pages/friends-photo/hooks/useUsageGuide.ts b/src/pages/friends-photo/hooks/useUsageGuide.ts deleted file mode 100644 index 1b97eb0..0000000 --- a/src/pages/friends-photo/hooks/useUsageGuide.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useState, useEffect } from 'react'; -import Taro from '@tarojs/taro'; -import { useI18n } from '../../../hooks/useI18n'; - -export function useUsageGuide() { - const [showGuide, setShowGuide] = useState(true); - const { t } = useI18n(); - - useEffect(() => { - if (!showGuide) return; - - const timer = setTimeout(() => { - Taro.showModal({ - title: t('friendsPhoto.usageTips'), - content: t('friendsPhoto.usageTipsContent'), - confirmText: t('friendsPhoto.iKnow'), - showCancel: false, - success: () => { - setShowGuide(false); - }, - }); - }, 1000); - - return () => clearTimeout(timer); - }, [showGuide, t]); - - return { - showGuide, - setShowGuide, - }; -} diff --git a/src/pages/friends-photo/index.css b/src/pages/friends-photo/index.css index 6fbed15..d033ad9 100644 --- a/src/pages/friends-photo/index.css +++ b/src/pages/friends-photo/index.css @@ -1,45 +1,20 @@ /* 页面容器 */ .friends-photo { height: 100vh; - background: #f5f6f8; + background: #f8f9fa; display: flex; flex-direction: column; position: relative; } -/* 滚动容器 */ -.friends-photo-scroll { - flex: 1; - height: 100%; - padding-bottom: 100px; /* 为固定底部按钮留空间 */ -} - - /* 上传区域 */ .upload-section { - padding: 0 20px; + flex: 1; + padding: 40px 14px 88px; display: flex; - flex-direction: column; - gap: 16px; -} - - -/* 底部间距 */ -.bottom-spacer { - height: 20px; -} - - -/* 响应式适配 */ -@media (max-width: 375px) { - .upload-section { - padding: 0 16px; - } -} - -/* 深色模式适配 */ -@media (prefers-color-scheme: dark) { - .friends-photo { - background: #1a1a1a; - } + flex-direction: row; + align-items: center; + gap: 12px; + min-height: 0; + overflow: hidden; } diff --git a/src/pages/friends-photo/index.tsx b/src/pages/friends-photo/index.tsx index 0501f67..f4be088 100644 --- a/src/pages/friends-photo/index.tsx +++ b/src/pages/friends-photo/index.tsx @@ -1,4 +1,4 @@ -import { View, ScrollView } from '@tarojs/components'; +import { View } from '@tarojs/components'; import { useState, useEffect } from 'react'; import Taro, { navigateTo, useRouter } from '@tarojs/taro'; import { useServerSdk } from '../../hooks/index'; @@ -6,10 +6,10 @@ import { useI18n } from '../../hooks/useI18n'; import { i18nManager } from '../../i18n/manager'; // 导入组件 -import { IntroCard, UploadCard, SubmitButton } from './components'; +import { UploadCard, SubmitButton } from './components'; // 导入hooks -import { useUsageGuide, useImageUpload } from './hooks'; +import { useImageUpload } from './hooks'; import './index.css'; @@ -20,8 +20,6 @@ export default function FriendsPhoto() { const [loading, setLoading] = useState(false); - // 使用自定义hooks - useUsageGuide(); // 自动处理使用指南显示 const { image1, image2, uploadSingleImage } = useImageUpload(); // 设置页面标题 @@ -89,29 +87,19 @@ export default function FriendsPhoto() { return ( - - {/* 顶部介绍卡片 */} - - - {/* 上传区域 */} - - uploadSingleImage(1)} - /> - uploadSingleImage(2)} - /> - - - {/* 底部间距 */} - - + {/* 上传区域 */} + + uploadSingleImage(1)} + /> + uploadSingleImage(2)} + /> + {/* 固定底部按钮 */} diff --git a/src/platforms/h5/payment.ts b/src/platforms/h5/payment.ts index cc22f93..84bef86 100644 --- a/src/platforms/h5/payment.ts +++ b/src/platforms/h5/payment.ts @@ -9,4 +9,4 @@ export class H5Payment extends Payment { } throw new Error(`payment error: ${response}`) } -} \ No newline at end of file +} From 4e5dc5374155204e49de38db49d6115b2d4e327d Mon Sep 17 00:00:00 2001 From: iHeyTang Date: Sun, 28 Sep 2025 13:03:03 +0800 Subject: [PATCH 5/6] fix: update button styling and image handling in history and result components --- src/pages/friends-photo/components/SubmitButton/index.css | 2 +- src/pages/history/index.tsx | 4 ++-- src/pages/result/components/GeneratingComponent.tsx | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/friends-photo/components/SubmitButton/index.css b/src/pages/friends-photo/components/SubmitButton/index.css index 18b81cc..725ab6a 100644 --- a/src/pages/friends-photo/components/SubmitButton/index.css +++ b/src/pages/friends-photo/components/SubmitButton/index.css @@ -30,7 +30,7 @@ font-weight: 600; } -.submit-button[disabled] { +.submit-button.disabled { background-color: #ccc; cursor: not-allowed; border: none; diff --git a/src/pages/history/index.tsx b/src/pages/history/index.tsx index 809f39a..c1a95d0 100644 --- a/src/pages/history/index.tsx +++ b/src/pages/history/index.tsx @@ -158,10 +158,10 @@ export default function History() { handleItemClick(record)}> {record.status === 'completed' && record.outputResult ? ( - + ) : ( - + {/* 生成中状态的扫描效果 */} {isGenerating && ( diff --git a/src/pages/result/components/GeneratingComponent.tsx b/src/pages/result/components/GeneratingComponent.tsx index 8f61bf8..992b51d 100644 --- a/src/pages/result/components/GeneratingComponent.tsx +++ b/src/pages/result/components/GeneratingComponent.tsx @@ -1,4 +1,4 @@ -import { Image, View,Text } from '@tarojs/components'; +import { Image, View, Text } from '@tarojs/components'; import { navigateBack } from '@tarojs/taro'; import { useEffect, useState } from 'react'; @@ -63,7 +63,7 @@ const GeneratingComponent: React.FC = ({ task }) => { {/* 预览图片或占位符 */} {task?.inputImageUrl ? ( - + ) : ( 🎨 @@ -73,7 +73,7 @@ const GeneratingComponent: React.FC = ({ task }) => { {/* 光扫描动画 */} - {Math.round(progress)}% + {Math.round(progress)}% From e95db8e321a9dc1a8988f612c66891fbfae99d8a Mon Sep 17 00:00:00 2001 From: iHeyTang Date: Sun, 28 Sep 2025 13:07:49 +0800 Subject: [PATCH 6/6] refactor: replace SubmitButton component with inline button implementation and enhance styling --- .../components/SubmitButton/index.css | 58 ------------------ .../components/SubmitButton/index.tsx | 29 --------- src/pages/friends-photo/components/index.ts | 2 - src/pages/friends-photo/index.css | 59 +++++++++++++++++++ src/pages/friends-photo/index.tsx | 51 +++++++++++----- 5 files changed, 96 insertions(+), 103 deletions(-) delete mode 100644 src/pages/friends-photo/components/SubmitButton/index.css delete mode 100644 src/pages/friends-photo/components/SubmitButton/index.tsx delete mode 100644 src/pages/friends-photo/components/index.ts diff --git a/src/pages/friends-photo/components/SubmitButton/index.css b/src/pages/friends-photo/components/SubmitButton/index.css deleted file mode 100644 index 725ab6a..0000000 --- a/src/pages/friends-photo/components/SubmitButton/index.css +++ /dev/null @@ -1,58 +0,0 @@ -/* 提交区域 */ -.submit-section { - position: fixed; - bottom: 0; - left: 0; - right: 0; - padding: 20px 14px; - padding-bottom: calc(20px + env(safe-area-inset-bottom)); - z-index: 10; -} - -/* 提交按钮 */ -.submit-button { - width: 100%; - height: 96px; - background: #000; - border-radius: 24px; - border: none; - outline: none; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - transition: all 0.2s ease; -} - -.submit-text { - color: white; - font-size: 24px; - font-weight: 600; -} - -.submit-button.disabled { - background-color: #ccc; - cursor: not-allowed; - border: none; - outline: none; -} - -/* 加载动画 */ -.loading-spinner { - width: 16px; - height: 16px; - border: 2px solid rgb(255 255 255 / 30%); - border-top: 2px solid white; - border-radius: 50%; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} diff --git a/src/pages/friends-photo/components/SubmitButton/index.tsx b/src/pages/friends-photo/components/SubmitButton/index.tsx deleted file mode 100644 index 5668270..0000000 --- a/src/pages/friends-photo/components/SubmitButton/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { View, Button } from '@tarojs/components'; -import { useI18n } from '../../../../hooks/useI18n'; -import './index.css'; - -interface SubmitButtonProps { - disabled: boolean; - loading: boolean; - onSubmit: () => void; - className?: string; -} - -export default function SubmitButton({ disabled, loading, onSubmit, className = '' }: SubmitButtonProps) { - const { t } = useI18n(); - - return ( - - - - ); -} diff --git a/src/pages/friends-photo/components/index.ts b/src/pages/friends-photo/components/index.ts deleted file mode 100644 index 044d379..0000000 --- a/src/pages/friends-photo/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as UploadCard } from './UploadCard'; -export { default as SubmitButton } from './SubmitButton'; diff --git a/src/pages/friends-photo/index.css b/src/pages/friends-photo/index.css index d033ad9..41115df 100644 --- a/src/pages/friends-photo/index.css +++ b/src/pages/friends-photo/index.css @@ -18,3 +18,62 @@ min-height: 0; overflow: hidden; } + +/* 提交区域 */ +.submit-section { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 20px 14px; + padding-bottom: calc(20px + env(safe-area-inset-bottom)); + z-index: 10; +} + +/* 提交按钮 */ +.submit-button { + width: 100%; + height: 96px; + background: #000; + border-radius: 24px; + border: none; + outline: none; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.2s ease; +} + +.submit-text { + color: white; + font-size: 24px; + font-weight: 600; +} + +.submit-button.disabled { + background-color: #ccc; + cursor: not-allowed; + border: none; + outline: none; +} + +/* 加载动画 */ +.loading-spinner { + width: 16px; + height: 16px; + border: 2px solid rgb(255 255 255 / 30%); + border-top: 2px solid white; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/src/pages/friends-photo/index.tsx b/src/pages/friends-photo/index.tsx index f4be088..39a1559 100644 --- a/src/pages/friends-photo/index.tsx +++ b/src/pages/friends-photo/index.tsx @@ -1,12 +1,13 @@ -import { View } from '@tarojs/components'; -import { useState, useEffect } from 'react'; +import { Template } from '@/sdk/sdk-server'; +import { Button, View } from '@tarojs/components'; import Taro, { navigateTo, useRouter } from '@tarojs/taro'; +import { useEffect, useState } from 'react'; import { useServerSdk } from '../../hooks/index'; import { useI18n } from '../../hooks/useI18n'; import { i18nManager } from '../../i18n/manager'; // 导入组件 -import { UploadCard, SubmitButton } from './components'; +import UploadCard from './components/UploadCard'; // 导入hooks import { useImageUpload } from './hooks'; @@ -15,6 +16,10 @@ import './index.css'; export default function FriendsPhoto() { const { templateCode } = useRouter().params; + const [template, setTemplate] = useState