bindbox-mini/pages/mine/index.vue

1395 lines
49 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="toAddresses"> <!-- 此处暂用 invite_log 占位 -->
<image class="menu-icon-img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiMzMzMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNiA0aDJhMiAyIDAgMCAxIDIgMnYxNGEyIDIgMCAwIDEtMiAySDZ4MiAyIDAgMCAxLTItMnYtMTQiIC8+PHJlY3QgeD0iOCIgeT0iMiIgd2lkdGg9IjgiIGhlaWdodD0iNCIgcng9IjEiIHJ5PSIyIj48L3JlY3Q+PC9zdmc+" 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">
<view class="popup-tab" :class="{ active: couponsTab === 0 }" @tap="switchCouponsTab(0)">未使用</view>
<view class="popup-tab" :class="{ active: couponsTab === 1 }" @tap="switchCouponsTab(1)">已使用</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">暂无优惠券</text>
</view>
<view v-for="(item, index) in couponsList" :key="index" class="coupon-ticket" :class="{ 'used': couponsTab === 1 }">
<view class="coupon-left-section">
<view class="coupon-value">
<text class="coupon-symbol">¥</text>
<text class="coupon-amount">{{ formatCouponValue(item.remaining || item.amount || 0) }}</text>
</view>
<text class="coupon-condition">满{{ formatCouponValue(item.min_amount || 0) }}可用</text>
</view>
<view class="coupon-divider">
<view class="divider-circle top"></view>
<view class="divider-line"></view>
<view class="divider-circle bottom"></view>
</view>
<view class="coupon-right-section">
<text class="coupon-name">{{ item.name || '优惠券' }}</text>
<text class="coupon-desc">{{ item.rules || item.description || '全场通用' }}</text>
<text class="coupon-expire">有效期至:{{ formatDate(item.valid_end || item.end_time) }}</text>
<view class="coupon-action" v-if="couponsTab === 0">
<text class="use-btn">去使用</text>
</view>
<view class="coupon-status-badge" v-else>
<text>{{ couponsTab === 1 ? '已使用' : '已过期' }}</text>
</view>
</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="task-stats-bar">
<view class="stats-item">
<text class="stats-num">{{ tasksList.filter(t => t.status === 2).length }}</text>
<text class="stats-label">已完成</text>
</view>
<view class="stats-divider"></view>
<view class="stats-item">
<text class="stats-num">{{ tasksList.filter(t => t.status === 1).length }}</text>
<text class="stats-label">进行中</text>
</view>
<view class="stats-divider"></view>
<view class="stats-item">
<text class="stats-num">{{ tasksList.length }}</text>
<text class="stats-label">全部任务</text>
</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" :class="{ 'completed': task.status === 2 }">
<view class="task-icon-wrap">
<text class="task-icon">{{ getTaskIcon(task.type || task.name) }}</text>
</view>
<view class="task-main">
<view class="task-header">
<text class="task-name">{{ task.name || '任务' }}</text>
<view class="task-status-tag" :class="getTaskStatusClass(task.status)">
{{ formatStatus(task.status) }}
</view>
</view>
<text class="task-desc">{{ task.description || '完成任务获取奖励' }}</text>
<!-- 任务进度条 -->
<view class="task-progress" v-if="task.progress !== undefined || task.current !== undefined">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: getTaskProgress(task) + '%' }"></view>
</view>
<text class="progress-text">{{ task.current || 0 }}/{{ task.target || 1 }}</text>
</view>
<!-- 奖励区域 -->
<view class="task-rewards" v-if="Array.isArray(task.rewards) && task.rewards.length">
<view class="reward-badge" v-for="(rw, ri) in task.rewards" :key="ri">
<text class="reward-icon">{{ getRewardIcon(rw.reward_type) }}</text>
<text class="reward-value">+{{ rw.quantity || 0 }}</text>
</view>
</view>
</view>
<view class="task-action">
<text class="action-btn" v-if="task.status === 1" @tap="goToTask(task)">去完成</text>
<text class="action-btn done" v-else-if="task.status === 2">已完成</text>
<text class="action-btn waiting" v-else>未开始</text>
</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>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onShow, onLoad, onShareAppMessage } from '@dcloudio/uni-app'
import { getUserStats, getPointsBalance, getUserPoints, getUserCoupons, redeemCoupon, getItemCards, getTasks } 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(0) // 0: 未使用, 1: 已使用, 2: 兑换
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)
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 = 0
redeemCode.value = ''
couponsList.value = []
couponsPage.value = 1
couponsHasMore.value = true
loadCoupons()
}
function closeCouponsPopup() { couponsVisible.value = false }
function switchCouponsTab(index) {
if (couponsTab.value === index) return
couponsTab.value = index
if (index !== 2) {
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 {
const status = couponsTab.value === 0 ? 0 : 1
const res = await getUserCoupons(user_id, status, 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 && couponsTab.value !== 2) loadCoupons() }
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 formatCouponValue(value) {
const num = Number(value || 0)
return (num / 100).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) {
closeTasksPopup()
// 根据任务类型跳转到对应页面
const name = String(task.name || '').toLowerCase()
if (name.includes('邀请')) {
handleInvite()
} else if (name.includes('签到')) {
uni.showToast({ title: '签到功能开发中', icon: 'none' })
} else {
uni.switchTab({ url: '/pages/index/index' })
}
}
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;
}
/* ============================================
道具卡弹窗样式
============================================ */
.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;
}
</style>