15 changed files with 2411 additions and 1357 deletions
@ -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; |
@ -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 } |
||||
] |
@ -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, |
||||
}, |
||||
}); |
@ -0,0 +1,13 @@
|
||||
import React from 'react'; |
||||
import { View, Text, StyleSheet } from 'react-native'; |
||||
|
||||
export const OrderDetails = () => { |
||||
return ( |
||||
<View>
|
||||
<Text>OrderDetails</Text> |
||||
</View> |
||||
); |
||||
}; |
||||
|
||||
|
||||
|
@ -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", |
||||
}, |
||||
}); |
@ -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; |
Loading…
Reference in new issue