index.tsx 30 KB

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