| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055 |
- 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: "/lottery" 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("/store" 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,
- },
- });
|