detail.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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 { id } = useLocalSearchParams<{ id: string }>();
  42. const router = useRouter();
  43. const insets = useSafeAreaInsets();
  44. const [loading, setLoading] = useState(true);
  45. const [item, setItem] = useState<any>(null);
  46. const [converting, setConverting] = useState(false);
  47. const loadData = useCallback(async () => {
  48. if (!id) return;
  49. setLoading(true);
  50. try {
  51. const data = await getLuckDetail(id);
  52. setItem(data);
  53. } catch (e) {
  54. console.error("加载详情失败:", e);
  55. }
  56. setLoading(false);
  57. }, [id]);
  58. useEffect(() => {
  59. loadData();
  60. }, [loadData]);
  61. const handleConvert = async () => {
  62. if (!item || item.magicAmount <= 0) {
  63. Alert.alert("提示", "不可兑换");
  64. return;
  65. }
  66. setConverting(true);
  67. try {
  68. const preview = await convertPreview([item.id]);
  69. if (preview?.data) {
  70. Alert.alert(
  71. "确认兑换",
  72. `将兑换 ${item.spu?.name} 获得 ${preview.data?.totalMagicAmount || item.magicAmount} 果实`,
  73. [
  74. { text: "取消", style: "cancel" },
  75. {
  76. text: "确定",
  77. onPress: async () => {
  78. const success = await convertApply([item.id]);
  79. if (success) {
  80. Alert.alert("提示", "兑换成功", [
  81. { text: "确定", onPress: () => router.back() },
  82. ]);
  83. }
  84. },
  85. },
  86. ],
  87. );
  88. }
  89. } catch (e) {
  90. Alert.alert("提示", "兑换失败");
  91. }
  92. setConverting(false);
  93. };
  94. if (loading) {
  95. return (
  96. <View style={styles.loadingBox}>
  97. <ActivityIndicator size="large" color="#fff" />
  98. </View>
  99. );
  100. }
  101. if (!item) {
  102. return (
  103. <View style={styles.loadingBox}>
  104. <Text style={{ color: "#999", fontSize: 16 }}>商品不存在</Text>
  105. </View>
  106. );
  107. }
  108. const levelInfo = LEVEL_MAP[item.level] || { title: "其他", color: "#999" };
  109. return (
  110. <View style={styles.container}>
  111. <StatusBar barStyle="light-content" />
  112. <ImageBackground
  113. source={{ uri: Images.mine.kaixinMineBg }}
  114. style={styles.background}
  115. resizeMode="cover"
  116. >
  117. {/* 顶部导航 */}
  118. <View style={[styles.header, { paddingTop: insets.top }]}>
  119. <TouchableOpacity
  120. style={styles.backBtn}
  121. onPress={() => router.back()}
  122. >
  123. <Text style={styles.backText}>←</Text>
  124. </TouchableOpacity>
  125. <Text style={styles.title}>仓库</Text>
  126. <View style={styles.placeholder} />
  127. </View>
  128. {/* 商品信息 */}
  129. <View style={styles.content}>
  130. <View style={styles.detailCard}>
  131. <Image
  132. source={{ uri: item.spu?.cover }}
  133. style={styles.coverImage}
  134. contentFit="contain"
  135. />
  136. <Text style={[styles.levelTag, { color: levelInfo.color }]}>
  137. {levelInfo.title}
  138. </Text>
  139. <Text style={styles.goodsName}>{item.spu?.name}</Text>
  140. <View style={styles.sourceBox}>
  141. <Text style={styles.sourceText}>
  142. 从{FROM_TYPE_MAP[item.fromRelationType] || "其他"}获得
  143. </Text>
  144. </View>
  145. </View>
  146. </View>
  147. {/* 兑换按钮 */}
  148. {item.magicAmount > 0 && (
  149. <View
  150. style={[
  151. styles.bottomBar,
  152. { paddingBottom: Math.max(insets.bottom, 20) },
  153. ]}
  154. >
  155. <TouchableOpacity
  156. style={styles.convertBtn}
  157. onPress={handleConvert}
  158. disabled={converting}
  159. activeOpacity={0.8}
  160. >
  161. <ImageBackground
  162. source={{ uri: Images.common.loginBtn }}
  163. style={styles.convertBtnBg}
  164. resizeMode="stretch"
  165. >
  166. <Text style={styles.convertBtnText}>兑换果实</Text>
  167. <Text style={styles.convertBtnSub}>
  168. 可兑换 {item.magicAmount} 果实
  169. </Text>
  170. </ImageBackground>
  171. </TouchableOpacity>
  172. </View>
  173. )}
  174. </ImageBackground>
  175. </View>
  176. );
  177. }
  178. const styles = StyleSheet.create({
  179. container: { flex: 1, backgroundColor: "#1a1a2e" },
  180. background: { flex: 1 },
  181. loadingBox: {
  182. flex: 1,
  183. backgroundColor: "#1a1a2e",
  184. justifyContent: "center",
  185. alignItems: "center",
  186. },
  187. header: {
  188. flexDirection: "row",
  189. alignItems: "center",
  190. justifyContent: "space-between",
  191. paddingHorizontal: 15,
  192. paddingBottom: 10,
  193. },
  194. backBtn: {
  195. width: 40,
  196. height: 40,
  197. justifyContent: "center",
  198. alignItems: "center",
  199. },
  200. backText: { color: "#fff", fontSize: 20 },
  201. title: { color: "#fff", fontSize: 16, fontWeight: "bold" },
  202. placeholder: { width: 40 },
  203. content: { flex: 1, paddingHorizontal: 20, paddingTop: 20 },
  204. detailCard: {
  205. backgroundColor: "rgba(255,255,255,0.95)",
  206. borderRadius: 16,
  207. padding: 24,
  208. alignItems: "center",
  209. },
  210. coverImage: {
  211. width: 180,
  212. height: 180,
  213. borderRadius: 10,
  214. marginBottom: 16,
  215. },
  216. levelTag: {
  217. fontSize: 16,
  218. fontWeight: "bold",
  219. marginBottom: 8,
  220. textShadowColor: "#000",
  221. textShadowOffset: { width: 1, height: 1 },
  222. textShadowRadius: 0,
  223. },
  224. goodsName: {
  225. fontSize: 16,
  226. color: "#333",
  227. fontWeight: "bold",
  228. textAlign: "center",
  229. marginBottom: 12,
  230. lineHeight: 22,
  231. },
  232. sourceBox: {
  233. backgroundColor: "rgba(0,0,0,0.05)",
  234. borderRadius: 20,
  235. paddingHorizontal: 16,
  236. paddingVertical: 6,
  237. },
  238. sourceText: { fontSize: 13, color: "#666" },
  239. bottomBar: {
  240. paddingHorizontal: 30,
  241. paddingTop: 10,
  242. alignItems: "center",
  243. },
  244. convertBtn: {
  245. width: "80%",
  246. height: 60,
  247. },
  248. convertBtnBg: {
  249. width: "100%",
  250. height: "100%",
  251. justifyContent: "center",
  252. alignItems: "center",
  253. },
  254. convertBtnText: {
  255. color: "#000",
  256. fontSize: 16,
  257. fontWeight: "bold",
  258. },
  259. convertBtnSub: {
  260. color: "#735200",
  261. fontSize: 11,
  262. },
  263. });