Browse Source

订单列表

main
Mac 1 month ago
parent
commit
949e45a521
  1. 33
      App.tsx
  2. 17
      app/components/MassageIcon.tsx
  3. 18
      app/constants/productStatus.ts
  4. 82
      app/screens/ProfileScreen.tsx
  5. 99
      app/screens/Recipient/ConfirmOrder.tsx
  6. 134
      app/screens/Recipient/Recipient.tsx
  7. 68
      app/screens/pay/Pay.tsx
  8. 13
      app/screens/productStatus/OrderDatails.tsx
  9. 423
      app/screens/productStatus/Status.tsx
  10. 119
      app/services/api/orders.ts
  11. 17
      app/services/api/payApi.ts
  12. 63
      app/store/order.ts
  13. 998
      package-lock.json
  14. 5
      package.json
  15. 1667
      yarn.lock

33
App.tsx

@ -28,6 +28,9 @@ 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"; import { ConfirmOrder } from "./app/screens/Recipient/ConfirmOrder";
import { Pay } from "./app/screens/pay/Pay";
import { Status } from "./app/screens/productStatus/Status";
import { OrderDetails } from "./app/screens/productStatus/OrderDatails";
export type RootStackParamList = { export type RootStackParamList = {
CountrySelect: undefined; CountrySelect: undefined;
@ -51,6 +54,9 @@ export type RootStackParamList = {
MyAccount:undefined; MyAccount:undefined;
Google: undefined; Google: undefined;
ConfirmOrder: undefined; ConfirmOrder: undefined;
Pay: undefined;
Status: undefined;
OrderDetails: undefined;
}; };
const Stack = createNativeStackNavigator<RootStackParamList>(); const Stack = createNativeStackNavigator<RootStackParamList>();
@ -241,6 +247,33 @@ export default function App() {
gestureDirection: "horizontal", gestureDirection: "horizontal",
}} }}
/> />
<Stack.Screen
name="Pay"
component={Pay}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
<Stack.Screen
name="Status"
component={Status}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
<Stack.Screen
name="OrderDetails"
component={OrderDetails}
options={{
animation: "slide_from_right",
gestureEnabled: true,
gestureDirection: "horizontal",
}}
/>
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
</AuthProvider> </AuthProvider>

17
app/components/MassageIcon.tsx

@ -0,0 +1,17 @@
import React from 'react';
import Svg, { Path } from 'react-native-svg';
const MassageIcon = ({size}:{size:number}) => (
<Svg
viewBox="0 0 1024 1024"
width={size}
height={size}
>
<Path
d="M821.333333 800H547.584l-86.464 96.074667a32 32 0 1 1-47.573333-42.816l96-106.666667A32 32 0 0 1 533.333333 736h288a53.333333 53.333333 0 0 0 53.333334-53.333333V234.666667a53.333333 53.333333 0 0 0-53.333334-53.333334H202.666667a53.333333 53.333333 0 0 0-53.333334 53.333334v448a53.333333 53.333333 0 0 0 53.333334 53.333333h138.666666a32 32 0 0 1 0 64H202.666667c-64.8 0-117.333333-52.533333-117.333334-117.333333V234.666667c0-64.8 52.533333-117.333333 117.333334-117.333334h618.666666c64.8 0 117.333333 52.533333 117.333334 117.333334v448c0 64.8-52.533333 117.333333-117.333334 117.333333zM704 341.333333a32 32 0 0 1 0 64H320a32 32 0 0 1 0-64h384zM512 512a32 32 0 0 1 0 64H320a32 32 0 0 1 0-64h192z"
fill="#2c2c2c"
/>
</Svg>
);
export default MassageIcon;

18
app/constants/productStatus.ts

@ -0,0 +1,18 @@
import DocumentApprovedIcon from "../components/DocumentApprovedIcon";
import PdfDocumentIcon from "../components/PdfDocumentIcon";
import DocumentClockIcon from "../components/DocumentClockIcon";
interface ProductStatus {
icon: React.ElementType;
text: string;
status: number | null;
}
export const productStatus: ProductStatus[] = [
{ icon: DocumentApprovedIcon, text: '待报价', status: 5 },
{ icon: PdfDocumentIcon, text: '待付款', status: 0 },
{ icon: DocumentClockIcon, text: '付运费', status: 6 },
{ icon: DocumentClockIcon, text: '待发货', status: 1 },
{ icon: DocumentApprovedIcon, text: '运输中', status: 7 },
{ icon: DocumentApprovedIcon, text: '代收货', status: 2 },
{ icon: PdfDocumentIcon, text: '已完成', status: 3 },
{ icon: DocumentClockIcon, text: '已取消', status: 4 }
]

