bindbox-mini/pages/mine/index.vue

2697 lines
79 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="wrap">
<!-- 头部区域 -->
<view class="header-section">
<view class="user-info">
<image class="avatar" :src="avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="user-meta">
<view class="name-row">
<text class="nickname">{{ nickname || '未登录' }}</text>
<view class="level-badge" v-if="nickname">
<image class="level-icon" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRjZCMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNNiAzSDE4TDIxIDlMOSAyMSAzIDlMNiAzWiIgLz48L3N2Zz4=" mode="aspectFit"></image>
<text class="level-text">Lv1 青铜</text>
</view>
</view>
<view class="progress-container" v-if="nickname">
<view class="progress-bar">
<view class="progress-fill" style="width: 20%;"></view>
</view>
<text class="progress-text">100/5000 升级Lv2</text>
</view>
<view class="userid" v-else>ID: {{ userId || '-' }}</view>
</view>
<view class="join-btn" @click="handleJoin" v-if="!nickname">立即登录</view>
</view>
<!-- 数据统计栏 (Modified: Points / Coupons / Item Cards) -->
<view class="stats-row">
<view class="stat-item" @click="showPointsPopup">
<text class="stat-num">{{ pointsBalance || 0 }}</text>
<text class="stat-label">积分</text>
</view>
<view class="stat-item" @click="showCouponsPopup">
<text class="stat-num">{{ stats.coupon_count || 0 }}</text>
<text class="stat-label">优惠券</text>
</view>
<view class="stat-item" @click="showItemCardsPopup">
<text class="stat-num">{{ stats.item_card_count || 0 }}</text>
<text class="stat-label">道具卡</text>
</view>
</view>
</view>
<!-- 邀请Banner (Relocated & Re-designed) -->
<view class="invite-banner" @click="handleInvite">
<view class="invite-info">
<view class="invite-tag">好礼相送</view>
<view class="invite-title">邀请好友送好礼</view>
<view class="invite-desc">全新版本等你来玩,奖励拿到手软</view>
</view>
<view class="invite-action">
<text class="invite-btn-text">立即邀请</text>
<image class="invite-arrow" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMxQTFBMUEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSc5IDE4bDYtNi02LTYnIC8+PC9zdmc+" mode="aspectFit"></image>
</view>
<image class="invite-bg-icon" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMxQTFBMUEiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjEgMTF2OGEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMnYtOGw5LTYgOSA2eiIgLz48L3N2Zz4=" mode="aspectFit"></image>
</view>
<!-- 我的订单 -->
<view class="card-section">
<view class="section-title">我的订单</view>
<view class="grid-row">
<view class="grid-item" @click="toOrders('pending')">
<image class="grid-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRjZCMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMSA4YTIgMiAwIDAgMC0xLTEuNzNsLTctNGEyIDIgMCAwIDAtMiAwbC03IDRBMiAyIDAgMCAwIDMgOHY4YTIgMiAwIDAgMCAxIDEuNzNsNyA0YTIgMiAwIDAgMCAyIDBsNy00QTIgMiAwIDAgMCAyMSAxNlY4eiIvPjxwYXRoIGQ9Ik0zLjI3IDYuOTZMMTIgMTIuMDFsMTAgLTUuMDUiLz48cGF0aCBkPSJNMTIgMjIuMDhWMTIiLz48L3N2Zz4=" mode="aspectFit"></image>
<text class="grid-label">盒柜</text>
</view>
<view class="grid-item" @click="toOrders('pending')">
<image class="grid-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRjZCMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjEiIHk9IjQiIHdpZHRoPSIyMiIgaGVpZ2h0PSIxNiIgcng9IjIiIHJ5PSIyIj48L3JlY3Q+PGxpbmUgeDE9IjEiIHkxPSIxMCIgeDI9IjIzIiB5Mj0iMTAiPjwvbGluZT48L3N2Zz4=" mode="aspectFit"></image>
<text class="grid-label">待付款</text>
</view>
<view class="grid-item" @click="toOrders('pending')">
<image class="grid-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRjZCMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiAyMmg1LjlhMiAyIDAgMCAwIDEuOS0xLjQybDMuMTMtMTEuMDlhMiAyIDAgMCAwLTEuOS0yLjUxaC00LjE2IiAvPjxwYXRoIGQ9Ik0xOC40MiA5aC02LjMyIiAvPjxwYXRoIGQ9Ik0xNSA2VjNhMSAxIDAgMCAwLTEtMUg2YTEgMSAwIDAgMC0xIDF2MTQiIC8+PHBhdGggZD0iTTkgMTNoNyIgLz48L3N2Zz4=" mode="aspectFit"></image>
<text class="grid-label">待发货</text>
</view>
<view class="grid-item" @click="toOrders('completed')">
<image class="grid-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRjZCMDAiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjEiIHk9IjMiIHdpZHRoPSIxNSIgaGVpZ2h0PSIxMyIgcng9IjIiIHJ5PSIyIj48L3JlY3Q+PHBvbHlsaW5lIHBvaW50cz0iMTYgOCAyMCA4IDIwIDIxIDIgMjEgMiAxNiA2IDE2IiAvPjwvc3ZnPg==" mode="aspectFit"></image>
<text class="grid-label">已发货</text>
</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="card-section">
<view class="grid-menu">
<view class="menu-item" @click="showCouponsPopup">
<image class="menu-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMSA2SDNhMiAyIDAgMCAwLTIgMnY4YTIgMiAwIDAgMCAyIDJoMThhMiAyIDAgMCAwIDItMnYtOGEyIDIgMCAwIDAtMi0yWiIgLz48cGF0aCBkPSJNNiAxMnYtMiIgLz48cGF0aCBkPSJNNiAxNnYtMiIgLz48cGF0aCBkPSJNMTYgNnYxMiIgLz48L3N2Zz4=" mode="aspectFit"></image>
<text class="menu-label">优惠券</text>
</view>
<view class="menu-item" @click="showItemCardsPopup">
<image class="menu-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjMiIHk9IjMiIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgcng9IjIiIHJ5PSIyIj48L3JlY3Q+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iNCIgLz48cGF0aCBkPSJNMjEgMTVsLTUuNDMtMy4yM2EyIDIgMCAwIDAtMS44NCAwbC0yLjMxIDEuMzciIC8+PC9zdmc+" mode="aspectFit"></image>
<text class="menu-label">道具卡</text>
</view>
<view class="menu-item" @click="showTasksPopup">
<image class="menu-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik05IDExbDMgM0wyMiA0Ii8+PHBhdGggZD0iTTIxIDEydjdhMiAyIDAgMCAxLTIgMkg1YTIgMiAwIDAgMS0yLTJWNWEyIDIgMCAwIDEgMi0yaDExIi8+PC9zdmc+" mode="aspectFit"></image>
<text class="menu-label">任务中心</text>
</view>
<view class="menu-item" @click="showInvitesPopup">
<image class="menu-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNyAyMXYtMmE0IDQgMCAwIDAtNC00SDdhNCA0IDAgMCAwLTQgNHYyIj48L3BhdGg+PGNpcmNsZSBjeD0iMTAiIGN5PSI3IiByPSI0Ij48L2NpcmNsZT48cGF0aCBkPSJNMjMgMjF2LTJhNCA0IDAgMCAwLTMtMy44NyI+PC9wYXRoPjxwYXRoIGQ9Ik0xNiAzLjEzYTQgNCAwIDAgMSAwIDcuNzUiPjwvcGF0aD48L3N2Zz4=" mode="aspectFit"></image>
<text class="menu-label">邀请记录</text>
</view>
<view class="menu-item" @click="toAddresses">
<image class="menu-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yMSAxMGMwIDctOSAxMy05IDEzcy05LTYtOS0xM2E5IDkgMCAwIDEgMTggMHoiIC8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMCIgcj0iMyIgLz48L3N2Zz4=" mode="aspectFit"></image>
<text class="menu-label">收货地址</text>
</view>
<view class="menu-item" @click="toHelp">
<image class="menu-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBhdGggZD0iTTkuMDkgOWEzIDMgMCAwIDEgNS44MyAxYzAgMi0zIDMtMyAzIi8+PHBhdGggZD0iTTEyIDE3aC4wMSIvPjwvc3ZnPg==" mode="aspectFit"></image>
<text class="menu-label">帮助中心</text>
</view>
</view>
</view>
<!-- 弹窗组件保持原有逻辑 -->
<!-- 积分明细弹窗 -->
<view class="popup-mask" v-if="pointsVisible" @tap="closePointsPopup">
<view class="popup-content" @tap.stop>
<view class="popup-header">
<text class="popup-title">积分明细</text>
<text class="close-btn" @tap="closePointsPopup">×</text>
</view>
<scroll-view scroll-y class="points-list" @scrolltolower="loadMorePoints">
<view v-if="pointsLoading && pointsList.length === 0" class="status-text">加载中...</view>
<view v-else-if="!pointsList || pointsList.length === 0" class="status-text">暂无积分记录</view>
<view v-for="(item, index) in pointsList" :key="index" class="point-item">
<view class="point-left">
<text class="point-desc">{{ getActionText(item.action) }}</text>
<text class="point-time">{{ formatDate(item.created_at) }}</text>
</view>
<view class="point-right">
<text class="point-amount" :class="{ 'positive': Number(item.points) > 0, 'negative': Number(item.points) < 0 }">
{{ Number(item.points) > 0 ? '+' : '' }}{{ item.points }}
</text>
</view>
</view>
<view v-if="pointsLoading && pointsList.length > 0" class="loading-more">加载更多...</view>
<view v-if="!pointsHasMore && pointsList.length > 0" class="no-more">没有更多了</view>
</scroll-view>
</view>
</view>
<!-- 优惠券弹窗 -->
<view class="popup-mask" v-if="couponsVisible" @tap="closeCouponsPopup">
<view class="popup-content coupon-popup" @tap.stop>
<view class="popup-header">
<text class="popup-title">我的优惠券</text>
<text class="close-btn" @tap="closeCouponsPopup">×</text>
</view>
<view class="popup-tabs coupon-tabs-3">
<view class="popup-tab" :class="{ active: couponsTab === 1 }" @tap="switchCouponsTab(1)">未使用</view>
<view class="popup-tab" :class="{ active: couponsTab === 2 }" @tap="switchCouponsTab(2)">已使用</view>
<view class="popup-tab" :class="{ active: couponsTab === 3 }" @tap="switchCouponsTab(3)">已过期</view>
</view>
<scroll-view scroll-y class="coupon-scroll" @scrolltolower="loadMoreCoupons">
<view v-if="couponsLoading && couponsList.length === 0" class="status-text">加载中...</view>
<view v-else-if="!couponsList || couponsList.length === 0" class="empty-state">
<text class="empty-icon">🎟️</text>
<text class="empty-text">{{ getCouponEmptyText() }}</text>
</view>
<view v-for="(item, index) in couponsList" :key="item.id || index" class="coupon-ticket-v2" :class="getCouponCardClass(item)">
<!-- 左侧金额区域 -->
<view class="coupon-left-v2">
<view class="coupon-remaining">
<text class="coupon-symbol">¥</text>
<text class="coupon-amount-num">{{ formatCouponValue(item.remaining ?? item.amount ?? 0) }}</text>
</view>
<text class="coupon-label">{{ couponsTab === 1 ? '可用' : (couponsTab === 2 ? '已用' : '过期') }}</text>
</view>
<!-- 中间分割线 -->
<view class="coupon-divider-v2">
<view class="divider-notch top"></view>
<view class="divider-dash"></view>
<view class="divider-notch bottom"></view>
</view>
<!-- 右侧信息区域 -->
<view class="coupon-right-v2">
<view class="coupon-header-row">
<text class="coupon-name-v2">{{ item.name || '优惠券' }}</text>
<view class="coupon-original" v-if="item.amount && item.remaining !== undefined && item.remaining !== item.amount">
<text>原值 ¥{{ formatCouponValue(item.amount) }}</text>
</view>
</view>
<text class="coupon-rules">{{ item.rules || '全场通用' }}</text>
<!-- 使用进度条 (仅当有使用记录时显示) -->
<view class="coupon-progress-wrap" v-if="item.amount && item.remaining !== undefined && item.remaining < item.amount">
<view class="coupon-progress-bar">
<view class="coupon-progress-fill" :style="{ width: getCouponUsedPercent(item) + '%' }"></view>
</view>
<text class="coupon-progress-text">已用 {{ formatCouponValue(item.amount - item.remaining) }} ({{ getCouponUsedPercent(item) }}%)</text>
</view>
<view class="coupon-footer-row">
<text class="coupon-expire-v2">{{ formatCouponExpiry(item) }}</text>
<view class="coupon-action-v2" v-if="couponsTab === 1">
<text class="use-btn-v2">去使用</text>
</view>
<view class="coupon-status-v2" v-else>
<text class="status-tag" :class="couponsTab === 2 ? 'used' : 'expired'">{{ couponsTab === 2 ? '已使用' : '已过期' }}</text>
</view>
</view>
<!-- 使用时间 (已使用状态) -->
<text class="coupon-used-time" v-if="couponsTab === 2 && item.used_at">使用时间:{{ formatDateTime(item.used_at) }}</text>
</view>
</view>
<view v-if="couponsLoading && couponsList.length > 0" class="loading-more">加载更多...</view>
<view v-if="!couponsHasMore && couponsList.length > 0" class="no-more">没有更多了</view>
</scroll-view>
</view>
</view>
<!-- 道具卡弹窗 -->
<view class="popup-mask" v-if="itemCardsVisible" @tap="closeItemCardsPopup">
<view class="popup-content item-cards-popup" @tap.stop>
<view class="popup-header">
<text class="popup-title">我的道具卡</text>
<text class="close-btn" @tap="closeItemCardsPopup">×</text>
</view>
<view class="popup-tabs">
<view class="popup-tab" :class="{ active: itemCardsTab === 0 }" @tap="switchItemCardsTab(0)">未使用</view>
<view class="popup-tab" :class="{ active: itemCardsTab === 1 }" @tap="switchItemCardsTab(1)">已使用</view>
</view>
<scroll-view scroll-y class="item-cards-scroll">
<view v-if="itemCardsLoading && itemCardsList.length === 0" class="status-text">加载中...</view>
<view v-else-if="!itemCardsList || itemCardsList.length === 0" class="empty-state">
<text class="empty-icon">🃏</text>
<text class="empty-text">{{ itemCardsTab === 0 ? '暂无可用道具卡' : '暂无使用记录' }}</text>
</view>
<view class="item-cards-grid">
<view v-for="(item, index) in itemCardsList" :key="index" class="item-card" :class="{ 'used': itemCardsTab === 1 }">
<view class="card-icon-wrap">
<text class="card-icon">{{ getCardIcon(item.type || item.name) }}</text>
</view>
<view class="card-info">
<text class="card-name">{{ item.name || item.title || '道具卡' }}</text>
<text class="card-desc">{{ item.description || item.rules || '可在抽奖时使用' }}</text>
<text class="card-use-time" v-if="itemCardsTab === 1 && item.used_at">使用时间:{{ formatDate(item.used_at) }}</text>
</view>
<view class="card-count-badge" v-if="itemCardsTab === 0">
<text class="count-num">×{{ item.remaining ?? item.count ?? 1 }}</text>
</view>
<view class="card-used-badge" v-else>
<text class="used-text">已使用</text>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 任务中心弹窗 -->
<view class="popup-mask" v-if="tasksVisible" @tap="closeTasksPopup">
<view class="popup-content task-center-popup" @tap.stop>
<view class="popup-header">
<text class="popup-title">任务中心</text>
<text class="close-btn" @tap="closeTasksPopup">×</text>
</view>
<!-- 总体进度指示器 -->
<view class="overall-progress">
<view class="progress-circle">
<text class="progress-percent">{{ getOverallProgress() }}%</text>
<text class="progress-label">总完成率</text>
</view>
<view class="progress-stats">
<view class="stat-row">
<view class="stat-dot done"></view>
<text class="stat-text">已完成 {{ tasksList.filter(t => t.status === 2).length }} 个</text>
</view>
<view class="stat-row">
<view class="stat-dot ongoing"></view>
<text class="stat-text">进行中 {{ tasksList.filter(t => t.status === 1).length }} 个</text>
</view>
<view class="stat-row">
<view class="stat-dot waiting"></view>
<text class="stat-text">未开始 {{ tasksList.filter(t => t.status !== 1 && t.status !== 2).length }} 个</text>
</view>
</view>
</view>
<scroll-view scroll-y class="task-scroll" @scrolltolower="loadMoreTasks">
<view v-if="tasksLoading && tasksList.length === 0" class="status-text">加载中...</view>
<view v-else-if="!tasksList || tasksList.length === 0" class="empty-state">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无任务</text>
</view>
<view v-for="(task, idx) in tasksList" :key="task.id || idx" class="task-card-v2" :class="getTaskCardClass(task.status)">
<!-- 左侧状态指示器 -->
<view class="task-status-indicator" :class="getTaskStatusClass(task.status)">
<text class="status-icon">{{ getStatusIcon(task.status) }}</text>
</view>
<view class="task-content">
<view class="task-title-row">
<text class="task-title">{{ task.name || '任务' }}</text>
</view>
<text class="task-desc-v2">{{ task.description || '完成任务获取奖励' }}</text>
<!-- 进度显示 - 始终可见 -->
<view class="task-progress-v2">
<view class="progress-bar-v2">
<view class="progress-fill-v2" :style="{ width: getTaskProgressV2(task) + '%' }"></view>
</view>
<text class="progress-num">{{ getTaskProgressV2(task) }}%</text>
</view>
<!-- 奖励显示 -->
<view class="task-reward-row" v-if="Array.isArray(task.rewards) && task.rewards.length">
<text class="reward-label">奖励:</text>
<view class="reward-items">
<text class="reward-item" v-for="(rw, ri) in task.rewards" :key="ri">
{{ getRewardIcon(rw.reward_type) }} {{ rw.quantity || 0 }}
</text>
</view>
</view>
</view>
<!-- 右侧操作按钮 -->
<view class="task-btn-wrap">
<button class="task-btn" :class="getTaskBtnClass(task.status)" @tap="goToTask(task)">
{{ getTaskBtnText(task.status) }}
</button>
</view>
</view>
<view v-if="tasksLoading && tasksList.length > 0" class="loading-more">加载更多...</view>
<view v-if="!tasksHasMore && tasksList.length > 0" class="no-more">没有更多了</view>
</scroll-view>
</view>
</view>
<!-- 邀请记录弹窗 (Redesigned) -->
<view class="popup-mask" v-if="invitesVisible" @tap="closeInvitesPopup">
<view class="popup-content invites-popup" @tap.stop>
<view class="invites-popup-header">
<text class="invites-title-text"> 我的邀请团 </text>
<view class="invites-close" @tap="closeInvitesPopup">
<image class="invites-close-icon" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM5OTkiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTggNkw2IDE4TTYgNmwxMiAxMiIvPjwvc3ZnPg==" mode="aspectFit"></image>
</view>
</view>
<!-- 头部资产卡片 -->
<view class="invite-prime-card">
<!-- 装饰圆环 -->
<view class="card-deco-circle c1"></view>
<view class="card-deco-circle c2"></view>
<view class="prime-card-content">
<view class="my-code-section" @tap="copyInviteCode">
<text class="code-label">我的邀请码</text>
<view class="code-value-row">
<text class="code-text">{{ inviteCode || '加载中...' }}</text>
<view class="copy-icon-btn">
<text class="copy-text">复制</text>
</view>
</view>
</view>
<view class="card-divider-h"></view>
<view class="stats-mini-row">
<view class="stat-mini-item">
<text class="mini-num">{{ invitesTotal }}</text>
<text class="mini-label">累计邀请 ()</text>
</view>
<view class="stat-mini-sep"></view>
<view class="stat-mini-item">
<text class="mini-num">Lv.1</text>
<text class="mini-label">邀请等级</text>
</view>
</view>
</view>
</view>
<!-- 列表标题 -->
<view class="list-section-header">
<text class="list-title">好友列表</text>
<text class="list-subtitle">邀请好友一起欧气满满</text>
</view>
<scroll-view scroll-y class="invites-scroll-refined" @scrolltolower="loadMoreInvites">
<view v-if="invitesLoading && invitesList.length === 0" class="status-loading-refined">
<view class="loading-spinner"></view>
<text>正在加载好友...</text>
</view>
<view v-else-if="!invitesList || invitesList.length === 0" class="empty-state-refined">
<image class="empty-img-refined" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI0REQURFNSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBhdGggZD0iTTE2IDE2czEtMy00LTctNCA3LTQgNyIvPjxsaW5lIHgxPSI5IiB5MT0iOSIgeDI9IjkuMDEiIHkyPSI5Ii8+PGxpbmUgeDE9IjE1IiB5MT0iOSIgeDI9IjE1LjAxIiB5Mj0iOSIvPjwvc3ZnPg==" mode="aspectFit"></image>
<text class="empty-text-refined">暂无邀请记录</text>
<text class="empty-sub-refined">每邀请一位好友双方都能获得奖励哦</text>
</view>
<view v-for="(item, index) in invitesList" :key="item.id || index" class="friend-card">
<view class="friend-avatar-wrap">
<image class="friend-avatar" :src="item.avatar || '/static/logo.png'" mode="aspectFill"></image>
</view>
<view class="friend-info">
<view class="friend-name-row">
<text class="friend-name">{{ item.nickname || '神秘好友' }}</text>
<view class="friend-tag">新晋</view>
</view>
<text class="friend-meta">ID: {{ item.id }} · 邀请码: {{ item.invite_code || '-' }}</text>
</view>
<view class="friend-status">
<text class="status-dot success"></text>
<text class="status-val">已激活</text>
</view>
</view>
<view v-if="invitesLoading && invitesList.length > 0" class="loading-more-text">加载更多...</view>
<view v-if="!invitesHasMore && invitesList.length > 0" class="no-more-text">- 到底啦 -</view>
</scroll-view>
<!-- 底部悬浮按钮 -->
<view class="popup-footer-action">
<button class="btn-share-gradient" open-type="share">
<text>立即邀请好友</text>
<image class="btn-arrow-icon" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNGRkYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNNSAxMmgyNDBNMTIgNWw3IDctNyA3Ii8+PC9zdmc+" mode="aspectFit"></image>
</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onShow, onLoad, onShareAppMessage } from '@dcloudio/uni-app'
import { getUserStats, getPointsBalance, getUserPoints, getUserCoupons, redeemCoupon, getItemCards, getTasks, getUserInvites } from '../../api/appUser'
const avatar = ref(uni.getStorageSync('avatar') || '')
const nickname = ref(uni.getStorageSync('nickname') || '')
const userId = ref(uni.getStorageSync('user_id') || '')
const phoneNumber = ref(uni.getStorageSync('phone_number') || '')
const inviteCode = ref(uni.getStorageSync('invite_code') || (uni.getStorageSync('user_info') || {}).invite_code || '')
const pointsBalance = ref(uni.getStorageSync('points_balance') || 0)
const stats = ref(uni.getStorageSync('user_stats') || {})
const loading = ref(false)
const error = ref('')
// 积分弹窗相关状态
const pointsVisible = ref(false)
const pointsList = ref([])
const pointsLoading = ref(false)
const pointsPage = ref(1)
const pointsPageSize = ref(20)
const pointsHasMore = ref(true)
// 优惠券弹窗相关状态
const couponsVisible = ref(false)
const couponsTab = ref(1) // 1: 未使用, 2: 已使用, 3: 已过期
const couponsList = ref([])
const couponsLoading = ref(false)
const couponsPage = ref(1)
const couponsHasMore = ref(true)
const redeemCode = ref('')
// 道具卡弹窗相关状态
const itemCardsVisible = ref(false)
const itemCardsList = ref([])
const itemCardsLoading = ref(false)
const itemCardsTab = ref(0) // 0: 未使用, 1: 已使用
const tasksVisible = ref(false)
const tasksList = ref([])
const tasksLoading = ref(false)
const tasksPage = ref(1)
const tasksPageSize = ref(20)
const tasksHasMore = ref(true)
// 邀请记录弹窗相关状态
const invitesVisible = ref(false)
const invitesList = ref([])
const invitesLoading = ref(false)
const invitesPage = ref(1)
const invitesPageSize = ref(20)
const invitesHasMore = ref(true)
const invitesTotal = ref(0)
async function refresh() {
const token = uni.getStorageSync('token')
// 允许未登录状态浏览基本页面结构,用户信息显示“未登录”
if (!token) {
nickname.value = ''
avatar.value = ''
return
}
const user_id = uni.getStorageSync('user_id')
loading.value = true
try {
const s = await getUserStats(user_id)
stats.value = s || {}
uni.setStorageSync('user_stats', stats.value)
const b = await getPointsBalance(user_id)
const balance = b && b.balance !== undefined ? b.balance : b
pointsBalance.value = balance || 0
uni.setStorageSync('points_balance', pointsBalance.value)
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
function handleJoin() {
const token = uni.getStorageSync('token')
if (!token) {
uni.navigateTo({ url: '/pages/login/index' })
} else {
uni.showToast({ title: '已登录', icon: 'none' })
}
}
function signCheckin() {
uni.showToast({ title: '签到功能开发中', icon: 'none' })
}
function toOrders(status) {
const s = status === 'completed' ? 'completed' : 'pending' // 简单映射
uni.navigateTo({ url: `/pages/orders/index?status=${s}` })
}
function toAddresses() {
uni.navigateTo({ url: '/pages/address/index' })
}
function toHelp() {
uni.navigateTo({ url: '/pages/help/index' })
}
function handleInvite() {
// Trigger WeChat share menu for inviting friends
// #ifdef MP-WEIXIN
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
uni.showToast({ title: '点击右上角分享给好友', icon: 'none', duration: 2000 })
// #endif
// #ifndef MP-WEIXIN
uni.showToast({ title: '请点击右上角分享', icon: 'none' })
// #endif
}
// 积分弹窗逻辑
function showPointsPopup() {
pointsVisible.value = true
if (pointsList.value.length === 0) {
pointsPage.value = 1
pointsHasMore.value = true
loadPoints()
}
}
function closePointsPopup() { pointsVisible.value = false }
async function loadPoints() {
if (pointsLoading.value) return
pointsLoading.value = true
const user_id = uni.getStorageSync('user_id')
try {
const res = await getUserPoints(user_id, pointsPage.value, pointsPageSize.value)
let list = []
let total = 0
if (res && Array.isArray(res.list)) { list = res.list; total = res.total || 0 }
else if (res && Array.isArray(res.data)) { list = res.data; total = res.total || 0 }
else if (Array.isArray(res)) { list = res; total = res.length }
if (pointsPage.value === 1) pointsList.value = list
else pointsList.value = [...pointsList.value, ...list]
if (list.length < pointsPageSize.value || (pointsPage.value * pointsPageSize.value >= total && total > 0)) pointsHasMore.value = false
else pointsPage.value += 1
if (list.length === 0 && pointsPage.value === 1) pointsHasMore.value = false
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
pointsLoading.value = false
}
}
function loadMorePoints() { if (pointsHasMore.value && !pointsLoading.value) loadPoints() }
// 优惠券弹窗逻辑
function showCouponsPopup() {
couponsVisible.value = true
couponsTab.value = 1 // 默认显示未使用 (status=1)
redeemCode.value = ''
couponsList.value = []
couponsPage.value = 1
couponsHasMore.value = true
loadCoupons()
}
function closeCouponsPopup() { couponsVisible.value = false }
function switchCouponsTab(status) {
if (couponsTab.value === status) return
couponsTab.value = status
couponsList.value = []
couponsPage.value = 1
couponsHasMore.value = true
loadCoupons()
}
async function loadCoupons() {
if (couponsLoading.value) return
couponsLoading.value = true
const user_id = uni.getStorageSync('user_id')
try {
// status: 1=未使用, 2=已使用, 3=已过期
const res = await getUserCoupons(user_id, couponsTab.value, couponsPage.value, 20)
let list = []
let total = 0
if (res && Array.isArray(res.list)) { list = res.list; total = res.total || 0 }
else if (res && Array.isArray(res.data)) { list = res.data; total = res.total || 0 }
else if (Array.isArray(res)) { list = res; total = res.length }
if (couponsPage.value === 1) couponsList.value = list
else couponsList.value = [...couponsList.value, ...list]
if (list.length < 20 || (total > 0 && couponsList.value.length >= total)) couponsHasMore.value = false
else couponsPage.value += 1
if (list.length === 0 && couponsPage.value === 1) couponsHasMore.value = false
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
couponsLoading.value = false
}
}
function loadMoreCoupons() { if (couponsHasMore.value && !couponsLoading.value) loadCoupons() }
// 优惠券辅助函数
function getCouponEmptyText() {
if (couponsTab.value === 1) return '暂无可用优惠券'
if (couponsTab.value === 2) return '暂无已使用优惠券'
return '暂无已过期优惠券'
}
function getCouponCardClass(item) {
if (couponsTab.value === 3) return 'expired'
if (couponsTab.value === 2) return 'used'
return ''
}
function getCouponUsedPercent(item) {
if (!item.amount || item.remaining === undefined) return 0
const used = item.amount - item.remaining
return Math.round((used / item.amount) * 100)
}
function formatCouponExpiry(item) {
const end = item.valid_end || item.end_time
if (!end) return ''
return `有效期至:${formatDate(end)}`
}
function formatDateTime(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr)
if (isNaN(date.getTime())) return dateStr
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
}
async function handleRedeem() {
if (!redeemCode.value) { uni.showToast({ title: '请输入兑换码', icon: 'none' }); return }
const user_id = uni.getStorageSync('user_id')
try {
await redeemCoupon(user_id, redeemCode.value)
uni.showToast({ title: '兑换成功', icon: 'success' })
redeemCode.value = ''
refresh()
} catch (e) {
uni.showToast({ title: e.message || '兑换失败', icon: 'none' })
}
}
// 道具卡弹窗逻辑
function showItemCardsPopup() {
itemCardsVisible.value = true
itemCardsTab.value = 0
itemCardsList.value = []
loadItemCards()
}
function closeItemCardsPopup() { itemCardsVisible.value = false }
function switchItemCardsTab(index) {
if (itemCardsTab.value === index) return
itemCardsTab.value = index
itemCardsList.value = []
loadItemCards()
}
async function loadItemCards() {
if (itemCardsLoading.value) return
itemCardsLoading.value = true
const user_id = uni.getStorageSync('user_id')
try {
const status = itemCardsTab.value // 0: 未使用, 1: 已使用
const res = await getItemCards(user_id, status)
let list = []
if (res && Array.isArray(res.list)) list = res.list
else if (res && Array.isArray(res.data)) list = res.data
else if (Array.isArray(res)) list = res
itemCardsList.value = list
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
itemCardsLoading.value = false
}
}
function showTasksPopup() {
tasksVisible.value = true
if (tasksList.value.length === 0) {
tasksPage.value = 1
tasksHasMore.value = true
loadTasks()
}
}
function closeTasksPopup() { tasksVisible.value = false }
async function loadTasks() {
if (tasksLoading.value) return
tasksLoading.value = true
try {
const res = await getTasks(tasksPage.value, tasksPageSize.value)
let list = []
let total = 0
if (res && Array.isArray(res.list)) { list = res.list; total = res.total || 0 }
else if (res && Array.isArray(res.data)) { list = res.data; total = res.total || 0 }
else if (Array.isArray(res)) { list = res; total = res.length }
if (tasksPage.value === 1) tasksList.value = list
else tasksList.value = [...tasksList.value, ...list]
if (list.length < tasksPageSize.value || (tasksPage.value * tasksPageSize.value >= total && total > 0)) tasksHasMore.value = false
else tasksPage.value += 1
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
tasksLoading.value = false
}
}
function loadMoreTasks() { if (tasksHasMore.value && !tasksLoading.value) loadTasks() }
// 邀请记录弹窗逻辑
function showInvitesPopup() {
const token = uni.getStorageSync('token')
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none' })
return
}
invitesVisible.value = true
invitesList.value = []
invitesPage.value = 1
invitesHasMore.value = true
loadInvites()
}
function closeInvitesPopup() { invitesVisible.value = false }
async function loadInvites() {
if (invitesLoading.value) return
invitesLoading.value = true
const user_id = uni.getStorageSync('user_id')
try {
const res = await getUserInvites(user_id, invitesPage.value, invitesPageSize.value)
let list = []
let total = 0
if (res && Array.isArray(res.list)) {
list = res.list
total = res.total || 0
} else if (res && Array.isArray(res.data)) {
list = res.data
total = res.total || 0
} else if (Array.isArray(res)) {
list = res
total = res.length
}
invitesTotal.value = total
if (invitesPage.value === 1) invitesList.value = list
else invitesList.value = [...invitesList.value, ...list]
if (list.length < invitesPageSize.value || (invitesPage.value * invitesPageSize.value >= total && total > 0)) {
invitesHasMore.value = false
} else {
invitesPage.value += 1
}
if (list.length === 0 && invitesPage.value === 1) invitesHasMore.value = false
} catch (e) {
console.error('加载邀请记录失败:', e)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
invitesLoading.value = false
}
}
function loadMoreInvites() {
if (invitesHasMore.value && !invitesLoading.value) loadInvites()
}
function copyInviteCode() {
if (!inviteCode.value) {
uni.showToast({ title: '暂无邀请码', icon: 'none' })
return
}
uni.setClipboardData({
data: inviteCode.value,
success: () => {
uni.showToast({ title: '复制成功', icon: 'success' })
},
fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' })
}
})
}
// 辅助函数
function formatCouponValue(value) {
const num = Number(value || 0)
return num.toFixed(0)
}
function getCardIcon(typeOrName) {
const name = String(typeOrName || '').toLowerCase()
if (name.includes('双倍') || name.includes('double')) return '✨'
if (name.includes('保底') || name.includes('guarantee')) return '🛡️'
if (name.includes('折扣') || name.includes('discount')) return '💰'
if (name.includes('免费') || name.includes('free')) return '🎁'
if (name.includes('加成') || name.includes('boost')) return '🚀'
return '🃏'
}
// 任务中心辅助函数
function getTaskIcon(typeOrName) {
const name = String(typeOrName || '').toLowerCase()
if (name.includes('签到') || name.includes('checkin')) return '📅'
if (name.includes('邀请') || name.includes('invite')) return '👥'
if (name.includes('分享') || name.includes('share')) return '📤'
if (name.includes('抽奖') || name.includes('lottery') || name.includes('draw')) return '🎲'
if (name.includes('充值') || name.includes('recharge')) return '💳'
if (name.includes('购买') || name.includes('buy')) return '🛒'
if (name.includes('完善') || name.includes('profile')) return '📝'
if (name.includes('新人') || name.includes('新手')) return '🌟'
return '📋'
}
function getTaskStatusClass(status) {
const n = Number(status)
if (n === 1) return 'status-ongoing'
if (n === 2) return 'status-done'
return 'status-waiting'
}
function getTaskProgress(task) {
if (task.progress !== undefined) return Math.min(100, Math.max(0, task.progress))
if (task.current !== undefined && task.target) {
return Math.min(100, Math.max(0, (task.current / task.target) * 100))
}
return 0
}
function getRewardIcon(rewardType) {
const type = String(rewardType || '').toLowerCase()
if (type.includes('积分') || type.includes('point')) return '💰'
if (type.includes('优惠券') || type.includes('coupon')) return '🎟️'
if (type.includes('道具') || type.includes('card') || type.includes('prop')) return '🃏'
if (type.includes('现金') || type.includes('cash')) return '💵'
return '🎁'
}
function goToTask(task) {
const status = Number(task.status)
// 已完成的任务不跳转
if (status === 2) {
uni.showToast({ title: '任务已完成', icon: 'success' })
return
}
closeTasksPopup()
const name = String(task.name || '').toLowerCase()
const type = String(task.type || '').toLowerCase()
// 根据任务类型跳转到对应页面
if (name.includes('邀请') || type.includes('invite')) {
handleInvite()
} else if (name.includes('签到') || type.includes('checkin')) {
uni.showToast({ title: '签到功能开发中', icon: 'none' })
} else if (name.includes('抽奖') || name.includes('抽取') || type.includes('draw') || type.includes('lottery')) {
uni.switchTab({ url: '/pages/index/index' })
} else if (name.includes('充值') || type.includes('recharge')) {
uni.navigateTo({ url: '/pages/points/index' })
} else if (name.includes('商城') || name.includes('购买') || type.includes('shop') || type.includes('buy')) {
uni.switchTab({ url: '/pages/shop/index' })
} else if (name.includes('完善') || name.includes('资料') || type.includes('profile')) {
uni.showToast({ title: '请完善个人资料', icon: 'none' })
} else {
// 默认跳转到首页
uni.switchTab({ url: '/pages/index/index' })
}
}
// 任务中心新增辅助函数
function getOverallProgress() {
if (!tasksList.value || tasksList.value.length === 0) return 0
const completed = tasksList.value.filter(t => t.status === 2).length
return Math.round((completed / tasksList.value.length) * 100)
}
function getTaskProgressV2(task) {
const status = Number(task.status)
if (status === 2) return 100
if (task.progress !== undefined) return Math.min(100, Math.max(0, Math.round(task.progress)))
if (task.current !== undefined && task.target) {
return Math.min(100, Math.max(0, Math.round((task.current / task.target) * 100)))
}
if (status === 1) return 50 // 进行中默认50%
return 0
}
function getStatusIcon(status) {
const n = Number(status)
if (n === 2) return '✓'
if (n === 1) return '▶'
return '○'
}
function getTaskCardClass(status) {
const n = Number(status)
if (n === 2) return 'task-completed'
if (n === 1) return 'task-ongoing'
return 'task-waiting'
}
function getTaskBtnClass(status) {
const n = Number(status)
if (n === 2) return 'btn-done'
if (n === 1) return 'btn-go'
return 'btn-waiting'
}
function getTaskBtnText(status) {
const n = Number(status)
if (n === 2) return '已完成'
if (n === 1) return '去完成'
return '未开始'
}
function formatDate(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr)
if (isNaN(date.getTime())) return dateStr
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
}
function getActionText(action) {
const map = { 'manual_add': '商品兑换积分', 'manual_sub': '系统扣除', 'register': '注册奖励', 'lottery_cost': '抽奖消耗', 'checkin': '签到奖励' }
return map[action] || action || '积分变动'
}
function formatStatus(s) {
const n = Number(s)
if (n === 1) return '进行中'
if (n === 2) return '已结束'
if (n === 0) return '未开始'
return '未知'
}
onShow(() => {
avatar.value = uni.getStorageSync('avatar') || avatar.value
nickname.value = uni.getStorageSync('nickname') || nickname.value
userId.value = uni.getStorageSync('user_id') || userId.value
refresh()
})
onLoad(() => { refresh() })
onShareAppMessage(() => {
return { title: '邀请你一起来加入', path: `/pages/index/index?invite_code=${inviteCode.value}`, imageUrl: '/static/logo.png' }
})
</script>
<style scoped>
/* Page Wrap - Creamy Background */
.wrap {
min-height: 100vh;
background: #F8F5F2; /* Cream background close to reference */
padding-bottom: 40rpx;
}
/* Header Section */
.header-section {
padding: 40rpx 32rpx 32rpx;
background: #F8F5F2;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 40rpx;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 4rpx solid #FFFFFF;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
background: #eee;
}
.user-meta {
flex: 1;
margin-left: 24rpx;
display: flex;
flex-direction: column;
}
.name-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.nickname {
font-size: 36rpx;
font-weight: 800;
color: #1A1A1A;
margin-right: 16rpx;
}
.level-badge {
display: flex;
align-items: center;
background: #FFF0E5;
padding: 4rpx 12rpx;
border-radius: 999rpx;
}
.level-icon { width: 24rpx; height: 24rpx; margin-right: 4rpx; }
.level-text { font-size: 22rpx; color: #FF6B00; font-weight: 700; }
.progress-container {
width: 100%;
}
.progress-bar {
height: 8rpx;
background: #EAEAEA;
border-radius: 4rpx;
overflow: hidden;
margin-bottom: 8rpx;
}
.progress-fill {
background: #FFD700;
height: 100%;
border-radius: 4rpx;
}
.progress-text {
font-size: 22rpx;
color: #999;
}
.userid {
font-size: 24rpx;
color: #999;
}
.join-btn {
background: #FFD700;
color: #1A1A1A;
font-size: 26rpx;
font-weight: 700;
padding: 12rpx 32rpx;
border-radius: 999rpx;
border: 2rpx solid #1A1A1A;
box-shadow: 2rpx 2rpx 0 #1A1A1A;
}
.join-btn:active { transform: translate(1rpx, 1rpx); box-shadow: none; }
/* Stats Row */
.stats-row {
display: flex;
justify-content: space-between;
padding: 0 32rpx; /* Increased padding */
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.stat-num {
font-size: 36rpx;
font-weight: 800;
color: #1A1A1A;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #888;
}
/* VIP Banner (Removed) */
/* Invite Banner */
.invite-banner {
margin: 0 32rpx 32rpx;
background: linear-gradient(135deg, #FFEFD5 0%, #FFF5E6 50%, #FFFFFF 100%);
border-radius: 24rpx;
padding: 32rpx;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
overflow: hidden;
box-shadow: 0 8rpx 24rpx rgba(0,0,0,0.05);
}
.invite-info { z-index: 2; flex: 1; }
.invite-tag { background: #FF6B00; color: #fff; font-size: 20rpx; padding: 4rpx 12rpx; border-radius: 8rpx; display: inline-block; margin-bottom: 8rpx; }
.invite-title { font-size: 32rpx; font-weight: 800; color: #1A1A1A; margin-bottom: 8rpx; }
.invite-desc { font-size: 24rpx; color: #666; }
.invite-action { display: flex; align-items: center; background: #FFD700; padding: 12rpx 24rpx; border-radius: 999rpx; z-index: 2; box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.4); }
.invite-btn-text { font-size: 24rpx; font-weight: 700; color: #1A1A1A; margin-right: 4rpx; }
.invite-arrow { width: 24rpx; height: 24rpx; }
.invite-bg-icon { position: absolute; right: 0; bottom: -10rpx; width: 140rpx; height: 140rpx; opacity: 0.1; transform: rotate(-15deg); pointer-events: none; }
/* Card Section */
.card-section {
background: #FFFFFF;
margin: 0 32rpx 32rpx;
border-radius: 24rpx;
padding: 32rpx 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.03);
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #1A1A1A;
margin-bottom: 32rpx;
}
/* Grid Layout */
.grid-row, .grid-menu {
display: flex;
flex-wrap: wrap;
}
.grid-menu {
margin-top: -16rpx; /* Adjust for spacing */
}
.grid-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
}
.menu-item {
width: 25%;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 32rpx;
}
.grid-icon-img, .menu-icon-img {
width: 48rpx;
height: 48rpx;
margin-bottom: 16rpx;
/* background-color: #FFF0E5; */
/* border-radius: 12rpx; */
/* padding: 8rpx; */
/* This can be uncommented for background circles */
}
.grid-label, .menu-label {
font-size: 24rpx;
color: #333;
}
/* Popup Styles (simplified/inherited) */
.popup-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 999; display: flex; align-items: center; justify-content: center; }
.popup-content { width: 600rpx; max-height: 80vh; background: #FFF; border-radius: 24rpx; padding: 32rpx; display: flex; flex-direction: column; }
.popup-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24rpx; }
.popup-title { font-size: 32rpx; font-weight: 700; }
.close-btn { font-size: 40rpx; padding: 0 10rpx; color: #999; }
.points-list { flex: 1; height: 0; min-height: 400rpx; overflow-y: scroll; }
.status-text { text-align: center; color: #999; padding: 40rpx 0; }
.point-item, .coupon-item { display: flex; justify-content: space-between; padding: 24rpx 0; border-bottom: 1rpx solid #eee; }
.coupon-item { align-items: flex-start; }
.coupon-left { flex: 1; display: flex; flex-direction: column; }
.point-desc, .coupon-name { font-size: 28rpx; color: #333; margin-bottom: 8rpx; }
.task-name { font-size: 30rpx; font-weight: 700; }
.point-time, .coupon-time, .coupon-desc, .task-desc { font-size: 22rpx; color: #999; }
.point-amount { font-size: 32rpx; font-weight: 700; color: #333; }
.positive { color: #ff6b00; }
.coupon-right, .task-right { display: flex; flex-direction: column; align-items: flex-end; justify-content: center; }
.coupon-amount-wrapper { display: flex; align-items: baseline; color: #ff4d4f; }
.symbol { font-size: 24rpx; }
.amount-value { font-size: 40rpx; font-weight: 700; }
.coupon-status, .task-status { font-size: 22rpx; color: #666; margin-top: 8rpx; background: #eee; padding: 4rpx 12rpx; border-radius: 8rpx;}
.popup-tabs { display: flex; margin-bottom: 24rpx; border-bottom: 1rpx solid #eee; }
.popup-tab { flex: 1; text-align: center; padding: 16rpx 0; font-size: 28rpx; color: #666; position: relative; }
.popup-tab.active { color: #ff6b00; font-weight: 700; }
.popup-tab.active::after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 40rpx; height: 4rpx; background: #ff6b00; border-radius: 2rpx; }
.redeem-container { display: flex; margin-top: 24rpx; }
.redeem-input { flex: 1; background: #f5f5f5; height: 80rpx; border-radius: 12rpx; padding: 0 24rpx; font-size: 28rpx; }
.redeem-btn { margin-left: 16rpx; background: #ff6b00; color: #fff; font-size: 28rpx; height: 80rpx; line-height: 80rpx; padding: 0 32rpx; border-radius: 12rpx; }
.reward-list { margin-top: 8rpx; display: flex; align-items: center; }
.reward-label { font-size: 20rpx; color: #ff6b00; margin-right: 8rpx; background: #FFF0E5; padding: 2rpx 8rpx; border-radius: 6rpx; }
.reward-tags { display: flex; flex-wrap: wrap; gap: 8rpx; }
.reward-item { font-size: 20rpx; color: #666; background: #f0f0f0; padding: 2rpx 8rpx; border-radius: 6rpx; }
/* ============================================
优惠券弹窗样式
============================================ */
.coupon-popup { width: 680rpx; }
.coupon-scroll { flex: 1; min-height: 400rpx; max-height: 60vh; }
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
}
.empty-icon { font-size: 80rpx; margin-bottom: 24rpx; }
.empty-text { font-size: 28rpx; color: #999; }
/* 兑换区域 */
.redeem-section {
padding: 24rpx 0;
}
.redeem-input-wrap {
background: #F5F5F5;
border-radius: 16rpx;
padding: 4rpx;
margin-bottom: 24rpx;
}
.redeem-section .redeem-input {
width: 100%;
height: 88rpx;
background: transparent;
padding: 0 24rpx;
font-size: 30rpx;
}
.redeem-section .redeem-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #FF9F43, #FF6B35);
color: #fff;
font-size: 32rpx;
font-weight: 700;
border-radius: 44rpx;
text-align: center;
border: none;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
}
.redeem-tips {
display: flex;
align-items: center;
justify-content: center;
margin-top: 32rpx;
padding: 20rpx;
background: #FFF8F3;
border-radius: 12rpx;
}
.tip-icon { font-size: 32rpx; margin-right: 12rpx; }
.tip-text { font-size: 24rpx; color: #FF6B35; }
/* 优惠券票券样式 */
.coupon-ticket {
display: flex;
background: linear-gradient(135deg, #FFF8F3, #FFFFFF);
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
position: relative;
}
.coupon-ticket.used {
background: #F5F5F5;
opacity: 0.7;
}
.coupon-ticket.used .coupon-left-section {
background: #E0E0E0;
}
.coupon-left-section {
width: 180rpx;
background: linear-gradient(135deg, #FF9F43, #FF6B35);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24rpx 16rpx;
}
.coupon-value {
display: flex;
align-items: baseline;
}
.coupon-symbol {
font-size: 28rpx;
color: #fff;
font-weight: 700;
}
.coupon-amount {
font-size: 56rpx;
font-weight: 800;
color: #fff;
}
.coupon-condition {
font-size: 20rpx;
color: rgba(255,255,255,0.85);
margin-top: 8rpx;
}
.coupon-divider {
width: 24rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.divider-circle {
width: 24rpx;
height: 12rpx;
background: #F8F5F2;
position: absolute;
}
.divider-circle.top { top: -1rpx; border-radius: 0 0 12rpx 12rpx; }
.divider-circle.bottom { bottom: -1rpx; border-radius: 12rpx 12rpx 0 0; }
.divider-line {
width: 2rpx;
flex: 1;
background: repeating-linear-gradient(to bottom, #E5E5E5 0, #E5E5E5 8rpx, transparent 8rpx, transparent 16rpx);
margin: 16rpx 0;
}
.coupon-right-section {
flex: 1;
padding: 20rpx 24rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.coupon-right-section .coupon-name {
font-size: 28rpx;
font-weight: 700;
color: #1F2937;
margin-bottom: 8rpx;
}
.coupon-right-section .coupon-desc {
font-size: 22rpx;
color: #6B7280;
margin-bottom: 6rpx;
}
.coupon-expire {
font-size: 20rpx;
color: #9CA3AF;
margin-bottom: 12rpx;
}
.coupon-action {
align-self: flex-end;
}
.use-btn {
display: inline-block;
background: linear-gradient(135deg, #FFD166, #FF9F43);
color: #1F2937;
font-size: 22rpx;
font-weight: 700;
padding: 10rpx 24rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 159, 67, 0.3);
}
.coupon-status-badge {
align-self: flex-end;
background: #E5E5E5;
color: #999;
font-size: 20rpx;
padding: 6rpx 16rpx;
border-radius: 12rpx;
}
/* ============================================
优惠券 V2 样式 (余额型优惠券)
============================================ */
.coupon-tabs-3 {
display: flex;
gap: 0;
}
.coupon-tabs-3 .popup-tab {
flex: 1;
}
.coupon-ticket-v2 {
display: flex;
background: #FFFFFF;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
position: relative;
}
.coupon-ticket-v2.used {
background: #F9FAFB;
}
.coupon-ticket-v2.used .coupon-left-v2 {
background: linear-gradient(135deg, #9CA3AF, #6B7280);
}
.coupon-ticket-v2.expired {
background: #F3F4F6;
opacity: 0.75;
}
.coupon-ticket-v2.expired .coupon-left-v2 {
background: linear-gradient(135deg, #D1D5DB, #9CA3AF);
}
/* 左侧金额区域 */
.coupon-left-v2 {
width: 160rpx;
background: linear-gradient(135deg, #FF9F43, #FF6B35);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24rpx 12rpx;
flex-shrink: 0;
}
.coupon-remaining {
display: flex;
align-items: baseline;
}
.coupon-left-v2 .coupon-symbol {
font-size: 24rpx;
color: #fff;
font-weight: 700;
}
.coupon-amount-num {
font-size: 48rpx;
font-weight: 800;
color: #fff;
line-height: 1;
}
.coupon-label {
font-size: 20rpx;
color: rgba(255,255,255,0.9);
margin-top: 8rpx;
}
/* 中间分割线 V2 */
.coupon-divider-v2 {
width: 20rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
}
.divider-notch {
width: 20rpx;
height: 10rpx;
background: #F8F5F2;
position: absolute;
}
.divider-notch.top { top: -1rpx; border-radius: 0 0 10rpx 10rpx; }
.divider-notch.bottom { bottom: -1rpx; border-radius: 10rpx 10rpx 0 0; }
.divider-dash {
width: 2rpx;
flex: 1;
background: repeating-linear-gradient(to bottom, #E5E7EB 0, #E5E7EB 6rpx, transparent 6rpx, transparent 12rpx);
margin: 14rpx 0;
}
/* 右侧信息区域 */
.coupon-right-v2 {
flex: 1;
padding: 16rpx 20rpx;
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
}
.coupon-header-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6rpx;
}
.coupon-name-v2 {
font-size: 28rpx;
font-weight: 700;
color: #1F2937;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.coupon-original {
background: #FEF3C7;
padding: 4rpx 10rpx;
border-radius: 8rpx;
margin-left: 12rpx;
flex-shrink: 0;
}
.coupon-original text {
font-size: 18rpx;
color: #D97706;
text-decoration: line-through;
}
.coupon-rules {
font-size: 22rpx;
color: #6B7280;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 使用进度条 */
.coupon-progress-wrap {
margin-bottom: 8rpx;
}
.coupon-progress-bar {
height: 8rpx;
background: #E5E7EB;
border-radius: 4rpx;
overflow: hidden;
margin-bottom: 4rpx;
}
.coupon-progress-fill {
height: 100%;
background: linear-gradient(90deg, #10B981, #34D399);
border-radius: 4rpx;
transition: width 0.3s ease;
}
.coupon-progress-text {
font-size: 18rpx;
color: #10B981;
}
/* 底部行 */
.coupon-footer-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.coupon-expire-v2 {
font-size: 20rpx;
color: #9CA3AF;
flex: 1;
}
.coupon-action-v2 {
flex-shrink: 0;
}
.use-btn-v2 {
display: inline-block;
background: linear-gradient(135deg, #FFD166, #FF9F43);
color: #1F2937;
font-size: 22rpx;
font-weight: 700;
padding: 8rpx 20rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 159, 67, 0.25);
}
.coupon-status-v2 {
flex-shrink: 0;
}
.status-tag {
font-size: 20rpx;
padding: 6rpx 14rpx;
border-radius: 10rpx;
}
.status-tag.used {
background: #DBEAFE;
color: #2563EB;
}
.status-tag.expired {
background: #F3F4F6;
color: #9CA3AF;
}
.coupon-used-time {
font-size: 18rpx;
color: #9CA3AF;
margin-top: 6rpx;
}
/* ============================================
道具卡弹窗样式
============================================ */
.item-cards-popup { width: 680rpx; }
.item-cards-scroll { flex: 1; min-height: 400rpx; max-height: 60vh; }
.item-cards-grid {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.item-card {
display: flex;
align-items: center;
background: linear-gradient(135deg, #FFFFFF, #FAFAFA);
border-radius: 20rpx;
padding: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
position: relative;
overflow: hidden;
}
.item-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 6rpx;
height: 100%;
background: linear-gradient(180deg, #FF9F43, #FF6B35);
}
.card-icon-wrap {
width: 80rpx;
height: 80rpx;
background: linear-gradient(135deg, #FFF4E6, #FFEDD5);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
}
.card-icon {
font-size: 40rpx;
}
.card-info {
flex: 1;
display: flex;
flex-direction: column;
}
.card-name {
font-size: 28rpx;
font-weight: 700;
color: #1F2937;
margin-bottom: 8rpx;
}
.card-desc {
font-size: 22rpx;
color: #6B7280;
}
.card-count-badge {
background: linear-gradient(135deg, #FF9F43, #FF6B35);
padding: 8rpx 20rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);
}
.count-num {
font-size: 26rpx;
font-weight: 700;
color: #fff;
}
/* 已使用道具卡样式 */
.item-card.used {
opacity: 0.7;
background: linear-gradient(135deg, #F3F4F6, #E5E7EB);
}
.item-card.used::before {
background: linear-gradient(180deg, #9CA3AF, #6B7280);
}
.item-card.used .card-icon-wrap {
background: linear-gradient(135deg, #E5E7EB, #D1D5DB);
}
.card-use-time {
font-size: 20rpx;
color: #9CA3AF;
margin-top: 6rpx;
}
.card-used-badge {
background: #E5E7EB;
padding: 8rpx 20rpx;
border-radius: 20rpx;
}
.used-text {
font-size: 24rpx;
font-weight: 600;
color: #6B7280;
}
.loading-more, .no-more {
text-align: center;
color: #9CA3AF;
padding: 24rpx 0;
font-size: 24rpx;
}
/* ============================================
任务中心弹窗样式
============================================ */
.task-center-popup { width: 700rpx; }
.task-scroll { flex: 1; min-height: 400rpx; max-height: 55vh; }
/* 任务统计栏 */
.task-stats-bar {
display: flex;
justify-content: space-around;
align-items: center;
background: linear-gradient(135deg, #FFF8F3, #FFEDD5);
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
}
.stats-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stats-num {
font-size: 40rpx;
font-weight: 800;
color: #FF6B35;
}
.stats-label {
font-size: 22rpx;
color: #6B7280;
margin-top: 4rpx;
}
.stats-divider {
width: 2rpx;
height: 48rpx;
background: #E5E5E5;
}
/* 任务卡片 */
.task-card {
display: flex;
align-items: flex-start;
background: #FFFFFF;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
position: relative;
overflow: hidden;
}
.task-card.completed {
opacity: 0.7;
background: #FAFAFA;
}
.task-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 6rpx;
height: 100%;
background: linear-gradient(180deg, #FF9F43, #FF6B35);
}
.task-card.completed::before {
background: #D1D5DB;
}
.task-icon-wrap {
width: 72rpx;
height: 72rpx;
background: linear-gradient(135deg, #FFF4E6, #FFEDD5);
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
}
.task-icon {
font-size: 36rpx;
}
.task-main {
flex: 1;
min-width: 0;
}
.task-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8rpx;
}
.task-card .task-name {
font-size: 28rpx;
font-weight: 700;
color: #1F2937;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.task-card .task-desc {
font-size: 22rpx;
color: #6B7280;
margin-bottom: 12rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 任务状态标签 */
.task-status-tag {
font-size: 20rpx;
font-weight: 600;
padding: 4rpx 12rpx;
border-radius: 8rpx;
flex-shrink: 0;
margin-left: 12rpx;
}
.status-ongoing {
background: #DBEAFE;
color: #2563EB;
}
.status-done {
background: #D1FAE5;
color: #059669;
}
.status-waiting {
background: #F3F4F6;
color: #6B7280;
}
/* 任务进度条 */
.task-progress {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.task-progress .progress-bar {
flex: 1;
height: 12rpx;
background: #F3F4F6;
border-radius: 6rpx;
overflow: hidden;
margin-right: 12rpx;
}
.task-progress .progress-fill {
height: 100%;
background: linear-gradient(90deg, #FF9F43, #FF6B35);
border-radius: 6rpx;
transition: width 0.3s ease;
}
.task-progress .progress-text {
font-size: 20rpx;
color: #9CA3AF;
flex-shrink: 0;
}
/* 任务奖励 */
.task-rewards {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
}
.reward-badge {
display: flex;
align-items: center;
background: linear-gradient(135deg, #FFF4E6, #FFEDD5);
padding: 6rpx 12rpx;
border-radius: 10rpx;
}
.reward-badge .reward-icon {
font-size: 22rpx;
margin-right: 4rpx;
}
.reward-badge .reward-value {
font-size: 22rpx;
font-weight: 600;
color: #FF6B35;
}
/* 任务操作按钮 */
.task-action {
display: flex;
align-items: center;
margin-left: 16rpx;
}
.task-action .action-btn {
font-size: 24rpx;
font-weight: 700;
padding: 12rpx 24rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, #FF9F43, #FF6B35);
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);
}
.task-action .action-btn.done {
background: #D1FAE5;
color: #059669;
box-shadow: none;
}
.task-action .action-btn.waiting {
background: #F3F4F6;
color: #9CA3AF;
box-shadow: none;
}
/* ============================================
任务中心 V2 样式
============================================ */
.task-center-popup { width: 700rpx; }
.task-scroll { flex: 1; min-height: 350rpx; max-height: 50vh; }
/* 总体进度指示器 */
.overall-progress {
display: flex;
align-items: center;
background: linear-gradient(135deg, #FFF8F3, #FFEDD5);
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 20rpx;
gap: 24rpx;
}
.progress-circle {
width: 120rpx;
height: 120rpx;
background: linear-gradient(135deg, #FF9F43, #FF6B35);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
}
.progress-percent {
font-size: 32rpx;
font-weight: 800;
color: #fff;
}
.overall-progress .progress-label {
font-size: 18rpx;
color: rgba(255,255,255,0.9);
}
.progress-stats {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.stat-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.stat-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
}
.stat-dot.done { background: #10B981; }
.stat-dot.ongoing { background: #3B82F6; }
.stat-dot.waiting { background: #9CA3AF; }
.stat-text {
font-size: 24rpx;
color: #4B5563;
}
/* 任务卡片 V2 */
.task-card-v2 {
display: flex;
align-items: stretch;
background: #FFFFFF;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
border-left: 6rpx solid #E5E7EB;
}
.task-card-v2.task-completed {
border-left-color: #10B981;
background: #F0FDF4;
}
.task-card-v2.task-ongoing {
border-left-color: #3B82F6;
background: #EFF6FF;
}
.task-card-v2.task-waiting {
border-left-color: #E5E7EB;
}
/* 状态指示器 */
.task-status-indicator {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
flex-shrink: 0;
align-self: flex-start;
margin-top: 4rpx;
}
.task-status-indicator.status-done {
background: #10B981;
}
.task-status-indicator.status-ongoing {
background: #3B82F6;
}
.task-status-indicator.status-waiting {
background: #E5E7EB;
}
.status-icon {
font-size: 24rpx;
color: #fff;
font-weight: bold;
}
.status-waiting .status-icon {
color: #9CA3AF;
}
/* 任务内容 */
.task-content {
flex: 1;
min-width: 0;
}
.task-title-row {
margin-bottom: 6rpx;
}
.task-title {
font-size: 28rpx;
font-weight: 700;
color: #1F2937;
}
.task-desc-v2 {
font-size: 22rpx;
color: #6B7280;
margin-bottom: 12rpx;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 进度条 V2 */
.task-progress-v2 {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 10rpx;
}
.progress-bar-v2 {
flex: 1;
height: 12rpx;
background: #E5E7EB;
border-radius: 6rpx;
overflow: hidden;
}
.progress-fill-v2 {
height: 100%;
background: linear-gradient(90deg, #FF9F43, #FF6B35);
border-radius: 6rpx;
transition: width 0.3s ease;
}
.task-completed .progress-fill-v2 {
background: linear-gradient(90deg, #34D399, #10B981);
}
.task-ongoing .progress-fill-v2 {
background: linear-gradient(90deg, #60A5FA, #3B82F6);
}
.progress-num {
font-size: 22rpx;
font-weight: 700;
color: #6B7280;
min-width: 60rpx;
text-align: right;
}
/* 奖励行 */
.task-reward-row {
display: flex;
align-items: center;
gap: 8rpx;
}
.task-reward-row .reward-label {
font-size: 22rpx;
color: #9CA3AF;
}
.reward-items {
display: flex;
gap: 12rpx;
}
.task-reward-row .reward-item {
font-size: 22rpx;
color: #FF6B35;
font-weight: 600;
background: #FFF0E5;
padding: 4rpx 12rpx;
border-radius: 8rpx;
}
/* 按钮区域 */
.task-btn-wrap {
display: flex;
align-items: center;
margin-left: 12rpx;
}
.task-btn {
font-size: 22rpx;
font-weight: 700;
padding: 0 24rpx;
height: 56rpx;
line-height: 56rpx;
border-radius: 28rpx;
border: none;
min-width: 120rpx;
}
.task-btn.btn-go {
background: linear-gradient(135deg, #FF9F43, #FF6B35);
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);
}
.task-btn.btn-done {
background: #D1FAE5;
color: #059669;
}
.task-btn.btn-waiting {
background: #F3F4F6;
color: #9CA3AF;
}
/* ============================================
用户中心 UX/UI 增强
============================================ */
/* 入场动画定义 */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30rpx); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes glowPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 159, 67, 0.4); }
50% { box-shadow: 0 0 20rpx 8rpx rgba(255, 159, 67, 0.2); }
}
@keyframes bounce {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(100rpx); }
to { opacity: 1; transform: translateY(0); }
}
/* 头像发光效果 */
.avatar {
animation: glowPulse 3s ease-in-out infinite;
transition: all 0.3s ease;
}
.avatar:active {
transform: scale(0.95);
}
/* 用户信息入场动画 */
.user-info {
animation: fadeInUp 0.4s ease-out;
}
/* 等级徽章动画 */
.level-badge {
transition: all 0.3s ease;
}
.level-badge:active {
transform: scale(0.95);
}
/* 统计栏入场动画 */
.stats-row {
animation: fadeInUp 0.4s ease-out 0.1s both;
}
/* 统计项点击效果 */
.stat-item {
transition: all 0.2s ease;
padding: 16rpx 8rpx;
border-radius: 16rpx;
cursor: pointer;
}
.stat-item:active {
background: rgba(255, 159, 67, 0.1);
transform: scale(0.95);
}
.stat-num {
transition: all 0.3s ease;
}
.stat-item:active .stat-num {
color: #FF6B35;
transform: scale(1.1);
}
/* 邀请Banner入场动画 */
.invite-banner {
animation: fadeInUp 0.4s ease-out 0.15s both;
transition: all 0.3s ease;
}
.invite-banner:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.08);
}
/* 卡片区域入场动画 */
.card-section {
animation: fadeInUp 0.4s ease-out 0.2s both;
transition: all 0.3s ease;
}
.card-section:active {
transform: scale(0.99);
}
/* 订单图标点击效果 */
.grid-item {
transition: all 0.2s ease;
padding: 12rpx;
border-radius: 16rpx;
box-sizing: border-box;
}
.grid-item:active {
background: rgba(255, 159, 67, 0.08);
transform: scale(0.92);
}
.grid-icon-img {
transition: all 0.2s ease;
}
.grid-item:active .grid-icon-img {
transform: scale(1.15);
}
/* 菜单项点击效果 */
.menu-item {
transition: all 0.2s ease;
padding: 16rpx 8rpx;
border-radius: 16rpx;
box-sizing: border-box;
}
.menu-item:active {
background: rgba(0, 0, 0, 0.04);
transform: scale(0.92);
}
.menu-icon-img {
transition: all 0.2s ease;
}
.menu-item:active .menu-icon-img {
transform: scale(1.15) rotate(-5deg);
}
/* 弹窗动画增强 */
.popup-mask {
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.popup-content {
animation: slideUp 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 关闭按钮效果 */
.close-btn {
transition: all 0.2s ease;
}
.close-btn:active {
transform: scale(0.85) rotate(90deg);
color: #FF6B35;
}
/* 登录按钮动画 */
.join-btn {
transition: all 0.2s ease;
animation: bounce 2s ease-in-out infinite;
}
.join-btn:active {
animation: none;
}
/* 邀请按钮增强 */
.invite-action {
transition: all 0.2s ease;
}
.invite-action:active {
transform: scale(0.95);
}
/* ============================================
邀请记录弹窗样式 (Redesigned)
============================================ */
.invites-popup {
width: 680rpx;
background: #F8F9FB; /* 浅灰底色,更显卡片质感 */
padding: 0;
border-radius: 32rpx;
overflow: hidden;
display: flex;
flex-direction: column;
max-height: 85vh;
}
/* 顶部标题栏 */
.invites-popup-header {
height: 110rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #fff;
border-bottom: 1rpx solid rgba(0,0,0,0.03);
}
.invites-title-text {
font-size: 34rpx;
font-weight: 800;
color: #1A1A1A;
}
.invites-close {
position: absolute;
right: 24rpx;
top: 50%;
transform: translateY(-50%);
padding: 12rpx;
}
.invites-close-icon {
width: 32rpx;
height: 32rpx;
opacity: 0.6;
}
/* 核心资产卡片 */
.invite-prime-card {
margin: 32rpx 32rpx 0;
height: 280rpx;
background: linear-gradient(135deg, #2A2A2A 0%, #1A1A1A 100%); /* 黑金风格或品牌橙色 */
background: linear-gradient(135deg, #FF9F43 0%, #FF6B35 100%); /* 选用品牌活力橙 */
border-radius: 24rpx;
position: relative;
overflow: hidden;
box-shadow: 0 12rpx 32rpx rgba(255, 107, 53, 0.25);
display: flex;
flex-direction: column;
justify-content: center;
}
/* 装饰圆环 */
.card-deco-circle {
position: absolute;
border-radius: 50%;
border: 2rpx solid rgba(255, 255, 255, 0.1);
}
.card-deco-circle.c1 { width: 300rpx; height: 300rpx; top: -100rpx; right: -60rpx; }
.card-deco-circle.c2 { width: 400rpx; height: 400rpx; bottom: -120rpx; left: -80rpx; opacity: 0.15; background: radial-gradient(circle, rgba(255,255,255,0.2), transparent); }
.prime-card-content {
position: relative;
z-index: 2;
padding: 0 40rpx;
}
.my-code-section {
display: flex;
flex-direction: column;
margin-bottom: 24rpx;
}
.code-label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
margin-bottom: 8rpx;
}
.code-value-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.code-text {
font-size: 48rpx;
font-weight: 800;
color: #fff;
font-family: monospace; /* 等宽字体显得更像邀请码 */
letter-spacing: 2rpx;
text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.1);
}
.copy-icon-btn {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
padding: 8rpx 20rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
border: 1rpx solid rgba(255,255,255,0.3);
}
.copy-text {
font-size: 24rpx;
color: #fff;
font-weight: 700;
}
.card-divider-h {
height: 1rpx;
background: rgba(255, 255, 255, 0.2);
margin-bottom: 24rpx;
}
.stats-mini-row {
display: flex;
align-items: center;
}
.stat-mini-item {
display: flex;
flex-direction: column;
}
.mini-num {
font-size: 32rpx;
font-weight: 800;
color: #fff;
}
.mini-label {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.7);
margin-top: 2rpx;
}
.stat-mini-sep {
width: 1rpx;
height: 40rpx;
background: rgba(255, 255, 255, 0.2);
margin: 0 40rpx;
}
/* 列表区域 */
.list-section-header {
padding: 40rpx 32rpx 20rpx;
display: flex;
align-items: baseline;
}
.list-title {
font-size: 30rpx;
font-weight: 800;
color: #333;
margin-right: 12rpx;
}
.list-subtitle {
font-size: 22rpx;
color: #999;
}
.invites-scroll-refined {
flex: 1;
height: 0; /* Important for flex layout scrolling */
min-height: 400rpx;
padding: 0 32rpx;
box-sizing: border-box;
}
/* 列表卡片 */
.friend-card {
background: #fff;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
border: 1rpx solid rgba(0,0,0,0.02);
position: relative;
overflow: hidden;
}
.friend-avatar-wrap {
position: relative;
margin-right: 24rpx;
}
.friend-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
background: #f0f0f0;
border: 2rpx solid #fff;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
}
.friend-info {
flex: 1;
display: flex;
flex-direction: column;
}
.friend-name-row {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.friend-name {
font-size: 28rpx;
font-weight: 700;
color: #333;
margin-right: 12rpx;
max-width: 200rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.friend-tag {
font-size: 18rpx;
color: #FF6B35;
background: rgba(255, 107, 53, 0.1);
padding: 2rpx 8rpx;
border-radius: 6rpx;
font-weight: 700;
}
.friend-meta {
font-size: 22rpx;
color: #999;
}
.friend-status {
display: flex;
align-items: center;
background: #F0FDF4;
padding: 8rpx 16rpx;
border-radius: 999rpx;
}
.status-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
margin-right: 8rpx;
}
.status-dot.success { background: #38ef7d; box-shadow: 0 0 6rpx rgba(56, 239, 125, 0.6); }
.status-val {
font-size: 20rpx;
color: #11998e;
font-weight: 700;
}
/* 空状态优化 */
.empty-state-refined {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
opacity: 0.8;
}
.empty-img-refined {
width: 160rpx;
height: 160rpx;
margin-bottom: 24rpx;
opacity: 0.5; /* 只有SVG没有颜色时 */
}
.empty-text-refined {
font-size: 28rpx;
color: #666;
font-weight: 700;
margin-bottom: 8rpx;
}
.empty-sub-refined {
font-size: 22rpx;
color: #999;
}
/* 底部操作区 */
.popup-footer-action {
padding: 24rpx 32rpx 32rpx;
background: #fff;
border-top: 1rpx solid rgba(0,0,0,0.03);
z-index: 10;
}
.btn-share-gradient {
background: linear-gradient(90deg, #FF6B35, #FF9F43);
color: #fff;
height: 96rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 700;
border-radius: 48rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
transition: all 0.2s;
}
.btn-share-gradient:active {
transform: scale(0.98);
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.2);
}
.btn-arrow-icon {
width: 32rpx;
height: 32rpx;
margin-left: 12rpx;
}
/* Loading States */
.status-loading-refined {
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 0;
color: #999;
font-size: 24rpx;
}
.loading-spinner {
width: 40rpx;
height: 40rpx;
border: 4rpx solid #EAEAEA;
border-top-color: #FF6B35;
border-radius: 50%;
margin-bottom: 16rpx;
animation: spin 1s linear infinite;
}
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.loading-more-text { text-align: center; padding: 24rpx; color: #999; font-size: 22rpx; }
.no-more-text { text-align: center; padding: 24rpx; color: #ddd; font-size: 22rpx; margin-bottom: 24rpx; }
</style>