index.tsx 30 KB

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