Browse Source

购物车最小起订量

main
Mac 2 weeks ago
parent
commit
ccc797b1f7
  1. 458
      app/screens/CartScreen.tsx
  2. 7
      app/screens/loginList/EmailLoginModal.tsx
  3. 7
      app/screens/loginList/PhoneLoginModal.tsx
  4. 14
      app/screens/previewOrder/ShippingFee.tsx
  5. 112
      app/screens/productStatus/Status.tsx

458
app/screens/CartScreen.tsx

@ -490,6 +490,22 @@ export const CartScreen = () => {
return;
}
// 检查每个商品组的最小起订量
for (const item of cartList) {
// 检查该商品组是否有选中的SKU
const hasSelectedSku = item.skus.some(sku => sku.selected === 1);
if (hasSelectedSku) {
const currentGroupTotal = calculateProductGroupTotalQuantity(item.cart_id);
if (currentGroupTotal < item.min_order_quantity) {
Alert.alert(
t("cart.notice"),
`${getSubjectTransLanguage(item)} ${t("cart.min_order")}${item.min_order_quantity}${t("cart.pieces")}${t("cart.current_quantity", "当前数量")}${currentGroupTotal}${t("cart.pieces")}`
);
return;
}
}
}
// 检查最低订单金额
if (country_code !== 225) {
// 只有当 country_code 不是 255 时才进行最低订单金额检查
@ -578,6 +594,14 @@ export const CartScreen = () => {
setMinQuantityModalVisible(true);
};
// 计算同一商品组的总数量
const calculateProductGroupTotalQuantity = (cartId: number) => {
const product = cartList.find(item => item.cart_id === cartId);
if (!product) return 0;
return product.skus.reduce((total, sku) => total + sku.quantity, 0);
};
// 处理减少数量
const handleDecreaseQuantity = (
cartId: number,
@ -590,7 +614,11 @@ export const CartScreen = () => {
return;
}
if (currentQuantity > minOrderQuantity) {
// 计算减少1个数量后,该商品组的总数量
const currentGroupTotal = calculateProductGroupTotalQuantity(cartId);
const newGroupTotal = currentGroupTotal - 1;
if (newGroupTotal >= minOrderQuantity) {
updateQuantity(cartId, cartItemId, currentQuantity - 1);
} else {
showMinQuantityModal(`${t("cart.notice")}${t("cart.min_order")}${minOrderQuantity}${t("cart.pieces")}`);
@ -625,9 +653,16 @@ export const CartScreen = () => {
}
const { cartId, cartItemId } = editingItem;
const minOrderQuantity = cartList.find(item => item.cart_id === cartId)?.min_order_quantity || 1;
const product = cartList.find(item => item.cart_id === cartId);
const minOrderQuantity = product?.min_order_quantity || 1;
// 计算修改数量后,该商品组的总数量
const currentGroupTotal = calculateProductGroupTotalQuantity(cartId);
const currentSkuQuantity = product?.skus.find(sku => sku.cart_item_id === cartItemId)?.quantity || 0;
const quantityDifference = newQuantity - currentSkuQuantity;
const newGroupTotal = currentGroupTotal + quantityDifference;
if (newQuantity < minOrderQuantity) {
if (newGroupTotal < minOrderQuantity) {
showMinQuantityModal(`${t("cart.notice")}${t("cart.min_order")}${minOrderQuantity}${t("cart.pieces")}`);
} else {
updateQuantity(cartId, cartItemId, newQuantity);
@ -683,225 +718,231 @@ export const CartScreen = () => {
{/* <Text style={styles.shoppingCartTitle}>Panier (5)</Text> */}
</View>
{cartList.map((item, index1) => (
<View style={styles.productCardListing} key={item.cart_id}>
<View style={styles.productCardContainer5}>
<View style={styles.svgContainer1}>
<TouchableOpacity
onPress={() =>
toggleSelection(String(item.cart_id), index1, null)
}
disabled={!user_id}
>
<View style={[styles.iconContainer]}>
{item.selected === 1 ? (
<OrangeCircleIcon size={fontSize(24)} />
) : (
<CircleOutlineIcon size={fontSize(24)} />
)}
</View>
</TouchableOpacity>
</View>
<Image
source={{ uri: item.product_image }}
style={styles.imageThumbnail}
/>
<View style={styles.productInfoContainer2}>
<Text
style={styles.casualTextSnippet}
numberOfLines={2}
ellipsizeMode="tail"
>
{getSubjectTransLanguage(item)}
</Text>
<Text style={styles.productDetailsTextStyle1}>
{t("cart.min_order")}: {item.min_order_quantity}
{t("cart.pieces")}
</Text>
</View>
</View>
{item.skus.map((sku, index) => {
return (
<Swipeable
key={sku.cart_item_id}
enabled={!!user_id}
renderRightActions={() => (
<TouchableOpacity
style={{
backgroundColor: "#ff5217",
justifyContent: "center",
alignItems: "center",
width: 80,
}}
onPress={() => handleDeleteSku(
item.cart_id,
sku.cart_item_id,
item.cart_id
)}
disabled={!user_id}
>
<Text
style={{ color: "white", fontWeight: "bold" }}
>
{t("cart.delete")}
</Text>
</TouchableOpacity>
)}
>
<View key={item.cart_id}>
<View style={styles.productCardListing}>
<View style={styles.productCardContainer5}>
<View style={styles.svgContainer1}>
<TouchableOpacity
onPress={() => {
if (user_id) {
navigation.navigate("ProductDetail", {
offer_id: item.offer_id,
searchKeyword: item.subject,
price: sku.price,
});
}
} }
style={[
styles.productCardContainer5,
styles.productCardContainer4,
]}
onPress={() =>
toggleSelection(String(item.cart_id), index1, null)
}
disabled={!user_id}
>
<View style={styles.svgContainer1}>
<View style={[styles.iconContainer]}>
{item.selected === 1 ? (
<OrangeCircleIcon size={fontSize(24)} />
) : (
<CircleOutlineIcon size={fontSize(24)} />
)}
</View>
</TouchableOpacity>
</View>
<Image
source={{ uri: item.product_image }}
style={styles.imageThumbnail}
/>
<View style={styles.productInfoContainer2}>
<Text
style={styles.casualTextSnippet}
numberOfLines={2}
ellipsizeMode="tail"
>
{getSubjectTransLanguage(item)}
</Text>
<Text style={styles.productDetailsTextStyle1}>
{t("cart.min_order")}: {calculateProductGroupTotalQuantity(item.cart_id)}/{item.min_order_quantity}
{t("cart.pieces")}
</Text>
</View>
</View>
{item.skus.map((sku, index) => {
return (
<Swipeable
key={sku.cart_item_id}
enabled={!!user_id}
renderRightActions={() => (
<TouchableOpacity
onPress={() => user_id && toggleSelection(
String(sku.cart_item_id),
index1,
index
style={{
backgroundColor: "#ff5217",
justifyContent: "center",
alignItems: "center",
width: 80,
}}
onPress={() => handleDeleteSku(
item.cart_id,
sku.cart_item_id,
item.cart_id
)}
disabled={!user_id}
>
<View style={[styles.iconContainer]}>
{sku.selected === 1 ? (
<OrangeCircleIcon size={fontSize(24)} />
) : (
<CircleOutlineIcon size={fontSize(24)} />
)}
{/* <CircleOutlineIcon size={fontSize(24)} strokeColor={sku.selected === 1 ? "#FF5100" : "#C6C6C6"}/> */}
</View>
<Text
style={{ color: "white", fontWeight: "bold" }}
>
{t("cart.delete")}
</Text>
</TouchableOpacity>
</View>
<Image
source={{
uri: sku.attributes[0]?.sku_image_url
? sku.attributes[0]?.sku_image_url
: item.product_image,
}}
style={styles.productImageDisplayStyle} />
<View style={styles.productCardWidget1}>
{/* 1. SKU attributes at the top */}
{sku.attributes[0]?.value && (
<View style={styles.longLifeRougeStyle}>
<Text
style={styles.longLifeTextSnippet}
numberOfLines={2}
ellipsizeMode="tail"
>
{getAttributeTransLanguage(sku.attributes[0])}{" "}
{sku.attributes[1] ? "/" : ""}{" "}
{sku.attributes[1] ? getAttributeTransLanguage(sku.attributes[1]) : ""}
</Text>
</View>
)}
)}
>
<TouchableOpacity
onPress={() => {
if (user_id) {
navigation.navigate("ProductDetail", {
offer_id: item.offer_id,
searchKeyword: item.subject,
price: sku.price,
});
}
} }
style={[
styles.productCardContainer5,
styles.productCardContainer4,
]}
>
<View style={styles.svgContainer1}>
<TouchableOpacity
onPress={() => user_id && toggleSelection(
String(sku.cart_item_id),
index1,
index
)}
disabled={!user_id}
>
<View style={[styles.iconContainer]}>
{sku.selected === 1 ? (
<OrangeCircleIcon size={fontSize(24)} />
) : (
<CircleOutlineIcon size={fontSize(24)} />
)}
{/* <CircleOutlineIcon size={fontSize(24)} strokeColor={sku.selected === 1 ? "#FF5100" : "#C6C6C6"}/> */}
</View>
</TouchableOpacity>
</View>
<Image
source={{
uri: sku.attributes[0]?.sku_image_url
? sku.attributes[0]?.sku_image_url
: item.product_image,
}}
style={styles.productImageDisplayStyle} />
<View style={styles.productCardWidget1}>
{/* 1. SKU attributes at the top */}
{sku.attributes[0]?.value && (
<View style={styles.longLifeRougeStyle}>
<Text
style={styles.longLifeTextSnippet}
numberOfLines={2}
ellipsizeMode="tail"
>
{getAttributeTransLanguage(sku.attributes[0])}{" "}
{sku.attributes[1] ? "/" : ""}{" "}
{sku.attributes[1] ? getAttributeTransLanguage(sku.attributes[1]) : ""}
</Text>
</View>
)}
{/* 2. Price section - discount and actual price close together */}
<View style={styles.priceSection}>
<View style={styles.priceColumnContainer}>
{/* Discount price */}
<View style={styles.productInfoContainer1}>
<View style={styles.priceInfoContainer1}>
<Text style={styles.discountPriceLabel}>
{sku.original_price} {sku.currency}
</Text>
{/* 2. Price section - discount and actual price close together */}
<View style={styles.priceSection}>
<View style={styles.priceColumnContainer}>
{/* Discount price */}
<View style={styles.productInfoContainer1}>
<View style={styles.priceInfoContainer1}>
<Text style={styles.discountPriceLabel}>
{sku.original_price} {sku.currency}
</Text>
</View>
<View style={styles.vipContainer}>
<Image
source={require("../../assets/img/折扣VIP1 (1).png")}
style={styles.VipImg} />
<Text style={styles.discountPercentageTextStyle}>
-{((1 - vip_discount) * 100).toFixed(0)}%
</Text>
</View>
</View>
<View style={styles.vipContainer}>
<Image
source={require("../../assets/img/折扣VIP1 (1).png")}
style={styles.VipImg} />
<Text style={styles.discountPercentageTextStyle}>
-{((1 - vip_discount) * 100).toFixed(0)}%
{/* Actual price - right below discount price */}
<View style={styles.productInfoContainer4}>
<Text style={styles.productCodeLabel}>
{sku.price}
</Text>
<Text style={styles.productDetailsTextStyle}>
{sku.currency}
</Text>
</View>
</View>
{/* Actual price - right below discount price */}
<View style={styles.productInfoContainer4}>
<Text style={styles.productCodeLabel}>
{sku.price}
</Text>
<Text style={styles.productDetailsTextStyle}>
{sku.currency}
</Text>
</View>
</View>
{/* 3. Quantity controls on the right */}
<View style={styles.orderQuantityContainer}>
<TouchableOpacity
style={[
styles.svgContainer4,
{ borderRightWidth: 0 },
]}
onPress={() => user_id && handleDecreaseQuantity(
item.cart_id,
sku.cart_item_id,
sku.quantity,
item.min_order_quantity
)}
disabled={!user_id}
>
<Text
style={{
fontSize: fontSize(18),
color: "#333",
fontWeight: "500",
}}
{/* 3. Quantity controls on the right */}
<View style={styles.orderQuantityContainer}>
<TouchableOpacity
style={[
styles.svgContainer4,
{ borderRightWidth: 0 },
]}
onPress={() => user_id && handleDecreaseQuantity(
item.cart_id,
sku.cart_item_id,
sku.quantity,
item.min_order_quantity
)}
disabled={!user_id}
>
-
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.quantityLabelContainer}
onPress={() => user_id && handleQuantityPress(
item.cart_id,
sku.cart_item_id,
sku.quantity
)}
disabled={!user_id}
>
<Text style={styles.quantityText}>
{sku.quantity}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.svgContainer4,
{ borderLeftWidth: 0, marginLeft: 0 },
]}
onPress={() => user_id && handleIncreaseQuantity(
item.cart_id,
sku.cart_item_id,
sku.quantity
)}
disabled={!user_id}
>
<Text
style={{
fontSize: fontSize(18),
color: "#333",
fontWeight: "500",
}}
<Text
style={{
fontSize: fontSize(18),
color: "#333",
fontWeight: "500",
}}
>
-
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.quantityLabelContainer}
onPress={() => user_id && handleQuantityPress(
item.cart_id,
sku.cart_item_id,
sku.quantity
)}
disabled={!user_id}
>
+
</Text>
</TouchableOpacity>
<Text style={styles.quantityText}>
{sku.quantity}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.svgContainer4,
{ borderLeftWidth: 0, marginLeft: 0 },
]}
onPress={() => user_id && handleIncreaseQuantity(
item.cart_id,
sku.cart_item_id,
sku.quantity
)}
disabled={!user_id}
>
<Text
style={{
fontSize: fontSize(18),
color: "#333",
fontWeight: "500",
}}
>
+
</Text>
</TouchableOpacity>
</View>
</View>
</View>
</View>
</TouchableOpacity>
</Swipeable>
);
})}
</TouchableOpacity>
</Swipeable>
);
})}
</View>
{/* 商品组分隔线 */}
{index1 < cartList.length - 1 && (
<View style={styles.productGroupDivider} />
)}
</View>
))}
@ -924,7 +965,7 @@ export const CartScreen = () => {
<Text>{t("cart.min_order_quantity")} </Text>
<Text style={styles.tiltWarpText}>
{convertedMinAmount !== null
? `${convertedMinAmount.toFixed(2)} ${currency}`
? `${(convertedMinAmount as number).toFixed(2)} ${currency}`
: "50,000FCFA"
}
</Text>
@ -1890,4 +1931,9 @@ const styles = StyleSheet.create({
fontSize: fontSize(16),
fontWeight: '600',
},
productGroupDivider: {
height: 5,
backgroundColor: '#f0f0f0',
width: '100%',
},
});

7
app/screens/loginList/EmailLoginModal.tsx

@ -25,6 +25,7 @@ import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { ForgotEmailPassword } from "./ForgotEmailPassword";
import BackIcon from "../../components/BackIcon";
import fontSize from "../../utils/fontsizeUtils";
import { changeLanguage } from "../../i18n";
// Common email domains list
const EMAIL_DOMAINS = [
@ -183,6 +184,12 @@ const EmailLoginModal = ({ visible, onClose }: EmailLoginModalProps) => {
const data = await settingApi.postFirstLogin(221);
setSettings(data);
const user = await userApi.getProfile();
// 根据用户的语言设置切换i18n语言
if (user.language) {
await changeLanguage(user.language);
}
setUser(user);
setLoading(false);
navigation.navigate("MainTabs", { screen: "Home" });

7
app/screens/loginList/PhoneLoginModal.tsx

@ -29,6 +29,7 @@ import {ForgotPhonePassword} from "./ForgotPhonePassword";
import useBurialPointStore from "../../store/burialPoint";
import {getBurialPointData} from "../../store/burialPoint";
import fontSize from "../../utils/fontsizeUtils";
import { changeLanguage } from "../../i18n";
type RootStackParamList = {
Login: undefined;
@ -238,6 +239,12 @@ const PhoneLoginModal = ({ visible, onClose }: PhoneLoginModalProps) => {
const data = await settingApi.postFirstLogin(selectedCountry?.country || 221);
setSettings(data);
const user = await userApi.getProfile();
// 根据用户的语言设置切换i18n语言
if (user.language) {
await changeLanguage(user.language);
}
setUser(user);
setLoading(false);
navigation.replace("MainTabs", { screen: "Home" });

14
app/screens/previewOrder/ShippingFee.tsx

@ -30,6 +30,8 @@ import useUserStore from "../../store/user";
import useBurialPointStore from "../../store/burialPoint";
import { getBurialPointData } from "../../store/burialPoint";
import { useTranslation } from "react-i18next";
import { getCurrentLanguage } from '../../i18n';
type RootStackParamList = {
ShippingFee: undefined;
@ -95,7 +97,7 @@ export const ShippingFee = () => {
state.freightForwarderAddress.other_addresses.length > 0
) {
const firstItem = state.freightForwarderAddress.other_addresses[0];
const label = firstItem.country + "|" + firstItem.city;
const label = (getCurrentLanguage() === 'fr' ? firstItem.country_name : firstItem.country_name_en) + " | " + firstItem.city;
setWarehouse(label);
setSelectedWarehouseLabel(label);
setCountryCode(firstItem.country_code);
@ -138,7 +140,7 @@ export const ShippingFee = () => {
const changeCountryHandel = async (value: string) => {
if (value && freightForwarderAddress?.other_addresses) {
const selectedWarehouse = freightForwarderAddress.other_addresses.find(
(item) => item.country + "|" + item.city === value
(item) => getCurrentLanguage() === 'fr' ? item.country_name : item.country_name_en+ " | " + item.city === value
);
setSelectedWarehouse(selectedWarehouse);
@ -335,21 +337,21 @@ export const ShippingFee = () => {
style={[
styles.optionItem,
warehouse ===
item.country + "|" + item.city &&
(getCurrentLanguage() === 'fr' ? item.country_name : item.country_name_en+ " | " + item.city) &&
styles.selectedOption,
]}
onPress={() =>
handleSelectWarehouse(
item.country_code,
item.country + "|" + item.city
(getCurrentLanguage() === 'fr' ? item.country_name : item.country_name_en) + " | " + item.city
)
}
>
<Text style={styles.optionText}>
{item.country + "|" + item.city}
{(getCurrentLanguage() === 'fr' ? item.country_name : item.country_name_en) + " | " + item.city}
</Text>
{warehouse ===
item.country + "|" + item.city && (
(getCurrentLanguage() === 'fr' ? item.country_name : item.country_name_en) + " | " + item.city && (
<Text style={styles.checkmark}></Text>
)}
</TouchableOpacity>

112
app/screens/productStatus/Status.tsx

@ -224,56 +224,64 @@ export function Status() {
}}
>
{orders?.items.map((item, index) => (
<View style={styles.orderItem} key={index}>
<View style={styles.orderStatus}>
<Text style={styles.orderStatusOrderText}>
{item.order_id}
</Text>
<Text style={styles.orderStatusText}>
{getStatus(item.status)}
</Text>
</View>
<View style={styles.orderProductList}>
{item.items.map((item, index) => (
<View style={styles.orderProductItem} key={index}>
<TouchableOpacity style={styles.orderProductItemImage}>
<Image
source={{ uri: item.sku_image }}
style={styles.orderProductItemImage}
/>
</TouchableOpacity>
<View style={styles.orderProductItemInfo}>
<Text style={styles.orderProductItemInfoName}>
{getOrderTransLanguage(item) || item.product_name_fr}
<View key={index}>
<View style={styles.orderItemContainer}>
<View style={styles.orderItem}>
<View style={styles.orderStatus}>
<Text style={styles.orderStatusOrderText}>
{item.order_id}
</Text>
<Text style={styles.orderStatusText}>
{getStatus(item.status)}
</Text>
</View>
<View style={styles.orderProductList}>
{item.items.map((item, index) => (
<View style={styles.orderProductItem} key={index}>
<TouchableOpacity style={styles.orderProductItemImage}>
<Image
source={{ uri: item.sku_image }}
style={styles.orderProductItemImage}
/>
</TouchableOpacity>
<View style={styles.orderProductItemInfo}>
<Text style={styles.orderProductItemInfoName}>
{getOrderTransLanguage(item) || item.product_name_fr}
</Text>
{item.sku_attributes?.map((attr, index) => (
<Text
style={styles.orderProductItemInfoPrice}
key={index}
>
{attr.attribute_name}:{attr.attribute_value}
</Text>
))}
</View>
</View>
))}
</View>
<View style={styles.orderProductPrice}>
<View style={styles.orderProductPriceItem}>
<Text style={styles.orderProductTotalText}>{t("order.total_price")}:</Text>
<Text style={styles.orderProductPriceText}>
{item.actual_amount} {item?.currency}
</Text>
{item.sku_attributes?.map((attr, index) => (
<Text
style={styles.orderProductItemInfoPrice}
key={index}
>
{attr.attribute_name}:{attr.attribute_value}
</Text>
))}
</View>
<TouchableOpacity
style={styles.orderProductView}
onPress={() => handleOrderDetailsPress(item.order_id)}
>
<Text style={styles.orderProductViewText}>
{t("order.view_details")}
</Text>
</TouchableOpacity>
</View>
))}
</View>
<View style={styles.orderProductPrice}>
<View style={styles.orderProductPriceItem}>
<Text style={styles.orderProductTotalText}>{t("order.total_price")}:</Text>
<Text style={styles.orderProductPriceText}>
{item.actual_amount} {item?.currency}
</Text>
</View>
<TouchableOpacity
style={styles.orderProductView}
onPress={() => handleOrderDetailsPress(item.order_id)}
>
<Text style={styles.orderProductViewText}>
{t("order.view_details")}
</Text>
</TouchableOpacity>
</View>
{/* 订单分隔线 */}
{index < orders.items.length - 1 && (
<View style={styles.orderDivider} />
)}
</View>
))}
{loading && page > 1 && (
@ -348,14 +356,17 @@ const styles = StyleSheet.create({
color: "#f77f3a",
},
orderContent: {
padding: 20,
flex: 1,
paddingTop: 20,
},
orderItem: {
width: "100%",
orderItemContainer: {
backgroundColor: "white",
borderRadius: 10,
marginBottom: 10,
marginHorizontal: 20,
},
orderItem: {
width: "100%",
},
orderStatus: {
width: "100%",
@ -445,4 +456,9 @@ const styles = StyleSheet.create({
paddingVertical: 10,
alignItems: "center",
},
orderDivider: {
height: 5,
backgroundColor: "#f0f0f0",
width: "100%",
},
});

Loading…
Cancel
Save