index.tsx 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  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={() => {
  315. console.log('[仓库] 点击商品详情, id:', item.id);
  316. router.push({ pathname: '/cloud-warehouse/detail', params: { id: item.id } } as any);
  317. }}
  318. activeOpacity={0.7}
  319. >
  320. <ImageBackground
  321. source={{ uri: Images.mine.storeGoodsImgBg }}
  322. style={styles.goodsImgBg}
  323. >
  324. <Image
  325. source={{ uri: item.spu?.cover }}
  326. style={styles.goodsImg}
  327. contentFit="contain"
  328. />
  329. </ImageBackground>
  330. <View style={styles.goodsInfo}>
  331. <Text style={styles.goodsName} numberOfLines={2}>
  332. {item.spu?.name}
  333. </Text>
  334. <Text style={styles.goodsSource}>
  335. 从{FROM_TYPE_MAP[item.fromRelationType] || "其他"}获得
  336. </Text>
  337. </View>
  338. <Text style={styles.arrow}>{">"}</Text>
  339. </TouchableOpacity>
  340. </View>
  341. </ImageBackground>
  342. );
  343. };
  344. const copyToClipboard = (text: string) => {
  345. showAlert(`订单号已复制: ${text}`);
  346. };
  347. const showExpress = (item: PickupItem) => {
  348. router.push({
  349. pathname: "/cloud-warehouse/packages" as any,
  350. params: { tradeNo: item.tradeNo },
  351. });
  352. };
  353. const renderPickupItem = ({
  354. item,
  355. }: {
  356. item: PickupItem & { groupedList?: Array<{ total: number; data: any }> };
  357. }) => {
  358. const statusInfo = STATUS_MAP[item.status] || {
  359. text: "未知",
  360. color: "#999",
  361. };
  362. return (
  363. <ImageBackground
  364. source={{ uri: Images.mine.storeItemBg }}
  365. style={styles.pickupCell}
  366. resizeMode="stretch"
  367. >
  368. {/* 顶部信息 */}
  369. <View style={styles.pickupTop}>
  370. <Text style={styles.pickupTime}>下单时间:{item.createTime}</Text>
  371. <Text style={[styles.pickupStatus, { color: statusInfo.color }]}>
  372. {statusInfo.text}
  373. </Text>
  374. </View>
  375. {item.status === 0 && item.paymentTimeoutTime && (
  376. <Text style={styles.pickupTimeout}>
  377. {item.paymentTimeoutTime} 将自动取消该订单,如有优惠券,将自动退回
  378. </Text>
  379. )}
  380. {/* 收货地址 */}
  381. <View style={styles.pickupAddress}>
  382. <Text style={styles.locationIcon}>📍</Text>
  383. <View style={styles.addressInfo}>
  384. <Text style={styles.addressName}>
  385. {item.contactName},{item.contactNo}
  386. </Text>
  387. <Text style={styles.addressDetail}>
  388. {item.province}
  389. {item.city}
  390. {item.district}
  391. {item.address}
  392. </Text>
  393. </View>
  394. </View>
  395. {/* 商品列表 */}
  396. <View style={styles.pickupGoodsBox}>
  397. <ScrollView
  398. horizontal
  399. showsHorizontalScrollIndicator={false}
  400. style={styles.pickupGoodsList}
  401. >
  402. {(item.groupedList || []).map((goods, idx) => (
  403. <View key={idx} style={styles.pickupGoodsItem}>
  404. <Image
  405. source={{ uri: goods.data.cover }}
  406. style={styles.pickupGoodsImg}
  407. contentFit="contain"
  408. />
  409. <View style={styles.pickupGoodsCount}>
  410. <Text style={styles.pickupGoodsCountText}>
  411. x{goods.total}
  412. </Text>
  413. </View>
  414. </View>
  415. ))}
  416. </ScrollView>
  417. </View>
  418. {/* 订单号 */}
  419. <View style={styles.pickupOrderRow}>
  420. <Text style={styles.pickupOrderLabel}>订单号:</Text>
  421. <Text style={styles.pickupOrderNo} numberOfLines={1}>
  422. {item.tradeNo}
  423. </Text>
  424. <TouchableOpacity
  425. style={styles.copyBtn}
  426. onPress={() => copyToClipboard(item.tradeNo)}
  427. >
  428. <Text style={styles.copyBtnText}>复制</Text>
  429. </TouchableOpacity>
  430. </View>
  431. {item.paymentTime && (
  432. <View style={styles.pickupInfoRow}>
  433. <Text style={styles.pickupInfoLabel}>付款时间:</Text>
  434. <Text style={styles.pickupInfoValue}>{item.paymentTime}</Text>
  435. </View>
  436. )}
  437. {item.status === 12 && item.cancelRemark && (
  438. <View style={styles.pickupInfoRow}>
  439. <Text style={styles.pickupInfoLabel}>备注</Text>
  440. <Text style={[styles.pickupInfoValue, { color: "#ff6b00" }]}>
  441. {item.cancelRemark}
  442. </Text>
  443. </View>
  444. )}
  445. {/* 底部操作 */}
  446. <View style={styles.pickupBottom}>
  447. <Text style={styles.pickupExpressAmount}>
  448. 配送费:<Text style={styles.priceText}>¥{item.expressAmount}</Text>
  449. </Text>
  450. {[1, 2, 99].includes(item.status) && (
  451. <TouchableOpacity
  452. style={styles.expressBtn}
  453. onPress={() => showExpress(item)}
  454. >
  455. <Text style={styles.expressBtnText}>物流信息</Text>
  456. </TouchableOpacity>
  457. )}
  458. </View>
  459. </ImageBackground>
  460. );
  461. };
  462. const selectedCount = Object.keys(checkMap).length;
  463. return (
  464. <View style={styles.container}>
  465. <StatusBar barStyle="light-content" />
  466. <ImageBackground
  467. source={{ uri: Images.mine.kaixinMineBg }}
  468. style={styles.background}
  469. resizeMode="cover"
  470. >
  471. <Image
  472. source={{ uri: Images.mine.kaixinMineHeadBg }}
  473. style={styles.headerBg}
  474. contentFit="cover"
  475. />
  476. <View style={[styles.header, { paddingTop: insets.top }]}>
  477. <TouchableOpacity
  478. style={styles.backBtn}
  479. onPress={() => router.back()}
  480. >
  481. <Text style={styles.backIcon}>‹</Text>
  482. </TouchableOpacity>
  483. <Text style={styles.title}>仓库</Text>
  484. <View style={styles.placeholder} />
  485. </View>
  486. <View style={[styles.content, { paddingTop: insets.top + 50 }]}>
  487. <View style={styles.mainTabs}>
  488. {mainTabs.map((tab, index) => {
  489. const isActive = mainTabIndex === index;
  490. // Use Yellow L bg for active, Grey (Hui) for inactive
  491. const bg = isActive
  492. ? Images.common.butBgL
  493. : Images.common.butBgHui;
  494. return (
  495. <TouchableOpacity
  496. key={index}
  497. style={styles.mainTabItem}
  498. onPress={() => setMainTabIndex(index)}
  499. >
  500. <ImageBackground
  501. source={{ uri: bg }}
  502. style={styles.mainTabBg}
  503. resizeMode="contain"
  504. >
  505. <Text
  506. style={
  507. isActive ? styles.mainTabTextActive : styles.mainTabText
  508. }
  509. >
  510. {tab}
  511. </Text>
  512. </ImageBackground>
  513. </TouchableOpacity>
  514. );
  515. })}
  516. </View>
  517. {mainTabIndex === 0 && (
  518. <View style={styles.levelTabs}>
  519. {LEVEL_TABS.map((tab, index) => {
  520. const isActive = levelTabIndex === index;
  521. return (
  522. <TouchableOpacity
  523. key={index}
  524. style={[
  525. styles.levelTabItem,
  526. isActive && styles.levelTabItemActive,
  527. ]}
  528. onPress={() => setLevelTabIndex(index)}
  529. >
  530. {isActive && <View style={styles.decorTL} />}
  531. <Text
  532. style={[
  533. styles.levelTabText,
  534. isActive && styles.levelTabTextActive,
  535. ]}
  536. >
  537. {tab.title}
  538. </Text>
  539. {isActive && <View style={styles.decorBR} />}
  540. </TouchableOpacity>
  541. );
  542. })}
  543. </View>
  544. )}
  545. <FlatList
  546. data={list as any[]}
  547. renderItem={
  548. mainTabIndex === 2
  549. ? (renderPickupItem as any)
  550. : (renderStoreItem as any)
  551. }
  552. keyExtractor={(item: any, index) =>
  553. item.id || item.tradeNo || index.toString()
  554. }
  555. contentContainerStyle={styles.listContent}
  556. refreshControl={
  557. <RefreshControl
  558. refreshing={refreshing}
  559. onRefresh={handleRefresh}
  560. tintColor="#fff"
  561. />
  562. }
  563. onEndReached={handleLoadMore}
  564. onEndReachedThreshold={0.3}
  565. ListHeaderComponent={
  566. mainTabIndex === 2 ? (
  567. <View style={styles.pickupTip}>
  568. <Text style={styles.pickupTipIcon}>⚠️</Text>
  569. <Text style={styles.pickupTipText}>
  570. 您的包裹一般在5个工作日内发货,如遇特殊情况可能会有延迟,敬请谅解~
  571. </Text>
  572. </View>
  573. ) : null
  574. }
  575. ListFooterComponent={
  576. loading && list.length > 0 ? (
  577. <ActivityIndicator
  578. color="#fff"
  579. style={{ marginVertical: 10 }}
  580. />
  581. ) : null
  582. }
  583. ListEmptyComponent={
  584. !loading ? (
  585. <View style={styles.emptyBox}>
  586. <Text style={styles.emptyText}>暂无物品</Text>
  587. </View>
  588. ) : null
  589. }
  590. />
  591. </View>
  592. {mainTabIndex !== 2 && list.length > 0 && (
  593. <View
  594. style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}
  595. >
  596. {mainTabIndex === 1 ? (
  597. <View style={styles.buttonRow}>
  598. <TouchableOpacity
  599. style={[styles.actionBtn, styles.selectAllBtn]}
  600. onPress={handleSelectAll}
  601. >
  602. <Text style={styles.selectAllText}>
  603. {list.length > 0 && list.every((item) => checkMap[item.id])
  604. ? "取消全选"
  605. : "全选"}
  606. </Text>
  607. </TouchableOpacity>
  608. <TouchableOpacity
  609. style={[styles.actionBtn, styles.removeBtn]}
  610. onPress={handleMoveOutAll}
  611. >
  612. <ImageBackground
  613. source={{ uri: Images.common.butBgL }}
  614. style={styles.btnBg}
  615. resizeMode="stretch"
  616. >
  617. <Text style={styles.removeText}>移出保险柜</Text>
  618. </ImageBackground>
  619. </TouchableOpacity>
  620. </View>
  621. ) : (
  622. <TouchableOpacity
  623. style={styles.bottomBtn}
  624. onPress={handleTakeGoods}
  625. >
  626. <ImageBackground
  627. source={{ uri: Images.common.butBgL }}
  628. style={styles.bottomBtnBg}
  629. resizeMode="stretch"
  630. >
  631. <Text style={styles.bottomBtnText}>立即提货</Text>
  632. </ImageBackground>
  633. </TouchableOpacity>
  634. )}
  635. <Text style={styles.bottomInfoText}>
  636. 已选 <Text style={styles.bottomInfoCount}>{selectedCount}</Text>{" "}
  637. 件商品
  638. </Text>
  639. </View>
  640. )}
  641. {/* 提货弹窗 */}
  642. <CheckoutModal
  643. visible={checkoutVisible}
  644. selectedItems={Object.values(checkMap)}
  645. onClose={() => setCheckoutVisible(false)}
  646. onSuccess={handleCheckoutSuccess}
  647. />
  648. </ImageBackground>
  649. </View>
  650. );
  651. }
  652. const styles = StyleSheet.create({
  653. container: { flex: 1, backgroundColor: "#1a1a2e" },
  654. background: { flex: 1 },
  655. headerBg: {
  656. position: "absolute",
  657. top: 0,
  658. left: 0,
  659. width: "100%",
  660. height: 160,
  661. },
  662. header: {
  663. position: "absolute",
  664. top: 0,
  665. left: 0,
  666. right: 0,
  667. zIndex: 100,
  668. flexDirection: "row",
  669. alignItems: "center",
  670. justifyContent: "space-between",
  671. paddingHorizontal: 10,
  672. paddingBottom: 10,
  673. },
  674. backBtn: {
  675. width: 40,
  676. height: 40,
  677. justifyContent: "center",
  678. alignItems: "center",
  679. },
  680. backIcon: { fontSize: 32, color: "#fff", fontWeight: "bold" },
  681. title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
  682. placeholder: { width: 40 },
  683. content: { flex: 1 },
  684. mainTabs: {
  685. flexDirection: "row",
  686. justifyContent: "space-between",
  687. paddingHorizontal: 12, // Reduced from 15 to match everything else
  688. paddingBottom: 2,
  689. },
  690. mainTabItem: {
  691. width: "30%",
  692. height: 44, // Slightly taller
  693. justifyContent: "center",
  694. alignItems: "center",
  695. },
  696. mainTabBg: {
  697. width: "100%",
  698. height: "100%",
  699. justifyContent: "center",
  700. alignItems: "center",
  701. },
  702. mainTabText: { fontSize: 15, color: "#333", fontWeight: "bold" },
  703. mainTabTextActive: { fontSize: 16, color: "#000", fontWeight: "bold" },
  704. mainTabLine: { display: "none" },
  705. levelTabs: {
  706. flexDirection: "row",
  707. alignItems: "center",
  708. paddingHorizontal: 10, // Add some padding back for the text content since container is full width
  709. paddingVertical: 12,
  710. borderBottomWidth: 1,
  711. borderBottomColor: "rgba(255,255,255,0.15)",
  712. },
  713. levelTabItem: {
  714. marginRight: 25,
  715. alignItems: "center",
  716. justifyContent: "center",
  717. paddingHorizontal: 6,
  718. paddingVertical: 2,
  719. position: "relative",
  720. minWidth: 40,
  721. },
  722. levelTabItemActive: { backgroundColor: "transparent" },
  723. levelTabText: { color: "#666", fontSize: 15, fontWeight: "bold" },
  724. levelTabTextActive: {
  725. color: "#ff6b00",
  726. fontSize: 17,
  727. fontWeight: "900",
  728. textShadowColor: "rgba(0, 0, 0, 0.3)",
  729. textShadowOffset: { width: 1, height: 1 },
  730. textShadowRadius: 1,
  731. },
  732. // Corner Decorations - Larger and jagged simulation
  733. decorTL: {
  734. position: "absolute",
  735. top: 0,
  736. left: -4,
  737. width: 0,
  738. height: 0,
  739. borderTopWidth: 8,
  740. borderRightWidth: 8,
  741. borderTopColor: "#ff6b00",
  742. borderRightColor: "transparent",
  743. },
  744. decorBR: {
  745. position: "absolute",
  746. bottom: 0,
  747. right: -4,
  748. width: 0,
  749. height: 0,
  750. borderBottomWidth: 8,
  751. borderLeftWidth: 8,
  752. borderBottomColor: "#ff6b00",
  753. borderLeftColor: "transparent",
  754. },
  755. levelInd: { display: "none" },
  756. listContent: {
  757. paddingHorizontal: 8,
  758. paddingVertical: 10,
  759. paddingBottom: 150,
  760. },
  761. cell: {
  762. marginBottom: 0,
  763. width: "100%",
  764. minHeight: 154, // 原项目 308rpx
  765. },
  766. cellContent: {
  767. paddingTop: 15,
  768. paddingBottom: 15,
  769. paddingLeft: 18,
  770. paddingRight: 18, // 原项目 36rpx
  771. },
  772. cellHeader: {
  773. flexDirection: "row",
  774. justifyContent: "space-between",
  775. alignItems: "center",
  776. marginBottom: 10,
  777. paddingBottom: 10,
  778. borderBottomWidth: 1,
  779. borderBottomColor: "rgba(0,0,0,0.15)",
  780. },
  781. headerLeft: { flexDirection: "row", alignItems: "center" },
  782. checkBox: {
  783. width: 16,
  784. height: 16,
  785. borderWidth: 2,
  786. borderColor: "#000",
  787. marginRight: 8,
  788. justifyContent: "center",
  789. alignItems: "center",
  790. backgroundColor: "#fff",
  791. },
  792. checkBoxChecked: { backgroundColor: "#000" },
  793. checkIcon: { color: "#fff", fontSize: 10, fontWeight: "bold" },
  794. levelTitle: {
  795. fontSize: 16,
  796. fontWeight: "bold",
  797. textShadowColor: "#000",
  798. textShadowOffset: { width: 1, height: 1 },
  799. textShadowRadius: 0,
  800. },
  801. lockBox: { flexDirection: "row", alignItems: "center" },
  802. lockText: { fontSize: 12, color: "#666", marginRight: 4 },
  803. lockIcon: { width: 16, height: 16 },
  804. cellBody: {
  805. flexDirection: "row",
  806. alignItems: "center",
  807. },
  808. goodsImgBg: {
  809. width: 65,
  810. height: 65,
  811. justifyContent: "center",
  812. alignItems: "center",
  813. marginRight: 12,
  814. padding: 7,
  815. },
  816. goodsImg: { width: "100%", height: "100%" },
  817. goodsInfo: { flex: 1, justifyContent: "center", paddingRight: 8 },
  818. goodsName: {
  819. fontSize: 15,
  820. color: "#333",
  821. fontWeight: "bold",
  822. marginBottom: 6,
  823. },
  824. goodsDesc: { fontSize: 12, color: "#999" },
  825. arrowIcon: { fontSize: 18, color: "#ccc", marginLeft: 8 },
  826. bottomBar: {
  827. position: "absolute",
  828. bottom: 0,
  829. left: 0,
  830. right: 0,
  831. height: 100, // Taller for Top Button / Bottom Text layout
  832. paddingBottom: 20,
  833. alignItems: "center",
  834. justifyContent: "center",
  835. backgroundColor: "transparent", // Screenshot shows transparent or gradient?
  836. },
  837. bottomBtn: { width: "80%", height: 45, marginBottom: 5 },
  838. buttonRow: {
  839. flexDirection: "row",
  840. justifyContent: "center",
  841. width: "100%",
  842. paddingHorizontal: 20,
  843. marginBottom: 5,
  844. },
  845. actionBtn: {
  846. height: 45,
  847. borderRadius: 22,
  848. justifyContent: "center",
  849. alignItems: "center",
  850. },
  851. selectAllBtn: {
  852. width: "30%",
  853. backgroundColor: "#fff",
  854. marginRight: 10,
  855. borderWidth: 1,
  856. borderColor: "#ccc",
  857. },
  858. removeBtn: { width: "65%" },
  859. btnBg: {
  860. width: "100%",
  861. height: "100%",
  862. justifyContent: "center",
  863. alignItems: "center",
  864. },
  865. selectAllText: { fontSize: 16, fontWeight: "bold", color: "#333" },
  866. removeText: { fontSize: 16, fontWeight: "bold", color: "#000" },
  867. bottomBtnSecondary: { display: "none" }, // Removed
  868. bottomBtnSecondaryText: { display: "none" }, // Removed
  869. bottomBtnBg: {
  870. width: "100%",
  871. height: "100%",
  872. justifyContent: "center",
  873. alignItems: "center",
  874. },
  875. bottomBtnText: { color: "#000", fontSize: 16, fontWeight: "bold" },
  876. bottomInfoText: { color: "#333", fontSize: 12 }, // Text below button
  877. bottomInfoCount: { fontWeight: "bold" },
  878. emptyBox: { marginTop: 100, alignItems: "center" },
  879. emptyText: { color: "#999", fontSize: 14 },
  880. pickupCell: { width: "100%", marginBottom: 10, padding: 12 },
  881. pickupTop: {
  882. flexDirection: "row",
  883. justifyContent: "space-between",
  884. paddingBottom: 8,
  885. borderBottomWidth: 1,
  886. borderBottomColor: "#eee",
  887. },
  888. pickupTime: { fontSize: 12, color: "#999" },
  889. pickupStatus: { fontSize: 12, fontWeight: "bold" },
  890. pickupTimeout: { fontSize: 11, color: "#ff6b00", marginTop: 4 },
  891. pickupAddress: {
  892. flexDirection: "row",
  893. paddingVertical: 10,
  894. borderBottomWidth: 1,
  895. borderBottomColor: "#eee",
  896. },
  897. locationIcon: { fontSize: 16, marginRight: 8 },
  898. addressInfo: { flex: 1 },
  899. addressName: { fontSize: 14, fontWeight: "bold", color: "#333" },
  900. addressDetail: { fontSize: 12, color: "#666", marginTop: 4 },
  901. pickupGoodsBox: { paddingVertical: 10 },
  902. pickupGoodsList: { flexDirection: "row" },
  903. pickupGoodsItem: { marginRight: 10, alignItems: "center" },
  904. pickupGoodsImg: { width: 60, height: 60, borderRadius: 4 },
  905. pickupGoodsCount: {
  906. position: "absolute",
  907. right: 0,
  908. bottom: 0,
  909. backgroundColor: "rgba(0,0,0,0.5)",
  910. paddingHorizontal: 4,
  911. borderRadius: 4,
  912. },
  913. pickupGoodsCountText: { color: "#fff", fontSize: 10 },
  914. pickupOrderRow: {
  915. flexDirection: "row",
  916. alignItems: "center",
  917. paddingVertical: 8,
  918. },
  919. pickupOrderLabel: { fontSize: 12, color: "#666" },
  920. pickupOrderNo: { flex: 1, fontSize: 12, color: "#333" },
  921. copyBtn: {
  922. paddingHorizontal: 8,
  923. paddingVertical: 4,
  924. backgroundColor: "#f5f5f5",
  925. borderRadius: 4,
  926. },
  927. copyBtnText: { fontSize: 12, color: "#666" },
  928. pickupInfoRow: { flexDirection: "row", paddingVertical: 4 },
  929. pickupInfoLabel: { fontSize: 12, color: "#666" },
  930. pickupInfoValue: { fontSize: 12, color: "#333" },
  931. pickupBottom: {
  932. flexDirection: "row",
  933. justifyContent: "space-between",
  934. alignItems: "center",
  935. paddingTop: 10,
  936. },
  937. pickupExpressAmount: { fontSize: 12, color: "#333" },
  938. priceText: { color: "#ff6b00", fontWeight: "bold" },
  939. expressBtn: {
  940. paddingHorizontal: 12,
  941. paddingVertical: 6,
  942. backgroundColor: "#fec433",
  943. borderRadius: 4,
  944. },
  945. expressBtnText: { fontSize: 12, color: "#000", fontWeight: "bold" },
  946. pickupTip: {
  947. flexDirection: "row",
  948. alignItems: "center",
  949. padding: 10,
  950. backgroundColor: "rgba(255,235,200,0.8)",
  951. marginHorizontal: 8,
  952. borderRadius: 6,
  953. marginBottom: 10,
  954. },
  955. pickupTipIcon: { fontSize: 14, marginRight: 6 },
  956. pickupTipText: { flex: 1, fontSize: 11, color: "#ff6b00" },
  957. goodsSource: { fontSize: 12, color: "#666", opacity: 0.8 },
  958. arrow: { fontSize: 18, color: "#fec433", marginLeft: 8 },
  959. });