82
app/screens/ProfileScreen.tsx

@ -12,8 +12,6 @@ import fontSize from "../utils/fontsizeUtils";
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { userApi, User } from "../services/api/userApi"; import { userApi, User } from "../services/api/userApi";
import { settingApi } from "../services/api/setting";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { useFocusEffect } from "@react-navigation/native"; import { useFocusEffect } from "@react-navigation/native";
import BookmarkIcon from "../components/BookmarkIcon"; import BookmarkIcon from "../components/BookmarkIcon";
import LeftArrowIcon from "../components/DownArrowIcon"; import LeftArrowIcon from "../components/DownArrowIcon";
@ -22,11 +20,13 @@ import DocumentApprovedIcon from "../components/DocumentApprovedIcon";
import PdfDocumentIcon from "../components/PdfDocumentIcon"; import PdfDocumentIcon from "../components/PdfDocumentIcon";
import DocumentClockIcon from "../components/DocumentClockIcon"; import DocumentClockIcon from "../components/DocumentClockIcon";
import widthUtils from "../utils/widthUtils"; import widthUtils from "../utils/widthUtils";
import { productStatus } from "../constants/productStatus";
type RootStackParamList = { type RootStackParamList = {
SettingList: undefined; SettingList: undefined;
Home: undefined; Home: undefined;
MyAccount: undefined; MyAccount: undefined;
Login: undefined; Login: undefined;
Status: { status: number };
}; };
export const ProfileScreen = () => { export const ProfileScreen = () => {
@ -162,74 +162,19 @@ export const ProfileScreen = () => {
</View> </View>
<View style={styles.groupItem}> <View style={styles.groupItem}>
<View style={styles.groupItemContent}> {productStatus.map((item, index) => (
<View style={styles.groupItemContentIcon}> <TouchableOpacity
<DocumentApprovedIcon size={fontSize(38)} color="#707070" /> key={index}
</View> style={styles.groupItemContent}
<Text style={styles.groupItemContentText}></Text> onPress={() => item.status !== null ? navigation.navigate("Status", { status: item.status }) : null}
</View> >
<View style={styles.groupItemContent}>
<View style={styles.groupItemContentIcon}>
<PdfDocumentIcon size={fontSize(38)} color="#707070" />
</View>
<Text style={styles.groupItemContentText}></Text>
</View>
<View style={styles.groupItemContent}>
<View style={styles.groupItemContentIcon}>
<DocumentClockIcon size={fontSize(38)} color="#707070" />
</View>
<Text style={styles.groupItemContentText}></Text>
</View>
<View style={styles.groupItemContent}>
<View style={styles.groupItemContentIcon}>
<DocumentClockIcon size={fontSize(38)} color="#707070" />
</View>
<Text style={styles.groupItemContentText}></Text>
</View>
</View>
<View style={styles.groupItem}>
<View style={styles.groupItemContent}>
<View style={styles.groupItemContentIcon}>
<DocumentApprovedIcon size={fontSize(38)} color="#707070" />
</View>
<Text style={styles.groupItemContentText}></Text>
</View>
<View style={styles.groupItemContent}>
<View style={styles.groupItemContentIcon}>
<PdfDocumentIcon size={fontSize(38)} color="#707070" />
</View>
<Text style={styles.groupItemContentText}></Text>
</View>
<View style={styles.groupItemContent}>
<View style={styles.groupItemContentIcon}>
<DocumentClockIcon size={fontSize(38)} color="#707070" />
</View>
<Text style={styles.groupItemContentText}></Text>
</View>
<View style={styles.groupItemContent}>
<View style={styles.groupItemContentIcon}> <View style={styles.groupItemContentIcon}>
<DocumentClockIcon size={fontSize(38)} color="#707070" /> <item.icon size={fontSize(38)} color="#707070" />
</View>
<Text style={styles.groupItemContentText}></Text>
</View>
</View> </View>
<Text style={styles.groupItemContentText}>{item.text}</Text>
{/* <View style={styles.groupItem}> </TouchableOpacity>
<View style={styles.groupItemContent}> ))}
<View>
</View>
<Text></Text>
</View>
<View style={styles.groupItemContent}>
<Text></Text>
</View>
<View style={styles.groupItemContent}>
<Text></Text>
</View> </View>
</View> */}
</View> </View>
</View> </View>
@ -709,7 +654,7 @@ const styles = StyleSheet.create({
flexDirection: "row", flexDirection: "row",
flexWrap: "wrap", flexWrap: "wrap",
width: "100%", width: "100%",
marginTop: 15, marginTop: 25,
}, },
groupItemContent: { groupItemContent: {
width: "23%", width: "23%",
@ -718,6 +663,7 @@ const styles = StyleSheet.create({
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
flexDirection: "column", flexDirection: "column",
marginBottom: 10,
}, },
groupItemContentText: { groupItemContentText: {
fontSize: fontSize(12), fontSize: fontSize(12),

99
app/screens/Recipient/ConfirmOrder.tsx

@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { import {
View, View,
Text, Text,
@ -10,53 +10,31 @@ import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import BackIcon from '../../components/BackIcon'; import BackIcon from '../../components/BackIcon';
import fontSize from '../../utils/fontsizeUtils'; import fontSize from '../../utils/fontsizeUtils';
import useOrderStore from '../../store/order';
import { ordersApi } from '../../services/api/orders';
import { Order } from '../../services/api/orders';
export function ConfirmOrder() { export function ConfirmOrder() {
const navigation = useNavigation<NativeStackNavigationProp<any>>(); const navigation = useNavigation<NativeStackNavigationProp<any>>();
const orderInfo = useOrderStore();
const [order, setOrder] = useState<Order>();
const getOrder = async () => {
const data = orderInfo.order
try {
const response = await ordersApi.createOrder(data);
console.log(response);
// 模拟订单数据 setOrder(response);
const orderInfo = { } catch (error) {
order_no: 'SO2024031500001', navigation.goBack();
status: 'pending', console.error('创建订单失败:', error);
payment_method: 'Brainnel Pay(Mobile Money)', alert('创建订单失败,请重试');
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'
} }
};
useEffect(() => {
getOrder()
},[])
return ( return (
<View style={styles.mainContainer}> <View style={styles.mainContainer}>
@ -75,7 +53,7 @@ export function ConfirmOrder() {
<Text style={styles.sectionIcon}>📋</Text> <Text style={styles.sectionIcon}>📋</Text>
<Text style={styles.sectionTitle}></Text> <Text style={styles.sectionTitle}></Text>
</View> </View>
<Text style={styles.orderNumber}>{orderInfo.order_no}</Text> <Text style={styles.orderNumber}>{order?.order_no}</Text>
</View> </View>
<View style={styles.border}></View> <View style={styles.border}></View>
@ -86,8 +64,8 @@ export function ConfirmOrder() {
<Text style={styles.sectionTitle}></Text> <Text style={styles.sectionTitle}></Text>
</View> </View>
<View style={styles.paymentInfo}> <View style={styles.paymentInfo}>
<Text style={styles.paymentMethod}>{orderInfo.payment_method}</Text> <Text style={styles.paymentMethod}>{order?.payment_method}</Text>
<Text style={styles.paymentOperator}>{orderInfo.payment_operator}</Text> <Text style={styles.paymentOperator}>{orderInfo?.payment_operator}</Text>
</View> </View>
</View> </View>
<View style={styles.border}></View> <View style={styles.border}></View>
@ -102,12 +80,12 @@ export function ConfirmOrder() {
<View style={styles.shippingRow}> <View style={styles.shippingRow}>
<Text style={styles.shippingLabel}></Text> <Text style={styles.shippingLabel}></Text>
<Text style={styles.shippingValue}> <Text style={styles.shippingValue}>
{orderInfo.shipping.method === 'sea' ? '海运' : '空运'} {orderInfo?.shipping?.method === 'sea' ? '海运' : '空运'}
</Text> </Text>
</View> </View>
<View style={styles.shippingRow}> <View style={styles.shippingRow}>
<Text style={styles.shippingLabel}></Text> <Text style={styles.shippingLabel}></Text>
<Text style={styles.shippingValue}>{orderInfo.shipping.estimated_arrival}</Text> <Text style={styles.shippingValue}>{orderInfo?.shipping?.estimated_arrival}</Text>
</View> </View>
</View> </View>
</View> </View>
@ -120,9 +98,9 @@ export function ConfirmOrder() {
<Text style={styles.sectionTitle}></Text> <Text style={styles.sectionTitle}></Text>
</View> </View>
<View style={styles.recipientInfo}> <View style={styles.recipientInfo}>
<Text style={styles.recipientName}>{orderInfo.recipient.name}</Text> <Text style={styles.recipientName}>{order?.receiver_name}</Text>
<Text style={styles.recipientPhone}>{orderInfo.recipient.phone}</Text> <Text style={styles.recipientPhone}>{order?.receiver_phone}</Text>
<Text style={styles.recipientAddress}>{orderInfo.recipient.address}</Text> <Text style={styles.recipientAddress}>{order?.receiver_address}</Text>
</View> </View>
</View> </View>
<View style={styles.border}></View> <View style={styles.border}></View>
@ -134,14 +112,15 @@ export function ConfirmOrder() {
<Text style={styles.sectionTitle}></Text> <Text style={styles.sectionTitle}></Text>
</View> </View>
<View style={styles.orderItems}> <View style={styles.orderItems}>
{orderInfo.items.map((item) => ( {order?.items?.map((item) => (
<View key={item.id} style={styles.orderItem}> <View key={item.sku_id} style={styles.orderItem}>
<View style={styles.itemDetails}> <View style={styles.itemDetails}>
<Text style={styles.itemName} numberOfLines={2}> <Text style={styles.itemName} numberOfLines={2}>
{item.product_name} {item.product_name}
</Text> </Text>
{item.attributes.map((attr) => ( {item.sku_attributes.map((attr) => (
<Text key={attr.attribute_name} style={styles.itemAttribute}> <Text key={attr.attribute_name
} style={styles.itemAttribute}>
{attr.attribute_name}: {attr.value} {attr.attribute_name}: {attr.value}
</Text> </Text>
))} ))}
@ -161,25 +140,25 @@ export function ConfirmOrder() {
<View style={styles.priceSummary}> <View style={styles.priceSummary}>
<View style={styles.priceRow}> <View style={styles.priceRow}>
<Text style={styles.priceLabel}></Text> <Text style={styles.priceLabel}></Text>
<Text style={styles.priceValue}>${orderInfo.items.reduce((sum, item) => sum + item.total_price, 0)}</Text> <Text style={styles.priceValue}>${order?.total_amount}</Text>
</View> </View>
<View style={styles.priceRow}> <View style={styles.priceRow}>
<Text style={styles.priceLabel}></Text> <Text style={styles.priceLabel}></Text>
<Text style={styles.priceValue}>${orderInfo.shipping.domestic_fee}</Text> <Text style={styles.priceValue}>${order?.discount_amount}</Text>
</View> </View>
<View style={styles.priceRow}> <View style={styles.priceRow}>
<Text style={styles.priceLabel}></Text> <Text style={styles.priceLabel}></Text>
<Text style={styles.priceValue}>${orderInfo.shipping.international_fee}</Text> <Text style={styles.priceValue}>${order?.shipping_fee}</Text>
</View> </View>
<View style={[styles.priceRow, styles.totalRow]}> <View style={[styles.priceRow, styles.totalRow]}>
<Text style={styles.totalLabel}></Text> <Text style={styles.totalLabel}></Text>
<Text style={styles.totalAmount}>${orderInfo.total_amount}</Text> <Text style={styles.totalAmount}>${order?.actual_amount}</Text>
</View> </View>
</View> </View>
</View> </View>
{/* Bottom Button */} {/* Bottom Button */}
<TouchableOpacity style={styles.bottomButton}> <TouchableOpacity style={styles.bottomButton} onPress={() => navigation.navigate('Pay',{order_id:order?.order_id})}>
<View style={styles.bottomButtonContent}> <View style={styles.bottomButtonContent}>
<Text style={styles.bottomButtonText}></Text> <Text style={styles.bottomButtonText}></Text>
</View> </View>

134
app/screens/Recipient/Recipient.tsx

@ -34,6 +34,8 @@ import {
} 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"; import { payApi, PaymentMethodsResponse } from "../../services/api/payApi";
import useOrderStore from '../../store/order';
interface PaymentOption { interface PaymentOption {
id: string; id: string;
@ -100,7 +102,7 @@ export function Recipient({
sameAsPhone: false, sameAsPhone: false,
setDefault: false, setDefault: false,
}); });
const setOrder = useOrderStore(state => state.setOrder);
const [shippingMethod, setShippingMethod] = useState("sea"); const [shippingMethod, setShippingMethod] = useState("sea");
const [warehouse, setWarehouse] = useState<number>(); const [warehouse, setWarehouse] = useState<number>();
const [arrival, setArrival] = useState("-"); const [arrival, setArrival] = useState("-");
@ -344,6 +346,65 @@ export function Recipient({
}); });
} }
}; };
// 创建订单
const createOrder = async () => {
if (!defaultAddress) {
alert('请添加收件人信息');
return;
}
if (!selectedPayment) {
alert('请选择支付方式');
return;
}
console.log(orderData)
// 构建订单数据
const submitOrderData = {
address_id: defaultAddress.address_id,
transport_type: shippingMethod === "sea" ? 1 : 2,
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 || ''}`
};
setOrder(submitOrderData);
navigation.navigate('ConfirmOrder');
};
return ( return (
<View style={styles.mainContainer}> <View style={styles.mainContainer}>
@ -957,76 +1018,7 @@ export function Recipient({
<TouchableOpacity <TouchableOpacity
style={styles.bottomButton} style={styles.bottomButton}
disabled={!domesticShippingFee?.currency} disabled={!domesticShippingFee?.currency}
onPress={async () => { onPress={createOrder}
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}>
<Text style={styles.bottomButtonText}> <Text style={styles.bottomButtonText}>

68
app/screens/pay/Pay.tsx

@ -0,0 +1,68 @@
import { View, StyleSheet } from 'react-native';
import { useRoute, RouteProp } from '@react-navigation/native';
import { useEffect, useState } from 'react';
import { payApi, PaymentInfoResponse } from '../../services/api/payApi';
import { WebView } from "react-native-webview";
type PayScreenRouteProp = RouteProp<{
Pay: { order_id: string };
}, 'Pay'>;
export const Pay = () => {
const [loading, setLoading] = useState(true);
const route = useRoute<PayScreenRouteProp>();
const {order_id} = route.params;
const [payInfo, setPayInfo] = useState<PaymentInfoResponse>();
const getPayInfo = async () => {
const data = {
order_id: order_id,
method: 'paypal',
amount: '100',
currency: 'USD'
}
const res = await payApi.getPayInfo(data);
console.log('res',res);
setPayInfo(res);
}
useEffect(() => {
getPayInfo();
},[])
const handleNavigationStateChange = (navState: any) => {
console.log(navState);
}
return <View style={{ flex: 1 }}>
{payInfo?.payment_url ? (
<WebView
source={{ uri: payInfo.payment_url }}
style={styles.webview}
onNavigationStateChange={handleNavigationStateChange}
onLoadStart={() => setLoading(true)}
onLoadEnd={() => setLoading(false)}
javaScriptEnabled={true}
domStorageEnabled={true}
startInLoadingState={true}
scalesPageToFit={true}
originWhitelist={['*']}
userAgent="Mozilla/5.0 (Linux; Android 10; Pixel 3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Mobile Safari/537.36"
onShouldStartLoadWithRequest={(request) => {
console.log(request);
// 允许所有请求
return true;
}}
/>
) : (
<View>
{/* Add fallback content here */}
</View>
)}
</View>
}
const styles = StyleSheet.create({
webview: {
flex: 1,
},
});

13
app/screens/productStatus/OrderDatails.tsx

@ -0,0 +1,13 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
export const OrderDetails = () => {
return (
<View>
<Text>OrderDetails</Text>
</View>
);
};

423
app/screens/productStatus/Status.tsx

@ -0,0 +1,423 @@
import {
View,
StyleSheet,
Text,
ScrollView,
TouchableOpacity,
Image,
ActivityIndicator,
} from "react-native";
import { useRoute, RouteProp } from "@react-navigation/native";
import { productStatus } from "../../constants/productStatus";
import BackIcon from "../../components/BackIcon";
import MassageIcon from "../../components/MassageIcon";
import { useEffect, useState, useRef } from "react";
import fontSize from "../../utils/fontsizeUtils";
import {
ordersApi,
PaginatedOrderResponse,
PaginatedOrderRequest,
} from "../../services/api/orders";
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
// import ImageView from "react-native-image-viewing";
type StatusScreenRouteProp = RouteProp<
{
Status: { status: number };
},
"Status"
>;
export function Status() {
const navigation = useNavigation<NativeStackNavigationProp<any>>();
const route = useRoute<StatusScreenRouteProp>();
const [statusList, setStatusList] = useState(() => {
const initialList = [...productStatus];
initialList.unshift({
text: "全部",
status: null,
icon: BackIcon,
});
return initialList;
});
const [status, setStatus] = useState<number | null>(null);
const [orderList, setOrderList] = useState<PaginatedOrderResponse>();
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [imageViewerVisible, setImageViewerVisible] = useState(false);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [images, setImages] = useState<string[]>([]);
const statusScrollViewRef = useRef<ScrollView>(null);
const statusItemRef = useRef<React.ElementRef<typeof TouchableOpacity>>(null);
const getAllOrders = async () => {
setLoading(true);
try {
const data = {
page: page,
page_size: pageSize,
};
const response = await ordersApi.getAllOrders(data);
setOrderList((prev) => ({
...response,
items:
page === 1
? response.items
: [...(prev?.items || []), ...response.items],
}));
setTotal(response.total);
} finally {
setLoading(false);
}
};
const scrollToStatus = () => {
const itemIndex = statusList.findIndex(
(item) => item.status === route.params.status
);
console.log(itemIndex);
statusItemRef.current?.measure((x, y, width, height, pageX, pageY) => {
if (width) {
statusScrollViewRef.current?.scrollTo({
x: width * (itemIndex - 1),
animated: true,
});
}
});
};
useEffect(() => {
setStatus(route.params.status);
scrollToStatus();
setPage(1);
getAllOrders();
}, []);
const getStatus = (status: number) => {
return status === 0
? "待付款"
: status === 1
? "待发货"
: status === 2
? "待收货"
: status === 3
? "已完成"
: status === 4
? "已取消"
: status === 5
? "已退款"
: "未知状态";
};
// const handleImagePress = (imageUrl: string) => {
// setImages([imageUrl]);
// setCurrentImageIndex(0);
// setImageViewerVisible(true);
// };
const changeStatus = async (status: number) => {
setLoading(true);
setPage(1);
const data: PaginatedOrderRequest = {
page: page,
page_size: pageSize,
};
if (status) {
data.status = status;
}
try {
const response = await ordersApi.getAllOrders(data);
setOrderList((prev) => ({
...response,
items:
page === 1
? response.items
: [...(prev?.items || []), ...response.items],
}));
// 滚动状态列表到选中项
} finally {
setLoading(false);
}
};
const handleOrderDetailsPress = (orderId: number) => {
navigation.navigate("OrderDetails", { orderId });
};
return (
<View style={styles.container}>
<View style={styles.statusHeader}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<BackIcon />
</TouchableOpacity>
<Text style={styles.statusTitle}></Text>
<MassageIcon size={26} />
</View>
<View style={styles.statusList}>
<ScrollView
ref={statusScrollViewRef}
style={styles.statusListScr}
horizontal={true}
pagingEnabled={false}
showsHorizontalScrollIndicator={false}
>
{statusList.map((item, index) => (
<TouchableOpacity
ref={statusItemRef}
style={[
styles.statusItem,
status === item.status ? styles.statusItemActive : null,
]}
key={index}
onPress={() => {
setStatus(item.status as number);
changeStatus(item.status as number);
}}
>
<Text
style={[
styles.statusItemText,
status === item.status ? styles.statusItemActiveText : null,
]}
>
{item.text}
</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
<View style={styles.orderContent}>
{loading && page === 1 ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#f77f3a" />
</View>
) : (
<ScrollView
horizontal={false}
showsVerticalScrollIndicator={false}
scrollEventThrottle={16}
onMomentumScrollEnd={(event) => {
const { contentOffset, contentSize, layoutMeasurement } =
event.nativeEvent;
const isAtBottom =
contentOffset.y + layoutMeasurement.height >=
contentSize.height;
if (isAtBottom) {
setLoading(true);
setPage(page + 1);
getAllOrders();
}
}}
>
{orderList?.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.order_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.product_image }}
style={styles.orderProductItemImage}
/>
</TouchableOpacity>
<View style={styles.orderProductItemInfo}>
<Text style={styles.orderProductItemInfoName}>
{item.product_name}
</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}>:</Text>
<Text style={styles.orderProductPriceText}>
{item.actual_amount}
</Text>
</View>
<TouchableOpacity
style={styles.orderProductView}
onPress={() => handleOrderDetailsPress(item.order_id)}
>
<Text style={styles.orderProductViewText}>
View Details
</Text>
</TouchableOpacity>
</View>
</View>
))}
{loading && page > 1 && (
<View style={styles.loadingMoreContainer}>
<ActivityIndicator size="small" color="#f77f3a" />
</View>
)}
</ScrollView>
)}
</View>
{/* <ImageView
images={images.map(uri => ({ uri }))}
imageIndex={currentImageIndex}
visible={imageViewerVisible}
onRequestClose={() => setImageViewerVisible(false)}
swipeToCloseEnabled={true}
doubleTapToZoomEnabled={true}
/> */}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#f3f3f3",
},
statusHeader: {
width: "100%",
backgroundColor: "white",
padding: 16,
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
},
statusTitle: {
fontSize: 16,
fontWeight: "600",
},
statusList: {
width: "100%",
borderTopWidth: 1,
borderColor: "#f5f5f5",
},
statusListScr: {
width: "100%",
},
statusItem: {
width: 100,
padding: 16,
backgroundColor: "white",
},
statusItemText: {
fontSize: fontSize(16),
textAlign: "center",
},
statusItemTextActive: {
color: "#f77f3a",
borderBottomWidth: 1,
borderColor: "#f77f3a",
},
statusItemActive: {
borderBottomWidth: 2,
borderColor: "#f77f3a",
},
statusItemActiveText: {
color: "#f77f3a",
},
orderContent: {
padding: 20,
flex: 1,
},
orderItem: {
width: "100%",
backgroundColor: "white",
borderRadius: 10,
marginBottom: 10,
},
orderStatus: {
width: "100%",
padding: 10,
borderBottomWidth: 1,
flexDirection: "row",
borderColor: "#f5f5f5",
justifyContent: "space-between",
},
orderStatusOrderText: {
fontSize: fontSize(16),
fontWeight: "600",
},
orderStatusText: {
color: "#f77f3a",
},
orderProductList: {
width: "100%",
padding: 10,
},
orderProductItem: {
flexDirection: "row",
paddingBottom: 10,
paddingTop: 10,
borderBottomWidth: 1,
borderColor: "#f5f5f5",
},
orderProductItemImage: {
width: 30,
height: 30,
marginRight: 10,
},
orderProductItemInfo: {
flex: 1,
},
orderProductItemInfoName: {
fontSize: fontSize(16),
fontWeight: "600",
},
orderProductItemInfoPrice: {
fontSize: fontSize(14),
color: "#666",
},
orderProductPrice: {
padding: 10,
flexDirection: "row",
justifyContent: "space-between",
},
orderProductView: {
padding: 5,
borderRadius: 8,
borderWidth: 1,
borderColor: "#f77f3a",
},
orderProductViewText: {
color: "#f77f3a",
fontSize: fontSize(14),
},
orderProductPriceText: {
fontSize: fontSize(16),
fontWeight: "600",
color: "#f77f3a",
},
orderProductTotalText: {
fontSize: fontSize(16),
fontWeight: "600",
},
orderProductPriceItem: {
flexDirection: "row",
},
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
loadingMoreContainer: {
paddingVertical: 10,
alignItems: "center",
},
});

119
app/services/api/orders.ts

@ -171,6 +171,115 @@ interface Address {
payment_url?: string; payment_url?: string;
} }
interface SkuAttribute {
[key: string]: any;
}
interface OrderItem {
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: SkuAttribute[];
quantity: number;
unit_price: number;
total_price: number;
order_item_id: number;
order_id: number;
create_time: string; // or Date if you prefer to parse it
update_time: string; // or Date
}
export interface Order {
user_id: number;
total_amount: number;
actual_amount: number;
discount_amount: number;
shipping_fee: number;
address_id: number;
receiver_name: string;
receiver_phone: string;
receiver_address: string;
buyer_message: string;
pay_status: number;
order_status: number;
shipping_status: number;
order_id: number;
payment_method: string;
order_no: string;
items: OrderItem[];
create_time: string; // or Date
pay_time: string; // or Date
shipping_time: string; // or Date
complete_time: string; // or Date
update_time: string; // or Date
}
interface SkuAttributes {
[key: string]: any;
}
interface OrderItem {
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: SkuAttributes[];
quantity: number;
unit_price: number;
total_price: number;
order_item_id: number;
order_id: number;
create_time: string; // or Date
update_time: string; // or Date
}
interface Orders {
user_id: number;
total_amount: number;
actual_amount: number;
discount_amount: number;
shipping_fee: number;
address_id: number;
receiver_name: string;
receiver_phone: string;
receiver_address: string;
buyer_message: string;
pay_status: number;
order_status: number;
shipping_status: number;
order_id: number;
order_no: string;
items: OrderItem[];
create_time: string; // or Date
pay_time: string; // or Date
shipping_time: string; // or Date
complete_time: string; // or Date
update_time: string; // or Date
}
export interface PaginatedOrderResponse {
items: Orders[];
total: number;
page: number;
page_size: number;
}
export interface PaginatedOrderRequest {
status?: number | null;
page: number;
page_size: number;
}
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),
@ -188,7 +297,15 @@ interface Address {
// 创建订单 // 创建订单
createOrder: (data: CreateOrderRequest) => createOrder: (data: CreateOrderRequest) =>
apiService.post<CreateOrderResponse>('/api/orders/cart', data), apiService.post<Order>('/api/orders/cart', data),
// 获取所有订单
getAllOrders: (data:PaginatedOrderRequest) =>
apiService.get<PaginatedOrderResponse>(`/api/orders`,data),
// 获取订单指定信息
getOrderDetails: (order_id:number) =>
apiService.get<Order>(`/api/orders/${order_id}`),
}; };

17
app/services/api/payApi.ts

@ -18,9 +18,26 @@ export interface PaymentMethodsResponse {
other_country_methods: CountryPaymentMethods[]; other_country_methods: CountryPaymentMethods[];
} }
export interface PaymentInfoResponse {
success: boolean;
payment_url: string;
msg: string;
}
export interface PayInfoBody {
order_id: string;
method: string;
amount: string;
currency: string;
}
export const payApi = { export const payApi = {
// 获取当前国家支付方式 // 获取当前国家支付方式
getCountryPaymentMethods: () => { getCountryPaymentMethods: () => {
return apiService.get<PaymentMethodsResponse>('/api/payment/country_payment_methods'); return apiService.get<PaymentMethodsResponse>('/api/payment/country_payment_methods');
}, },
// 获取支付信息
getPayInfo: (data: PayInfoBody) => {
return apiService.post<PaymentInfoResponse>(`/api/payment/initiate`, data);
},
}; };

63
app/store/order.ts

@ -0,0 +1,63 @@
import { create } from 'zustand';
interface SkuAttribute {
[key: string]: any; // Since the example shows an empty object, we use a generic type
}
interface CartItem {
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: SkuAttribute[];
quantity: number;
unit_price: number;
total_price: number;
}
export interface StoreState {
address_id: number;
items: CartItem[];
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;
}
interface OrderStore {
order:StoreState
setOrder: (newOrder: Partial<StoreState>) => void;
}
const useOrderStore = create<OrderStore>((set) => ({
order:{
address_id: 0,
items: [],
buyer_message: '',
payment_method: '',
create_payment: false,
total_amount: 0,
actual_amount: 0,
discount_amount: 0,
shipping_fee: 0,
domestic_shipping_fee: 0,
currency: '',
receiver_address: '',
},
setOrder: (newOrder: Partial<StoreState>) => set((state) => ({
order: { ...state.order, ...newOrder }
}))
}));
export default useOrderStore;

998
package-lock.json generated

File diff suppressed because it is too large Load Diff

5
package.json

@ -36,6 +36,8 @@
"react-native-dropdown-picker": "^5.4.6", "react-native-dropdown-picker": "^5.4.6",
"react-native-fast-image": "^8.6.3", "react-native-fast-image": "^8.6.3",
"react-native-gesture-handler": "^2.25.0", "react-native-gesture-handler": "^2.25.0",
"react-native-image-viewing": "^0.2.2",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-linear-gradient": "^2.8.3", "react-native-linear-gradient": "^2.8.3",
"react-native-localize": "^3.4.1", "react-native-localize": "^3.4.1",
"react-native-pager-view": "^6.7.0", "react-native-pager-view": "^6.7.0",
@ -51,7 +53,8 @@
"react-native-toast-message": "^2.3.0", "react-native-toast-message": "^2.3.0",
"react-native-vector-icons": "^10.2.0", "react-native-vector-icons": "^10.2.0",
"react-native-web": "~0.19.13", "react-native-web": "~0.19.13",
"react-native-webview": "^13.13.5" "react-native-webview": "^13.13.5",
"zustand": "^5.0.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",

1667
yarn.lock

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