index.tsx 31 KB

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