import { Image } from 'expo-image'; import { useRouter } from 'expo-router'; import React, { useCallback, useEffect, useState } from 'react'; import { Alert, ImageBackground, ScrollView, StatusBar, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Images } from '@/constants/images'; import services from '@/services/api'; interface DayInfo { text: string; date: string; isFutureDate: boolean; istoday: boolean; isSignIn: boolean; } interface RecordItem { id: string; credit: number; createTime: string; } export default function IntegralScreen() { const router = useRouter(); const insets = useSafeAreaInsets(); const [integral, setIntegral] = useState(0); const [daysIntegral, setDaysIntegral] = useState(0); const [istodaySignIn, setIstodaySignIn] = useState(false); const [record, setRecord] = useState([]); const [dataInfo, setDataInfo] = useState([]); const [todayIntegral, setTodayIntegral] = useState(1); const [tomorrowIntegral, setTomorrowIntegral] = useState(1); const [isTooltip, setIsTooltip] = useState(false); // 格式化数字为两位 const padWithZeros = (number: number, length: number) => { return String(number).padStart(length, '0'); }; // 设置日期数据 const setDate = useCallback(() => { const now = new Date(); const dataInfoArr: DayInfo[] = []; // 前4天 for (let i = -4; i <= 0; i++) { const date = new Date(now); date.setDate(date.getDate() + i); const m = date.getMonth() + 1; const d = date.getDate(); const y = date.getFullYear(); dataInfoArr.push({ text: `${m}.${d}`, date: `${y}-${padWithZeros(m, 2)}-${padWithZeros(d, 2)}`, isFutureDate: false, istoday: i === 0, isSignIn: false, }); } // 明天 const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); const tm = tomorrow.getMonth() + 1; const td = tomorrow.getDate(); const ty = tomorrow.getFullYear(); dataInfoArr.push({ text: `${tm}.${td}`, date: `${ty}-${padWithZeros(tm, 2)}-${padWithZeros(td, 2)}`, isFutureDate: true, istoday: false, isSignIn: false, }); setDataInfo(dataInfoArr); return dataInfoArr; }, []); // 获取积分信息 const getInfo = useCallback(async () => { try { // Use wallet service info instead of user service getWalletInfo const info = await services.wallet.info('USER_CREDIT'); setIntegral(info?.balance || 0); } catch (error) { console.error('获取积分失败:', error); } }, []); // 获取积分规则 const showRule = async () => { try { const res = await services.user.getParamConfig('credit_sign'); if (res && res.data) { Alert.alert('积分规则', res.data.replace(/<[^>]+>/g, '')); // Strip HTML tags for Alert } } catch (error) { console.error('获取规则失败:', error); } }; // 获取签到记录 const getData = useCallback(async (dateInfo: DayInfo[]) => { try { const fourDaysAgo = new Date(); fourDaysAgo.setDate(fourDaysAgo.getDate() - 4); const m = fourDaysAgo.getMonth() + 1; const d = fourDaysAgo.getDate(); const y = fourDaysAgo.getFullYear(); const param = { createTime: `${y}-${padWithZeros(m, 2)}-${padWithZeros(d, 2)}`, }; const res = await services.user.getCreditRecord(param); const recordData = res?.data || []; setRecord(recordData); // 今天日期 const now = new Date(); const todayStr = `${now.getFullYear()}-${padWithZeros(now.getMonth() + 1, 2)}-${padWithZeros(now.getDate(), 2)}`; // 更新签到状态 const updatedDataInfo = dateInfo.map(item => { const newItem = { ...item }; // 检查是否已签到 for (const rec of recordData) { const createTime = rec.createTime?.slice(0, 10); if (createTime === item.date) { newItem.isSignIn = true; if (item.date === todayStr) { setIstodaySignIn(true); } } } return newItem; }); setDataInfo(updatedDataInfo); // 计算签到积分 let total = 0; for (const rec of recordData) { if (rec.credit > 0) { total += rec.credit; } } setDaysIntegral(total); // 计算今天和明天的积分 if (recordData.length > 0) { const lastCredit = recordData[0]?.credit || 1; const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); const yesterdayStr = `${yesterday.getFullYear()}-${padWithZeros(yesterday.getMonth() + 1, 2)}-${padWithZeros(yesterday.getDate(), 2)}`; const lastRecordDate = recordData[0]?.createTime?.slice(0, 10); if (yesterdayStr === lastRecordDate) { setTodayIntegral(lastCredit + 1); setTomorrowIntegral(lastCredit + 2); } else { setTodayIntegral(lastCredit); setTomorrowIntegral(Math.min(lastCredit + 1, 7)); } } } catch (error) { console.error('获取签到记录失败:', error); } }, []); // 初始化 useEffect(() => { const dateInfo = setDate(); getInfo(); getData(dateInfo); }, [setDate, getInfo, getData]); // 签到 const handleSignIn = async () => { if (istodaySignIn) { Alert.alert('提示', '今天已签到'); return; } try { const res = await services.user.signIn(); if (res?.code === 0) { Alert.alert('提示', '签到成功'); setIstodaySignIn(true); // Optimistic update for UI const optimisticDateInfo = dataInfo.map(item => { if (item.istoday) { return { ...item, isSignIn: true }; } return item; }); setDataInfo(optimisticDateInfo); // Delay fetch to ensure backend data consistency setTimeout(() => { const dateInfo = setDate(); getInfo(); getData(dateInfo); }, 500); } else { Alert.alert('提示', res?.msg || '签到失败'); } } catch (error) { console.error('签到失败:', error); Alert.alert('提示', '签到失败'); } }; const handleBack = () => { router.back(); }; return ( {/* 顶部导航 */} 我的积分 {/* 头部积分展示 */} {integral} setIsTooltip(!isTooltip)} > 所有积分 {isTooltip && ( 消费积分与签到积分总和 )} 今日已获得{istodaySignIn ? todayIntegral : 0}积分 积分规则 {/* 签到面板 */} 签到领积分 {istodaySignIn ? '今日已签到' : '今日未签到'} 已签到{record.filter(r => r.credit > 0).length}天,共获得{daysIntegral}积分 {/* 签到进度 */} {[1, 2, 3, 4, 5].map((_, index) => ( ))} {dataInfo.map((item, index) => ( {item.istoday && !istodaySignIn ? ( +{todayIntegral} ) : item.isFutureDate ? ( +{tomorrowIntegral} ) : item.isSignIn ? ( ) : ( )} {item.istoday ? '今天' : item.text} ))} {/* 积分明细 */} 积分明细 {record.map((item, index) => ( {item.credit > 0 ? '积分签到获得' : '大转盘消费'} {item.createTime} 0 ? styles.creditPositive : styles.creditNegative ]}> {item.credit > 0 ? '+' : ''}{item.credit} ))} {record.length === 0 && ( 暂无积分记录 )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F8FAFB', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 10, height: 80, backgroundColor: 'transparent', position: 'absolute', top: 0, left: 0, right: 0, zIndex: 100, }, backBtn: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center', }, backIcon: { fontSize: 32, color: '#000', fontWeight: 'bold', }, title: { fontSize: 15, fontWeight: 'bold', color: '#000', }, placeholder: { width: 40, }, scrollView: { flex: 1, }, headerBg: { width: '100%', height: 283, paddingTop: 100, alignItems: 'center', position: 'relative', }, integralNum: { fontSize: 48, fontWeight: '400', color: '#5B460F', textAlign: 'center', }, allBox: { flexDirection: 'row', alignItems: 'center', marginTop: 7, marginBottom: 12, position: 'relative', }, allText: { fontSize: 12, color: '#C8B177', }, infoIcon: { width: 16, height: 16, marginLeft: 4, }, tooltip: { position: 'absolute', width: 150, height: 27, top: 15, left: 30, // Adjust execution based on design justifyContent: 'center', alignItems: 'center', zIndex: 999, paddingHorizontal: 5, }, tooltipText: { color: '#fff', fontSize: 10, }, todayBox: { backgroundColor: 'rgba(0,0,0,0.08)', borderRadius: 217, paddingHorizontal: 15, paddingVertical: 5, }, todayText: { fontSize: 12, color: '#8A794F', }, todayNum: { fontWeight: '800', color: '#5B460F', }, ruleBtn: { position: 'absolute', right: 0, top: 130, // Half of 260rpx approx backgroundColor: '#fff', borderTopLeftRadius: 20, borderBottomLeftRadius: 20, paddingHorizontal: 10, paddingVertical: 6, zIndex: 10, }, ruleText: { fontSize: 12, color: '#333', }, content: { paddingHorizontal: 10, marginTop: -50, }, panel: { backgroundColor: '#fff', borderRadius: 15, padding: 12, }, panelTitle: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 10, }, panelTitleLeft: { flexDirection: 'row', alignItems: 'center', }, goldIcon: { width: 24, height: 24, marginRight: 6, }, panelTitleText: { fontSize: 16, fontWeight: '700', color: '#3D3D3D', }, panelTitleRight: { fontSize: 12, color: '#FC7D2E', }, signedText: { color: '#999', }, explain: { fontSize: 12, color: '#999', paddingHorizontal: 10, marginTop: 7, marginBottom: 11, }, highlightText: { color: '#FC7D2E', }, progressBox: { paddingTop: 20, }, lineBox: { flexDirection: 'row', paddingHorizontal: 10, }, lineSection: { flex: 1, }, line: { height: 1, backgroundColor: '#9E9E9E', }, lineOn: { backgroundColor: '#FC7D2E', }, daysBox: { flexDirection: 'row', marginTop: -25, }, dayItem: { flex: 1, alignItems: 'center', }, dayCircleBox: { width: 46, height: 46, justifyContent: 'center', alignItems: 'center', marginBottom: 3, }, dayCircleBg: { width: 46, height: 46, justifyContent: 'center', alignItems: 'center', }, dayCircle: { width: 32, height: 32, borderRadius: 16, justifyContent: 'center', alignItems: 'center', }, todayCircle: { backgroundColor: '#FC7D2E', }, todayCircleText: { color: '#fff', fontSize: 12, fontWeight: 'bold', }, futureCircle: { backgroundColor: '#F4F6F8', borderWidth: 1, borderColor: 'rgba(226,226,226,0.5)', }, futureText: { color: '#505050', fontSize: 12, }, signedCircle: { backgroundColor: '#FFE7C4', }, checkIcon: { color: '#FC7D2E', fontSize: 16, fontWeight: 'bold', }, missedCircle: { backgroundColor: '#F4F6F8', }, missedIcon: { color: '#999', fontSize: 16, }, dayText: { fontSize: 12, color: '#9E9E9E', }, todayDayText: { color: '#FC7D2E', }, listSection: { backgroundColor: '#fff', borderRadius: 15, padding: 12, marginTop: 10, marginBottom: 30, }, listTitle: { fontSize: 16, fontWeight: '700', color: '#3D3D3D', marginBottom: 10, }, listItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.05)', }, listItemLeft: {}, listItemTitle: { fontSize: 14, color: '#333', marginBottom: 3, }, listItemTime: { fontSize: 12, color: '#999', }, listItemCredit: { fontSize: 18, fontWeight: '700', }, creditPositive: { color: '#588CFF', }, creditNegative: { color: '#FC7D2E', }, emptyBox: { paddingVertical: 50, alignItems: 'center', }, emptyText: { fontSize: 14, color: '#999', }, });