index.tsx 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. import { Image } from "expo-image";
  2. import { useRouter } from "expo-router";
  3. import React, { useCallback, useEffect, useState } from "react";
  4. import {
  5. ActivityIndicator,
  6. Alert,
  7. FlatList,
  8. ImageBackground,
  9. Platform,
  10. RefreshControl,
  11. ScrollView,
  12. StatusBar,
  13. StyleSheet,
  14. Text,
  15. TouchableOpacity,
  16. View,
  17. } from "react-native";
  18. import { useSafeAreaInsets } from "react-native-safe-area-context";
  19. import { Images } from "@/constants/images";
  20. import {
  21. getStore,
  22. getTakeList,
  23. moveOutSafeStore,
  24. moveToSafeStore,
  25. } from "@/services/award";
  26. import CheckoutModal from "./components/CheckoutModal";
  27. const LEVEL_MAP: Record<string, { title: string; color: string }> = {
  28. A: { title: "超神", color: "#FF3366" }, // Neon Pink
  29. B: { title: "欧皇", color: "#FFD700" }, // Gold
  30. C: { title: "隐藏", color: "#00E5FF" }, // Cyan
  31. D: { title: "普通", color: "#B0BEC5" }, // Grey
  32. SUBSTITUTE: { title: "置换款", color: "#B0BEC5" },
  33. OTHER: { title: "其他", color: "#B0BEC5" },
  34. };
  35. const LEVEL_TABS = [
  36. { title: "全部", value: "" },
  37. { title: "普通", value: "D" },
  38. { title: "隐藏", value: "C" },
  39. { title: "欧皇", value: "B" },
  40. { title: "超神", value: "A" },
  41. { title: "其他", value: "OTHER" },
  42. ];
  43. const FROM_TYPE_MAP: Record<string, string> = {
  44. LUCK: "奖池",
  45. MALL: "商城",
  46. LUCK_ROOM: "福利房",
  47. LUCK_WHEEL: "魔天轮",
  48. DOLL_MACHINE: "扭蛋",
  49. ACTIVITY: "活动",
  50. SUBSTITUTE: "商品置换",
  51. TRANSFER: "商品转赠",
  52. DISTRIBUTION: "分销",
  53. LUCK_PICKUP: "商品提货",
  54. LUCK_EXCHANGE: "商品兑换",
  55. RECHARGE: "充值",
  56. WITHDRAW: "提现",
  57. OFFICIAL: "官方",
  58. LUCK_ACTIVITY: "奖池活动",
  59. CONSUMPTION_ACTIVITY: "消费活动",
  60. NEW_USER_RANK_ACTIVITY: "拉新排名活动",
  61. CONSUMPTION_RANK_ACTIVITY: "消费排行榜活动",
  62. WHEEL_ACTIVITY: "大转盘活动",
  63. TA_ACTIVITY: "勇者之塔活动",
  64. ISLAND_ACTIVITY: "海岛活动",
  65. REDEEM_CODE_ACTIVITY: "兑换码活动",
  66. };
  67. interface StoreItem {
  68. id: string;
  69. level: string;
  70. safeFlag: number;
  71. magicAmount?: number;
  72. fromRelationType: string;
  73. spu: { id: string; name: string; cover: string };
  74. }
  75. interface PickupItem {
  76. tradeNo: string;
  77. createTime: string;
  78. status: number;
  79. contactName: string;
  80. contactNo: string;
  81. province: string;
  82. city: string;
  83. district: string;
  84. address: string;
  85. expressAmount: number;
  86. paymentTime?: string;
  87. paymentTimeoutTime?: string;
  88. cancelRemark?: string;
  89. itemList: Array<{ id: string; spuId: string; level: string; cover: string }>;
  90. }
  91. const STATUS_MAP: Record<number, { text: string; color: string }> = {
  92. 0: { text: "待支付运费", color: "#ff6b00" },
  93. 1: { text: "已进仓库进行配货", color: "#ff6b00" },
  94. 2: { text: "待收货", color: "#ff6b00" },
  95. 10: { text: "已取消", color: "#ff6b00" },
  96. 11: { text: "超时取消", color: "#ff6b00" },
  97. 12: { text: "系统取消", color: "#ff6b00" },
  98. 99: { text: "已完成", color: "#52c41a" },
  99. };
  100. export default function StoreScreen() {
  101. const router = useRouter();
  102. const insets = useSafeAreaInsets();
  103. const [mainTabIndex, setMainTabIndex] = useState(0);
  104. const [levelTabIndex, setLevelTabIndex] = useState(0);
  105. const [list, setList] = useState<any[]>([]);
  106. const [loading, setLoading] = useState(false);
  107. const [refreshing, setRefreshing] = useState(false);
  108. const [page, setPage] = useState(1);
  109. const [hasMore, setHasMore] = useState(true);
  110. const [checkMap, setCheckMap] = useState<Record<string, StoreItem>>({});
  111. const [checkoutVisible, setCheckoutVisible] = useState(false);
  112. const mainTabs = ["未使用", "保险柜", "已提货"];
  113. const loadData = useCallback(
  114. async (pageNum: number, isRefresh = false) => {
  115. if (loading && !isRefresh) return;
  116. if (!hasMore && pageNum > 1 && !isRefresh) return;
  117. try {
  118. if (pageNum === 1) setLoading(true);
  119. let res: any;
  120. if (mainTabIndex === 0) {
  121. res = await getStore(pageNum, 20, 0, LEVEL_TABS[levelTabIndex].value);
  122. } else if (mainTabIndex === 1) {
  123. res = await getStore(pageNum, 20, 1);
  124. } else {
  125. res = await getTakeList(pageNum, 20);
  126. }
  127. let records = Array.isArray(res) ? res : res?.records || res || [];
  128. // 处理已提货数据,合并相同商品
  129. if (mainTabIndex === 2 && records.length > 0) {
  130. records = records.map((item: PickupItem) => {
  131. const goodsMap: Record<string, { total: number; data: any }> = {};
  132. (item.itemList || []).forEach((goods: any) => {
  133. const key = `${goods.spuId}_${goods.level}`;
  134. if (goodsMap[key]) {
  135. goodsMap[key].total += 1;
  136. } else {
  137. goodsMap[key] = { total: 1, data: goods };
  138. }
  139. });
  140. return { ...item, groupedList: Object.values(goodsMap) };
  141. });
  142. }
  143. if (records.length < 20) setHasMore(false);
  144. if (pageNum === 1 || isRefresh) setList(records);
  145. else setList((prev) => [...prev, ...records]);
  146. } catch (e) {
  147. console.error("加载仓库数据失败:", e);
  148. } finally {
  149. setLoading(false);
  150. setRefreshing(false);
  151. }
  152. },
  153. [mainTabIndex, levelTabIndex, loading, hasMore],
  154. );
  155. useEffect(() => {
  156. setPage(1);
  157. setList([]);
  158. setHasMore(true);
  159. setCheckMap({});
  160. loadData(1, true);
  161. }, [mainTabIndex]);
  162. useEffect(() => {
  163. if (mainTabIndex === 0) {
  164. setPage(1);
  165. setList([]);
  166. setHasMore(true);
  167. setCheckMap({});
  168. loadData(1, true);
  169. }
  170. }, [levelTabIndex]);
  171. const handleRefresh = () => {
  172. setRefreshing(true);
  173. setPage(1);
  174. setHasMore(true);
  175. loadData(1, true);
  176. };
  177. const handleLoadMore = () => {
  178. if (!loading && hasMore) {
  179. const np = page + 1;
  180. setPage(np);
  181. loadData(np);
  182. }
  183. };
  184. const handleChoose = (item: StoreItem) => {
  185. if (item.safeFlag === 1 && mainTabIndex === 0) return;
  186. setCheckMap((prev) => {
  187. const newMap = { ...prev };
  188. if (newMap[item.id]) delete newMap[item.id];
  189. else newMap[item.id] = item;
  190. return newMap;
  191. });
  192. };
  193. const handleLock = async (item: StoreItem, index: number) => {
  194. const res = await moveToSafeStore([item.id]);
  195. if (res) {
  196. const newList = [...list];
  197. newList[index] = { ...item, safeFlag: 1 };
  198. setList(newList);
  199. setCheckMap((prev) => {
  200. const m = { ...prev };
  201. delete m[item.id];
  202. return m;
  203. });
  204. }
  205. };
  206. const handleUnlock = async (item: StoreItem, index: number) => {
  207. const res = await moveOutSafeStore([item.id]);
  208. if (res) {
  209. if (mainTabIndex === 1) setList(list.filter((_, i) => i !== index));
  210. else {
  211. const newList = [...list];
  212. newList[index] = { ...item, safeFlag: 0 };
  213. setList(newList);
  214. }
  215. }
  216. };
  217. const handleMoveOutAll = async () => {
  218. const selected = Object.values(checkMap);
  219. if (selected.length === 0) {
  220. showAlert("请至少选择一个商品!");
  221. return;
  222. }
  223. const res = await moveOutSafeStore(selected.map((i) => i.id));
  224. if (res) {
  225. setCheckMap({});
  226. handleRefresh();
  227. }
  228. };
  229. const handleTakeGoods = () => {
  230. const selected = Object.values(checkMap);
  231. if (selected.length === 0) {
  232. showAlert("请至少选择一个商品!");
  233. return;
  234. }
  235. setCheckoutVisible(true);
  236. };
  237. const handleSelectAll = () => {
  238. const allSelected = list.every((item) => checkMap[item.id]);
  239. if (allSelected) {
  240. setCheckMap({});
  241. } else {
  242. const newMap: Record<string, StoreItem> = {};
  243. list.forEach((item) => {
  244. newMap[item.id] = item;
  245. });
  246. setCheckMap(newMap);
  247. }
  248. };
  249. const handleCheckoutSuccess = () => {
  250. setCheckoutVisible(false);
  251. setCheckMap({});
  252. handleRefresh();
  253. };
  254. const showAlert = (msg: string) => {
  255. // @ts-ignore
  256. if (Platform.OS === "web") window.alert(msg);
  257. else Alert.alert("提示", msg);
  258. };
  259. const renderStoreItem = ({
  260. item,
  261. index,
  262. }: {
  263. item: StoreItem;
  264. index: number;
  265. }) => {
  266. const levelInfo = LEVEL_MAP[item.level] || LEVEL_MAP.D;
  267. const isChecked = !!checkMap[item.id];
  268. const canSelect = mainTabIndex === 1 || item.safeFlag !== 1;
  269. return (
  270. <ImageBackground
  271. source={{ uri: Images.mine.storeItemBg }}
  272. style={styles.cell}
  273. resizeMode="stretch"
  274. >
  275. <View style={styles.cellContent}>
  276. <TouchableOpacity
  277. style={styles.cellHeader}
  278. onPress={() => canSelect && handleChoose(item)}
  279. >
  280. <View style={styles.headerLeft}>
  281. {canSelect && (
  282. <View
  283. style={[styles.checkBox, isChecked && styles.checkBoxChecked]}
  284. >
  285. {isChecked && <Text style={styles.checkIcon}>✓</Text>}
  286. </View>
  287. )}
  288. <Text style={[styles.levelTitle, { color: levelInfo.color }]}>
  289. {levelInfo.title}
  290. </Text>
  291. </View>
  292. <TouchableOpacity
  293. style={styles.lockBox}
  294. onPress={() =>
  295. item.safeFlag !== 1
  296. ? handleLock(item, index)
  297. : handleUnlock(item, index)
  298. }
  299. >
  300. <Text style={styles.lockText}>
  301. {item.safeFlag !== 1 ? "锁定" : "解锁"}
  302. </Text>
  303. <Image
  304. source={{
  305. uri:
  306. item.safeFlag !== 1 ? Images.mine.lock : Images.mine.unlock,
  307. }}
  308. style={styles.lockIcon}
  309. />
  310. </TouchableOpacity>
  311. </TouchableOpacity>
  312. <TouchableOpacity
  313. style={styles.cellBody}
  314. onPress={() => router.push({ pathname: '/cloud-warehouse/detail', params: { id: item.id } } as any)}
  315. activeOpacity={0.7}
  316. >
  317. <ImageBackground
  318. source={{ uri: Images.mine.storeGoodsImgBg }}
  319. style={styles.goodsImgBg}
  320. >
  321. <Image
  322. source={{ uri: item.spu?.cover }}
  323. style={styles.goodsImg}
  324. contentFit="contain"
  325. />
  326. </ImageBackground>
  327. <View style={styles.goodsInfo}>
  328. <Text style={styles.goodsName} numberOfLines={2}>
  329. {item.spu?.name}
  330. </Text>
  331. <Text style={styles.goodsSource}>
  332. 从{FROM_TYPE_MAP[item.fromRelationType] || "其他"}获得
  333. </Text>
  334. </View>
  335. <Text style={styles.arrow}>{">"}</Text>
  336. </TouchableOpacity>
  337. </View>
  338. </ImageBackground>
  339. );
  340. };
  341. const copyToClipboard = (text: string) => {
  342. showAlert(`订单号已复制: ${text}`);
  343. };
  344. const showExpress = (item: PickupItem) => {
  345. router.push({
  346. pathname: "/cloud-warehouse/packages" as any,
  347. params: { tradeNo: item.tradeNo },
  348. });
  349. };
  350. const renderPickupItem = ({
  351. item,
  352. }: {
  353. item: PickupItem & { groupedList?: Array<{ total: number; data: any }> };
  354. }) => {
  355. const statusInfo = STATUS_MAP[item.status] || {
  356. text: "未知",
  357. color: "#999",
  358. };
  359. return (
  360. <ImageBackground
  361. source={{ uri: Images.mine.storeItemBg }}
  362. style={styles.pickupCell}
  363. resizeMode="stretch"
  364. >
  365. {/* 顶部信息 */}
  366. <View style={styles.pickupTop}>
  367. <Text style={styles.pickupTime}>下单时间:{item.createTime}</Text>
  368. <Text style={[styles.pickupStatus, { color: statusInfo.color }]}>
  369. {statusInfo.text}
  370. </Text>
  371. </View>
  372. {item.status === 0 && item.paymentTimeoutTime && (
  373. <Text style={styles.pickupTimeout}>
  374. {item.paymentTimeoutTime} 将自动取消该订单,如有优惠券,将自动退回
  375. </Text>
  376. )}
  377. {/* 收货地址 */}
  378. <View style={styles.pickupAddress}>
  379. <Text style={styles.locationIcon}>📍</Text>
  380. <View style={styles.addressInfo}>
  381. <Text style={styles.addressName}>
  382. {item.contactName},{item.contactNo}
  383. </Text>
  384. <Text style={styles.addressDetail}>
  385. {item.province}
  386. {item.city}
  387. {item.district}
  388. {item.address}
  389. </Text>
  390. </View>
  391. </View>
  392. {/* 商品列表 */}
  393. <View style={styles.pickupGoodsBox}>
  394. <ScrollView
  395. horizontal
  396. showsHorizontalScrollIndicator={false}
  397. style={styles.pickupGoodsList}
  398. >
  399. {(item.groupedList || []).map((goods, idx) => (
  400. <View key={idx} style={styles.pickupGoodsItem}>
  401. <Image
  402. source={{ uri: goods.data.cover }}
  403. style={styles.pickupGoodsImg}
  404. contentFit="contain"
  405. />
  406. <View style={styles.pickupGoodsCount}>
  407. <Text style={styles.pickupGoodsCountText}>
  408. x{goods.total}
  409. </Text>
  410. </View>
  411. </View>
  412. ))}
  413. </ScrollView>
  414. </View>
  415. {/* 订单号 */}
  416. <View style={styles.pickupOrderRow}>
  417. <Text style={styles.pickupOrderLabel}>订单号:</Text>
  418. <Text style={styles.pickupOrderNo} numberOfLines={1}>
  419. {item.tradeNo}
  420. </Text>
  421. <TouchableOpacity
  422. style={styles.copyBtn}
  423. onPress={() => copyToClipboard(item.tradeNo)}
  424. >
  425. <Text style={styles.copyBtnText}>复制</Text>
  426. </TouchableOpacity>
  427. </View>
  428. {item.paymentTime && (
  429. <View style={styles.pickupInfoRow}>
  430. <Text style={styles.pickupInfoLabel}>付款时间:</Text>
  431. <Text style={styles.pickupInfoValue}>{item.paymentTime}</Text>
  432. </View>
  433. )}
  434. {item.status === 12 && item.cancelRemark && (
  435. <View style={styles.pickupInfoRow}>
  436. <Text style={styles.pickupInfoLabel}>备注</Text>
  437. <Text style={[styles.pickupInfoValue, { color: "#ff6b00" }]}>
  438. {item.cancelRemark}
  439. </Text>
  440. </View>
  441. )}
  442. {/* 底部操作 */}
  443. <View style={styles.pickupBottom}>
  444. <Text style={styles.pickupExpressAmount}>
  445. 配送费:<Text style={styles.priceText}>¥{item.expressAmount}</Text>
  446. </Text>
  447. {[1, 2, 99].includes(item.status) && (
  448. <TouchableOpacity
  449. style={styles.expressBtn}
  450. onPress={() => showExpress(item)}
  451. >
  452. <Text style={styles.expressBtnText}>物流信息</Text>
  453. </TouchableOpacity>
  454. )}
  455. </View>
  456. </ImageBackground>
  457. );
  458. };
  459. const selectedCount = Object.keys(checkMap).length;
  460. return (
  461. <View style={styles.container}>
  462. <StatusBar barStyle="light-content" />
  463. <ImageBackground
  464. source={{ uri: Images.mine.kaixinMineBg }}
  465. style={styles.background}
  466. resizeMode="cover"
  467. >
  468. <Image
  469. source={{ uri: Images.mine.kaixinMineHeadBg }}
  470. style={styles.headerBg}
  471. contentFit="cover"
  472. />
  473. <View style={[styles.header, { paddingTop: insets.top }]}>
  474. <TouchableOpacity
  475. style={styles.backBtn}
  476. onPress={() => router.back()}
  477. >
  478. <Text style={styles.backIcon}>‹</Text>
  479. </TouchableOpacity>
  480. <Text style={styles.title}>仓库</Text>
  481. <View style={styles.placeholder} />
  482. </View>
  483. <View style={[styles.content, { paddingTop: insets.top + 50 }]}>
  484. <View style={styles.mainTabs}>
  485. {mainTabs.map((tab, index) => {
  486. const isActive = mainTabIndex === index;
  487. // Use Yellow L bg for active, Grey (Hui) for inactive
  488. const bg = isActive
  489. ? Images.common.butBgL
  490. : Images.common.butBgHui;
  491. return (
  492. <TouchableOpacity
  493. key={index}
  494. style={styles.mainTabItem}
  495. onPress={() => setMainTabIndex(index)}
  496. >
  497. <ImageBackground
  498. source={{ uri: bg }}
  499. style={styles.mainTabBg}
  500. resizeMode="contain"
  501. >
  502. <Text
  503. style={
  504. isActive ? styles.mainTabTextActive : styles.mainTabText
  505. }
  506. >
  507. {tab}
  508. </Text>
  509. </ImageBackground>
  510. </TouchableOpacity>
  511. );
  512. })}
  513. </View>
  514. {mainTabIndex === 0 && (
  515. <View style={styles.levelTabs}>
  516. {LEVEL_TABS.map((tab, index) => {
  517. const isActive = levelTabIndex === index;
  518. return (
  519. <TouchableOpacity
  520. key={index}
  521. style={[
  522. styles.levelTabItem,
  523. isActive && styles.levelTabItemActive,
  524. ]}
  525. onPress={() => setLevelTabIndex(index)}
  526. >
  527. {isActive && <View style={styles.decorTL} />}
  528. <Text
  529. style={[
  530. styles.levelTabText,
  531. isActive && styles.levelTabTextActive,
  532. ]}
  533. >
  534. {tab.title}
  535. </Text>
  536. {isActive && <View style={styles.decorBR} />}
  537. </TouchableOpacity>
  538. );
  539. })}
  540. </View>
  541. )}
  542. <FlatList
  543. data={list as any[]}
  544. renderItem={
  545. mainTabIndex === 2
  546. ? (renderPickupItem as any)
  547. : (renderStoreItem as any)
  548. }
  549. keyExtractor={(item: any, index) =>
  550. item.id || item.tradeNo || index.toString()
  551. }
  552. contentContainerStyle={styles.listContent}
  553. refreshControl={
  554. <RefreshControl
  555. refreshing={refreshing}
  556. onRefresh={handleRefresh}
  557. tintColor="#fff"
  558. />
  559. }
  560. onEndReached={handleLoadMore}
  561. onEndReachedThreshold={0.3}
  562. ListHeaderComponent={
  563. mainTabIndex === 2 ? (
  564. <View style={styles.pickupTip}>
  565. <Text style={styles.pickupTipIcon}>⚠️</Text>
  566. <Text style={styles.pickupTipText}>
  567. 您的包裹一般在5个工作日内发货,如遇特殊情况可能会有延迟,敬请谅解~
  568. </Text>
  569. </View>
  570. ) : null
  571. }
  572. ListFooterComponent={
  573. loading && list.length > 0 ? (
  574. <ActivityIndicator
  575. color="#fff"
  576. style={{ marginVertical: 10 }}
  577. />
  578. ) : null
  579. }
  580. ListEmptyComponent={
  581. !loading ? (
  582. <View style={styles.emptyBox}>
  583. <Text style={styles.emptyText}>暂无物品</Text>
  584. </View>
  585. ) : null
  586. }
  587. />
  588. </View>
  589. {mainTabIndex !== 2 && list.length > 0 && (
  590. <View
  591. style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}
  592. >
  593. {mainTabIndex === 1 ? (
  594. <View style={styles.buttonRow}>
  595. <TouchableOpacity
  596. style={[styles.actionBtn, styles.selectAllBtn]}
  597. onPress={handleSelectAll}
  598. >
  599. <Text style={styles.selectAllText}>
  600. {list.length > 0 && list.every((item) => checkMap[item.id])
  601. ? "取消全选"
  602. : "全选"}
  603. </Text>
  604. </TouchableOpacity>
  605. <TouchableOpacity
  606. style={[styles.actionBtn, styles.removeBtn]}
  607. onPress={handleMoveOutAll}
  608. >
  609. <ImageBackground
  610. source={{ uri: Images.common.butBgL }}
  611. style={styles.btnBg}
  612. resizeMode="stretch"
  613. >
  614. <Text style={styles.removeText}>移出保险柜</Text>
  615. </ImageBackground>
  616. </TouchableOpacity>
  617. </View>
  618. ) : (
  619. <TouchableOpacity
  620. style={styles.bottomBtn}
  621. onPress={handleTakeGoods}
  622. >
  623. <ImageBackground
  624. source={{ uri: Images.common.butBgL }}
  625. style={styles.bottomBtnBg}
  626. resizeMode="stretch"
  627. >
  628. <Text style={styles.bottomBtnText}>立即提货</Text>
  629. </ImageBackground>
  630. </TouchableOpacity>
  631. )}
  632. <Text style={styles.bottomInfoText}>
  633. 已选 <Text style={styles.bottomInfoCount}>{selectedCount}</Text>{" "}
  634. 件商品
  635. </Text>
  636. </View>
  637. )}
  638. {/* 提货弹窗 */}
  639. <CheckoutModal
  640. visible={checkoutVisible}
  641. selectedItems={Object.values(checkMap)}
  642. onClose={() => setCheckoutVisible(false)}
  643. onSuccess={handleCheckoutSuccess}
  644. />
  645. </ImageBackground>
  646. </View>
  647. );
  648. }
  649. const styles = StyleSheet.create({
  650. container: { flex: 1, backgroundColor: "#1a1a2e" },
  651. background: { flex: 1 },
  652. headerBg: {
  653. position: "absolute",
  654. top: 0,
  655. left: 0,
  656. width: "100%",
  657. height: 160,
  658. },
  659. header: {
  660. position: "absolute",
  661. top: 0,
  662. left: 0,
  663. right: 0,
  664. zIndex: 100,
  665. flexDirection: "row",
  666. alignItems: "center",
  667. justifyContent: "space-between",
  668. paddingHorizontal: 10,
  669. paddingBottom: 10,
  670. },
  671. backBtn: {
  672. width: 40,
  673. height: 40,
  674. justifyContent: "center",
  675. alignItems: "center",
  676. },
  677. backIcon: { fontSize: 32, color: "#fff", fontWeight: "bold" },
  678. title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
  679. placeholder: { width: 40 },
  680. content: { flex: 1 },
  681. mainTabs: {
  682. flexDirection: "row",
  683. justifyContent: "space-between",
  684. paddingHorizontal: 12, // Reduced from 15 to match everything else
  685. paddingBottom: 2,
  686. },
  687. mainTabItem: {
  688. width: "30%",
  689. height: 44, // Slightly taller
  690. justifyContent: "center",
  691. alignItems: "center",
  692. },
  693. mainTabBg: {
  694. width: "100%",
  695. height: "100%",
  696. justifyContent: "center",
  697. alignItems: "center",
  698. },
  699. mainTabText: { fontSize: 15, color: "#333", fontWeight: "bold" },
  700. mainTabTextActive: { fontSize: 16, color: "#000", fontWeight: "bold" },
  701. mainTabLine: { display: "none" },
  702. levelTabs: {
  703. flexDirection: "row",
  704. alignItems: "center",
  705. paddingHorizontal: 10, // Add some padding back for the text content since container is full width
  706. paddingVertical: 12,
  707. borderBottomWidth: 1,
  708. borderBottomColor: "rgba(255,255,255,0.15)",
  709. },
  710. levelTabItem: {
  711. marginRight: 25,
  712. alignItems: "center",
  713. justifyContent: "center",
  714. paddingHorizontal: 6,
  715. paddingVertical: 2,
  716. position: "relative",
  717. minWidth: 40,
  718. },
  719. levelTabItemActive: { backgroundColor: "transparent" },
  720. levelTabText: { color: "#666", fontSize: 15, fontWeight: "bold" },
  721. levelTabTextActive: {
  722. color: "#ff6b00",
  723. fontSize: 17,
  724. fontWeight: "900",
  725. textShadowColor: "rgba(0, 0, 0, 0.3)",
  726. textShadowOffset: { width: 1, height: 1 },
  727. textShadowRadius: 1,
  728. },
  729. // Corner Decorations - Larger and jagged simulation
  730. decorTL: {
  731. position: "absolute",
  732. top: 0,
  733. left: -4,
  734. width: 0,
  735. height: 0,
  736. borderTopWidth: 8,
  737. borderRightWidth: 8,
  738. borderTopColor: "#ff6b00",
  739. borderRightColor: "transparent",
  740. },
  741. decorBR: {
  742. position: "absolute",
  743. bottom: 0,
  744. right: -4,
  745. width: 0,
  746. height: 0,
  747. borderBottomWidth: 8,
  748. borderLeftWidth: 8,
  749. borderBottomColor: "#ff6b00",
  750. borderLeftColor: "transparent",
  751. },
  752. levelInd: { display: "none" },
  753. listContent: {
  754. paddingHorizontal: 8,
  755. paddingVertical: 10,
  756. paddingBottom: 150,
  757. },
  758. cell: {
  759. marginBottom: 0,
  760. width: "100%",
  761. minHeight: 154, // 原项目 308rpx
  762. },
  763. cellContent: {
  764. paddingTop: 15,
  765. paddingBottom: 15,
  766. paddingLeft: 18,
  767. paddingRight: 18, // 原项目 36rpx
  768. },
  769. cellHeader: {
  770. flexDirection: "row",
  771. justifyContent: "space-between",
  772. alignItems: "center",
  773. marginBottom: 10,
  774. paddingBottom: 10,
  775. borderBottomWidth: 1,
  776. borderBottomColor: "rgba(0,0,0,0.15)",
  777. },
  778. headerLeft: { flexDirection: "row", alignItems: "center" },
  779. checkBox: {
  780. width: 16,
  781. height: 16,
  782. borderWidth: 2,
  783. borderColor: "#000",
  784. marginRight: 8,
  785. justifyContent: "center",
  786. alignItems: "center",
  787. backgroundColor: "#fff",
  788. },
  789. checkBoxChecked: { backgroundColor: "#000" },
  790. checkIcon: { color: "#fff", fontSize: 10, fontWeight: "bold" },
  791. levelTitle: {
  792. fontSize: 16,
  793. fontWeight: "bold",
  794. textShadowColor: "#000",
  795. textShadowOffset: { width: 1, height: 1 },
  796. textShadowRadius: 0,
  797. },
  798. lockBox: { flexDirection: "row", alignItems: "center" },
  799. lockText: { fontSize: 12, color: "#666", marginRight: 4 },
  800. lockIcon: { width: 16, height: 16 },
  801. cellBody: {
  802. flexDirection: "row",
  803. alignItems: "center",
  804. },
  805. goodsImgBg: {
  806. width: 65,
  807. height: 65,
  808. justifyContent: "center",
  809. alignItems: "center",
  810. marginRight: 12,
  811. padding: 7,
  812. },
  813. goodsImg: { width: "100%", height: "100%" },
  814. goodsInfo: { flex: 1, justifyContent: "center", paddingRight: 8 },
  815. goodsName: {
  816. fontSize: 15,
  817. color: "#333",
  818. fontWeight: "bold",
  819. marginBottom: 6,
  820. },
  821. goodsDesc: { fontSize: 12, color: "#999" },
  822. arrowIcon: { fontSize: 18, color: "#ccc", marginLeft: 8 },
  823. bottomBar: {
  824. position: "absolute",
  825. bottom: 0,
  826. left: 0,
  827. right: 0,
  828. height: 100, // Taller for Top Button / Bottom Text layout
  829. paddingBottom: 20,
  830. alignItems: "center",
  831. justifyContent: "center",
  832. backgroundColor: "transparent", // Screenshot shows transparent or gradient?
  833. },
  834. bottomBtn: { width: "80%", height: 45, marginBottom: 5 },
  835. buttonRow: {
  836. flexDirection: "row",
  837. justifyContent: "center",
  838. width: "100%",
  839. paddingHorizontal: 20,
  840. marginBottom: 5,
  841. },
  842. actionBtn: {
  843. height: 45,
  844. borderRadius: 22,
  845. justifyContent: "center",
  846. alignItems: "center",
  847. },
  848. selectAllBtn: {
  849. width: "30%",
  850. backgroundColor: "#fff",
  851. marginRight: 10,
  852. borderWidth: 1,
  853. borderColor: "#ccc",
  854. },
  855. removeBtn: { width: "65%" },
  856. btnBg: {
  857. width: "100%",
  858. height: "100%",
  859. justifyContent: "center",
  860. alignItems: "center",
  861. },
  862. selectAllText: { fontSize: 16, fontWeight: "bold", color: "#333" },
  863. removeText: { fontSize: 16, fontWeight: "bold", color: "#000" },
  864. bottomBtnSecondary: { display: "none" }, // Removed
  865. bottomBtnSecondaryText: { display: "none" }, // Removed
  866. bottomBtnBg: {
  867. width: "100%",
  868. height: "100%",
  869. justifyContent: "center",
  870. alignItems: "center",
  871. },
  872. bottomBtnText: { color: "#000", fontSize: 16, fontWeight: "bold" },
  873. bottomInfoText: { color: "#333", fontSize: 12 }, // Text below button
  874. bottomInfoCount: { fontWeight: "bold" },
  875. emptyBox: { marginTop: 100, alignItems: "center" },
  876. emptyText: { color: "#999", fontSize: 14 },
  877. pickupCell: { width: "100%", marginBottom: 10, padding: 12 },
  878. pickupTop: {
  879. flexDirection: "row",
  880. justifyContent: "space-between",
  881. paddingBottom: 8,
  882. borderBottomWidth: 1,
  883. borderBottomColor: "#eee",
  884. },
  885. pickupTime: { fontSize: 12, color: "#999" },
  886. pickupStatus: { fontSize: 12, fontWeight: "bold" },
  887. pickupTimeout: { fontSize: 11, color: "#ff6b00", marginTop: 4 },
  888. pickupAddress: {
  889. flexDirection: "row",
  890. paddingVertical: 10,
  891. borderBottomWidth: 1,
  892. borderBottomColor: "#eee",
  893. },
  894. locationIcon: { fontSize: 16, marginRight: 8 },
  895. addressInfo: { flex: 1 },
  896. addressName: { fontSize: 14, fontWeight: "bold", color: "#333" },
  897. addressDetail: { fontSize: 12, color: "#666", marginTop: 4 },
  898. pickupGoodsBox: { paddingVertical: 10 },
  899. pickupGoodsList: { flexDirection: "row" },
  900. pickupGoodsItem: { marginRight: 10, alignItems: "center" },
  901. pickupGoodsImg: { width: 60, height: 60, borderRadius: 4 },
  902. pickupGoodsCount: {
  903. position: "absolute",
  904. right: 0,
  905. bottom: 0,
  906. backgroundColor: "rgba(0,0,0,0.5)",
  907. paddingHorizontal: 4,
  908. borderRadius: 4,
  909. },
  910. pickupGoodsCountText: { color: "#fff", fontSize: 10 },
  911. pickupOrderRow: {
  912. flexDirection: "row",
  913. alignItems: "center",
  914. paddingVertical: 8,
  915. },
  916. pickupOrderLabel: { fontSize: 12, color: "#666" },
  917. pickupOrderNo: { flex: 1, fontSize: 12, color: "#333" },
  918. copyBtn: {
  919. paddingHorizontal: 8,
  920. paddingVertical: 4,
  921. backgroundColor: "#f5f5f5",
  922. borderRadius: 4,
  923. },
  924. copyBtnText: { fontSize: 12, color: "#666" },
  925. pickupInfoRow: { flexDirection: "row", paddingVertical: 4 },
  926. pickupInfoLabel: { fontSize: 12, color: "#666" },
  927. pickupInfoValue: { fontSize: 12, color: "#333" },
  928. pickupBottom: {
  929. flexDirection: "row",
  930. justifyContent: "space-between",
  931. alignItems: "center",
  932. paddingTop: 10,
  933. },
  934. pickupExpressAmount: { fontSize: 12, color: "#333" },
  935. priceText: { color: "#ff6b00", fontWeight: "bold" },
  936. expressBtn: {
  937. paddingHorizontal: 12,
  938. paddingVertical: 6,
  939. backgroundColor: "#fec433",
  940. borderRadius: 4,
  941. },
  942. expressBtnText: { fontSize: 12, color: "#000", fontWeight: "bold" },
  943. pickupTip: {
  944. flexDirection: "row",
  945. alignItems: "center",
  946. padding: 10,
  947. backgroundColor: "rgba(255,235,200,0.8)",
  948. marginHorizontal: 8,
  949. borderRadius: 6,
  950. marginBottom: 10,
  951. },
  952. pickupTipIcon: { fontSize: 14, marginRight: 6 },
  953. pickupTipText: { flex: 1, fontSize: 11, color: "#ff6b00" },
  954. goodsSource: { fontSize: 12, color: "#666", opacity: 0.8 },
  955. arrow: { fontSize: 18, color: "#fec433", marginLeft: 8 },
  956. });