index.tsx 32 KB

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