Browse Source

购物车选择,创建订单,订单页面

main
unknown 1 month ago
parent
commit
b854723c07
  1. 5
      .idea/.gitignore
  2. 12
      .idea/app.iml
  3. 8
      .idea/modules.xml
  4. 6
      .idea/vcs.xml
  5. 12
      App.tsx
  6. 164
      app/screens/CartScreen.tsx
  7. 4
      app/screens/Recipient/Address.tsx
  8. 373
      app/screens/Recipient/ConfirmOrder.tsx
  9. 471
      app/screens/Recipient/Recipient.tsx
  10. 5
      app/services/api/cart.ts
  11. 41
      app/services/api/orders.ts
  12. 26
      app/services/api/payApi.ts
  13. 0
      orders.txt
  14. 1048
      yarn.lock

5
.idea/.gitignore vendored

@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

12
.idea/app.iml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/app.iml" filepath="$PROJECT_DIR$/.idea/app.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

12
App.tsx

@ -27,6 +27,8 @@ import { MyAddress } from "./app/screens/setting/MyAddress";
import { CartScreen } from "./app/screens/CartScreen"; import { CartScreen } from "./app/screens/CartScreen";
import { PaymentSuccessScreen } from "./app/screens/pay/PaySuccess"; import { PaymentSuccessScreen } from "./app/screens/pay/PaySuccess";
import { MyAccount } from "./app/screens/MyAccount/myAccount"; import { MyAccount } from "./app/screens/MyAccount/myAccount";
import { ConfirmOrder } from "./app/screens/Recipient/ConfirmOrder";
export type RootStackParamList = { export type RootStackParamList = {
CountrySelect: undefined; CountrySelect: undefined;
MainApp: undefined; MainApp: undefined;
@ -48,6 +50,7 @@ export type RootStackParamList = {
PaymentSuccessScreen:undefined; PaymentSuccessScreen:undefined;
MyAccount:undefined; MyAccount:undefined;
Google: undefined; Google: undefined;
ConfirmOrder: undefined;
}; };
const Stack = createNativeStackNavigator<RootStackParamList>(); const Stack = createNativeStackNavigator<RootStackParamList>();
@ -229,6 +232,15 @@ export default function App() {
gestureDirection: "horizontal", gestureDirection: "horizontal",
}} }}
/> />
<Stack.Screen
name="ConfirmOrder"
component={ConfirmOrder}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
</AuthProvider> </AuthProvider>

164
app/screens/CartScreen.tsx

