| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054 |
- import { Image } from "expo-image";
- import { useRouter } from "expo-router";
- import React, {
- forwardRef,
- useImperativeHandle,
- useRef,
- useState,
- } from "react";
- import {
- ActivityIndicator,
- Alert,
- AppState,
- Dimensions,
- Modal,
- ScrollView,
- StyleSheet,
- Text,
- TouchableOpacity,
- View,
- } from "react-native";
- import { applyOrder, getApplyResult, previewOrder } from "@/services/award";
- import Alipay from "expo-native-alipay";
- import {
- LotteryResultModal,
- LotteryResultModalRef,
- } from "./LotteryResultModal";
- const { width: SCREEN_WIDTH } = Dimensions.get("window");
- // 等级配置
- const LEVEL_MAP: Record<string, { title: string; color: string }> = {
- A: { title: "超神款", color: "#FF4444" },
- B: { title: "欧皇款", color: "#FF9600" },
- C: { title: "隐藏款", color: "#9B59B6" },
- D: { title: "普通款", color: "#666666" },
- };
- interface CheckoutModalProps {
- data: any;
- poolId: string;
- onSuccess: (param: { num: number; tradeNo: string }) => void;
- boxNumber?: string;
- }
- export interface CheckoutModalRef {
- show: (
- num: number,
- preview: any,
- boxNum?: string,
- seatNumbers?: number[],
- packFlag?: boolean,
- ) => void;
- showFreedom: () => void;
- close: () => void;
- }
- interface LotteryItem {
- id: string;
- name: string;
- cover: string;
- level: string;
- spu?: { marketPrice: number };
- }
- // 自由购买数量选项
- const FREEDOM_NUMS = [10, 20, 30, 40, 50];
- export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
- ({ data, poolId, onSuccess, boxNumber }, ref) => {
- const router = useRouter();
- const lotteryResultRef = useRef<LotteryResultModalRef>(null);
- const [visible, setVisible] = useState(false);
- const [num, setNum] = useState(1);
- const [checked, setChecked] = useState(true);
- const [loading, setLoading] = useState(false);
- const [freedomNum, setFreedomNum] = useState(10);
- const [freedomSelectVisible, setFreedomSelectVisible] = useState(false);
- // 预览数据
- const [coin, setCoin] = useState<number | null>(null);
- const [couponAmount, setCouponAmount] = useState<number | null>(null);
- const [lastPrice, setLastPrice] = useState<number | null>(null);
- const [magic, setMagic] = useState<any>(null);
- const [cash, setCash] = useState<any>(null);
- const [cashChecked, setCashChecked] = useState(false);
- // 盒子相关
- const [boxNum, setBoxNum] = useState<string | undefined>(boxNumber);
- const [seatNumbers, setSeatNumbers] = useState<number[] | undefined>();
- const [packFlag, setPackFlag] = useState<boolean | undefined>();
- // 抽奖结果
- const [resultVisible, setResultVisible] = useState(false);
- const [resultLoading, setResultLoading] = useState(false);
- const [resultList, setResultList] = useState<LotteryItem[]>([]);
- // 设置预览数据
- const setPreviewData = (previewData: any) => {
- setCoin(previewData.magicAmount || null);
- setCouponAmount(previewData.couponAmount || null);
- setLastPrice(previewData.paymentAmount);
- setMagic(previewData.magic || null);
- if (
- previewData.cash &&
- previewData.cash.balance > previewData.paymentAmount
- ) {
- setCash(previewData.cash);
- setCashChecked(true);
- } else {
- setCash(previewData.cash || null);
- setCashChecked(false);
- }
- };
- // ... (Imports handled via separate edit or assume existing)
- const [payConfig, setPayConfig] = useState<any>(null);
- const [paymentMethod, setPaymentMethod] = useState<"ALIPAY">("ALIPAY");
- // ...
- useImperativeHandle(ref, () => ({
- show: (
- n: number,
- previewData: any = {},
- bNum?: string,
- seats?: number[],
- pack?: boolean,
- ) => {
- setNum(n);
- setBoxNum(bNum);
- setSeatNumbers(seats);
- setPackFlag(pack || undefined);
- setPreviewData(previewData);
- setVisible(true);
- fetchPayConfig();
- },
- showFreedom: () => {
- setFreedomNum(10);
- setFreedomSelectVisible(true);
- },
- close: () => {
- setVisible(false);
- setFreedomSelectVisible(false);
- setResultVisible(false);
- },
- }));
- const handleFreedomSelect = async (selectedNum: number) => {
- setFreedomSelectVisible(false);
- setLoading(true);
- try {
- const preview = await previewOrder(poolId, selectedNum);
- if (preview) {
- setNum(selectedNum);
- setFreedomNum(selectedNum);
- setPreviewData(preview);
- setVisible(true);
- fetchPayConfig();
- }
- } catch (error: any) {
- Alert.alert("提示", error?.message || "获取订单信息失败");
- } finally {
- setLoading(false);
- }
- };
- const close = () => {
- setVisible(false);
- setFreedomSelectVisible(false);
- };
- const closeResult = () => {
- setResultVisible(false);
- setResultList([]);
- onSuccess({ tradeNo: "", num });
- };
- const fetchPayConfig = async () => {
- try {
- const res = await import("@/services/user").then((m) =>
- m.getParamConfig("wxpay_alipay"),
- );
- if (res && res.data) {
- setPayConfig(JSON.parse(res.data));
- }
- } catch (e) {
- console.log("Fetch Pay Config Error", e);
- }
- };
- // ...
- const pay = async () => {
- if (loading) return;
- if (!checked) {
- Alert.alert("提示", "请同意《宝箱服务协议》");
- return;
- }
- setLoading(true);
- try {
- let paymentType = "";
- // Prioritize Wallet if checked
- if (cashChecked) {
- paymentType = "WALLET";
- } else {
- // 默认只支持支付宝
- paymentType = "ALIPAY_APP";
- }
- const payNum = packFlag ? 1 : num;
- console.log("Submit Order Params:", {
- poolId,
- quantity: payNum,
- paymentType,
- boxNum,
- seatNumbers,
- packFlag,
- payConfig,
- });
- const res = await applyOrder(
- poolId,
- payNum,
- paymentType,
- boxNum,
- seatNumbers,
- packFlag,
- );
- console.log("Apply Order Result:", res);
- if (!res) {
- Alert.alert("提示", "订单创建失败");
- return;
- }
- if (res.paySuccess) {
- // Direct Success (Wallet)
- handleSuccess(res.bizTradeNo || res.tradeNo);
- } else if (res.payInfo) {
- // Handle Native Payment
- handleNativePay(
- res.payInfo,
- paymentType,
- res.bizTradeNo || res.tradeNo,
- );
- } else {
- Alert.alert("提示", res?.message || "支付失败,请重试");
- }
- } catch (error: any) {
- Alert.alert("支付失败", error?.message || "请稍后重试");
- } finally {
- setLoading(false);
- }
- };
- const [verifyLoading, setVerifyLoading] = useState(false);
- const isNavigatingRef = useRef(false);
- const handleSuccess = (tradeNo: string, inventoryList?: any[]) => {
- setVerifyLoading(false);
- setVisible(false);
- if (isNavigatingRef.current) return;
- isNavigatingRef.current = true;
- router.replace({
- pathname: "/treasure-hunt/happy-spin" as any,
- params: {
- tradeNo,
- num,
- poolId,
- ...(inventoryList
- ? { prefetchedResults: JSON.stringify(inventoryList) }
- : {}),
- },
- });
- onSuccess({ tradeNo, num });
- };
- const handleNativePay = async (
- payInfo: string,
- type: string,
- tradeNo: string,
- ) => {
- if (type === "ALIPAY" || type.includes("ALIPAY")) {
- let appStateSub: any = null;
- let isResolved = false;
- let pollingStarted = false;
- try {
- Alipay.setAlipayScheme("alipay2021005175632205");
- // Watch for app returning to foreground as a fallback
- appStateSub = AppState.addEventListener(
- "change",
- async (nextAppState) => {
- if (nextAppState === "active" && !isResolved && !pollingStarted) {
- pollingStarted = true;
- const loadingTimer = setTimeout(() => {
- if (!isResolved) setVerifyLoading(true);
- }, 800);
- let attempts = 0;
- const pollInterval = setInterval(async () => {
- if (isResolved) {
- clearInterval(pollInterval);
- return;
- }
- attempts++;
- try {
- const res = await getApplyResult(tradeNo);
- // 关键修复:await 返回后再次检查 isResolved,防止竞态
- if (isResolved) {
- clearInterval(pollInterval);
- return;
- }
- if (
- res?.paySuccess ||
- (res?.inventoryList && res.inventoryList.length > 0)
- ) {
- isResolved = true;
- clearInterval(pollInterval);
- clearTimeout(loadingTimer);
- setVerifyLoading(false);
- handleSuccess(tradeNo, res?.inventoryList);
- }
- } catch (e) {
- console.log("Fallback poll failed", e);
- }
- if (attempts >= 15) {
- clearInterval(pollInterval);
- clearTimeout(loadingTimer);
- setVerifyLoading(false);
- }
- }, 300);
- }
- },
- );
- const result = await Alipay.pay(payInfo);
- if (isResolved) return; // if fallback already worked
- isResolved = true;
- setVerifyLoading(false);
- console.log("Alipay Result:", result);
- const status = result?.resultStatus;
- if (status === "9000") {
- handleSuccess(tradeNo);
- } else if (status === "6001") {
- Alert.alert("提示", "用户取消支付");
- } else {
- // Also poll server just in case the status is wrong but it succeeded
- try {
- setVerifyLoading(true);
- const res = await getApplyResult(tradeNo);
- if (
- res?.paySuccess ||
- (res?.inventoryList && res.inventoryList.length > 0)
- ) {
- handleSuccess(tradeNo);
- return;
- }
- } catch (ignore) {
- } finally {
- setVerifyLoading(false);
- }
- Alert.alert("支付中断", `状态码: ${status}`);
- }
- } catch (e: any) {
- if (isResolved) return;
- isResolved = true;
- setVerifyLoading(false);
- console.log("Alipay Error:", e);
- Alert.alert("支付异常", e.message || "调用支付宝失败");
- } finally {
- setVerifyLoading(false);
- if (appStateSub) {
- appStateSub.remove();
- }
- }
- } else {
- Alert.alert("提示", "微信支付暂未实现");
- }
- };
- // 获取抽奖结果(10发以下用弹窗)
- const fetchLotteryResult = async (tradeNo: string) => {
- setResultLoading(true);
- setResultVisible(true);
- setResultList([]);
- let attempts = 0;
- const maxAttempts = 5;
- const poll = async () => {
- try {
- const res = await getApplyResult(tradeNo);
- if (res?.inventoryList && res.inventoryList.length > 0) {
- setResultList(res.inventoryList);
- setResultLoading(false);
- // 不在这里调用 onSuccess,等用户关闭弹窗时再调用
- } else if (attempts < maxAttempts) {
- attempts++;
- setTimeout(poll, 1000);
- } else {
- setResultLoading(false);
- Alert.alert("提示", "获取结果超时,请在仓库中查看");
- }
- } catch {
- if (attempts < maxAttempts) {
- attempts++;
- setTimeout(poll, 1000);
- } else {
- setResultLoading(false);
- Alert.alert("提示", "获取结果失败,请在仓库中查看");
- }
- }
- };
- poll();
- };
- const displayPrice = lastPrice ?? (data?.price || 0) * num;
- return (
- <>
- {/* 10发以上的全屏抽奖结果弹窗 */}
- <LotteryResultModal
- ref={lotteryResultRef}
- onClose={() => {
- // 抽奖结果弹窗关闭后刷新数据
- onSuccess({ tradeNo: "", num });
- }}
- onGoStore={() => {
- onSuccess({ tradeNo: "", num });
- router.replace("/cloud-warehouse" as any);
- }}
- />
- {/* 自由购买数量选择弹窗 */}
- <Modal
- visible={freedomSelectVisible}
- transparent
- animationType="fade"
- onRequestClose={() => setFreedomSelectVisible(false)}
- >
- <View style={styles.overlay}>
- <TouchableOpacity
- style={styles.mask}
- activeOpacity={1}
- onPress={() => setFreedomSelectVisible(false)}
- />
- <View style={styles.freedomContainer}>
- <View style={styles.header}>
- <Text style={styles.title}>购买多盒</Text>
- <TouchableOpacity
- onPress={() => setFreedomSelectVisible(false)}
- style={styles.closeBtn}
- >
- <Text style={styles.closeText}>×</Text>
- </TouchableOpacity>
- </View>
- <View style={styles.freedomContent}>
- <View style={styles.freedomBtnList}>
- {FREEDOM_NUMS.map((item) => (
- <TouchableOpacity
- key={item}
- style={[
- styles.freedomBtn,
- freedomNum === item && styles.freedomBtnActive,
- ]}
- onPress={() => setFreedomNum(item)}
- >
- <Text
- style={[
- styles.freedomBtnText,
- freedomNum === item && styles.freedomBtnTextActive,
- ]}
- >
- {item}
- <Text style={styles.freedomUnit}>盒</Text>
- </Text>
- {freedomNum === item && (
- <Text style={styles.checkIcon}>✓</Text>
- )}
- </TouchableOpacity>
- ))}
- </View>
- <TouchableOpacity
- style={[
- styles.freedomSubmitBtn,
- loading && styles.payBtnDisabled,
- ]}
- onPress={() => handleFreedomSelect(freedomNum)}
- disabled={loading}
- >
- {loading ? (
- <ActivityIndicator color="#fff" size="small" />
- ) : (
- <Text style={styles.freedomSubmitText}>
- 确认 ¥{(data?.price || 0) * freedomNum}
- </Text>
- )}
- </TouchableOpacity>
- </View>
- </View>
- </View>
- </Modal>
- {/* 支付确认弹窗 */}
- <Modal
- visible={visible}
- transparent
- animationType="slide"
- onRequestClose={close}
- >
- <View style={styles.overlay}>
- <TouchableOpacity
- style={styles.mask}
- activeOpacity={1}
- onPress={close}
- />
- <View style={styles.container}>
- <View style={styles.header}>
- <Text style={styles.title}>{data?.name}</Text>
- <TouchableOpacity onPress={close} style={styles.closeBtn}>
- <Text style={styles.closeText}>×</Text>
- </TouchableOpacity>
- </View>
- <View style={styles.content}>
- <View style={styles.row}>
- <Text style={styles.label}>购买件数</Text>
- <Text style={styles.priceText}>
- ¥{data?.price} x {num}
- </Text>
- </View>
- <View style={styles.row}>
- <Text style={styles.label}>优惠券</Text>
- <Text
- style={[
- styles.valueText,
- couponAmount ? styles.themeColor : {},
- ]}
- >
- {couponAmount
- ? `已使用优惠¥${couponAmount}`
- : "暂无优惠券可选"}
- </Text>
- </View>
- {magic && magic.balance > 0 && (
- <View style={styles.row}>
- <View style={styles.rowLeft}>
- <Text style={styles.label}>果实</Text>
- <Text style={styles.balanceText}>
- (剩余:{magic.balance})
- </Text>
- </View>
- <Text style={styles.balanceText}>
- 已抵扣 <Text style={styles.themeColor}>¥{coin || 0}</Text>
- </Text>
- </View>
- )}
- {cash && (
- <View style={styles.row}>
- <View style={styles.rowLeft}>
- <Text style={styles.label}>钱包支付</Text>
- <Text style={styles.themeColor}>
- (余额:¥{cash.balance})
- </Text>
- </View>
- <TouchableOpacity
- style={[styles.radio, cashChecked && styles.radioChecked]}
- onPress={() => setCashChecked(!cashChecked)}
- >
- {cashChecked && <View style={styles.radioInner} />}
- </TouchableOpacity>
- </View>
- )}
- {/* Payment Methods Section */}
- {(!cashChecked ||
- (cash &&
- cash.balance < (lastPrice || (data?.price || 0) * num))) &&
- payConfig ? (
- <View style={styles.paymentSection}>
- <Text style={styles.sectionTitle}>支付方式</Text>
- {payConfig?.alipay?.enabled ? (
- <TouchableOpacity
- style={styles.payOption}
- onPress={() => setPaymentMethod("ALIPAY")}
- >
- <View style={styles.rowLeft}>
- <Text style={styles.payLabel}>支付宝支付</Text>
- </View>
- <View
- style={[
- styles.radio,
- paymentMethod === "ALIPAY" && styles.radioChecked,
- ]}
- >
- {paymentMethod === "ALIPAY" ? (
- <View style={styles.radioInner} />
- ) : null}
- </View>
- </TouchableOpacity>
- ) : null}
- </View>
- ) : null}
- <View style={styles.agreementRow}>
- <View style={styles.agreementLeft}>
- <Text style={styles.agreementText}>
- 我已满18周岁,已阅读并同意
- <Text style={styles.link}>《宝箱服务协议》</Text>
- </Text>
- <Text style={styles.tips}>
- 宝箱商品存在概率性,请谨慎消费
- </Text>
- </View>
- <TouchableOpacity
- style={[styles.radio, checked && styles.radioChecked]}
- onPress={() => setChecked(!checked)}
- >
- {checked && <View style={styles.radioInner} />}
- </TouchableOpacity>
- </View>
- </View>
- <View style={styles.footer}>
- <View style={styles.priceInfo}>
- <Text style={styles.totalLabel}>实付:</Text>
- <Text style={styles.totalPrice}>
- ¥{displayPrice.toFixed(2)}
- </Text>
- </View>
- <TouchableOpacity
- style={[styles.payBtn, loading && styles.payBtnDisabled]}
- onPress={pay}
- disabled={loading}
- >
- {loading ? (
- <ActivityIndicator color="#fff" size="small" />
- ) : (
- <Text style={styles.payBtnText}>立即支付</Text>
- )}
- </TouchableOpacity>
- </View>
- </View>
- </View>
- </Modal>
- {/* 抽奖结果弹窗 */}
- <Modal
- visible={resultVisible}
- transparent
- animationType="fade"
- onRequestClose={closeResult}
- >
- <View style={styles.resultOverlay}>
- <View style={styles.resultContainer}>
- <View style={styles.resultHeader}>
- <Text style={styles.resultTitle}>🎉 恭喜您获得 🎉</Text>
- <TouchableOpacity
- onPress={closeResult}
- style={styles.resultCloseBtn}
- >
- <Text style={styles.closeText}>×</Text>
- </TouchableOpacity>
- </View>
- {resultLoading ? (
- <View style={styles.resultLoading}>
- <ActivityIndicator size="large" color="#ff9600" />
- <Text style={styles.resultLoadingText}>正在开启宝箱...</Text>
- </View>
- ) : (
- <ScrollView
- style={styles.resultScroll}
- showsVerticalScrollIndicator={false}
- >
- <View style={styles.resultList}>
- {resultList.map((item, index) => (
- <View key={item.id || index} style={styles.resultItem}>
- <View
- style={[
- styles.levelBadge,
- {
- backgroundColor:
- LEVEL_MAP[item.level]?.color || "#666",
- },
- ]}
- >
- <Text style={styles.levelText}>
- {LEVEL_MAP[item.level]?.title || item.level}
- </Text>
- </View>
- <View style={styles.resultImageBox}>
- <Image
- source={{ uri: item.cover }}
- style={styles.resultImage}
- contentFit="contain"
- />
- </View>
- <Text style={styles.resultName} numberOfLines={2}>
- {item.name}
- </Text>
- {item.spu?.marketPrice && (
- <Text style={styles.resultPrice}>
- 参考价:¥{item.spu.marketPrice}
- </Text>
- )}
- </View>
- ))}
- </View>
- </ScrollView>
- )}
- <View style={styles.resultFooter}>
- <TouchableOpacity
- style={styles.resultBtn}
- onPress={closeResult}
- >
- <Text style={styles.resultBtnText}>继续抽奖</Text>
- </TouchableOpacity>
- </View>
- </View>
- </View>
- </Modal>
- {/* 支付结果轮询确认加载中 */}
- <Modal
- visible={verifyLoading}
- transparent
- animationType="fade"
- onRequestClose={() => {}}
- >
- <View style={styles.verifyLoadingOverlay}>
- <View style={styles.verifyLoadingBox}>
- <ActivityIndicator size="large" color="#ff9600" />
- <Text style={styles.verifyLoadingText}>正在确认支付结果...</Text>
- </View>
- </View>
- </Modal>
- </>
- );
- },
- );
- const styles = StyleSheet.create({
- verifyLoadingOverlay: {
- ...StyleSheet.absoluteFillObject,
- backgroundColor: "rgba(0,0,0,0.4)",
- justifyContent: "center",
- alignItems: "center",
- zIndex: 9999,
- },
- verifyLoadingBox: {
- backgroundColor: "rgba(0,0,0,0.8)",
- padding: 20,
- borderRadius: 12,
- alignItems: "center",
- },
- verifyLoadingText: {
- color: "#fff",
- marginTop: 10,
- fontSize: 14,
- },
- overlay: {
- flex: 1,
- backgroundColor: "rgba(0,0,0,0.5)",
- justifyContent: "flex-end",
- },
- mask: { flex: 1 },
- container: {
- backgroundColor: "#fff",
- borderTopLeftRadius: 20,
- borderTopRightRadius: 20,
- paddingBottom: 34,
- },
- header: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "center",
- padding: 15,
- borderBottomWidth: 1,
- borderBottomColor: "#eee",
- position: "relative",
- },
- title: { fontSize: 16, fontWeight: "600", color: "#333" },
- closeBtn: { position: "absolute", right: 15, top: 10 },
- closeText: { fontSize: 24, color: "#999" },
- content: { padding: 15 },
- row: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- paddingVertical: 10,
- },
- rowLeft: { flexDirection: "row", alignItems: "center", flex: 1 },
- label: { fontSize: 14, color: "#333" },
- priceText: { fontSize: 14, color: "#ff9600", fontWeight: "600" },
- valueText: { fontSize: 12, color: "#999" },
- themeColor: { color: "#ff9600" },
- balanceText: { fontSize: 12, color: "#999", marginLeft: 5 },
- radio: {
- width: 20,
- height: 20,
- borderRadius: 10,
- borderWidth: 2,
- borderColor: "#ddd",
- justifyContent: "center",
- alignItems: "center",
- },
- radioChecked: { borderColor: "#ff9600" },
- radioInner: {
- width: 10,
- height: 10,
- borderRadius: 5,
- backgroundColor: "#ff9600",
- },
- agreementRow: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "flex-start",
- paddingVertical: 10,
- marginTop: 10,
- },
- agreementLeft: { flex: 1, marginRight: 10 },
- agreementText: { fontSize: 12, color: "#333", lineHeight: 18 },
- link: { color: "#ff9600" },
- tips: { fontSize: 11, color: "#999", marginTop: 5 },
- footer: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
- paddingHorizontal: 15,
- paddingTop: 15,
- borderTopWidth: 1,
- borderTopColor: "#eee",
- },
- priceInfo: { flexDirection: "row", alignItems: "center" },
- totalLabel: { fontSize: 14, color: "#333" },
- totalPrice: { fontSize: 20, color: "#ff9600", fontWeight: "bold" },
- payBtn: {
- backgroundColor: "#ff9600",
- paddingHorizontal: 30,
- paddingVertical: 12,
- borderRadius: 25,
- },
- payBtnDisabled: { opacity: 0.6 },
- payBtnText: { color: "#fff", fontSize: 16, fontWeight: "600" },
- // 自由购买弹窗
- freedomContainer: {
- backgroundColor: "#fff",
- borderTopLeftRadius: 20,
- borderTopRightRadius: 20,
- paddingBottom: 34,
- },
- freedomContent: { padding: 20 },
- freedomBtnList: {
- flexDirection: "row",
- flexWrap: "wrap",
- justifyContent: "space-between",
- },
- freedomBtn: {
- width: "48%",
- height: 50,
- backgroundColor: "#fff",
- borderRadius: 8,
- justifyContent: "center",
- alignItems: "center",
- marginBottom: 12,
- borderWidth: 1,
- borderColor: "#eee",
- position: "relative",
- },
- freedomBtnActive: { backgroundColor: "#F1423D", borderColor: "#F1423D" },
- freedomBtnText: { fontSize: 18, fontWeight: "bold", color: "#333" },
- freedomBtnTextActive: { color: "#fff" },
- freedomUnit: { fontSize: 12, fontWeight: "500" },
- checkIcon: {
- position: "absolute",
- bottom: 0,
- right: 0,
- backgroundColor: "#fff",
- color: "#F1423D",
- fontSize: 12,
- paddingHorizontal: 6,
- paddingVertical: 2,
- borderTopLeftRadius: 8,
- borderBottomRightRadius: 8,
- },
- freedomSubmitBtn: {
- backgroundColor: "#ff9600",
- height: 50,
- borderRadius: 25,
- justifyContent: "center",
- alignItems: "center",
- marginTop: 20,
- },
- freedomSubmitText: { color: "#fff", fontSize: 16, fontWeight: "600" },
- // 抽奖结果弹窗
- resultOverlay: {
- flex: 1,
- backgroundColor: "rgba(0,0,0,0.7)",
- justifyContent: "center",
- alignItems: "center",
- padding: 20,
- },
- resultContainer: {
- width: "100%",
- maxHeight: "80%",
- backgroundColor: "#fff",
- borderRadius: 16,
- overflow: "hidden",
- },
- resultHeader: {
- alignItems: "center",
- padding: 20,
- backgroundColor: "#ff9600",
- position: "relative",
- },
- resultTitle: {
- fontSize: 20,
- fontWeight: "bold",
- color: "#fff",
- },
- resultCloseBtn: {
- position: "absolute",
- right: 15,
- top: 15,
- width: 30,
- height: 30,
- backgroundColor: "rgba(255,255,255,0.3)",
- borderRadius: 15,
- justifyContent: "center",
- alignItems: "center",
- },
- resultLoading: {
- padding: 60,
- alignItems: "center",
- },
- resultLoadingText: {
- marginTop: 15,
- fontSize: 14,
- color: "#666",
- },
- resultScroll: {
- maxHeight: 400,
- },
- resultList: {
- flexDirection: "row",
- flexWrap: "wrap",
- padding: 10,
- justifyContent: "space-between",
- },
- resultItem: {
- width: (SCREEN_WIDTH - 80) / 2,
- backgroundColor: "#f9f9f9",
- borderRadius: 10,
- padding: 10,
- marginBottom: 10,
- alignItems: "center",
- },
- levelBadge: {
- paddingHorizontal: 10,
- paddingVertical: 3,
- borderRadius: 10,
- marginBottom: 8,
- },
- levelText: {
- color: "#fff",
- fontSize: 11,
- fontWeight: "bold",
- },
- resultImageBox: {
- width: "100%",
- aspectRatio: 1,
- backgroundColor: "#fff",
- borderRadius: 8,
- overflow: "hidden",
- },
- resultImage: {
- width: "100%",
- height: "100%",
- },
- resultName: {
- fontSize: 12,
- color: "#333",
- textAlign: "center",
- marginTop: 8,
- lineHeight: 16,
- },
- resultPrice: {
- fontSize: 10,
- color: "#999",
- marginTop: 4,
- },
- resultFooter: {
- padding: 15,
- borderTopWidth: 1,
- borderTopColor: "#eee",
- },
- resultBtn: {
- backgroundColor: "#ff9600",
- height: 46,
- borderRadius: 23,
- justifyContent: "center",
- alignItems: "center",
- },
- resultBtnText: {
- color: "#fff",
- fontSize: 16,
- fontWeight: "600",
- },
- paymentSection: {
- marginTop: 20,
- borderTopWidth: 1,
- borderTopColor: "#f0f0f0",
- paddingTop: 10,
- },
- sectionTitle: {
- fontSize: 14,
- fontWeight: "bold",
- marginBottom: 10,
- color: "#333",
- },
- payOption: {
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- paddingVertical: 12,
- borderBottomWidth: 1,
- borderBottomColor: "#f9f9f9",
- },
- payLabel: {
- fontSize: 14,
- color: "#333",
- marginLeft: 10,
- },
- });
|