fix: bug
This commit is contained in:
parent
30fa29b0ac
commit
b46ad76161
|
|
@ -154,6 +154,7 @@ export default function HomeScreen() {
|
||||||
<TemplateCard
|
<TemplateCard
|
||||||
id={item.id}
|
id={item.id}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
|
titleEn={item.titleEn}
|
||||||
previewUrl={item.previewUrl}
|
previewUrl={item.previewUrl}
|
||||||
webpPreviewUrl={item.webpPreviewUrl}
|
webpPreviewUrl={item.webpPreviewUrl}
|
||||||
coverImageUrl={item.coverImageUrl}
|
coverImageUrl={item.coverImageUrl}
|
||||||
|
|
|
||||||
|
|
@ -178,9 +178,11 @@ export const DynamicForm = forwardRef<DynamicFormRef, DynamicFormProps>(
|
||||||
const handlePickAndUploadImage = useCallback(async (nodeId: string) => {
|
const handlePickAndUploadImage = useCallback(async (nodeId: string) => {
|
||||||
try {
|
try {
|
||||||
const [imageUri] = await imgPicker({ maxImages: 1 })
|
const [imageUri] = await imgPicker({ maxImages: 1 })
|
||||||
|
console.log('[DynamicForm] 选择图片成功:', imageUri)
|
||||||
|
|
||||||
// 上传图片
|
// 上传图片
|
||||||
const url = await uploadFile({ uri: imageUri })
|
const url = await uploadFile({ uri: imageUri })
|
||||||
|
console.log('[DynamicForm] 上传成功,URL:', url)
|
||||||
updateFormData(nodeId, url)
|
updateFormData(nodeId, url)
|
||||||
setPreviewImages((prev) => ({ ...prev, [nodeId]: imageUri }))
|
setPreviewImages((prev) => ({ ...prev, [nodeId]: imageUri }))
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -188,7 +190,7 @@ export const DynamicForm = forwardRef<DynamicFormRef, DynamicFormProps>(
|
||||||
if (error?.message === '未选择任何图片') {
|
if (error?.message === '未选择任何图片') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.error('Pick and upload failed:', error)
|
console.error('[DynamicForm] 上传失败:', { error, message: error?.message, stack: error?.stack })
|
||||||
Toast.show(t('dynamicForm.uploadFailed') || '上传失败,请重试')
|
Toast.show(t('dynamicForm.uploadFailed') || '上传失败,请重试')
|
||||||
}
|
}
|
||||||
}, [t, updateFormData])
|
}, [t, updateFormData])
|
||||||
|
|
|
||||||
|
|
@ -77,4 +77,50 @@ describe('TemplateCard Component', () => {
|
||||||
expect(typeof TemplateCard).toBe('object')
|
expect(typeof TemplateCard).toBe('object')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Multi-language support', () => {
|
||||||
|
it('should display Chinese title when language is zh-CN', () => {
|
||||||
|
const title = '测试模板'
|
||||||
|
const titleEn = 'Test Template'
|
||||||
|
const language = 'zh-CN'
|
||||||
|
|
||||||
|
// 根据语言选择显示的标题
|
||||||
|
const displayTitle = language === 'en-US' ? titleEn : title
|
||||||
|
|
||||||
|
expect(displayTitle).toBe('测试模板')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should display English title when language is en-US', () => {
|
||||||
|
const title = '测试模板'
|
||||||
|
const titleEn = 'Test Template'
|
||||||
|
const language = 'en-US'
|
||||||
|
|
||||||
|
// 根据语言选择显示的标题
|
||||||
|
const displayTitle = language === 'en-US' ? titleEn : title
|
||||||
|
|
||||||
|
expect(displayTitle).toBe('Test Template')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fallback to Chinese title when titleEn is missing', () => {
|
||||||
|
const title = '测试模板'
|
||||||
|
const titleEn = undefined
|
||||||
|
const language = 'en-US'
|
||||||
|
|
||||||
|
// 当 titleEn 不存在时,回退到中文标题
|
||||||
|
const displayTitle = language === 'en-US' && titleEn ? titleEn : title
|
||||||
|
|
||||||
|
expect(displayTitle).toBe('测试模板')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fallback to Chinese title when titleEn is empty string', () => {
|
||||||
|
const title = '测试模板'
|
||||||
|
const titleEn = ''
|
||||||
|
const language = 'en-US'
|
||||||
|
|
||||||
|
// 当 titleEn 为空字符串时,回退到中文标题
|
||||||
|
const displayTitle = language === 'en-US' && titleEn ? titleEn : title
|
||||||
|
|
||||||
|
expect(displayTitle).toBe('测试模板')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ import React, { memo, useCallback, useMemo } from 'react'
|
||||||
import { Pressable, StyleSheet, Text, View, ViewStyle, ImageStyle } from 'react-native'
|
import { Pressable, StyleSheet, Text, View, ViewStyle, ImageStyle } from 'react-native'
|
||||||
import { Image } from 'expo-image'
|
import { Image } from 'expo-image'
|
||||||
import { LinearGradient } from 'expo-linear-gradient'
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export interface TemplateCardProps {
|
export interface TemplateCardProps {
|
||||||
id?: string
|
id?: string
|
||||||
title: string
|
title: string
|
||||||
|
titleEn?: string
|
||||||
previewUrl?: string
|
previewUrl?: string
|
||||||
webpPreviewUrl?: string
|
webpPreviewUrl?: string
|
||||||
coverImageUrl?: string
|
coverImageUrl?: string
|
||||||
|
|
@ -49,6 +51,7 @@ const GRADIENT_END = { x: 0, y: 1 }
|
||||||
const TemplateCardComponent: React.FC<TemplateCardProps> = ({
|
const TemplateCardComponent: React.FC<TemplateCardProps> = ({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
|
titleEn,
|
||||||
previewUrl,
|
previewUrl,
|
||||||
webpPreviewUrl,
|
webpPreviewUrl,
|
||||||
coverImageUrl,
|
coverImageUrl,
|
||||||
|
|
@ -57,9 +60,16 @@ const TemplateCardComponent: React.FC<TemplateCardProps> = ({
|
||||||
onPress,
|
onPress,
|
||||||
testID,
|
testID,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { i18n } = useTranslation()
|
||||||
const aspectRatio = useMemo(() => parseAspectRatio(aspectRatioString), [aspectRatioString])
|
const aspectRatio = useMemo(() => parseAspectRatio(aspectRatioString), [aspectRatioString])
|
||||||
const imageUri = useMemo(() => getImageUri(webpPreviewUrl, previewUrl, coverImageUrl), [webpPreviewUrl, previewUrl, coverImageUrl])
|
const imageUri = useMemo(() => getImageUri(webpPreviewUrl, previewUrl, coverImageUrl), [webpPreviewUrl, previewUrl, coverImageUrl])
|
||||||
|
|
||||||
|
// Select display title based on current language
|
||||||
|
const displayTitle = useMemo(() => {
|
||||||
|
const isEnglish = i18n.language === 'en-US'
|
||||||
|
return isEnglish && titleEn ? titleEn : title
|
||||||
|
}, [i18n.language, title, titleEn])
|
||||||
|
|
||||||
// 使用 useCallback 缓存 onPress 回调
|
// 使用 useCallback 缓存 onPress 回调
|
||||||
const handlePress = useCallback(() => {
|
const handlePress = useCallback(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
|
|
@ -105,7 +115,7 @@ const TemplateCardComponent: React.FC<TemplateCardProps> = ({
|
||||||
style={styles.cardImageGradient}
|
style={styles.cardImageGradient}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.cardTitle} numberOfLines={1}>
|
<Text style={styles.cardTitle} numberOfLines={1}>
|
||||||
{title}
|
{displayTitle}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,13 @@ const mockCategories: Category[] = [
|
||||||
{ id: 'cat-3', name: 'Category 3', templates: [] },
|
{ id: 'cat-3', name: 'Category 3', templates: [] },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Test data with multi-language support
|
||||||
|
const mockCategoriesWithI18n: Array<Category & { nameEn: string }> = [
|
||||||
|
{ id: 'cat-1', name: '分类1', nameEn: 'Category 1', templates: [] },
|
||||||
|
{ id: 'cat-2', name: '分类2', nameEn: 'Category 2', templates: [] },
|
||||||
|
{ id: 'cat-3', name: '分类3', nameEn: 'Category 3', templates: [] },
|
||||||
|
]
|
||||||
|
|
||||||
describe('useTabNavigation - core logic', () => {
|
describe('useTabNavigation - core logic', () => {
|
||||||
describe('initialization', () => {
|
describe('initialization', () => {
|
||||||
it('should select first category when initialCategoryId is empty', () => {
|
it('should select first category when initialCategoryId is empty', () => {
|
||||||
|
|
@ -256,4 +263,41 @@ describe('useTabNavigation - core logic', () => {
|
||||||
expect(state.currentCategory).toBeUndefined()
|
expect(state.currentCategory).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('multi-language support', () => {
|
||||||
|
it('should generate tabs with Chinese names when language is zh-CN', () => {
|
||||||
|
// 模拟中文环境
|
||||||
|
const language = 'zh-CN'
|
||||||
|
const tabs = mockCategoriesWithI18n.map(cat =>
|
||||||
|
language === 'en-US' ? cat.nameEn : cat.name
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(tabs).toEqual(['分类1', '分类2', '分类3'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should generate tabs with English names when language is en-US', () => {
|
||||||
|
// 模拟英文环境
|
||||||
|
const language = 'en-US'
|
||||||
|
const tabs = mockCategoriesWithI18n.map(cat =>
|
||||||
|
language === 'en-US' ? cat.nameEn : cat.name
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(tabs).toEqual(['Category 1', 'Category 2', 'Category 3'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fallback to Chinese name when nameEn is missing', () => {
|
||||||
|
const categoriesWithMissingEn = [
|
||||||
|
{ id: 'cat-1', name: '分类1', nameEn: 'Category 1' },
|
||||||
|
{ id: 'cat-2', name: '分类2', nameEn: '' }, // nameEn 为空
|
||||||
|
{ id: 'cat-3', name: '分类3' }, // 没有 nameEn 字段
|
||||||
|
]
|
||||||
|
|
||||||
|
const language = 'en-US'
|
||||||
|
const tabs = categoriesWithMissingEn.map(cat =>
|
||||||
|
language === 'en-US' && cat.nameEn ? cat.nameEn : cat.name
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(tabs).toEqual(['Category 1', '分类2', '分类3'])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { useState, useMemo, useCallback, useEffect } from 'react'
|
import { useState, useMemo, useCallback, useEffect } from 'react'
|
||||||
import { CategoryTemplate } from '@repo/sdk'
|
import { CategoryTemplate } from '@repo/sdk'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export interface Category {
|
export interface Category {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
nameEn?: string
|
||||||
templates?: CategoryTemplate[]
|
templates?: CategoryTemplate[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,6 +25,7 @@ export interface UseTabNavigationReturn {
|
||||||
|
|
||||||
export function useTabNavigation(options: UseTabNavigationOptions): UseTabNavigationReturn {
|
export function useTabNavigation(options: UseTabNavigationOptions): UseTabNavigationReturn {
|
||||||
const { categories, initialCategoryId } = options
|
const { categories, initialCategoryId } = options
|
||||||
|
const { i18n } = useTranslation()
|
||||||
|
|
||||||
// State for active tab index
|
// State for active tab index
|
||||||
const [activeIndex, setActiveIndex] = useState<number>(() => {
|
const [activeIndex, setActiveIndex] = useState<number>(() => {
|
||||||
|
|
@ -52,10 +55,14 @@ export function useTabNavigation(options: UseTabNavigationOptions): UseTabNaviga
|
||||||
}
|
}
|
||||||
}, [categories, selectedCategoryId])
|
}, [categories, selectedCategoryId])
|
||||||
|
|
||||||
// Generate tabs array from category names
|
// Generate tabs array from category names with i18n support
|
||||||
const tabs = useMemo(() => {
|
const tabs = useMemo(() => {
|
||||||
return categories.map(category => category.name)
|
const isEnglish = i18n.language === 'en-US'
|
||||||
}, [categories])
|
return categories.map(category => {
|
||||||
|
// Use English name if available and language is English, otherwise use Chinese name
|
||||||
|
return isEnglish && category.nameEn ? category.nameEn : category.name
|
||||||
|
})
|
||||||
|
}, [categories, i18n.language])
|
||||||
|
|
||||||
// Get current category based on selectedCategoryId
|
// Get current category based on selectedCategoryId
|
||||||
const currentCategory = useMemo(() => {
|
const currentCategory = useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ import { handleError } from '@/hooks/use-error'
|
||||||
|
|
||||||
export async function uploadFile(params: { uri: string; mimeType?: string; fileName?: string }): Promise<string> {
|
export async function uploadFile(params: { uri: string; mimeType?: string; fileName?: string }): Promise<string> {
|
||||||
const { uri, mimeType, fileName } = params
|
const { uri, mimeType, fileName } = params
|
||||||
|
|
||||||
|
console.log('[uploadFile] 开始上传:', { uri, mimeType, fileName, platform: Platform.OS })
|
||||||
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', {
|
formData.append('file', {
|
||||||
uri: uri,
|
uri: uri,
|
||||||
|
|
@ -17,8 +20,10 @@ export async function uploadFile(params: { uri: string; mimeType?: string; fileN
|
||||||
const { data, error } = await handleError(async () => await fileController.uploadS3(formData))
|
const { data, error } = await handleError(async () => await fileController.uploadS3(formData))
|
||||||
|
|
||||||
if (error || !data?.data) {
|
if (error || !data?.data) {
|
||||||
|
console.error('[uploadFile] 上传失败:', { error, data, uri })
|
||||||
throw error || new Error('上传失败')
|
throw error || new Error('上传失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[uploadFile] 上传成功:', data.data)
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue