Browse Source

修改创建订单信息

main
Your Name 3 weeks ago
parent
commit
62c2e2302c
  1. 1
      .gitignore
  2. 11
      App.tsx
  3. 7
      app.json
  4. 17
      app/screens/CartScreen.tsx
  5. 1
      app/screens/ChatScreen.tsx
  6. 6
      app/screens/ProductCard.tsx
  7. 4
      app/screens/ProductDetailScreen.tsx
  8. 528
      app/screens/previewOrder/PaymentMethod.tsx
  9. 51
      app/screens/previewOrder/ShippingFee.tsx
  10. 185
      app/screens/previewOrder/perviewOrder.tsx
  11. 12
      app/services/api/apiClient.ts
  12. 2
      app/services/api/orders.ts
  13. 12
      app/services/api/payApi.ts
  14. 10
      app/services/api/productApi.ts
  15. 2
      app/store/createOrder.ts
  16. 4
      app/store/previewShipping.ts
  17. 113
      app/types/createOrder.ts
  18. 1
      package.json
  19. 10
      yarn.lock

1
.gitignore vendored

@ -34,3 +34,4 @@ yarn-error.*
# typescript
*.tsbuildinfo
.aider*

11
App.tsx

@ -47,6 +47,7 @@ import { AddAddress } from "./app/screens/address/AddAddress";
import { EditAddress } from "./app/screens/address/EditAddress";
import { PaymentMethod } from "./app/screens/previewOrder/PaymentMethod";
import { ShippingFee } from "./app/screens/previewOrder/ShippingFee";
import { PreviewOrder } from "./app/screens/previewOrder/perviewOrder";
export type RootStackParamList = {
CountrySelect: undefined;
@ -84,6 +85,7 @@ export type RootStackParamList = {
EditAddress: undefined;
PaymentMethod: undefined;
ShippingFee: undefined;
PreviewOrder: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
@ -427,6 +429,15 @@ function AppContent() {
gestureDirection: "horizontal",
}}
/>
<Stack.Screen
name="PreviewOrder"
component={PreviewOrder}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
</Stack.Navigator>
<Toast />
</NavigationContainer>

7
app.json

@ -53,7 +53,12 @@
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-video"
"expo-video",
["expo-build-properties", {
"android": {
"usesCleartextTraffic": true
}
}]
],
"extra": {
"eas": {

17
app/screens/CartScreen.tsx

@ -28,8 +28,11 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useFocusEffect } from "@react-navigation/native";
import { useCallback } from "react";
import useCreateOrderStore from "../store/createOrder";
import useUserStore from "../store/user";
export const CartScreen = () => {
const [cartList, setCartList] = useState<GetCartList[]>([]);
const {user:{user_id}} = useUserStore();
const [selectedItems, setSelectedItems] = useState<{
[key: string]: boolean;
}>({});
@ -205,6 +208,10 @@ export const CartScreen = () => {
};
const getCart = async () => {
if(!user_id){
Alert.alert("暂无数据", "请先登录");
return;
}
const res = await getCartList();
setCartList(res.items);
calculateTotalAmount(res.items);
@ -323,7 +330,7 @@ export const CartScreen = () => {
};
// useEffect(() => {
// getCart();
// }, []);
useFocusEffect(
useCallback(() => {
@ -332,6 +339,10 @@ export const CartScreen = () => {
);
const gotoOrder = () => {
if(!user_id){
Alert.alert("添加失败", "请先登录");
return;
}
const items: { cart_item_id: number }[] = [];
cartList.forEach((item) => {
item.skus.forEach((sku) => {
@ -344,6 +355,10 @@ export const CartScreen = () => {
}
});
});
if(items.length === 0){
Alert.alert("添加失败", "请先选择商品");
return;
}
setItems(items);
navigation.navigate("PreviewAddress");
};

1
app/screens/ChatScreen.tsx

@ -16,6 +16,7 @@ import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { RootStackParamList } from "../../App";
interface Message {
id: string;
text: string;

6
app/screens/ProductCard.tsx

@ -27,6 +27,7 @@ import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import ImageView from "react-native-image-viewing";
import useProductCartStore from "../store/productCart";
import useUserStore from "../store/user";
interface ProductCardProps {
onClose: () => void;
@ -48,6 +49,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
const [images, setImages] = useState<string[]>([]);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [imageViewerVisible, setImageViewerVisible] = useState(false);
const {user:{user_id}} = useUserStore();
const {
product,
@ -86,6 +88,10 @@ const ProductCard: React.FC<ProductCardProps> = ({
// 加入购物车
const addCartHandel = () => {
if(!user_id){
Alert.alert("添加失败", "请先登录");
return;
}
if (totalPrice === 0) {
Alert.alert("添加失败", "请选择商品");
return;

4
app/screens/ProductDetailScreen.tsx

@ -261,7 +261,7 @@ export const ProductDetailScreen = () => {
console.log('开始时间:', startTime);
setIsLoading(true);
try {
const res = await productApi.getProductDetail(route.params.offer_id,userStore.user.user_id);
const res = await productApi.getProductDetail(route.params.offer_id, userStore.user?.user_id);
console.log('API 请求完成');
if (res.skus != null) {
const priceSelectedSku = res.skus.find(
@ -325,7 +325,7 @@ export const ProductDetailScreen = () => {
}
};
const getSimilars = () => {
productApi.getSimilarProducts(route.params.offer_id,userStore.user.user_id).then((res) => {
productApi.getSimilarProducts(route.params.offer_id, userStore.user?.user_id).then((res) => {
setSimilars(res);
setIsSimilarsFlag(true);
});

528
app/screens/previewOrder/PaymentMethod.tsx

@ -12,22 +12,33 @@ import {
import { payApi, PaymentMethodsResponse } from "../../services/api/payApi";
import fontSize from "../../utils/fontsizeUtils";
import BackIcon from "../../components/BackIcon";
import { useNavigation } from "@react-navigation/native";
import { useNavigation, NavigationProp } from "@react-navigation/native";
import widthUtils from "../../utils/widthUtils";
import useOrderStore from "../../store/order";
import useCreateOrderStore from "../../store/createOrder";
import { useRoute, RouteProp } from "@react-navigation/native";
import useUserStore from "../../store/user";
import { createOrderDataType } from "../../types/createOrder";
import {
ordersApi,
OrderData,
AddressDataItem,
DomesticShippingFeeData,
OrderData, CreateOrderRequest,
} from "../../services/api/orders";
// Define route params type
type PaymentMethodRouteParams = {
countryCode?: string;
freight_forwarder_address_id?: number;
};
// Define the root navigation params
type RootStackParamList = {
PreviewOrder: undefined;
Pay: { order_id: string };
ShippingFee: { freight_forwarder_address_id?: number };
PaymentMethod: { freight_forwarder_address_id?: number };
PreviewAddress: undefined;
AddressList: undefined;
// Add other routes as needed
};
interface PaymentOption {
@ -43,45 +54,126 @@ interface PaymentTab {
options: PaymentOption[];
}
interface PaymentMethodProps {
onSelectPayment?: (paymentMethod: string) => void;
selectedPayment?: string | null;
}
const PaymentMethodItem = ({
option,
isSelected,
onSelect,
selectedCurrency,
onSelectCurrency,
exchangeRates,
totalAmount,
convertedAmount,
isConverting,
}: {
option: PaymentOption;
isSelected: boolean;
onSelect: () => void;
selectedCurrency?: string;
onSelectCurrency?: (currency: string) => void;
exchangeRates?: {
usd: number;
eur: number;
};
totalAmount?: number;
convertedAmount?: number;
isConverting?: boolean;
}) => (
<TouchableOpacity
style={[styles.paymentOption, isSelected && styles.paymentSelected]}
onPress={onSelect}
>
<View style={styles.paymentContent}>
<View style={styles.defaultPaymentContainer}>
<Text style={styles.paymentIcon}>{option.icon}</Text>
<Text style={styles.paymentLabel}>{option.label}</Text>
</View>
{Array.isArray(option.value) && option.value.length > 0 && (
<View style={styles.operatorContainer}>
{option.value.map((op: string) => (
<View key={op} style={styles.operatorBox}>
<Text style={styles.operatorText}>{op}</Text>
</View>
))}
<View>
<TouchableOpacity
style={[styles.paymentOption, isSelected && styles.paymentSelected]}
onPress={onSelect}
>
<View style={styles.paymentContent}>
<View style={styles.defaultPaymentContainer}>
<Text style={styles.paymentIcon}>{option.icon}</Text>
<Text style={styles.paymentLabel}>{option.label}</Text>
</View>
)}
</View>
<View style={styles.radioButton}>
<View
style={[styles.radioInner, isSelected && styles.radioInnerSelected]}
{Array.isArray(option.value) && option.value.length > 0 && (
<View style={styles.operatorContainer}>
{option.value.map((op: string) => (
<View key={op} style={styles.operatorBox}>
<Text style={styles.operatorText}>{op}</Text>
</View>
))}
</View>
)}
</View>
<View style={styles.radioButton}>
<View
style={[styles.radioInner, isSelected && styles.radioInnerSelected]}
/>
</View>
</TouchableOpacity>
{/* Show currency selector directly under PayPal when selected */}
{isSelected && option.label === "Paypal" && selectedCurrency && onSelectCurrency && exchangeRates && totalAmount && (
<CurrencySelector
selectedCurrency={selectedCurrency}
onSelectCurrency={onSelectCurrency}
exchangeRates={exchangeRates}
totalAmount={totalAmount}
convertedAmount={convertedAmount}
isConverting={isConverting}
/>
)}
</View>
);
// Currency selector component
interface CurrencySelectorProps {
selectedCurrency: string;
onSelectCurrency: (currency: string) => void;
exchangeRates: {
usd: number;
eur: number;
};
totalAmount: number;
convertedAmount?: number;
isConverting?: boolean;
}
const CurrencySelector = ({
selectedCurrency,
onSelectCurrency,
exchangeRates,
totalAmount,
convertedAmount = 0,
isConverting = false,
}: CurrencySelectorProps) => (
<View style={styles.currencySelectorContainer}>
<Text style={styles.currencySelectorTitle}>Select Currency</Text>
<View style={styles.currencyOptions}>
<TouchableOpacity
style={[
styles.currencyOption,
selectedCurrency === "USD" && styles.selectedCurrencyOption,
]}
onPress={() => onSelectCurrency("USD")}
>
<Text style={styles.currencyText}>USD</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.currencyOption,
selectedCurrency === "EUR" && styles.selectedCurrencyOption,
]}
onPress={() => onSelectCurrency("EUR")}
>
<Text style={styles.currencyText}>EUR</Text>
</TouchableOpacity>
</View>
<View style={styles.totalContainer}>
{!isConverting && (
<Text style={styles.totalText}>
{selectedCurrency === "USD" ? "$" : "€"}{convertedAmount}
</Text>
)}
{isConverting && (
<ActivityIndicator size="small" color="#ff6000" style={styles.loadingIndicator} />
)}
</View>
</TouchableOpacity>
</View>
);
export const PaymentMethod = () => {
@ -101,27 +193,77 @@ export const PaymentMethod = () => {
const [paymentMethods, setPaymentMethods] =
useState<PaymentMethodsResponse>();
const [selectedPayment, setSelectedPayment] = useState<string | null>(null);
const navigation = useNavigation();
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const route =
useRoute<RouteProp<Record<string, PaymentMethodRouteParams>, string>>();
const [expanded, setExpanded] = useState(false);
const order = useOrderStore((state) => state.order);
const [previewOrder, setPreviewOrder] = useState<OrderData>();
const [loading, setLoading] = useState(false);
const state = useUserStore();
const { items,orderData } = useCreateOrderStore();
const { user } = useUserStore();
const [createOrderData, setCreateOrderData] = useState<createOrderDataType>();
const { items, orderData,setOrderData } = useCreateOrderStore();
const [selectedCurrency, setSelectedCurrency] = useState("USD");
const [convertedAmount, setConvertedAmount] = useState(0);
const [isConverting, setIsConverting] = useState(false);
const [exchangeRates] = useState({
usd: 580.00,
eur: 655.96
});
const [totalAmount, setTotalAmount] = useState(121.97);
const toggleExpanded = () => {
setExpanded(!expanded);
};
const onSelectPayment = (paymentId: string) => {
if(paymentId === "Paypal"){
setIsConverting(true);
payApi.convertCurrency({
from_currency: user.currency,
to_currency: selectedCurrency,
amount: (
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
),
}).then((res) => {
setConvertedAmount(res.converted_amount);
setIsConverting(false);
}).catch(error => {
console.error("Currency conversion failed:", error);
setIsConverting(false);
});
}
setSelectedPayment(paymentId);
};
const onSelectCurrency = (currency: string) => {
setSelectedCurrency(currency);
// Call the API to convert the currency
setIsConverting(true);
payApi.convertCurrency({
from_currency: user.currency,
to_currency: currency,
amount: (
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
),
}).then((res) => {
setConvertedAmount(res.converted_amount);
setIsConverting(false);
}).catch(error => {
console.error("Currency conversion failed:", error);
setIsConverting(false);
});
};
const getPaymentMethods = async () => {
try {
const response = await payApi.getCountryPaymentMethods();
setPaymentMethods(response);
// 设置默认支付方式选项
setTabs([
@ -167,9 +309,9 @@ export const PaymentMethod = () => {
useEffect(() => {
setLoading(true);
if (route.params?.countryCode) {
if (route.params?.freight_forwarder_address_id) {
const data = {
country_code: route.params.countryCode,
country_code: route.params.freight_forwarder_address_id,
items: items,
};
ordersApi
@ -183,10 +325,62 @@ export const PaymentMethod = () => {
Alert.alert("Error", "Failed to get preview order");
});
}
}, [route.params?.countryCode]);
}, [route.params?.freight_forwarder_address_id]);
useEffect(() => {
setCreateOrderData({
...orderData,
address_id: orderData.address_id,
domestic_shipping_fee: orderData.domestic_shipping_fee,
shipping_fee: orderData.shipping_fee,
transport_type: orderData.transport_type,
currency: user.currency,
});
console.log("orderData", orderData);
}, [orderData]);
const handleSubmit = async () => {
if (!selectedPayment) {
Alert.alert("请选择支付方式");
return;
}
const items =
previewOrder?.items.map((item) => ({
offer_id: item.offer_id,
cart_item_id: item.cart_item_id,
sku_id: item.sku_id,
product_name: item.product_name,
product_name_en: item.product_name_en,
product_name_ar: item.product_name_ar,
product_name_fr: item.product_name_fr,
sku_attributes: item.attributes.map((attr) => ({
attribute_name: attr.attribute_name,
attribute_value: attr.value,
})),
sku_image:item.sku_image_url,
quantity: item.quantity,
unit_price: item.unit_price,
total_price: item.total_price,
})) || [];
if (createOrderData) {
createOrderData.items = items;
createOrderData.payment_method = selectedPayment;
createOrderData.total_amount = selectedPayment === 'Paypal' ? convertedAmount :
Number(((previewOrder?.total_amount || 0) + (orderData?.domestic_shipping_fee || 0)
+ (orderData?.shipping_fee || 0)).toFixed(2));
createOrderData.actual_amount = Number((previewOrder?.total_amount || 0).toFixed(2));
createOrderData.currency = selectedPayment === 'Paypal' ? selectedCurrency : user.currency;
}
setOrderData(createOrderData || {});
const res = await ordersApi.createOrder(createOrderData as CreateOrderRequest)
console.log(res)
navigation.navigate("PreviewOrder");
};
return (
<>
<View style={{ flex: 1, backgroundColor: "#fff" }}>
<View style={styles.titleContainer}>
<View style={styles.backIconContainer}>
<TouchableOpacity onPress={() => navigation.goBack()}>
@ -202,7 +396,12 @@ export const PaymentMethod = () => {
<ActivityIndicator size="large" color="#f77f3a" />
</View>
) : (
<ScrollView style={styles.container}>
<ScrollView
style={[styles.container, { backgroundColor: "#fff" }]}
showsVerticalScrollIndicator={false}
bounces={false}
overScrollMode="never"
>
<View style={styles.sectionHeader}>
<Text style={styles.sectionIcon}>💳</Text>
<Text style={styles.sectionTitle}>Payment Method</Text>
@ -217,7 +416,11 @@ export const PaymentMethod = () => {
styles.tabButton,
currentTab === tab.id && styles.tabButtonActive,
]}
onPress={() => setCurrentTab(tab.id)}
onPress={() => {
console.log(tab.id);
setCurrentTab(tab.id)
}}
>
<Text
style={[
@ -241,6 +444,12 @@ export const PaymentMethod = () => {
option={option}
isSelected={selectedPayment === option.id}
onSelect={() => onSelectPayment(option.id)}
selectedCurrency={selectedCurrency}
onSelectCurrency={onSelectCurrency}
exchangeRates={exchangeRates}
totalAmount={totalAmount}
convertedAmount={convertedAmount}
isConverting={isConverting}
/>
))}
</View>
@ -310,22 +519,29 @@ export const PaymentMethod = () => {
<View style={styles.priceBox}>
<View style={styles.priceBox1}>
<Text>Subtotal</Text>
<Text></Text>
<View>
<Text>{previewOrder?.total_amount || 0}</Text>
<Text>
{previewOrder?.total_amount || 0} {previewOrder?.currency}
</Text>
</View>
</View>
<View style={styles.priceBox1}>
<Text>Domestic Shipping</Text>
<Text></Text>
<View>
<Text>{previewOrder?.shipping_fee || 0}</Text>
<Text>
{createOrderData?.domestic_shipping_fee || 0}{" "}
{previewOrder?.currency}
</Text>
</View>
</View>
<View style={styles.priceBox1}>
<Text>Estimated International Shipping</Text>
<Text></Text>
<View>
<Text>{orderData.transport_type === 1 ?previewOrder?.shipping_fee_sea : previewOrder?.shipping_fee_air || 0}</Text>
<Text>
{createOrderData?.shipping_fee || 0} {previewOrder?.currency}
</Text>
</View>
</View>
</View>
@ -342,20 +558,106 @@ export const PaymentMethod = () => {
>
Total
</Text>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#ff6000",
}}
>
${previewOrder?.actual_amount?.toFixed(2)}
</Text>
{
selectedPayment === 'Paypal' && selectedCurrency !== user.currency && (
<View style={{flexDirection:'row'}}>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#151515",
textDecorationLine: 'line-through'
}}
>
{(
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
).toFixed(2)}{" "}
{previewOrder?.currency}
</Text>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#ff6000",
marginLeft:10
}}
>
{convertedAmount}{selectedCurrency === "USD" ? "USD" : "EUR"}
</Text>
</View>
)
}
{
selectedPayment === 'Paypal' && selectedCurrency === user.currency && (
<View style={{flexDirection:'row'}}>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#ff6000",
}}
>
{(
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
).toFixed(2)}{" "}
{previewOrder?.currency}
</Text>
</View>
)
}
{
selectedPayment !== 'Paypal'&& (
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#ff6000",
}}
>
{(
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
).toFixed(2)}{" "}
{previewOrder?.currency}
</Text>
)
}
</View>
</View>
<View style={styles.submitButtonContainer}>
<TouchableOpacity
style={[
styles.primaryButtonStyle,
// (isDomesticShippingLoading || domesticShippingFeeData?.total_shipping_fee == null)
// ? styles.disabledButtonStyle : {}
]}
onPress={handleSubmit}
// disabled={isDomesticShippingLoading || domesticShippingFeeData?.total_shipping_fee == null}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
</ScrollView>
)}
</>
</View>
);
};
@ -363,6 +665,7 @@ const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
marginTop: 10,
},
sectionHeader: {
flexDirection: "row",
@ -442,12 +745,13 @@ const styles = StyleSheet.create({
marginTop: 8,
},
operatorBox: {
backgroundColor: "#EEEEEE",
backgroundColor: "#fff",
paddingVertical: 4,
paddingHorizontal: 8,
borderRadius: 4,
marginRight: 8,
marginBottom: 4,
},
operatorText: {
fontSize: fontSize(12),
@ -478,11 +782,14 @@ const styles = StyleSheet.create({
alignItems: "center",
justifyContent: "center",
position: "relative",
marginBottom: 10,
backgroundColor: "#fff",
},
backIconContainer: {
position: "absolute",
left: 15,
backgroundColor: "#fff",
},
titleHeading: {
fontWeight: "600",
@ -490,6 +797,7 @@ const styles = StyleSheet.create({
lineHeight: 22,
fontFamily: "PingFang SC",
color: "black",
},
// Order Summary Styles
section: {
@ -631,5 +939,105 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff",
},
submitButtonContainer: {
paddingRight: 11,
paddingLeft: 11,
marginTop: 20,
marginBottom: 20,
},
primaryButtonStyle: {
width: "100%",
height: 50,
justifyContent: "center",
alignItems: "center",
fontWeight: "600",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "white",
backgroundColor: "#002fa7",
borderWidth: 0,
borderRadius: 25,
},
buttonText: {
color: "white",
fontWeight: "600",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
},
selectedCountryText: {
padding: 0,
margin: 0,
fontWeight: "500",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "#646472",
},
disabledButtonStyle: {
backgroundColor: "#ccc",
},
currencySelectorContainer: {
padding: 15,
backgroundColor: "#f9f9f9",
borderRadius: 8,
marginTop: 5,
marginBottom: 15,
},
currencySelectorTitle: {
fontSize: fontSize(16),
fontWeight: "600",
color: "#000",
marginBottom: 15,
},
currencyOptions: {
flexDirection: "row",
marginBottom: 15,
},
currencyOption: {
flex: 1,
padding: 15,
borderWidth: 1,
borderColor: "#DDDDDD",
borderRadius: 4,
marginRight: 10,
alignItems: "center",
justifyContent: "center",
},
selectedCurrencyOption: {
borderColor: "#002fa7",
backgroundColor: "#fff",
},
currencyText: {
fontSize: fontSize(16),
fontWeight: "500",
color: "#000",
},
exchangeRateContainer: {
marginBottom: 15,
},
exchangeRateText: {
fontSize: fontSize(14),
color: "#666",
marginBottom: 5,
},
totalContainer: {
marginTop: 10,
borderTopWidth: 1,
borderTopColor: "#EEEEEE",
paddingTop: 10,
flexDirection: 'row',
alignItems: 'center',
},
totalText: {
fontSize: fontSize(16),
fontWeight: "600",
color: "#ff6000",
},
loadingIndicator: {
marginLeft: 10,
},
});

51
app/screens/previewOrder/ShippingFee.tsx

@ -15,6 +15,7 @@ import LocationPinIcon from "../../components/LocationPinIcon";
import {
AddressDataItem,
OrderData,
Address,
CartShippingFeeData,
DomesticShippingFeeData,
} from "../../services/api/orders";
@ -27,7 +28,7 @@ import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import useUserStore from "../../store/user";
type RootStackParamList = {
ShippingFee: undefined;
PaymentMethod: { countryCode: number };
PaymentMethod: { freight_forwarder_address_id: number };
};
type ShippingFeeParams = {
cart_item_id: {
@ -56,14 +57,21 @@ export const ShippingFee = () => {
const [modalVisible, setModalVisible] = useState(false);
const [selectedWarehouseLabel, setSelectedWarehouseLabel] = useState("");
const [selectedWarehouse, setSelectedWarehouse] = useState<Address>();
const [domesticShippingFeeData, setDomesticShippingFeeData] =
useState<DomesticShippingFeeData>();
const [isDomesticShippingLoading, setIsDomesticShippingLoading] = useState(false);
const [count,setCount] = useState<string>();
const [apiResponses, setApiResponses] = useState({
shippingFees: false,
domesticShippingFees: false
});
const { setOrderData ,orderData,items} = useCreateOrderStore();
const [countryCode,setCountryCode] = useState<number>();
const userStore = useUserStore();
const getFreightForwarderAddress = async () => {
await fetchFreightForwarderAddress(1);
await fetchFreightForwarderAddress(0);
};
useEffect(() => {
@ -90,16 +98,25 @@ export const ShippingFee = () => {
useEffect(() => {
if (state.shippingFees) {
setShippingFeeData(state.shippingFees);
setCount('正在计算国际价格');
setApiResponses(prev => ({...prev, shippingFees: true}));
}
}, [state.shippingFees]);
useEffect(() => {
if (state.domesticShippingFees) {
setDomesticShippingFeeData(state.domesticShippingFees);
setIsDomesticShippingLoading(false);
setApiResponses(prev => ({...prev, domesticShippingFees: true}));
}
}, [state.domesticShippingFees]);
// Effect to handle loading state based on both API responses
useEffect(() => {
if (apiResponses.shippingFees && apiResponses.domesticShippingFees) {
setIsDomesticShippingLoading(false);
}
}, [apiResponses]);
// Call changeCountryHandel when warehouse changes
useEffect(() => {
if (warehouse && freightForwarderAddress?.other_addresses) {
@ -113,16 +130,24 @@ export const ShippingFee = () => {
(item) => item.country + "|" + item.city === value
);
setSelectedWarehouse(selectedWarehouse);
if (selectedWarehouse && items) {
const data = {
items: items,
country_code: selectedWarehouse.country_code,
freight_forwarder_address_id: selectedWarehouse.address_id,
};
// Only calculate if we have the necessary data
if (data.items && data.country_code) {
if (data.items && data.freight_forwarder_address_id) {
// Set loading state to true before making API calls
setIsDomesticShippingLoading(true);
setCount('正在计算国内价格');
// Reset API response tracking
setApiResponses({
shippingFees: false,
domesticShippingFees: false
});
calculateShippingFee(data);
calculateDomesticShippingFee(data);
@ -142,23 +167,14 @@ export const ShippingFee = () => {
if (!isDomesticShippingLoading && domesticShippingFeeData?.total_shipping_fee != null) {
setOrderData({
...orderData,
transport_type: shippingMethod === "sea" ? 1 : 2,
transport_type: shippingMethod === "sea" ? 0 : 1,
domestic_shipping_fee: domesticShippingFeeData?.total_shipping_fee,
shipping_fee: shippingMethod === "sea"
? shippingFeeData?.total_shipping_fee_sea
: shippingFeeData?.total_shipping_fee_air,
receiver_address:selectedWarehouseLabel
});
// Get the first warehouse's country code as default if countryCode is undefined
let defaultCountryCode = countryCode;
if (defaultCountryCode === undefined) {
// Safely access the first warehouse's country code
const firstWarehouse = freightForwarderAddress?.other_addresses?.[0];
if (firstWarehouse) {
defaultCountryCode = firstWarehouse.country_code;
}
}
navigation.navigate("PaymentMethod", {countryCode: defaultCountryCode || 0});
navigation.navigate("PaymentMethod", {freight_forwarder_address_id: selectedWarehouse?.address_id || 0});
}else{
Alert.alert("请选择运输方式");
}
@ -325,7 +341,7 @@ export const ShippingFee = () => {
}}
>
{isDomesticShippingLoading ? (
<Text style={{ color: "#ff6000" }}>...</Text>
<Text style={{ color: "#ff6000",alignItems:"center" }}>{count} <ActivityIndicator size="small" color="#ff6000" style={{ marginLeft: 5 }} /></Text>
) : (
<Text style={{ color: "#ff6000" }}>
{((domesticShippingFeeData?.total_shipping_fee || 0) + (shippingMethod === "sea"
@ -564,6 +580,7 @@ const styles = StyleSheet.create({
color: "#333",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
shippingInfoLabel: {
color: "#777",

185
app/screens/previewOrder/perviewOrder.tsx

@ -0,0 +1,185 @@
import { View, Text, TouchableOpacity, StyleSheet, TextInput } from "react-native";
import useCreateOrderStore from "../../store/createOrder";
import BackIcon from "../../components/BackIcon";
import { useNavigation } from "@react-navigation/native";
import { useState, useEffect } from "react";
export const PreviewOrder = () => {
const {orderData, setOrderData} = useCreateOrderStore();
const navigation = useNavigation();
const [phoneNumber, setPhoneNumber] = useState("");
const [showPhoneInput, setShowPhoneInput] = useState(false);
useEffect(() => {
if (orderData?.payment_method === "Brainnel Pay(Mobile Money)") {
setShowPhoneInput(true);
} else {
setShowPhoneInput(false);
}
}, [orderData?.payment_method]);
const handleSubmit = () => {
if (showPhoneInput && !phoneNumber) {
// Show error or alert if needed
console.log("Phone number is required for Mobile Money");
return;
}
if (showPhoneInput) {
setOrderData({ ...orderData, mobile_money_phone: phoneNumber });
}
console.log("orderData", orderData);
// Add your submission logic here
}
return (
<View style={styles.container}>
<View style={styles.titleContainer}>
<View style={styles.backIconContainer}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<BackIcon size={20} />
</TouchableOpacity>
</View>
<Text style={styles.titleHeading}></Text>
</View>
{/* Payment Details */}
<View style={styles.section}>
{showPhoneInput && (
<View style={styles.phoneInputContainer}>
<Text style={styles.phoneInputLabel}></Text>
<TextInput
style={styles.phoneInput}
value={phoneNumber}
onChangeText={setPhoneNumber}
placeholder="输入手机号码"
keyboardType="phone-pad"
/>
</View>
)}
</View>
<View style={styles.submitButtonContainer}>
<TouchableOpacity
style={[
styles.primaryButtonStyle,
(!showPhoneInput || (showPhoneInput && phoneNumber)) ? {} : styles.disabledButtonStyle
]}
onPress={handleSubmit}
disabled={showPhoneInput && !phoneNumber}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white",
},
submitButtonContainer: {
paddingRight: 11,
paddingLeft: 11,
marginTop: 20,
marginBottom: 20,
},
primaryButtonStyle: {
width: "100%",
height: 50,
justifyContent: "center",
alignItems: "center",
fontWeight: "600",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "white",
backgroundColor: "#002fa7",
borderWidth: 0,
borderRadius: 25,
},
buttonText: {
color: "white",
fontWeight: "600",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
},
selectedCountryText: {
padding: 0,
margin: 0,
fontWeight: "500",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "#646472",
},
disabledButtonStyle: {
backgroundColor: "#ccc",
},
titleContainer: {
width: "100%",
padding: 15,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
position: "relative",
backgroundColor: "#fff",
},
backIconContainer: {
position: "absolute",
left: 15,
backgroundColor: "#fff",
},
titleHeading: {
fontWeight: "600",
fontSize: 20,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "black",
},
// Order Summary Styles
section: {
backgroundColor: "#fff",
borderRadius: 8,
paddingHorizontal: 15,
marginTop: 15,
paddingVertical: 12,
},
sectionTitle: {
fontWeight: "600",
fontSize: 16,
marginBottom: 8,
color: "#333",
},
paymentMethod: {
fontSize: 15,
color: "#666",
marginBottom: 10,
},
phoneInputContainer: {
marginTop: 10,
marginBottom: 10,
},
phoneInputLabel: {
fontSize: 14,
color: "#333",
marginBottom: 5,
},
phoneInput: {
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 8,
padding: 10,
fontSize: 16,
backgroundColor: "#f9f9f9",
},
});

12
app/services/api/apiClient.ts

@ -61,12 +61,12 @@ apiClient.interceptors.request.use(
fullUrl += (fullUrl.includes('?') ? '&' : '?') + params.toString();
}
console.log("环境:", __DEV__ ? "开发环境" : "生产环境");
console.log("请求方法:", config.method);
console.log("完整URL:", fullUrl);
console.log("请求头:", config.headers);
console.log("请求参数:", config.params);
console.log("请求数据:", config.data);
// console.log("环境:", __DEV__ ? "开发环境" : "生产环境");
// console.log("请求方法:", config.method);
// console.log("完整URL:", fullUrl);
// console.log("请求头:", config.headers);
// console.log("请求参数:", config.params);
// console.log("请求数据:", config.data);
// 从AsyncStorage获取token
const token = await AsyncStorage.getItem("token");

2
app/services/api/orders.ts

@ -2,7 +2,7 @@ import apiService from './apiClient';
// 地址类型
interface Address {
export interface Address {
address_id: number;
user_id: number;
receiver_first_name: string;

12
app/services/api/payApi.ts

@ -30,6 +30,13 @@ export interface PayInfoBody {
currency: string;
}
export interface ConvertCurrencyBody {
from_currency: string;
to_currency: string;
amount: number;
}
export const payApi = {
// 获取当前国家支付方式
getCountryPaymentMethods: () => {
@ -40,4 +47,9 @@ export const payApi = {
getPayInfo: (data: PayInfoBody) => {
return apiService.post<PaymentInfoResponse>(`/api/payment/initiate/`, data);
},
// 货币转换
convertCurrency: (data: ConvertCurrencyBody) => {
return apiService.post<any>(`/api/currency/convert/`, data);
},
};

10
app/services/api/productApi.ts

@ -183,12 +183,14 @@ export type Products = Product[]
return apiService.get<products>('/api/search/', params);
},
// 获取商品详情
getProductDetail: (offer_id: string,user_id:number) => {
return apiService.get<ProductDetailParams>(`/api/products/${offer_id}/?user_id=${user_id}`);
getProductDetail: (offer_id: string, user_id?: number) => {
const url = user_id ? `/api/products/${offer_id}/?user_id=${user_id}` : `/api/products/${offer_id}/`;
return apiService.get<ProductDetailParams>(url);
},
// 获取相似商品
getSimilarProducts: (offer_id: string,user_id:number) => {
return apiService.get<Similars>(`/api/products/${offer_id}/similar/?limit=5&user_id=${user_id}`);
getSimilarProducts: (offer_id: string, user_id?: number) => {
const url = user_id ? `/api/products/${offer_id}/similar/?limit=5&user_id=${user_id}` : `/api/products/${offer_id}/similar/?limit=5`;
return apiService.get<Similars>(url);
}
}

2
app/store/createOrder.ts

@ -131,7 +131,7 @@ const initialOrderData: OrderCreateRequest = {
items: [],
buyer_message: '',
payment_method: '',
create_payment: false,
create_payment: true,
actual_amount: 0,
discount_amount: 0,
shipping_fee: 0,

4
app/store/previewShipping.ts

@ -61,7 +61,7 @@ const usePreviewShippingStore = create<PreviewShippingStore>((set) => ({
calculateShippingFee: async (data: ShippingFeeData) => {
set((state) => ({
state: { ...state.state, isLoading: true, error: null }
state: { ...state.state, isLoading: false, error: null }
}));
try {
@ -87,7 +87,7 @@ const usePreviewShippingStore = create<PreviewShippingStore>((set) => ({
calculateDomesticShippingFee: async (data: ShippingFeeData) => {
set((state) => ({
state: { ...state.state, isLoading: true, error: null }
state: { ...state.state, isLoading: false, error: null }
}));
try {

113
app/types/createOrder.ts

@ -0,0 +1,113 @@
/**
* OrderCreate
*/
export interface createOrderDataType {
/**
* Actual Amount
*/
actual_amount?: number | null;
/**
* Address IdID
*/
address_id: number;
/**
* Buyer Message
*/
buyer_message?: null | string;
/**
* Create Payment
*/
create_payment?: boolean | null;
/**
* Currency
*/
currency?: null | string;
/**
* Discount Amount
*/
discount_amount?: number | null;
/**
* Domestic Shipping Fee
*/
domestic_shipping_fee?: number | null;
/**
* Items
*/
items: OrderItemBase[];
/**
* Payment Method
*/
payment_method?: null | string;
/**
* Receiver Address
*/
receiver_address: string;
/**
* Shipping Fee
*/
shipping_fee?: number | null;
/**
* Total Amount
*/
total_amount?: number | null;
/**
* Transport Type 1- 2-
*/
transport_type?: number | null;
[property: string]: any;
}
/**
* OrderItemBase
*/
export interface OrderItemBase {
/**
* Cart Item IdID
*/
cart_item_id?: number | null;
/**
* Offer IdID
*/
offer_id: number;
/**
* Product Image
*/
product_image?: null | string;
/**
* Product Name
*/
product_name: string;
/**
* Product Name Ar
*/
product_name_ar?: null | string;
/**
* Product Name En
*/
product_name_en?: null | string;
/**
* Product Name Fr
*/
product_name_fr?: null | string;
/**
* Quantity
*/
quantity: number;
/**
* Sku AttributesSKU属性
*/
sku_attributes?: { [key: string]: any }[] | null;
/**
* Sku IdSKU ID
*/
sku_id?: number | null;
/**
* Total Price
*/
total_price: number;
/**
* Unit Price
*/
unit_price: number;
[property: string]: any;
}

1
package.json

@ -24,6 +24,7 @@
"events": "^3.3.0",
"expo": "~52.0.41",
"expo-auth-session": "~6.0.3",
"expo-build-properties": "~0.13.3",
"expo-image": "~2.0.7",
"expo-linear-gradient": "~14.0.2",
"expo-localization": "^16.0.1",

10
yarn.lock

@ -3328,7 +3328,7 @@ ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.0.0, ajv@^8.9.0:
ajv@^8.0.0, ajv@^8.11.0, ajv@^8.9.0:
version "8.17.1"
resolved "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
@ -5688,6 +5688,14 @@ expo-auth-session@~6.0.3:
expo-web-browser "~14.0.2"
invariant "^2.2.4"
expo-build-properties@~0.13.3:
version "0.13.3"
resolved "https://registry.npmmirror.com/expo-build-properties/-/expo-build-properties-0.13.3.tgz#6b96d0486148fca6e74e62c7c502c0a9990931aa"
integrity sha512-gw7AYP+YF50Gr912BedelRDTfR4GnUEn9p5s25g4nv0hTJGWpBZdCYR5/Oi2rmCHJXxBqhPjxzV7JRh72fntLg==
dependencies:
ajv "^8.11.0"
semver "^7.6.0"
expo-constants@~17.0.5, expo-constants@~17.0.8:
version "17.0.8"
resolved "https://registry.npmmirror.com/expo-constants/-/expo-constants-17.0.8.tgz"

Loading…
Cancel
Save