From 7b7bfca490d9619c5179535fc38d3b79829634b9 Mon Sep 17 00:00:00 2001 From: Mac Date: Sat, 24 May 2025 09:43:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=AD=E7=89=A9=E8=BD=A6=E5=92=8C=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E7=95=8C=E9=9D=A2=E6=9C=AA=E7=99=BB=E5=BD=95=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/locales/en/translation.json | 250 ++++++++++--------------- app/locales/fr/translation.json | 29 ++- app/locales/zh/translation.json | 26 ++- app/screens/CartScreen.tsx | 312 ++++++++++++++++++++++++------- app/screens/ChatScreen.tsx | 320 ++++++++++++++------------------ app/screens/loginList/index.tsx | 80 ++++++-- 6 files changed, 596 insertions(+), 421 deletions(-) diff --git a/app/locales/en/translation.json b/app/locales/en/translation.json index 0c601a0..c20377d 100644 --- a/app/locales/en/translation.json +++ b/app/locales/en/translation.json @@ -96,24 +96,104 @@ "popularCategories": "Popular Categories", "my":"My", "categories": "Categories", - "chat": "Chat", - "customerServiceChat": "Customer Service", - "productChat": "Product Support", - "notificationChat": "Notifications", - "inputMessage": "Type a message...", - "send": "Send", - "searchProducts": "Search products", - "recommendations": "Recommendations", - "sales": "sales", - "productPicture": "Product Picture", - "no_stock": "No stock", - "setting": { - "title": "Select Language and Currency", - "country": "Country", - "currency": "Currency", - "language": "Language", - "confirm": "Confirm", - "success": "Settings saved successfully" + "chat": { + "customer_service": "Customer Service", + "product_support": "Product Support", + "notifications": "Notifications", + "input_message": "Type a message...", + "send": "Send", + "typing_message": "Typing", + "default_response": "Thank you for your message. Our team will get back to you shortly.", + "error_response": "Sorry, there was an error processing your request. Please try again later.", + "login_required_title": "Please Login First", + "login_required_subtitle": "Login to use chat features", + "login_now": "Login Now" + }, + "banner": { + "today": "Today", + "shipping": { + "fee": "Shipping Fee", + "calculation_result": "Shipping Fee Calculation Result", + "estimated_fee": "Estimated Shipping Fee", + "confirm": "Confirm", + "maritime": "Sea Shipping", + "airway": "Air Shipping", + "select_country": "Select Country", + "parcel_volume": "Parcel Volume", + "enter_parcel_volume": "Please enter parcel volume", + "calculate_shipping": "Calculate Shipping Fee", + "select_country_modal_title": "Select Country", + "close": "Close" + }, + "inquiry": { + "title": "Quote Request", + "upload_image": "Upload Image", + "hint": "Please upload an image", + "quantity_required": "Please enter quantity", + "error": "Error", + "submission_failed": "Submission failed, please try again", + "image_inquiry": "Image Inquiry", + "product_name": "Product Name", + "enter_product_name": "Please enter product name", + "quantity": "Quantity", + "enter_quantity": "Please enter quantity", + "material": "Material", + "enter_material": "Please enter material", + "link": "Link", + "enter_link": "Please enter link", + "remark": "Remark", + "enter_remark": "Please enter remark", + "cancel": "Cancel", + "confirm": "Confirm", + "submitting": "Submitting...", + "tab_request": "Request Quote", + "tab_in_progress": "In Progress", + "tab_completed": "Completed", + "loading": "Loading...", + "no_more_data": "No more data", + "no_in_progress": "No inquiries in progress", + "no_completed": "No completed inquiries", + "take_photo": "Take Photo", + "choose_from_gallery": "Choose from Gallery", + "reset_camera": "Reset Camera Function", + "camera_reset": "Reset", + "camera_reset_message": "Now you can use camera function", + "upload_image_get_quote": "Get product quote via image", + "upload_image_get_quote_message": "Upload product images,\nwe will provide you with detailed quotes\nand product information.", + "take_photo_or_upload": "Take Photo or Upload", + "fetch_failed": "Failed to fetch inquiry list", + "item_name": "Item Name", + "none": "None", + "more": "More" + }, + "tiktok": { + "store": "Store TikTok", + "live": "Live", + "video": "Video", + "shorts": "Shorts", + "category": "Category" + }, + "chat": { + "login_required_title": "Please Login First", + "login_required_subtitle": "Login to use chat features", + "login_now": "Login Now" + } + }, + "settings": { + "title": "Settings", + "profile": "My Profile", + "change_password": "Change Password", + "change_phone": "Change Phone Number", + "my_address": "My Address", + "feedback": "Feedback", + "privacy_policy": "Brainnel Privacy Policy", + "terms_of_use": "Terms of Use", + "clear_cache": "Clear Cache", + "language_currency": "Select Language and Currency", + "add_new_address": "Add New Address", + "address_management": "Address Management", + "set_default": "Set Default Address", + "delete": "Delete" }, "login.now": "Login Now", "login.button": "Login", @@ -300,70 +380,6 @@ "submit_order": "Submit Order", "select_payment": "Please select payment method" }, - "settings": { - "title": "Settings", - "profile": "My Profile", - "change_password": "Change Password", - "change_phone": "Change Phone Number", - "my_address": "My Address", - "feedback": "Feedback", - "privacy_policy": "Brainnel Privacy Policy", - "terms_of_use": "Terms of Use", - "clear_cache": "Clear Cache", - "language_currency": "Select Language and Currency", - "add_new_address": "Add New Address", - "address_management": "Address Management", - "set_default": "Set Default Address", - "delete": "Delete" - }, - "typingMessage": "Typing", - "defaultResponse": "Thank you for your message. Our team will get back to you shortly.", - "errorResponse": "Sorry, there was an error processing your request. Please try again later.", - "loginRequired": "Login Required", - "loginPrompt": "Please log in to access this feature. Your login will provide a better experience and allow you to track your orders.", - "login": { - "logInOrSignUp": "Log in or sign up", - "phoneNumber": "Phone number", - "enterPassword": "Please re-enter your password", - "passwordIncorrect": "Password incorrect, please confirm your password.", - "verificationCodeInfo": "We will send a verification code on your number to confirm it's you.", - "continue": "Continue", - "pleaseEnterEmail": "Please enter your e-mail address", - "forgotPassword": { - "title": "Forget your password?", - "phoneDescription": "Enter your phone number below, and we'll send you a 6-digit password reset code.", - "emailDescription": "Enter your email address below, and we'll send you a 6-digit password reset code.", - "submit": "Submit", - "invalidPhone": "Invalid phone number", - "requiresDigits": "Requires digits", - "invalidEmail": "Invalid email address" - }, - "verification": { - "title": "Enter the verification code", - "description": "Please check your phone now! Enter the 6-digit password reset code sent to", - "expiration": "The code expires after 2 hours.", - "incorrect": "Incorrect verification code", - "resend": "Resend code", - "didntReceive": "Didn't receive the code?", - "helpPoint1": "1. Make sure your phone number is correct.", - "helpPoint2": "2. Please check if your phone can receive SMS messages." - }, - "resetPassword": { - "title": "Setting a password", - "enterPassword": "Please enter your password", - "confirmPassword": "Please reconfirm your password", - "required": "Password is required", - "minLength": "Password must be at least 6 characters", - "confirmRequired": "Please confirm your password", - "mismatch": "Passwords do not match", - "failed": "Failed to reset password. Please try again.", - "error": "An error occurred. Please try again.", - "submit": "Submit" - }, - "loginRequired": "Login Required", - "loginPrompt": "Please log in to access this feature. Your login will provide a better experience and allow you to track your orders.", - "loginNow": "Login Now" - }, "homePage":{ "searchPlaceholder": "Search products", "shipping": "Shipping Calculator", @@ -455,71 +471,9 @@ "yes": "Yes", "modify_quantity": "Modify Quantity", "cancel": "Cancel", - "confirm": "Confirm" - }, - "banner": { - "today": "Today", - "shipping": { - "fee": "Shipping Fee", - "calculation_result": "Shipping Fee Calculation Result", - "estimated_fee": "Estimated Shipping Fee", - "confirm": "Confirm", - "maritime": "Sea Shipping", - "airway": "Air Shipping", - "select_country": "Select Country", - "parcel_volume": "Parcel Volume", - "enter_parcel_volume": "Please enter parcel volume", - "calculate_shipping": "Calculate Shipping Fee", - "select_country_modal_title": "Select Country", - "close": "Close" - }, - "inquiry": { - "title": "Quote Request", - "upload_image": "Upload Image", - "hint": "Please upload an image", - "quantity_required": "Please enter quantity", - "error": "Error", - "submission_failed": "Submission failed, please try again", - "image_inquiry": "Image Inquiry", - "product_name": "Product Name", - "enter_product_name": "Please enter product name", - "quantity": "Quantity", - "enter_quantity": "Please enter quantity", - "material": "Material", - "enter_material": "Please enter material", - "link": "Link", - "enter_link": "Please enter link", - "remark": "Remark", - "enter_remark": "Please enter remark", - "cancel": "Cancel", - "confirm": "Confirm", - "submitting": "Submitting...", - "tab_request": "Request Quote", - "tab_in_progress": "In Progress", - "tab_completed": "Completed", - "loading": "Loading...", - "no_more_data": "No more data", - "no_in_progress": "No inquiries in progress", - "no_completed": "No completed inquiries", - "take_photo": "Take Photo", - "choose_from_gallery": "Choose from Gallery", - "reset_camera": "Reset Camera Function", - "camera_reset": "Reset", - "camera_reset_message": "Now you can use camera function", - "upload_image_get_quote": "Get product quote via image", - "upload_image_get_quote_message": "Upload product images,\nwe will provide you with detailed quotes\nand product information.", - "take_photo_or_upload": "Take Photo or Upload", - "fetch_failed": "Failed to fetch inquiry list", - "item_name": "Item Name", - "none": "None", - "more": "More" - }, - "tiktok": { - "store": "Store TikTok", - "live": "Live", - "video": "Video", - "shorts": "Shorts", - "category": "Category" - } + "confirm": "Confirm", + "login_required_title": "Please Login First", + "login_required_subtitle": "Login to use cart features", + "login_now": "Login Now" } } \ No newline at end of file diff --git a/app/locales/fr/translation.json b/app/locales/fr/translation.json index 7873538..794aa61 100644 --- a/app/locales/fr/translation.json +++ b/app/locales/fr/translation.json @@ -96,12 +96,19 @@ "popularCategories": "Catégories populaires", "my":"Mon", "categories":"Catégories", - "chat":"Chat", - "customerServiceChat":"Chat client", - "productChat":"Chat produit", - "notificationChat":"Chat notifications", - "inputMessage":"Message", - "send":"Envoyer", + "chat": { + "customer_service": "Service Client", + "product_support": "Support Produit", + "notifications": "Notifications", + "input_message": "Tapez un message...", + "send": "Envoyer", + "typing_message": "En cours de frappe", + "default_response": "Merci pour votre message. Notre équipe vous répondra sous peu.", + "error_response": "Désolé, une erreur s'est produite lors du traitement de votre demande. Veuillez réessayer plus tard.", + "login_required_title": "Veuillez vous connecter d'abord", + "login_required_subtitle": "Connectez-vous pour utiliser les fonctionnalités de chat", + "login_now": "Se connecter maintenant" + }, "searchProducts":"Rechercher des produits", "recommendations":"Recommandations", "sales":"ventes", @@ -392,6 +399,11 @@ }, "searchCountry": "Rechercher un pays", "noCountriesFound": "Aucun pays trouvé", + "typingMessage": "En cours de frappe", + "defaultResponse": "Merci pour votre message. Notre équipe vous répondra sous peu.", + "errorResponse": "Désolé, une erreur s'est produite lors du traitement de votre demande. Veuillez réessayer plus tard.", + "loginRequired": "Connexion Requise", + "loginPrompt": "Veuillez vous connecter pour accéder à cette fonctionnalité. Votre connexion vous offrira une meilleure expérience et vous permettra de suivre vos commandes.", "balance": { "screen": { "title": "Mon Solde", @@ -450,7 +462,10 @@ "yes": "Oui", "modify_quantity": "Modifier la quantité", "cancel": "Annuler", - "confirm": "Confirmer" + "confirm": "Confirmer", + "login_required_title": "Veuillez vous connecter d'abord", + "login_required_subtitle": "Connectez-vous pour utiliser les fonctionnalités du panier", + "login_now": "Se connecter maintenant" }, "banner": { "today": "Aujourd'hui", diff --git a/app/locales/zh/translation.json b/app/locales/zh/translation.json index 6bee015..78a5c63 100644 --- a/app/locales/zh/translation.json +++ b/app/locales/zh/translation.json @@ -93,6 +93,27 @@ "noMoreProducts": "没有更多商品", "chatNow": "立即聊天", "popularCategories": "热门分类", + "my": "我的", + "categories": "分类", + "chat": { + "customer_service": "客服聊天", + "product_support": "产品咨询", + "notifications": "消息通知", + "input_message": "输入消息...", + "send": "发送", + "typing_message": "正在输入", + "default_response": "感谢您的消息。我们的团队会尽快回复您。", + "error_response": "抱歉,处理您的请求时出现错误。请稍后再试。", + "login_required_title": "请先登录", + "login_required_subtitle": "登录后即可使用聊天功能", + "login_now": "立即登录" + }, + "recommendations": "推荐", + "sales": "销量", + "productPicture": "商品图片", + "no_stock": "缺货", + "loginRequired": "需要登录", + "loginPrompt": "请登录以访问此功能。登录后您将获得更好的体验并能跟踪您的订单。", "setting": { "title": "选择语言和货币", "country": "国家", @@ -152,6 +173,9 @@ "yes": "是", "modify_quantity": "修改数量", "cancel": "取消", - "confirm": "确认" + "confirm": "确认", + "login_required_title": "请先登录", + "login_required_subtitle": "登录后即可使用购物车功能", + "login_now": "立即登录" } } diff --git a/app/screens/CartScreen.tsx b/app/screens/CartScreen.tsx index d34e034..86924db 100644 --- a/app/screens/CartScreen.tsx +++ b/app/screens/CartScreen.tsx @@ -62,6 +62,11 @@ export const CartScreen = () => { // 货币转换函数 const convertCurrency = async () => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + console.log(country_code); // 如果 country_code 是 255,不需要转换 @@ -140,6 +145,11 @@ export const CartScreen = () => { index1: number, index: number | null ) => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + if (index != null) { // 处理子类 SKU 的选择 const data = { @@ -268,6 +278,11 @@ export const CartScreen = () => { }; const getCart = async () => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + const res = await getCartList(); // 修正父商品的选择状态,确保与子商品状态一致 @@ -293,6 +308,11 @@ export const CartScreen = () => { }; const selectAllHandel = () => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + const newAllSelected = !allSelected; setAllSelected(newAllSelected); @@ -345,70 +365,78 @@ export const CartScreen = () => { cartItemId: number, cartId1: number ) => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + // 设置要删除的商品信息并显示确认对话框 setItemToDelete({ cartId, cartItemId, cartId1 }); setDeleteModalVisible(true); }; const confirmDelete = () => { - if (itemToDelete) { - const { cartId, cartItemId, cartId1 } = itemToDelete; - console.log(itemToDelete); + // 如果用户未登录,直接返回 + if (!user_id || !itemToDelete) { + return; + } + + const { cartId, cartItemId, cartId1 } = itemToDelete; + console.log(itemToDelete); - // 找到要删除的商品,从总价中减去 - const itemToRemove = cartList.find((item) => item.cart_id === cartId); - if (itemToRemove) { - const skuToRemove = itemToRemove.skus.find( - (sku) => sku.cart_item_id === cartItemId + // 找到要删除的商品,从总价中减去 + const itemToRemove = cartList.find((item) => item.cart_id === cartId); + if (itemToRemove) { + const skuToRemove = itemToRemove.skus.find( + (sku) => sku.cart_item_id === cartItemId + ); + if (skuToRemove && skuToRemove.selected === 1) { + // 如果商品是已选中状态,从总价中减去 + setTotalAmount((prev) => + Number((prev - skuToRemove.price * skuToRemove.quantity).toFixed(2)) ); - if (skuToRemove && skuToRemove.selected === 1) { - // 如果商品是已选中状态,从总价中减去 - setTotalAmount((prev) => - Number((prev - skuToRemove.price * skuToRemove.quantity).toFixed(2)) - ); - } } + } - setCartList((prev) => { - // 先找到要删除的商品 - const itemToUpdate = prev.find((item) => item.cart_id === cartId); - if (!itemToUpdate) return prev; + setCartList((prev) => { + // 先找到要删除的商品 + const itemToUpdate = prev.find((item) => item.cart_id === cartId); + if (!itemToUpdate) return prev; - // 计算删除后的 SKU 数量 - const remainingSkus = itemToUpdate.skus.filter( - (sku) => sku.cart_item_id !== cartItemId - ); + // 计算删除后的 SKU 数量 + const remainingSkus = itemToUpdate.skus.filter( + (sku) => sku.cart_item_id !== cartItemId + ); - // 如果删除后没有 SKU 了,则删除整个商品 - if (remainingSkus.length === 0) { - deleteCartItem(cartId1, cartItemId).then((res) => { - console.log(res); - }); - const newCartList = prev.filter((item) => item.cart_id !== cartId); - - // 检查是否没有商品了,如果没有则将全选设为false - if (newCartList.length === 0) { - setAllSelected(false); - } + // 如果删除后没有 SKU 了,则删除整个商品 + if (remainingSkus.length === 0) { + deleteCartItem(cartId1, cartItemId).then((res) => { + console.log(res); + }); + const newCartList = prev.filter((item) => item.cart_id !== cartId); - return newCartList; - } else { - // 否则只删除特定的 SKU - return prev.map((item) => { - if (item.cart_id === cartId) { - return { - ...item, - skus: remainingSkus, - }; - } - return item; - }); + // 检查是否没有商品了,如果没有则将全选设为false + if (newCartList.length === 0) { + setAllSelected(false); } - }); - deleteCartItem(cartId, cartItemId).then((res) => { - console.log(res); - }); - } + + return newCartList; + } else { + // 否则只删除特定的 SKU + return prev.map((item) => { + if (item.cart_id === cartId) { + return { + ...item, + skus: remainingSkus, + }; + } + return item; + }); + } + }); + deleteCartItem(cartId, cartItemId).then((res) => { + console.log(res); + }); // 关闭确认对话框 setDeleteModalVisible(false); @@ -426,9 +454,12 @@ export const CartScreen = () => { // }, []); useFocusEffect( useCallback(() => { - getCart(); - convertCurrency(); // 添加货币转换调用 - }, []) + // 只有在用户已登录时才执行API调用 + if (user_id) { + getCart(); + convertCurrency(); // 添加货币转换调用 + } + }, [user_id]) ); const gotoOrder = () => { @@ -498,6 +529,11 @@ export const CartScreen = () => { cartItemId: number, newQuantity: number ) => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + try { // 更新本地状态 setCartList((prev) => { @@ -538,6 +574,11 @@ export const CartScreen = () => { cartItemId: number, currentQuantity: number ) => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + if (currentQuantity > 1) { updateQuantity(cartId, cartItemId, currentQuantity - 1); } @@ -549,13 +590,21 @@ export const CartScreen = () => { cartItemId: number, currentQuantity: number ) => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + updateQuantity(cartId, cartItemId, currentQuantity + 1); }; // 处理数量输入框确认 const handleQuantityInputConfirm = () => { - if (!editingItem) return; - + // 如果用户未登录,直接返回 + if (!user_id || !editingItem) { + return; + } + const newQuantity = parseInt(quantityInput); if (isNaN(newQuantity) || newQuantity < 1) { Alert.alert(t("cart.notice"), t("cart.enter_valid_quantity")); @@ -574,11 +623,21 @@ export const CartScreen = () => { cartItemId: number, currentQuantity: number ) => { + // 如果用户未登录,直接返回 + if (!user_id) { + return; + } + setEditingItem({ cartId, cartItemId, currentQuantity }); setQuantityInput(currentQuantity.toString()); setQuantityInputVisible(true); }; + // 添加导航到登录页面的函数 + const goToLogin = () => { + navigation.navigate("Login"); // 假设登录页面的路由名为 "Login" + }; + return ( @@ -605,6 +664,7 @@ export const CartScreen = () => { onPress={() => toggleSelection(String(item.cart_id), index1, null) } + disabled={!user_id} > {item.selected === 1 ? ( @@ -632,6 +692,7 @@ export const CartScreen = () => { {item.skus.map((sku, index) => ( ( { item.cart_id ) } + disabled={!user_id} > { > { - navigation.navigate("ProductDetail", { - offer_id: item.offer_id, - searchKeyword: item.subject, - price: sku.price, - }); + if (user_id) { + navigation.navigate("ProductDetail", { + offer_id: item.offer_id, + searchKeyword: item.subject, + price: sku.price, + }); + } }} style={[ styles.productCardContainer5, @@ -672,12 +736,13 @@ export const CartScreen = () => { - toggleSelection( + user_id && toggleSelection( String(sku.cart_item_id), index1, index ) } + disabled={!user_id} > {sku.selected === 1 ? ( @@ -753,12 +818,13 @@ export const CartScreen = () => { { borderRightWidth: 0 }, ]} onPress={() => - handleDecreaseQuantity( + user_id && handleDecreaseQuantity( item.cart_id, sku.cart_item_id, sku.quantity ) } + disabled={!user_id} > { - handleQuantityPress( + user_id && handleQuantityPress( item.cart_id, sku.cart_item_id, sku.quantity ) } + disabled={!user_id} > {sku.quantity} @@ -790,12 +857,13 @@ export const CartScreen = () => { { borderLeftWidth: 0, marginLeft: 0 }, ]} onPress={() => - handleIncreaseQuantity( + user_id && handleIncreaseQuantity( item.cart_id, sku.cart_item_id, sku.quantity ) } + disabled={!user_id} > { - + {allSelected ? ( @@ -872,8 +940,9 @@ export const CartScreen = () => { {currency} { + + {/* 未登录遮罩 */} + {!user_id && ( + + + + + 🔒 + + + {t("cart.login_required_title", "请先登录")} + + + {t("cart.login_required_subtitle", "登录后即可使用购物车功能")} + + + + {t("cart.login_now", "立即登录")} + + + + + + )} { {/* 数量输入弹窗 */} setQuantityInputVisible(false)} @@ -1641,4 +1737,82 @@ const styles = StyleSheet.create({ flexDirection: "column", justifyContent: "center", }, + disabledButton: { + opacity: 0.6, + }, + loginOverlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(255, 255, 255, 0.9)', + backdropFilter: 'blur(10px)', // iOS 毛玻璃效果 + justifyContent: 'center', + alignItems: 'center', + zIndex: 1000, + }, + blurContainer: { + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: Platform.OS === 'android' ? 'rgba(255, 255, 255, 0.95)' : 'transparent', + }, + loginPromptContainer: { + backgroundColor: 'white', + borderRadius: 20, + padding: 40, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + maxWidth: '80%', + }, + loginIcon: { + width: 80, + height: 80, + marginBottom: 20, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(255, 81, 0, 0.1)', + borderRadius: 40, + }, + loginIconText: { + fontSize: 40, + fontWeight: 'bold', + color: '#FF5100', + }, + loginPromptTitle: { + fontSize: fontSize(24), + fontWeight: '700', + color: '#333', + marginBottom: 10, + textAlign: 'center', + }, + loginPromptSubtitle: { + fontSize: fontSize(16), + color: '#666', + marginBottom: 30, + textAlign: 'center', + lineHeight: fontSize(22), + }, + loginButton: { + backgroundColor: '#FF5100', + paddingHorizontal: 40, + paddingVertical: 15, + borderRadius: 25, + minWidth: 160, + }, + loginButtonText: { + color: 'white', + fontSize: fontSize(18), + fontWeight: '700', + textAlign: 'center', + }, }); diff --git a/app/screens/ChatScreen.tsx b/app/screens/ChatScreen.tsx index 2378364..860049d 100644 --- a/app/screens/ChatScreen.tsx +++ b/app/screens/ChatScreen.tsx @@ -7,7 +7,6 @@ import { TextInput, TouchableOpacity, FlatList, - KeyboardAvoidingView, Platform, ImageBackground, StatusBar, @@ -20,7 +19,8 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { chatService } from "../services/api/chat"; import useUserStore from "../store/user"; import AsyncStorage from "@react-native-async-storage/async-storage"; -import { useTranslation } from 'react-i18next'; +import { t } from "../i18n"; + interface Message { id?: string; mimetype: string; @@ -33,31 +33,28 @@ interface Message { isMe?: boolean; timestamp?: Date; } + type TabType = "customer" | "product" | "notification"; + type RootStackParamList = { Login: undefined; // other screens... }; + export const ChatScreen = () => { const [messages, setMessages] = useState([]); const [inputText, setInputText] = useState(""); const [activeTab, setActiveTab] = useState("customer"); - const [loginModalVisible, setLoginModalVisible] = useState(false); const [country, setCountry] = useState(""); // Store the country code - const { t } = useTranslation(); const { user } = useUserStore(); const navigation = useNavigation>(); // Add FlatList ref for auto-scrolling const flatListRef = useRef(null); - // Add animation values - const [fadeAnim] = useState(new Animated.Value(0)); - const [slideAnim] = useState(new Animated.Value(300)); - // Auto-scroll to bottom when messages change useEffect(() => { - if (messages.length > 0) { + if (messages.length > 0 && user.user_id) { setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); }, 100); @@ -78,56 +75,25 @@ export const ChatScreen = () => { } }; - getCountry(); - }, []); - // Check if user is logged in - useEffect(() => { - if (!user.user_id) { - setLoginModalVisible(true); - // Animate modal appearance - Animated.parallel([ - Animated.timing(fadeAnim, { - toValue: 1, - duration: 300, - useNativeDriver: true, - }), - Animated.spring(slideAnim, { - toValue: 0, - tension: 50, - friction: 9, - useNativeDriver: true, - }) - ]).start(); - } else { - setLoginModalVisible(false); + // Only get country if user is logged in + if (user.user_id) { + getCountry(); } }, [user.user_id]); - const handleCloseModal = () => { - // Animate modal exit - Animated.parallel([ - Animated.timing(fadeAnim, { - toValue: 0, - duration: 200, - useNativeDriver: true, - }), - Animated.timing(slideAnim, { - toValue: 300, - duration: 200, - useNativeDriver: true, - }) - ]).start(() => { - setLoginModalVisible(false); - }); - }; - const handleGoToLogin = () => { - handleCloseModal(); - // Navigate to login after modal is closed - setTimeout(() => { - navigation.navigate('Login'); - }, 200); + + // 添加导航到登录页面的函数 + const goToLogin = () => { + navigation.navigate("Login"); }; + const sendMessage = () => { + // 如果用户未登录,直接返回 + if (!user.user_id) { + return; + } + if (inputText.trim() === "") return; + const newMessage: Message = { mimetype: "text/plain", userWs: "unknown", @@ -140,6 +106,7 @@ export const ChatScreen = () => { timestamp: new Date(), id: Date.now().toString(), // Add unique id for keyExtractor }; + // Extract only the properties that chatService.sendMessage expects const chatServiceMessage = { type: newMessage.type, @@ -150,6 +117,7 @@ export const ChatScreen = () => { body: newMessage.body, text: newMessage.text }; + // Add user message to the chat UI setMessages([...messages, newMessage]); setInputText(""); @@ -162,7 +130,7 @@ export const ChatScreen = () => { app_id: "system", country: user.country_code, body: "", - text: `${t('typingMessage')}...`, + text: `${t('chat.typing_message')}...`, type: "chat", isMe: false, timestamp: new Date(), @@ -177,6 +145,7 @@ export const ChatScreen = () => { const data = { newMessage:chatServiceMessage, } + // Send actual message to API chatService.sendMessage(data) .then(response => { @@ -192,7 +161,7 @@ export const ChatScreen = () => { app_id: "system", country: user.country_code, body: "", - text: response?.reply || t('defaultResponse'), + text: response?.reply || t('chat.default_response'), type: "chat", isMe: false, timestamp: new Date(), @@ -214,7 +183,7 @@ export const ChatScreen = () => { app_id: "system", country: user.country_code, body: "", - text: t('errorResponse'), + text: t('chat.error_response'), type: "chat", isMe: false, timestamp: new Date(), @@ -225,10 +194,12 @@ export const ChatScreen = () => { }); }); }; + // Generate a unique key for each message const keyExtractor = (item: Message, index: number): string => { return item.id || index.toString(); }; + const renderMessage = ({ item }: { item: Message }) => ( { ); + const renderTabContent = () => { switch (activeTab) { case "customer": @@ -257,6 +229,7 @@ export const ChatScreen = () => { keyExtractor={keyExtractor} contentContainerStyle={styles.messageList} showsVerticalScrollIndicator={false} + scrollEnabled={!!user.user_id} /> ); @@ -270,6 +243,7 @@ export const ChatScreen = () => { keyExtractor={keyExtractor} contentContainerStyle={styles.messageList} showsVerticalScrollIndicator={false} + scrollEnabled={!!user.user_id} /> ); @@ -283,11 +257,13 @@ export const ChatScreen = () => { keyExtractor={keyExtractor} contentContainerStyle={styles.messageList} showsVerticalScrollIndicator={false} + scrollEnabled={!!user.user_id} /> ); } }; + return ( @@ -301,7 +277,8 @@ export const ChatScreen = () => { setActiveTab("customer")} + onPress={() => user.user_id && setActiveTab("customer")} + disabled={!user.user_id} > { activeTab === "customer" && styles.activeTabText, ]} > - {t('customerServiceChat')} + {t('chat.customer_service')} setActiveTab("product")} + onPress={() => user.user_id && setActiveTab("product")} + disabled={!user.user_id} > { activeTab === "product" && styles.activeTabText, ]} > - {t('productChat')} + {t('chat.product_support')} setActiveTab("notification")} + onPress={() => user.user_id && setActiveTab("notification")} + disabled={!user.user_id} > { activeTab === "notification" && styles.activeTabText, ]} > - {t('notificationChat')} + {t('chat.notifications')} {renderTabContent()} - - {t('send')} + + {t('chat.send')} - {/* Login Modal */} - - - - - - - - - - - + {/* 未登录遮罩 */} + {!user.user_id && ( + + + + + 💬 + + + {t("chat.login_required_title", "请先登录")} + + + {t("chat.login_required_subtitle", "登录后即可使用聊天功能")} + + + + {t("chat.login_now", "立即登录")} + + - {t('loginRequired')} - - {t('pleaseLoginToChat')} - - - {t('loginNow')} - - - - + + + )} ); }; + const styles = StyleSheet.create({ safeArea: { flex: 1, @@ -522,75 +474,85 @@ const styles = StyleSheet.create({ color: "white", fontSize: 16, }, - // Modal styles - modalContainer: { - flex: 1, - justifyContent: 'flex-end', - }, - modalOverlay: { - ...StyleSheet.absoluteFillObject, - backgroundColor: 'rgba(0, 0, 0, 0.5)', - }, - modalOverlayTouch: { - flex: 1, - }, - modalContent: { - backgroundColor: '#fff', - borderTopLeftRadius: 24, - borderTopRightRadius: 24, - paddingHorizontal: 20, - paddingBottom: Platform.OS === 'ios' ? 40 : 20, - position: 'relative', - }, - closeButton: { + loginOverlay: { position: 'absolute', - top: 20, - right: 20, - width: 24, - height: 24, + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: 'rgba(255, 255, 255, 0.9)', + backdropFilter: 'blur(10px)', // iOS 毛玻璃效果 justifyContent: 'center', alignItems: 'center', - zIndex: 1, - }, - closeButtonText: { - fontSize: 20, - color: '#999', + zIndex: 1000, }, - modalHeader: { + blurContainer: { + width: '100%', + height: '100%', + justifyContent: 'center', alignItems: 'center', - paddingTop: 12, - paddingBottom: 20, + backgroundColor: Platform.OS === 'android' ? 'rgba(255, 255, 255, 0.95)' : 'transparent', }, - modalIndicator: { - width: 40, - height: 4, - backgroundColor: '#E0E0E0', - borderRadius: 2, + loginPromptContainer: { + backgroundColor: 'white', + borderRadius: 20, + padding: 40, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + maxWidth: '80%', }, - modalTitle: { - fontSize: 20, - fontWeight: '600', - color: '#000', + loginIcon: { + width: 80, + height: 80, + marginBottom: 20, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 122, 108, 0.1)', + borderRadius: 40, + }, + loginIconText: { + fontSize: 40, + fontWeight: 'bold', + color: '#007a6c', + }, + loginPromptTitle: { + fontSize: 24, + fontWeight: '700', + color: '#333', + marginBottom: 10, textAlign: 'center', - marginBottom: 12, }, - modalText: { + loginPromptSubtitle: { fontSize: 16, color: '#666', + marginBottom: 30, textAlign: 'center', - marginBottom: 24, lineHeight: 22, }, - modalButton: { + loginButton: { backgroundColor: '#007a6c', - height: 50, + paddingHorizontal: 40, + paddingVertical: 15, borderRadius: 25, - justifyContent: 'center', - alignItems: 'center', + minWidth: 160, }, - modalButtonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', + loginButtonText: { + color: 'white', + fontSize: 18, + fontWeight: '700', + textAlign: 'center', + }, + disabledInput: { + opacity: 0.6, + }, + disabledButton: { + opacity: 0.6, }, }); diff --git a/app/screens/loginList/index.tsx b/app/screens/loginList/index.tsx index fda8f23..beac8a2 100644 --- a/app/screens/loginList/index.tsx +++ b/app/screens/loginList/index.tsx @@ -15,22 +15,35 @@ import { useTranslation } from "react-i18next"; import { useNavigation } from "@react-navigation/native"; import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import fontSize from "../../utils/fontsizeUtils"; -import { - GoogleSignin, - GoogleSigninButton, - statusCodes, -} from '@react-native-google-signin/google-signin'; - import EmailLoginModal from "./EmailLoginModal"; import PhoneLoginModal from "./PhoneLoginModal"; -// 配置 Google 登录 -GoogleSignin.configure({ - iosClientId: "YOUR_IOS_CLIENT_ID_HERE.apps.googleusercontent.com", // iOS CLIENT_ID - webClientId: "529750832779-d0jpf2493plgm8eutkmk9e9t3rhkta8b.apps.googleusercontent.com", // Web CLIENT_ID (用于Android) - scopes: ['profile', 'email'], - offlineAccess: true, -}); +// 条件导入Google Sign-in(仅在生产模式) +let GoogleSignin: any = null; +let GoogleSigninButton: any = null; +let statusCodes: any = null; + +const isDevelopment = __DEV__; // 开发模式检测 + +if (!isDevelopment) { + try { + const googleSigninModule = require('@react-native-google-signin/google-signin'); + GoogleSignin = googleSigninModule.GoogleSignin; + GoogleSigninButton = googleSigninModule.GoogleSigninButton; + statusCodes = googleSigninModule.statusCodes; + + // 配置 Google 登录 + GoogleSignin.configure({ + iosClientId: "YOUR_IOS_CLIENT_ID_HERE.apps.googleusercontent.com", // iOS CLIENT_ID + webClientId: "529750832779-d0jpf2493plgm8eutkmk9e9t3rhkta8b.apps.googleusercontent.com", // Web CLIENT_ID (用于Android) + scopes: ['profile', 'email'], + offlineAccess: true, + forceCodeForRefreshToken: true, + }); + } catch (error) { + console.log('Google Sign-in模块未安装或配置错误:', error); + } +} type RootStackParamList = { Login: undefined; @@ -93,6 +106,32 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => { // 处理谷歌登录 const handleGoogleLogin = async () => { try { + // 开发模式下的模拟登录 + if (isDevelopment) { + console.log('开发模式:模拟Google登录成功'); + const mockUserInfo = { + user: { + id: 'dev_user_123', + name: 'Test User', + email: 'test@example.com', + photo: null, + }, + }; + console.log('模拟用户信息:', mockUserInfo); + + // 这里可以处理登录成功后的逻辑 + // 比如导航到主页面或保存用户信息 + // navigation.navigate("MainTabs", { screen: "Home" }); + + return; + } + + // 生产模式下的真实Google登录 + if (!GoogleSignin) { + console.log('Google Sign-in模块未配置'); + return; + } + await GoogleSignin.hasPlayServices(); const userInfo = await GoogleSignin.signIn(); console.log('Google 登录成功:', userInfo); @@ -103,11 +142,18 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => { } catch (error: any) { console.log('Google 登录错误:', error); - if (error.code === statusCodes.SIGN_IN_CANCELLED) { + + // 开发模式下的错误处理 + if (isDevelopment) { + console.log('开发模式:忽略Google登录错误'); + return; + } + + if (statusCodes && error.code === statusCodes.SIGN_IN_CANCELLED) { console.log('用户取消登录'); - } else if (error.code === statusCodes.IN_PROGRESS) { + } else if (statusCodes && error.code === statusCodes.IN_PROGRESS) { console.log('登录正在进行中'); - } else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) { + } else if (statusCodes && error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) { console.log('Play Services 不可用'); } else { console.log('其他错误:', error.message); @@ -222,7 +268,7 @@ export const LoginScreen = ({ onClose, isModal }: LoginScreenProps) => { /> - {t("continueWithGoogle")} + {isDevelopment ? '🧪 ' + t("continueWithGoogle") + ' (测试模式)' : t("continueWithGoogle")}