detail.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import { Image } from "expo-image";
  2. import { useLocalSearchParams, useRouter } from "expo-router";
  3. import React, { useCallback, useEffect, useRef, useState } from "react";
  4. import {
  5. ActivityIndicator,
  6. Alert,
  7. ImageBackground,
  8. StatusBar,
  9. StyleSheet,
  10. Text,
  11. TouchableOpacity,
  12. View,
  13. } from "react-native";
  14. import { useSafeAreaInsets } from "react-native-safe-area-context";
  15. import { Images } from "@/constants/images";
  16. import { convertApply, convertPreview, getLuckDetail } from "@/services/award";
  17. const LEVEL_MAP: Record<string, { title: string; color: string }> = {
  18. A: { title: "超神", color: "#FF3B30" },
  19. B: { title: "欧皇", color: "#FF9500" },
  20. C: { title: "隐藏", color: "#AF52DE" },
  21. D: { title: "普通", color: "#8E8E93" },
  22. };
  23. const FROM_TYPE_MAP: Record<string, string> = {
  24. MALL: "商城",
  25. DISTRIBUTION: "分销",
  26. LUCK: "奖池",
  27. LUCK_PICKUP: "商品提货",
  28. LUCK_EXCHANGE: "商品兑换",
  29. SUBSTITUTE: "商品置换",
  30. TRANSFER: "商品转赠",
  31. LUCK_ROOM: "福利房",
  32. LUCK_WHEEL: "魔天轮",
  33. DOLL_MACHINE: "扭蛋",
  34. RECHARGE: "充值",
  35. OFFICIAL: "官方",
  36. ACTIVITY: "活动",
  37. LUCK_ACTIVITY: "奖池活动",
  38. REDEEM_CODE_ACTIVITY: "兑换码活动",
  39. };
  40. export default function StoreDetailScreen() {
  41. const params = useLocalSearchParams<{ id: string }>();
  42. const id = params?.id;
  43. const router = useRouter();
  44. const insets = useSafeAreaInsets();
  45. const [loading, setLoading] = useState(true);
  46. const [item, setItem] = useState<any>(null);
  47. const [converting, setConverting] = useState(false);
  48. const [error, setError] = useState<string | null>(null);
  49. console.log('[仓库详情] 页面加载, params:', JSON.stringify(params), 'id:', id);
  50. const loadData = useCallback(async () => {
  51. console.log('[仓库详情] loadData 开始, id:', id);
  52. if (!id) {
  53. setError('未获取到商品ID');
  54. setLoading(false);
  55. return;
  56. }
  57. setLoading(true);
  58. setError(null);
  59. try {
  60. const data = await getLuckDetail(id);
  61. console.log('[仓库详情] getLuckDetail 返回:', data ? '有数据' : 'null');
  62. setItem(data);
  63. if (!data) {
  64. setError('商品数据为空');
  65. }
  66. } catch (e: any) {
  67. console.error("[仓库详情] 加载失败:", e);
  68. setError(`加载失败: ${e?.message || '未知错误'}`);
  69. }
  70. setLoading(false);
  71. }, [id]);
  72. useEffect(() => {
  73. loadData();
  74. }, [loadData]);
  75. const handleConvert = async () => {
  76. if (!item || item.magicAmount <= 0) {
  77. Alert.alert("提示", "不可兑换");
  78. return;
  79. }
  80. setConverting(true);
  81. try {
  82. const preview = await convertPreview([item.id]);
  83. if (preview?.data) {
  84. Alert.alert(
  85. "确认兑换",
  86. `将兑换 ${item.spu?.name} 获得 ${preview.data?.totalMagicAmount || item.magicAmount} 果实`,
  87. [
  88. { text: "取消", style: "cancel" },
  89. {
  90. text: "确定",
  91. onPress: async () => {
  92. const success = await convertApply([item.id]);
  93. if (success) {
  94. Alert.alert("提示", "兑换成功", [
  95. { text: "确定", onPress: () => router.back() },
  96. ]);
  97. }
  98. },
  99. },
  100. ],
  101. );
  102. }
  103. } catch (e) {
  104. Alert.alert("提示", "兑换失败");
  105. }
  106. setConverting(false);
  107. };
  108. if (loading) {
  109. return (
  110. <View style={styles.loadingBox}>
  111. <ActivityIndicator size="large" color="#fff" />
  112. </View>
  113. );
  114. }
  115. if (!item) {
  116. return (
  117. <View style={styles.loadingBox}>
  118. <Text style={{ color: "#999", fontSize: 16 }}>{error || '商品不存在'}</Text>
  119. <TouchableOpacity onPress={() => router.back()} style={{ marginTop: 20 }}>
  120. <Text style={{ color: "#fff", fontSize: 14 }}>返回</Text>
  121. </TouchableOpacity>
  122. </View>
  123. );
  124. }
  125. const levelInfo = LEVEL_MAP[item.level] || { title: "其他", color: "#999" };
  126. return (
  127. <View style={styles.container}>
  128. <StatusBar barStyle="light-content" />
  129. <ImageBackground
  130. source={{ uri: Images.mine.kaixinMineBg }}
  131. style={styles.background}
  132. resizeMode="cover"
  133. >
  134. {/* 顶部导航 */}
  135. <View style={[styles.header, { paddingTop: insets.top }]}>
  136. <TouchableOpacity
  137. style={styles.backBtn}
  138. onPress={() => router.back()}
  139. >
  140. <Text style={styles.backText}>←</Text>
  141. </TouchableOpacity>
  142. <Text style={styles.title}>仓库</Text>
  143. <View style={styles.placeholder} />
  144. </View>
  145. {/* 商品信息 */}
  146. <View style={styles.content}>
  147. <View style={styles.detailCard}>
  148. <Image
  149. source={{ uri: item.spu?.cover }}
  150. style={styles.coverImage}
  151. contentFit="contain"
  152. />
  153. <Text style={[styles.levelTag, { color: levelInfo.color }]}>
  154. {levelInfo.title}
  155. </Text>
  156. <Text style={styles.goodsName}>{item.spu?.name}</Text>
  157. <View style={styles.sourceBox}>
  158. <Text style={styles.sourceText}>
  159. 从{FROM_TYPE_MAP[item.fromRelationType] || "其他"}获得
  160. </Text>
  161. </View>
  162. </View>
  163. </View>
  164. {/* 兑换按钮 */}
  165. {item.magicAmount > 0 && (
  166. <View
  167. style={[
  168. styles.bottomBar,
  169. { paddingBottom: Math.max(insets.bottom, 20) },
  170. ]}
  171. >
  172. <TouchableOpacity
  173. style={styles.convertBtn}
  174. onPress={handleConvert}
  175. disabled={converting}
  176. activeOpacity={0.8}
  177. >
  178. <ImageBackground
  179. source={{ uri: Images.common.loginBtn }}
  180. style={styles.convertBtnBg}
  181. resizeMode="stretch"
  182. >
  183. <Text style={styles.convertBtnText}>兑换果实</Text>
  184. <Text style={styles.convertBtnSub}>
  185. 可兑换 {item.magicAmount} 果实
  186. </Text>
  187. </ImageBackground>
  188. </TouchableOpacity>
  189. </View>
  190. )}
  191. </ImageBackground>
  192. </View>
  193. );
  194. }
  195. const styles = StyleSheet.create({
  196. container: { flex: 1, backgroundColor: "#1a1a2e" },
  197. background: { flex: 1 },
  198. loadingBox: {
  199. flex: 1,
  200. backgroundColor: "#1a1a2e",
  201. justifyContent: "center",
  202. alignItems: "center",
  203. },
  204. header: {
  205. flexDirection: "row",
  206. alignItems: "center",
  207. justifyContent: "space-between",
  208. paddingHorizontal: 15,
  209. paddingBottom: 10,
  210. },
  211. backBtn: {
  212. width: 40,
  213. height: 40,
  214. justifyContent: "center",
  215. alignItems: "center",
  216. },
  217. backText: { color: "#fff", fontSize: 20 },
  218. title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
  219. placeholder: { width: 40 },
  220. content: { flex: 1, paddingHorizontal: 20, paddingTop: 20 },
  221. detailCard: {
  222. backgroundColor: "rgba(255,255,255,0.95)",
  223. borderRadius: 16,
  224. padding: 24,
  225. alignItems: "center",
  226. },
  227. coverImage: {
  228. width: 180,
  229. height: 180,
  230. borderRadius: 10,
  231. marginBottom: 16,
  232. },
  233. levelTag: {
  234. fontSize: 16,
  235. fontWeight: "bold",
  236. marginBottom: 8,
  237. textShadowColor: "#000",
  238. textShadowOffset: { width: 1, height: 1 },
  239. textShadowRadius: 0,
  240. },
  241. goodsName: {
  242. fontSize: 16,
  243. color: "#333",
  244. fontWeight: "bold",
  245. textAlign: "center",
  246. marginBottom: 12,
  247. lineHeight: 22,
  248. },
  249. sourceBox: {
  250. backgroundColor: "rgba(0,0,0,0.05)",
  251. borderRadius: 20,
  252. paddingHorizontal: 16,
  253. paddingVertical: 6,
  254. },
  255. sourceText: { fontSize: 13, color: "#666" },
  256. bottomBar: {
  257. paddingHorizontal: 30,
  258. paddingTop: 10,
  259. alignItems: "center",
  260. },
  261. convertBtn: {
  262. width: "80%",
  263. height: 60,
  264. },
  265. convertBtnBg: {
  266. width: "100%",
  267. height: "100%",
  268. justifyContent: "center",
  269. alignItems: "center",
  270. },
  271. convertBtnText: {
  272. color: "#000",
  273. fontSize: 16,
  274. fontWeight: "bold",
  275. },
  276. convertBtnSub: {
  277. color: "#735200",
  278. fontSize: 11,
  279. },
  280. });