@ -16,6 +16,7 @@ import {
GetCartList, GetCartList,
updateCartItem, updateCartItem,
deleteCartItem, deleteCartItem,
updateBatchCartSelected,
} from "../services/api/cart"; } from "../services/api/cart";
import widthUtils from "../utils/widthUtils"; import widthUtils from "../utils/widthUtils";
import { Swipeable } from "react-native-gesture-handler"; import { Swipeable } from "react-native-gesture-handler";
@ -30,6 +31,7 @@ export const CartScreen = () => {
[key: string]: boolean; [key: string]: boolean;
}>({}); }>({});
const [allSelected, setAllSelected] = useState(false); const [allSelected, setAllSelected] = useState(false);
const [totalAmount, setTotalAmount] = useState(0);
const [deleteModalVisible, setDeleteModalVisible] = useState(false); const [deleteModalVisible, setDeleteModalVisible] = useState(false);
const [itemToDelete, setItemToDelete] = useState<{ const [itemToDelete, setItemToDelete] = useState<{
cartId: number; cartId: number;
@ -38,6 +40,26 @@ export const CartScreen = () => {
} | null>(null); } | null>(null);
const navigation = useNavigation<NativeStackNavigationProp<any>>(); const navigation = useNavigation<NativeStackNavigationProp<any>>();
// 计算选中商品的总金额
const calculateTotalAmount = (list: GetCartList[]) => {
let total = 0;
list.forEach(item => {
item.skus.forEach(sku => {
if (sku.selected === 1) {
// 使用 Number.toFixed(2) 保留两位小数,然后转换为数字
total = Number((total + sku.price * sku.quantity).toFixed(2));
}
});
});
setTotalAmount(total);
};
// 在状态更新后计算总金额
const updateCartList = (newList: GetCartList[]) => {
setCartList(newList);
calculateTotalAmount(newList);
};
const changeAllSelected = () => { const changeAllSelected = () => {
const allSkusSelected = cartList.every((item) => const allSkusSelected = cartList.every((item) =>
item.skus.every((sku) => sku.selected === 1) item.skus.every((sku) => sku.selected === 1)
@ -47,15 +69,17 @@ export const CartScreen = () => {
const toggleSelection = async ( const toggleSelection = async (
cartItemId: string, cartItemId: string,
index1: number, index1: number,
index: number index: number | null
) => { ) => {
if (index != null) { if (index != null) {
// 处理子类 SKU 的选择
const data = { const data = {
cart_item_id: cartList[index1].skus[index].cart_item_id, cart_item_id: cartList[index1].skus[index].cart_item_id,
selected: cartList[index1].skus[index].selected === 1 ? 0 : 1, selected: cartList[index1].skus[index].selected === 1 ? 0 : 1,
quantity: cartList[index1].skus[index].quantity, quantity: cartList[index1].skus[index].quantity,
}; };
// 立即更新本地状态
setCartList((prev) => { setCartList((prev) => {
const newList = prev.map((item, idx) => { const newList = prev.map((item, idx) => {
if (idx === index1) { if (idx === index1) {
@ -67,12 +91,6 @@ export const CartScreen = () => {
: sku.selected, : sku.selected,
})); }));
const allSelected = newSkus.every((sku) => sku.selected === 1); const allSelected = newSkus.every((sku) => sku.selected === 1);
updateCartItem(cartList[index1].cart_id, {
quantity: null,
selected: allSelected ? 1 : 0,
}).then((res) => {
console.log(res);
});
return { return {
...item, ...item,
skus: newSkus, skus: newSkus,
@ -81,19 +99,95 @@ export const CartScreen = () => {
} }
return item; return item;
}); });
calculateTotalAmount(newList);
return newList; return newList;
}); });
updateCartItem(cartList[index1].cart_id, data).then((res) => {
console.log(res);
});
setSelectedItems((prev) => ({ setSelectedItems((prev) => ({
...prev, ...prev,
[cartItemId]: !prev[cartItemId], [cartItemId]: !prev[cartItemId],
})); }));
// 在后台发起网络请求
updateCartItem(cartList[index1].cart_id, data).catch((error) => {
console.error('更新购物车商品状态失败:', error);
// 如果请求失败,回滚本地状态
setCartList((prev) => {
const newList = prev.map((item, idx) => {
if (idx === index1) {
const newSkus = item.skus.map((sku) => ({
...sku,
selected:
sku.cart_item_id === data.cart_item_id
? data.selected === 1 ? 0 : 1
: sku.selected,
}));
const allSelected = newSkus.every((sku) => sku.selected === 1);
return {
...item,
skus: newSkus,
selected: allSelected ? 1 : 0,
};
} }
return item;
});
calculateTotalAmount(newList);
return newList;
});
});
} else {
// 处理父类商品的选择
const newSelected = cartList[index1].selected === 1 ? 0 : 1;
// 检查所有商品的 skus 数组中的 selected 属性是否都为 1 // 立即更新本地状态
setCartList((prev) => {
const newList = prev.map((item, idx) => {
if (idx === index1) {
return {
...item,
skus: item.skus.map(sku => ({
...sku,
selected: newSelected
})),
selected: newSelected
};
}
return item;
});
calculateTotalAmount(newList);
return newList;
});
// 获取所有子类的 cart_item_id
const cartItemIds = cartList[index1].skus.map(sku => sku.cart_item_id);
// 在后台发起网络请求
updateBatchCartSelected({
cart_id: cartList[index1].cart_id,
selected: newSelected,
offer_ids: cartItemIds
}).catch((error) => {
console.error('批量更新购物车商品状态失败:', error);
// 如果请求失败,回滚本地状态
setCartList((prev) => {
const newList = prev.map((item, idx) => {
if (idx === index1) {
return {
...item,
skus: item.skus.map(sku => ({
...sku,
selected: newSelected === 1 ? 0 : 1
})),
selected: newSelected === 1 ? 0 : 1
};
}
return item;
});
calculateTotalAmount(newList);
return newList;
});
});
}
changeAllSelected(); changeAllSelected();
}; };
@ -101,6 +195,7 @@ export const CartScreen = () => {
const getCart = async () => { const getCart = async () => {
const res = await getCartList(); const res = await getCartList();
setCartList(res.items); setCartList(res.items);
calculateTotalAmount(res.items);
// 检查所有商品的 skus 数组中的 selected 属性是否都为 1 // 检查所有商品的 skus 数组中的 selected 属性是否都为 1
const allSkusSelected = res.items.every((item) => const allSkusSelected = res.items.every((item) =>
item.skus.every((sku) => sku.selected === 1) item.skus.every((sku) => sku.selected === 1)
@ -109,17 +204,50 @@ export const CartScreen = () => {
}; };
const selectAllHandel = () => { const selectAllHandel = () => {
setAllSelected(!allSelected); const newAllSelected = !allSelected;
setAllSelected(newAllSelected);
// 立即更新本地状态
setCartList((prev) => { setCartList((prev) => {
return prev.map((item) => { const newList = prev.map((item) => {
// 获取所有子类的 cart_item_id
const cartItemIds = item.skus.map(sku => sku.cart_item_id);
// 在后台发起网络请求
updateBatchCartSelected({
cart_id: item.cart_id,
selected: newAllSelected ? 1 : 0,
offer_ids: cartItemIds
}).catch((error) => {
console.error('批量更新购物车商品状态失败:', error);
// 如果请求失败,回滚本地状态
setCartList((prev) => {
const newList = prev.map((item) => {
return { return {
...item, ...item,
selected: allSelected ? 0 : 1, selected: newAllSelected ? 0 : 1,
skus: item.skus.map(sku => ({
...sku,
selected: newAllSelected ? 0 : 1
}))
};
});
calculateTotalAmount(newList);
return newList;
});
setAllSelected(!newAllSelected);
});
return {
...item,
selected: newAllSelected ? 1 : 0,
skus: item.skus.map((sku) => { skus: item.skus.map((sku) => {
return { ...sku, selected: allSelected ? 0 : 1 }; return { ...sku, selected: newAllSelected ? 1 : 0 };
}), }),
}; };
}); });
calculateTotalAmount(newList);
return newList;
}); });
}; };
@ -205,6 +333,8 @@ export const CartScreen = () => {
}); });
}); });
console.log(items);
navigation.navigate("Recipient",{items}); navigation.navigate("Recipient",{items});
}; };
return ( return (
@ -226,7 +356,7 @@ export const CartScreen = () => {
<View style={styles.productCardListing} key={item.cart_id}> <View style={styles.productCardListing} key={item.cart_id}>
<View style={styles.productCardContainer5}> <View style={styles.productCardContainer5}>
<View style={styles.svgContainer1}> <View style={styles.svgContainer1}>
<TouchableOpacity> <TouchableOpacity onPress={() => toggleSelection(String(item.cart_id), index1, null)}>
<View style={[styles.iconContainer]}> <View style={[styles.iconContainer]}>
{item.selected === 1 ? ( {item.selected === 1 ? (
<OrangeCircleIcon size={fontSize(24)} /> <OrangeCircleIcon size={fontSize(24)} />
@ -406,7 +536,7 @@ export const CartScreen = () => {
<View style={styles.productInfoContainer}> <View style={styles.productInfoContainer}>
<View style={styles.productInfoContainer}> <View style={styles.productInfoContainer}>
<Text style={styles.highlightedText1}>24928</Text> <Text style={styles.highlightedText1}>{totalAmount}</Text>
<Text style={styles.priceLabel}>FCFA</Text> <Text style={styles.priceLabel}>FCFA</Text>
</View> </View>
<TouchableOpacity <TouchableOpacity

4
app/screens/Recipient/Address.tsx

@ -28,6 +28,10 @@ type AddRessRouteProp = RouteProp<RootStackParamList, "AddRess">;
export const AddRess = () => { export const AddRess = () => {
const navigation = useNavigation(); const navigation = useNavigation();
const route = useRoute<AddRessRouteProp>(); const route = useRoute<AddRessRouteProp>();
// 添加打印语句
console.log('Address.tsx 接收到的数据:', route.params);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [value, setValue] = useState(null); const [value, setValue] = useState(null);
const [items, setItems] = useState( const [items, setItems] = useState(

373
app/screens/Recipient/ConfirmOrder.tsx

@ -0,0 +1,373 @@
import React from 'react';
import {
View,
Text,
ScrollView,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import BackIcon from '../../components/BackIcon';
import fontSize from '../../utils/fontsizeUtils';
export function ConfirmOrder() {
const navigation = useNavigation<NativeStackNavigationProp<any>>();
// 模拟订单数据
const orderInfo = {
order_no: 'SO2024031500001',
status: 'pending',
payment_method: 'Brainnel Pay(Mobile Money)',
payment_operator: 'MobiCash',
total_amount: 486.92,
items: [
{
id: 1,
product_name: 'Classic Cotton T-Shirt',
attributes: [
{ attribute_name: 'Color', value: 'White' },
{ attribute_name: 'Size', value: 'L' }
],
quantity: 2,
price: 89.96,
total_price: 179.92
},
{
id: 2,
product_name: 'Casual Denim Jeans',
attributes: [
{ attribute_name: 'Style', value: 'Slim Fit' },
{ attribute_name: 'Size', value: '32' }
],
quantity: 1,
price: 297.00,
total_price: 297.00
}
],
shipping: {
method: 'sea',
domestic_fee: 25.50,
international_fee: 45.00,
estimated_arrival: '15-20 days'
},
recipient: {
name: 'John Doe',
phone: '+225 0123456789',
address: 'Abidjan, Côte d\'Ivoire'
}
};
return (
<View style={styles.mainContainer}>
<ScrollView style={styles.container}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<BackIcon size={20} />
</TouchableOpacity>
<Text style={styles.title}></Text>
</View>
{/* Order Number */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionIcon}>📋</Text>
<Text style={styles.sectionTitle}></Text>
</View>
<Text style={styles.orderNumber}>{orderInfo.order_no}</Text>
</View>
<View style={styles.border}></View>
{/* Payment Method */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionIcon}>💳</Text>
<Text style={styles.sectionTitle}></Text>
</View>
<View style={styles.paymentInfo}>
<Text style={styles.paymentMethod}>{orderInfo.payment_method}</Text>
<Text style={styles.paymentOperator}>{orderInfo.payment_operator}</Text>
</View>
</View>
<View style={styles.border}></View>
{/* Shipping Info */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionIcon}>🚢</Text>
<Text style={styles.sectionTitle}></Text>
</View>
<View style={styles.shippingInfo}>
<View style={styles.shippingRow}>
<Text style={styles.shippingLabel}></Text>
<Text style={styles.shippingValue}>
{orderInfo.shipping.method === 'sea' ? '海运' : '空运'}
</Text>
</View>
<View style={styles.shippingRow}>
<Text style={styles.shippingLabel}></Text>
<Text style={styles.shippingValue}>{orderInfo.shipping.estimated_arrival}</Text>
</View>
</View>
</View>
<View style={styles.border}></View>
{/* Recipient Info */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionIcon}>👤</Text>
<Text style={styles.sectionTitle}></Text>
</View>
<View style={styles.recipientInfo}>
<Text style={styles.recipientName}>{orderInfo.recipient.name}</Text>
<Text style={styles.recipientPhone}>{orderInfo.recipient.phone}</Text>
<Text style={styles.recipientAddress}>{orderInfo.recipient.address}</Text>
</View>
</View>
<View style={styles.border}></View>
{/* Order Items */}
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionIcon}>📦</Text>
<Text style={styles.sectionTitle}></Text>
</View>
<View style={styles.orderItems}>
{orderInfo.items.map((item) => (
<View key={item.id} style={styles.orderItem}>
<View style={styles.itemDetails}>
<Text style={styles.itemName} numberOfLines={2}>
{item.product_name}
</Text>
{item.attributes.map((attr) => (
<Text key={attr.attribute_name} style={styles.itemAttribute}>
{attr.attribute_name}: {attr.value}
</Text>
))}
<Text style={styles.itemQuantity}>x{item.quantity}</Text>
</View>
<View style={styles.itemPrice}>
<Text style={styles.priceText}>${item.total_price}</Text>
</View>
</View>
))}
</View>
</View>
<View style={styles.border}></View>
{/* Price Summary */}
<View style={styles.section}>
<View style={styles.priceSummary}>
<View style={styles.priceRow}>
<Text style={styles.priceLabel}></Text>
<Text style={styles.priceValue}>${orderInfo.items.reduce((sum, item) => sum + item.total_price, 0)}</Text>
</View>
<View style={styles.priceRow}>
<Text style={styles.priceLabel}></Text>
<Text style={styles.priceValue}>${orderInfo.shipping.domestic_fee}</Text>
</View>
<View style={styles.priceRow}>
<Text style={styles.priceLabel}></Text>
<Text style={styles.priceValue}>${orderInfo.shipping.international_fee}</Text>
</View>
<View style={[styles.priceRow, styles.totalRow]}>
<Text style={styles.totalLabel}></Text>
<Text style={styles.totalAmount}>${orderInfo.total_amount}</Text>
</View>
</View>
</View>
{/* Bottom Button */}
<TouchableOpacity style={styles.bottomButton}>
<View style={styles.bottomButtonContent}>
<Text style={styles.bottomButtonText}></Text>
</View>
</TouchableOpacity>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
backgroundColor: '#fff',
},
container: {
flex: 1,
backgroundColor: '#fff',
},
header: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#fff',
elevation: 3,
},
title: {
fontSize: fontSize(18),
fontWeight: '500',
flex: 1,
textAlign: 'center',
},
section: {
backgroundColor: '#fff',
padding: 16,
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
sectionIcon: {
fontSize: fontSize(18),
marginRight: 8,
},
sectionTitle: {
fontSize: fontSize(16),
fontWeight: '500',
},
border: {
height: 6,
backgroundColor: '#f5f5f5',
},
orderNumber: {
fontSize: fontSize(16),
color: '#666',
},
paymentInfo: {
backgroundColor: '#f8f8f8',
padding: 12,
borderRadius: 8,
},
paymentMethod: {
fontSize: fontSize(16),
fontWeight: '500',
marginBottom: 4,
},
paymentOperator: {
fontSize: fontSize(14),
color: '#666',
},
shippingInfo: {
backgroundColor: '#f8f8f8',
padding: 12,
borderRadius: 8,
},
shippingRow: {
flexDirection: 'row',
marginBottom: 8,
},
shippingLabel: {
fontSize: fontSize(14),
color: '#666',
width: 80,
},
shippingValue: {
fontSize: fontSize(14),
flex: 1,
},
recipientInfo: {
backgroundColor: '#f8f8f8',
padding: 12,
borderRadius: 8,
},
recipientName: {
fontSize: fontSize(16),
fontWeight: '500',
marginBottom: 4,
},
recipientPhone: {
fontSize: fontSize(14),
color: '#666',
marginBottom: 4,
},
recipientAddress: {
fontSize: fontSize(14),
color: '#666',
},
orderItems: {
gap: 12,
},
orderItem: {
flexDirection: 'row',
padding: 12,
backgroundColor: '#f8f8f8',
borderRadius: 8,
},
itemDetails: {
flex: 1,
},
itemName: {
fontSize: fontSize(14),
marginBottom: 4,
},
itemAttribute: {
fontSize: fontSize(12),
color: '#666',
marginBottom: 2,
},
itemQuantity: {
fontSize: fontSize(12),
color: '#999',
marginTop: 4,
},
itemPrice: {
justifyContent: 'center',
},
priceText: {
fontSize: fontSize(16),
fontWeight: '500',
color: '#ff6000',
},
priceSummary: {
backgroundColor: '#f8f8f8',
padding: 16,
borderRadius: 8,
},
priceRow: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 12,
},
priceLabel: {
fontSize: fontSize(14),
color: '#666',
},
priceValue: {
fontSize: fontSize(14),
color: '#333',
},
totalRow: {
marginTop: 8,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: '#eee',
},
totalLabel: {
fontSize: fontSize(16),
fontWeight: '500',
},
totalAmount: {
fontSize: fontSize(18),
fontWeight: '600',
color: '#ff6000',
},
bottomButton: {
width: '100%',
padding: 16,
backgroundColor: '#fff',
},
bottomButtonContent: {
backgroundColor: '#ff6000',
padding: 16,
borderRadius: 25,
alignItems: 'center',
},
bottomButtonText: {
color: '#fff',
fontSize: fontSize(16),
fontWeight: '500',
},
});

471
app/screens/Recipient/Recipient.tsx

@ -33,6 +33,56 @@ import {
DomesticShippingFeeData, DomesticShippingFeeData,
} from "../../services/api/orders"; } from "../../services/api/orders";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import { payApi, PaymentMethodsResponse } from "../../services/api/payApi";
interface PaymentOption {
id: string;
label: string;
icon: string;
value?: string | string[];
}
interface PaymentTab {
id: string;
label: string;
options: PaymentOption[];
}
const PaymentMethodItem = ({ option, isSelected, onSelect }: {
option: PaymentOption;
isSelected: boolean;
onSelect: () => void;
}) => (
<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>
)}
</View>
<View style={styles.radioButton}>
<View style={[
styles.radioInner,
isSelected && styles.radioInnerSelected
]} />
</View>
</TouchableOpacity>
);
export function Recipient({ export function Recipient({
route, route,
@ -61,17 +111,21 @@ export function Recipient({
useState<AddressDataItem>(); useState<AddressDataItem>();
const [domesticShippingFee, setDomesticShippingFee] = const [domesticShippingFee, setDomesticShippingFee] =
useState<DomesticShippingFeeData>(); useState<DomesticShippingFeeData>();
const [tabs, setTabs] = useState([ const [tabs, setTabs] = useState<PaymentTab[]>([
{ {
id: "Online Payment", id: "online",
label: "Online Payment", label: "Online Payment",
options: []
}, },
{ {
id: "Offline Payment", id: "offline",
label: "Offline Payment", label: "Offline Payment",
}, options: []
}
]); ]);
const [currentTab, setCurrentTab] = useState("Online Paymen"); const [currentTab, setCurrentTab] = useState("online");
const [selectedPayment, setSelectedPayment] = useState<string | null>(null);
const [paymentMethods, setPaymentMethods] = useState<PaymentMethodsResponse>();
const getAddress = async () => { const getAddress = async () => {
const response = await addressApi.addressesDefault(); const response = await addressApi.addressesDefault();
@ -106,8 +160,55 @@ export function Recipient({
items: route.params.items, items: route.params.items,
}; };
const response = await ordersApi.calcDomesticShippingFee(data); const response = await ordersApi.calcDomesticShippingFee(data);
// 转换响应数据以匹配 DomesticShippingFeeData 类型
const domesticShippingFeeData: DomesticShippingFeeData = {
total_shipping_fee: response.total_shipping_fee_air || 0,
currency: response.currency || '',
// 添加其他必要的属性
};
setDomesticShippingFee(domesticShippingFeeData);
};
const getPaymentMethods = async () => {
try {
const response = await payApi.getCountryPaymentMethods();
setPaymentMethods(response);
// 设置默认支付方式选项
setTabs([
{
id: "online",
label: "Online Payment",
options: response.current_country_methods.map(method => ({
id: method.key,
label: method.key,
icon: getPaymentIcon(method.key),
value: method.value
}))
},
{
id: "offline",
label: "Offline Payment",
options: []
}
]);
} catch (error) {
console.error('获取支付方式失败:', error);
}
};
setDomesticShippingFee(response); const getPaymentIcon = (key: string): string => {
switch (key) {
case 'Brainnel Pay(Mobile Money)':
return '💳';
case 'Wave':
return '💸';
case 'Paypal':
return '🅿';
case 'Bank Card Payment':
return '💳';
default:
return '💰';
}
}; };
useEffect(() => { useEffect(() => {
@ -116,6 +217,7 @@ export function Recipient({
getOrders(); getOrders();
getFreightForwarderAddress(); getFreightForwarderAddress();
getDomesticShippingFee(); getDomesticShippingFee();
getPaymentMethods();
const listener = (data: any) => { const listener = (data: any) => {
if (data.type === "add") { if (data.type === "add") {
data.address_id = new Date().getTime(); data.address_id = new Date().getTime();
@ -136,30 +238,32 @@ export function Recipient({
const [mobileNumber, setMobileNumber] = useState(""); const [mobileNumber, setMobileNumber] = useState("");
const [countryCode, setCountryCode] = useState("225"); const [countryCode, setCountryCode] = useState("225");
const balance = 245.0;
const exchangeRates = { usd: 1, eur: 0.885 };
const getOrderAmount = () => {
// 模拟总金额,可加入实际订单计算逻辑
const baseAmount = parseFloat(shippingFee.replace("$", "")) || 0;
return 100 + baseAmount; // 示例:商品 $100 + 运费
};
const [couponModalVisible, setCouponModalVisible] = useState(false); const [couponModalVisible, setCouponModalVisible] = useState(false);
const [appliedCoupons, setAppliedCoupons] = useState([]); const [appliedCoupons, setAppliedCoupons] = useState<{
code: string;
name: string;
discount: number;
type: 'percent' | 'fixed';
}[]>([]);
const [orderTotal, setOrderTotal] = useState(121.97); const [orderTotal, setOrderTotal] = useState(121.97);
const [originalTotal] = useState(121.97); const [originalTotal] = useState(121.97);
const [subtotal] = useState(96.47); const [subtotal] = useState(96.47);
const [domesticShipping] = useState(25.5); const [domesticShipping] = useState(25.5);
const [internationalShipping] = useState(45.0); const [internationalShipping] = useState(45.0);
const validCoupons = { const validCoupons: {
[key: string]: {
discount: number;
type: 'percent' | 'fixed';
name: string;
};
} = {
WELCOME10: { discount: 10, type: "percent", name: "Welcome 10% Off" }, WELCOME10: { discount: 10, type: "percent", name: "Welcome 10% Off" },
SAVE20: { discount: 20, type: "fixed", name: "$20 Off" }, SAVE20: { discount: 20, type: "fixed", name: "$20 Off" },
FREESHIP: { discount: 25.5, type: "fixed", name: "Free Domestic Shipping" }, FREESHIP: { discount: 25.5, type: "fixed", name: "Free Domestic Shipping" },
}; };
const addCoupon = (code) => { const addCoupon = (code: string) => {
if (appliedCoupons.find((c) => c.code === code)) { if (appliedCoupons.find((c) => c.code === code)) {
alert("This coupon is already applied."); alert("This coupon is already applied.");
return; return;
@ -180,13 +284,13 @@ export function Recipient({
updateTotalWithDiscounts(newCoupons); updateTotalWithDiscounts(newCoupons);
}; };
const removeCoupon = (code) => { const removeCoupon = (code: string) => {
const newCoupons = appliedCoupons.filter((c) => c.code !== code); const newCoupons = appliedCoupons.filter((c) => c.code !== code);
setAppliedCoupons(newCoupons); setAppliedCoupons(newCoupons);
updateTotalWithDiscounts(newCoupons); updateTotalWithDiscounts(newCoupons);
}; };
const updateTotalWithDiscounts = (coupons) => { const updateTotalWithDiscounts = (coupons: typeof appliedCoupons) => {
let totalDiscount = 0; let totalDiscount = 0;
coupons.forEach((coupon) => { coupons.forEach((coupon) => {
@ -202,7 +306,7 @@ export function Recipient({
setOrderTotal(newTotal); setOrderTotal(newTotal);
}; };
const isCouponApplied = (code) => { const isCouponApplied = (code: string) => {
return appliedCoupons.some((c) => c.code === code); return appliedCoupons.some((c) => c.code === code);
}; };
@ -405,103 +509,38 @@ export function Recipient({
<Text style={styles.sectionIcon}>💳</Text> <Text style={styles.sectionIcon}>💳</Text>
<Text style={styles.sectionTitle}>Payment Method</Text> <Text style={styles.sectionTitle}>Payment Method</Text>
</View> </View>
<View style={styles.paymentOptions}>
<View style={styles.paymentOption}>
<Text style={styles.paymentLabel}>Online Payment</Text>
</View>
<View style={styles.paymentOption}>
<Text style={styles.paymentLabel}>Offline Payment</Text>
</View>
</View>
<View>
{/* {[
{ id: "balance", icon: "💰", label: "Account Balance" },
{ id: "mobile_money", icon: "📱", label: "Mobile Money" },
{ id: "paypal", icon: "🅿", label: "PayPal" },
{ id: "card", icon: "💳", label: "Credit/Debit Card" },
].map((option) => (
<TouchableOpacity
key={option.id}
style={[
styles.paymentOption,
paymentMethod === option.id && styles.paymentSelected,
]}
onPress={() => {
setPaymentMethod(option.id);
}}
>
<Text style={styles.paymentIcon}>{option.icon}</Text>
<Text style={styles.paymentLabel}>{option.label}</Text>
</TouchableOpacity>
))} */}
{tabs.map((item, index) => (
<TouchableOpacity
key={index}
onPress={() => {
setCurrentTab(item.id);
}}
>
<View style={styles.tabContainer}>
<Text style={styles.paymentLabel}>{item.label}</Text>
</View>
</TouchableOpacity>
))}
{/* Mobile Money 表单 */}
{paymentMethod === "mobile_money" && (
<View style={styles.mobileForm}>
<Text style={styles.formLabel}>Mobile Number</Text>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<TouchableOpacity
onPress={() => {
const next =
countryCode === "225"
? "234"
: countryCode === "234"
? "233"
: "225";
setCountryCode(next);
}}
>
<Text style={styles.countryCode}>{countryCode}</Text>
</TouchableOpacity>
<TextInput
style={[styles.input, { flex: 1, marginLeft: 10 }]}
keyboardType="phone-pad"
placeholder="Enter your number"
value={mobileNumber}
onChangeText={setMobileNumber}
/>
</View>
</View>
)}
{/* PayPal Currency 切换 */} {/* 选项卡 */}
{paymentMethod === "paypal" && ( <View style={styles.tabContainer}>
<View style={{ marginTop: 12 }}> {tabs.map((tab) => (
<Text style={styles.formLabel}>Select Currency</Text>
<View style={{ flexDirection: "row", marginTop: 8 }}>
{["usd", "eur"].map((cur) => (
<TouchableOpacity <TouchableOpacity
key={cur} key={tab.id}
onPress={() => {
setCurrency(cur);
}}
style={[ style={[
styles.currencyButton, styles.tabButton,
currency === cur && styles.currencyButtonSelected, currentTab === tab.id && styles.tabButtonActive
]} ]}
onPress={() => setCurrentTab(tab.id)}
> >
<Text <Text style={[
style={{ color: currency === cur ? "#ff6000" : "#333" }} styles.tabText,
> currentTab === tab.id && styles.tabTextActive
{cur.toUpperCase()} ]}>
{tab.label}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
))} ))}
</View> </View>
</View>
)} {/* 支付选项 */}
<View style={styles.paymentOptions}>
{tabs.find(tab => tab.id === currentTab)?.options.map((option) => (
<PaymentMethodItem
key={option.id}
option={option}
isSelected={selectedPayment === option.id}
onSelect={() => setSelectedPayment(option.id)}
/>
))}
</View> </View>
</View> </View>
@ -918,8 +957,75 @@ export function Recipient({
<TouchableOpacity <TouchableOpacity
style={styles.bottomButton} style={styles.bottomButton}
disabled={!domesticShippingFee?.currency} disabled={!domesticShippingFee?.currency}
onPress={() => { onPress={async () => {
console.log(123); if (!defaultAddress) {
alert('请添加收件人信息');
return;
}
if (!selectedPayment) {
alert('请选择支付方式');
return;
}
console.log(orderData);
// 构建订单数据
const submitOrderData = {
address_id: defaultAddress.address_id,
items: orderData?.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,
product_image: item.sku_image_url,
sku_attributes: item.attributes.map(attr => ({
attribute_name: attr.attribute_name,
attribute_value: attr.value
})),
quantity: item.quantity,
unit_price: item.unit_price,
total_price: item.total_price
})) || [],
buyer_message: "",
payment_method: selectedPayment,
create_payment: true,
total_amount: orderData?.total_amount || 0,
actual_amount: (
(orderData?.total_amount ?? 0) +
(shippingMethod === 'sea'
? orderData?.shipping_fee_sea ?? 0
: orderData?.shipping_fee_air ?? 0) +
(domesticShippingFee?.total_shipping_fee ?? 0)
),
discount_amount: 0,
shipping_fee: shippingMethod === 'sea'
? orderData?.shipping_fee_sea ?? 0
: orderData?.shipping_fee_air ?? 0,
domestic_shipping_fee: domesticShippingFee?.total_shipping_fee || 0,
currency: domesticShippingFee?.currency || 'USD',
receiver_address: `${defaultAddress.country} ${defaultAddress.province || ''} ${defaultAddress.city || ''} ${defaultAddress.detail_address || ''}`
};
// 打印订单数据
console.log('订单数据:', JSON.stringify(submitOrderData, null, 2));
// 创建订单请求(暂时注释)
try {
const response = await ordersApi.createOrder(submitOrderData);
console.log('订单创建成功:', response);
if (response.payment_url) {
// 处理支付链接
console.log('支付链接:', response.payment_url);
}
navigation.navigate('ConfirmOrder', { orderId: response.order_id });
} catch (error) {
console.error('创建订单失败:', error);
alert('创建订单失败,请重试');
}
}} }}
> >
<View style={styles.bottomButtonContent}> <View style={styles.bottomButtonContent}>
@ -973,11 +1079,9 @@ const styles = StyleSheet.create({
fontSize: fontSize(13), fontSize: fontSize(13),
fontWeight: "500", fontWeight: "500",
}, },
paymentOptions:{ paymentOptions: {
flexDirection: "row", marginTop: 12,
justifyContent: "space-between", flexDirection: 'column',
alignItems: "center",
width: "100%",
}, },
recipientInfo: { recipientInfo: {
backgroundColor: "#fff", backgroundColor: "#fff",
@ -1062,7 +1166,6 @@ const styles = StyleSheet.create({
color: "#777", color: "#777",
fontWeight: "500", fontWeight: "500",
fontSize: fontSize(13), fontSize: fontSize(13),
color: "#333",
}, },
modalOverlay: { modalOverlay: {
@ -1121,24 +1224,47 @@ const styles = StyleSheet.create({
fontWeight: "500", fontWeight: "500",
}, },
paymentOption: { paymentOption: {
flexDirection: "row", flexDirection: 'row',
alignItems: "center", alignItems: 'center',
padding: 16,
marginBottom: 12,
borderWidth: 1, borderWidth: 1,
borderColor: "#ddd", borderColor: '#eee',
padding: 12, borderRadius: 8,
borderRadius: 6, backgroundColor: '#fff',
width: "50%", width: '100%',
}, },
paymentSelected: { paymentSelected: {
borderColor: "#ff6000", backgroundColor: '#fff8f3',
backgroundColor: "#fff8f3", borderColor: '#ff8c47',
}, },
paymentIcon: { fontSize: fontSize(20), marginRight: 10 }, paymentIcon: { fontSize: fontSize(24), marginRight: 8 },
paymentLabel: { fontSize: fontSize(14), fontWeight: "500" }, paymentLabel: { fontSize: fontSize(16), fontWeight: '500' },
tabContainer: { tabContainer: {
width: 100, flexDirection: 'row',
flexDirection: "row", marginBottom: 16,
backgroundColor: '#f5f5f5',
borderRadius: 8,
padding: 4,
},
tabButton: {
flex: 1,
paddingVertical: 12,
alignItems: 'center',
borderRadius: 6,
marginRight: 4,
},
tabButtonActive: {
backgroundColor: '#fff',
},
tabText: {
fontSize: fontSize(14),
color: '#666',
},
tabTextActive: {
color: '#000',
fontWeight: '500',
}, },
mobileForm: { marginTop: 12 }, mobileForm: { marginTop: 12 },
countryCode: { countryCode: {
@ -1674,4 +1800,91 @@ const styles = StyleSheet.create({
fontSize: fontSize(16), fontSize: fontSize(16),
fontWeight: "500", fontWeight: "500",
}, },
paymentContent: {
flex: 1,
},
brainnelHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
paymentLogo: {
height: 30,
width: 100,
resizeMode: 'contain',
},
paymentDescription: {
fontSize: fontSize(12),
color: '#666',
marginLeft: 8,
},
operatorContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
marginTop: 8,
},
operatorBox: {
padding: 8,
backgroundColor: '#f5f5f5',
borderRadius: 4,
},
soldesContainer: {
flexDirection: 'row',
alignItems: 'center',
},
balanceText: {
marginLeft: 8,
fontSize: fontSize(14),
color: '#666',
},
cardTypesContainer: {
flexDirection: 'row',
gap: 8,
},
cardTypeIcon: {
height: 24,
width: 36,
resizeMode: 'contain',
},
radioButton: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: '#ddd',
justifyContent: 'center',
alignItems: 'center',
},
radioInner: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: 'transparent',
},
radioInnerSelected: {
backgroundColor: '#ff8c47',
},
cardHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
cardTypeBox: {
padding: 6,
backgroundColor: '#f5f5f5',
borderRadius: 4,
},
cardTypeText: {
fontSize: fontSize(12),
color: '#666',
},
defaultPaymentContainer: {
flexDirection: 'row',
alignItems: 'center',
},
operatorText: {
fontSize: fontSize(12),
color: '#666',
},
}); });

5
app/services/api/cart.ts

@ -62,6 +62,11 @@ export const updateCartItem = (cart_id:number,data?:{cart_item_id?:number | null
return apiService.put(`/api/cart/${cart_id}`,data); return apiService.put(`/api/cart/${cart_id}`,data);
} }
// 批量更新选中状态
export const updateBatchCartSelected = (data?:{cart_id?:number | null,selected:number | null,offer_ids:Array<number> | null}) => {
return apiService.patch(`/api/cart/selected`,data);
}
export const deleteCartItem = (cart_id:number,cart_item_id:number) => { export const deleteCartItem = (cart_id:number,cart_item_id:number) => {
return apiService.delete(`/api/cart/${cart_id}?cart_item_id=${cart_item_id}`); return apiService.delete(`/api/cart/${cart_id}?cart_item_id=${cart_item_id}`);
} }

41
app/services/api/orders.ts

@ -22,6 +22,7 @@ interface Address {
// 订单商品项类型 // 订单商品项类型
interface OrderItem { interface OrderItem {
offer_id: number; offer_id: number;
cart_item_id: number;
sku_id: number; sku_id: number;
product_name: string; product_name: string;
sku_image_url: string; sku_image_url: string;
@ -133,6 +134,43 @@ interface Address {
// 创建订单请求参数类型
export interface CreateOrderRequest {
address_id: number;
items: {
offer_id: number;
cart_item_id: number;
sku_id: number;
product_name: string;
product_name_en: string;
product_name_ar: string;
product_name_fr: string;
product_image: string;
sku_attributes: Record<string, any>[];
quantity: number;
unit_price: number;
total_price: number;
}[];
buyer_message: string;
payment_method: string;
create_payment: boolean;
total_amount: number;
actual_amount: number;
discount_amount: number;
shipping_fee: number;
domestic_shipping_fee: number;
currency: string;
receiver_address: string;
}
// 创建订单响应类型
export interface CreateOrderResponse {
order_id: number;
order_no: string;
status: string;
payment_url?: string;
}
export const ordersApi = { export const ordersApi = {
getOrders: (data:OrderPreviewData) => apiService.post<OrderData>("/api/orders/preview",data), getOrders: (data:OrderPreviewData) => apiService.post<OrderData>("/api/orders/preview",data),
@ -148,6 +186,9 @@ interface Address {
calcDomesticShippingFee: (data:ShippingFeeData) => calcDomesticShippingFee: (data:ShippingFeeData) =>
apiService.post<CartShippingFeeData>(`/api/orders/calc_domestic_shipping`,data), apiService.post<CartShippingFeeData>(`/api/orders/calc_domestic_shipping`,data),
// 创建订单
createOrder: (data: CreateOrderRequest) =>
apiService.post<CreateOrderResponse>('/api/orders/cart', data),
}; };

26
app/services/api/payApi.ts

@ -0,0 +1,26 @@
import apiService from './apiClient';
// 支付方式类型定义
export interface PaymentMethod {
key: string;
value: string | string[];
}
export interface CountryPaymentMethods {
country?: number;
country_name?: string;
payment_methods: PaymentMethod[];
}
export interface PaymentMethodsResponse {
current_country_code: number;
current_country_methods: PaymentMethod[];
other_country_methods: CountryPaymentMethods[];
}
export const payApi = {
// 获取当前国家支付方式
getCountryPaymentMethods: () => {
return apiService.get<PaymentMethodsResponse>('/api/payment/country_payment_methods');
},
};

0
orders.txt

1048
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save