|
@@ -1,120 +1,365 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <view class="container">
|
|
|
|
|
- <!-- 空状态:没有心愿 -->
|
|
|
|
|
- <view class="empty-state" v-if="wishes.length === 0 && !loading">
|
|
|
|
|
- <view class="empty-icon">🎁</view>
|
|
|
|
|
- <text class="empty-title">还没有心愿</text>
|
|
|
|
|
- <text class="empty-desc">快去添加心愿,开始积攒积分吧!</text>
|
|
|
|
|
- <button class="btn-add" @click="goCreate">添加心愿</button>
|
|
|
|
|
|
|
+ <view class="page">
|
|
|
|
|
+ <!-- 加载状态 -->
|
|
|
|
|
+ <BaseLoading :loading="loading" text="加载心愿中..." />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 积分卡片 -->
|
|
|
|
|
+ <view class="points-card" v-if="!loading">
|
|
|
|
|
+ <view class="points-card__glow"></view>
|
|
|
|
|
+ <view class="points-card__inner">
|
|
|
|
|
+ <view class="points-card__header">
|
|
|
|
|
+ <text class="points-card__icon">⭐</text>
|
|
|
|
|
+ <text class="points-card__label">我的积分</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="points-card__value">{{ formattedPoints }}</text>
|
|
|
|
|
+ <view class="milestone">
|
|
|
|
|
+ <view class="milestone__track">
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="milestone__fill"
|
|
|
|
|
+ :style="{ width: milestoneInfo.progress + '%' }"
|
|
|
|
|
+ ></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="milestone__text">🎯 距离下一个奖励还差 {{ milestoneInfo.remaining }} 分</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 心愿列表 -->
|
|
|
|
|
- <view class="wish-list" v-else>
|
|
|
|
|
- <view
|
|
|
|
|
- v-for="wish in wishes"
|
|
|
|
|
- :key="wish.id"
|
|
|
|
|
- class="wish-card"
|
|
|
|
|
- :class="wish.status"
|
|
|
|
|
- @click="goDetail(wish.id)"
|
|
|
|
|
- >
|
|
|
|
|
- <!-- 心愿头部 -->
|
|
|
|
|
- <view class="wish-header">
|
|
|
|
|
- <text class="wish-title">{{ wish.title }}</text>
|
|
|
|
|
- <text class="wish-status" :class="wish.status">{{ wish.statusText }}</text>
|
|
|
|
|
|
|
+ <!-- 筛选标签 -->
|
|
|
|
|
+ <scroll-view
|
|
|
|
|
+ class="filter-tabs"
|
|
|
|
|
+ scroll-x="true"
|
|
|
|
|
+ show-scrollbar="false"
|
|
|
|
|
+ v-if="!loading && wishes.length > 0"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="filter-tabs__inner">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="tab in filterTabs"
|
|
|
|
|
+ :key="tab.key"
|
|
|
|
|
+ class="filter-tab"
|
|
|
|
|
+ :class="{ 'filter-tab--active': activeFilter === tab.key }"
|
|
|
|
|
+ @click="onFilterChange(tab.key)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="filter-tab__label">{{ tab.emoji }} {{ tab.label }}</text>
|
|
|
|
|
+ <view class="filter-tab__count" v-if="getFilterCount(tab.key) > 0">{{ getFilterCount(tab.key) }}</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </scroll-view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 空状态:无任何心愿 -->
|
|
|
|
|
+ <BaseEmpty
|
|
|
|
|
+ v-if="!loading && wishes.length === 0"
|
|
|
|
|
+ icon="🗺️"
|
|
|
|
|
+ title="还没有心愿呢"
|
|
|
|
|
+ description="点击 + 添加第一个心愿吧!"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template v-slot:action>
|
|
|
|
|
+ <PlayfulButton variant="clay" size="lg" @click="showAddModal = true">
|
|
|
|
|
+ ✨ 添加第一个心愿
|
|
|
|
|
+ </PlayfulButton>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </BaseEmpty>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 空状态:筛选后无结果 -->
|
|
|
|
|
+ <BaseEmpty
|
|
|
|
|
+ v-else-if="!loading && filteredWishes.length === 0 && wishes.length > 0"
|
|
|
|
|
+ icon="🔍"
|
|
|
|
|
+ title="没有找到"
|
|
|
|
|
+ :description="'还没有' + getActiveTabLabel + '的心愿哦'"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 心愿列表(宝箱卡片) -->
|
|
|
|
|
+ <view class="wish-list" v-if="!loading && wishes.length > 0">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(wish, index) in filteredWishes"
|
|
|
|
|
+ :key="wish.id"
|
|
|
|
|
+ class="wish-item"
|
|
|
|
|
+ :class="['animate-slide-up', 'animate-stagger-' + (index + 1)]"
|
|
|
|
|
+ >
|
|
|
|
|
+ <PlayfulCard variant="clay" padding="0" :clickable="false">
|
|
|
|
|
+ <view class="wish-card">
|
|
|
|
|
+ <!-- 左侧 emoji 图标 -->
|
|
|
|
|
+ <view class="wish-card__icon-wrap">
|
|
|
|
|
+ <text class="wish-card__emoji">{{ getCategoryEmoji(wish.category) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 中间内容 -->
|
|
|
|
|
+ <view class="wish-card__body">
|
|
|
|
|
+ <view class="wish-card__top">
|
|
|
|
|
+ <text class="wish-card__title">{{ wish.title }}</text>
|
|
|
|
|
+ <BaseBadge :variant="getStatusBadgeVariant(wish.status)" size="xs">
|
|
|
|
|
+ <text>{{ getStatusEmoji(wish.status) }} {{ wish.statusText || wish.status }}</text>
|
|
|
|
|
+ </BaseBadge>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="wish-card__points-row">
|
|
|
|
|
+ <text class="wish-card__points-label">需要</text>
|
|
|
|
|
+ <text class="wish-card__points-value">{{ wish.pointsRequired || '???' }}</text>
|
|
|
|
|
+ <text class="wish-card__points-unit">⭐</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 根据不同状态显示操作区域 -->
|
|
|
|
|
+ <view class="wish-card__actions">
|
|
|
|
|
+ <!-- draft:等待定价 -->
|
|
|
|
|
+ <view class="wish-card__status-text" v-if="wish.status === 'draft'">
|
|
|
|
|
+ <text class="wish-card__status-emoji">⏳</text>
|
|
|
|
|
+ <text class="wish-card__status-msg">等待妈妈定价中</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- active + 积分足够 → 兑换按钮 -->
|
|
|
|
|
+ <PlayfulButton
|
|
|
|
|
+ v-if="wish.status === 'active'"
|
|
|
|
|
+ :variant="canRedeem(wish) ? 'primary' : 'secondary'"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ :disabled="!canRedeem(wish)"
|
|
|
|
|
+ @click="onRedeem(wish)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ canRedeem(wish) ? '🎁 兑换' : '还差 ' + (wish.pointsRequired - currentPoints) + ' ⭐' }}
|
|
|
|
|
+ </PlayfulButton>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- pending_exchange:审批中 -->
|
|
|
|
|
+ <view class="wish-card__status-text wish-card__status-text--warning" v-if="wish.status === 'pending_exchange'">
|
|
|
|
|
+ <text class="wish-card__status-emoji">⏳</text>
|
|
|
|
|
+ <text class="wish-card__status-msg">等待妈妈审批</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- approved:已批准 -->
|
|
|
|
|
+ <view class="wish-card__status-text wish-card__status-text--info" v-if="wish.status === 'approved'">
|
|
|
|
|
+ <text class="wish-card__status-emoji">📦</text>
|
|
|
|
|
+ <text class="wish-card__status-msg">妈妈准备中</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- exchanging:确认收货 -->
|
|
|
|
|
+ <PlayfulButton
|
|
|
|
|
+ v-if="wish.status === 'exchanging'"
|
|
|
|
|
+ variant="success"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ @click="onConfirmReceived(wish)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 📦 确认收货
|
|
|
|
|
+ </PlayfulButton>
|
|
|
|
|
|
|
|
- <!-- 分类标签 -->
|
|
|
|
|
- <view class="wish-category" v-if="wish.category">
|
|
|
|
|
- <text class="category-tag">{{ wish.category }}</text>
|
|
|
|
|
|
|
+ <!-- fulfilled:已完成 -->
|
|
|
|
|
+ <view class="wish-card__status-text wish-card__status-text--success" v-if="wish.status === 'fulfilled'">
|
|
|
|
|
+ <text class="wish-card__status-emoji">🎉</text>
|
|
|
|
|
+ <text class="wish-card__status-msg">已兑换</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- rejected:未通过 -->
|
|
|
|
|
+ <view class="wish-card__status-text wish-card__status-text--error" v-if="wish.status === 'rejected'">
|
|
|
|
|
+ <text class="wish-card__status-emoji">😢</text>
|
|
|
|
|
+ <text class="wish-card__status-msg">未通过</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 右侧删除按钮(仅可取消状态) -->
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="wish-card__delete"
|
|
|
|
|
+ v-if="wish.status === 'draft' || wish.status === 'active'"
|
|
|
|
|
+ @click.stop="onDeleteWish(wish)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="wish-card__delete-icon">✕</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </PlayfulCard>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部间距(给 FAB 让位) -->
|
|
|
|
|
+ <view class="wish-list__spacer"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 浮动操作按钮 -->
|
|
|
|
|
+ <view class="fab" @click="showAddModal = true">
|
|
|
|
|
+ <view class="fab__pulse"></view>
|
|
|
|
|
+ <text class="fab__icon">+</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 添加心愿弹窗 -->
|
|
|
|
|
+ <view class="modal-overlay" v-if="showAddModal" @click="closeAddModal">
|
|
|
|
|
+ <view class="modal" @click.stop>
|
|
|
|
|
+ <view class="modal__header">
|
|
|
|
|
+ <text class="modal__title">✨ 添加新心愿</text>
|
|
|
|
|
+ <text class="modal__close" @click="closeAddModal">✕</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 积分进度(active状态显示) -->
|
|
|
|
|
- <view v-if="wish.status === 'active'" class="progress-section">
|
|
|
|
|
- <view class="progress-bar">
|
|
|
|
|
- <view class="progress-fill" :style="{ width: wish.progress + '%' }"></view>
|
|
|
|
|
|
|
+ <!-- 表情/分类选择 -->
|
|
|
|
|
+ <view class="form-group">
|
|
|
|
|
+ <text class="form-group__label">选择分类</text>
|
|
|
|
|
+ <view class="emoji-grid">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="cat in categories"
|
|
|
|
|
+ :key="cat.value"
|
|
|
|
|
+ class="emoji-item"
|
|
|
|
|
+ :class="{ 'emoji-item--active': addForm.category === cat.value }"
|
|
|
|
|
+ @click="addForm.category = cat.value; addForm.emoji = cat.emoji"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="emoji-item__icon">{{ cat.emoji }}</text>
|
|
|
|
|
+ <text class="emoji-item__label">{{ cat.label }}</text>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
- <text class="progress-text">
|
|
|
|
|
- {{ currentPoints }}/{{ wish.pointsRequired }}积分
|
|
|
|
|
- </text>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <!-- 操作按钮 -->
|
|
|
|
|
- <view class="wish-actions">
|
|
|
|
|
- <!-- 积攒中:积分足够可申请兑换 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="wish.status === 'active' && currentPoints >= wish.pointsRequired"
|
|
|
|
|
- class="btn-exchange"
|
|
|
|
|
- @click.stop="requestExchange(wish)"
|
|
|
|
|
- >
|
|
|
|
|
- 申请兑换
|
|
|
|
|
- </button>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 积攒中:积分不足显示差多少 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="wish.status === 'active' && currentPoints < wish.pointsRequired"
|
|
|
|
|
- class="btn-waiting"
|
|
|
|
|
- disabled
|
|
|
|
|
- >
|
|
|
|
|
- 还差{{ wish.pointsRequired - currentPoints }}积分
|
|
|
|
|
- </button>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 待审批:显示等待中 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="wish.status === 'pending_exchange'"
|
|
|
|
|
- class="btn-waiting"
|
|
|
|
|
- disabled
|
|
|
|
|
- >
|
|
|
|
|
- 等待家长审批
|
|
|
|
|
- </button>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 待收货:确认收货按钮 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="wish.status === 'exchanging'"
|
|
|
|
|
- class="btn-confirm"
|
|
|
|
|
- @click.stop="confirmReceived(wish)"
|
|
|
|
|
- >
|
|
|
|
|
- 确认收货
|
|
|
|
|
- </button>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 已完成:显示完成时间 -->
|
|
|
|
|
- <text v-if="wish.status === 'fulfilled'" class="fulfilled-text">
|
|
|
|
|
- ✅ 已于{{ wish.fulfilledAt }}完成
|
|
|
|
|
- </text>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 待定价:显示等待家长 -->
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="wish.status === 'draft'"
|
|
|
|
|
- class="btn-waiting"
|
|
|
|
|
- disabled
|
|
|
|
|
- >
|
|
|
|
|
- 等待家长定价
|
|
|
|
|
- </button>
|
|
|
|
|
|
|
+ <!-- 心愿名称 -->
|
|
|
|
|
+ <view class="form-group">
|
|
|
|
|
+ <text class="form-group__label">心愿名称</text>
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="form-input"
|
|
|
|
|
+ v-model="addForm.title"
|
|
|
|
|
+ placeholder="写下你的心愿吧~"
|
|
|
|
|
+ maxlength="20"
|
|
|
|
|
+ @input="onTitleInput"
|
|
|
|
|
+ />
|
|
|
|
|
+ <text class="form-input__counter">{{ addForm.title.length }}/20</text>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 所需积分 -->
|
|
|
|
|
+ <view class="form-group">
|
|
|
|
|
+ <text class="form-group__label">所需积分(给妈妈参考)</text>
|
|
|
|
|
+ <view class="form-input__wrapper">
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="form-input form-input--number"
|
|
|
|
|
+ v-model="addForm.pointsRequired"
|
|
|
|
|
+ placeholder="建议积分"
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ maxlength="4"
|
|
|
|
|
+ />
|
|
|
|
|
+ <text class="form-input__suffix">⭐</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 提交按钮 -->
|
|
|
|
|
+ <PlayfulButton variant="clay" size="lg" block :disabled="!canSubmit" @click="onAddWish">
|
|
|
|
|
+ ✨ 许下心愿
|
|
|
|
|
+ </PlayfulButton>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
-
|
|
|
|
|
- <!-- 添加按钮 -->
|
|
|
|
|
- <button class="btn-add-fixed" @click="goCreate" v-if="wishes.length > 0">
|
|
|
|
|
- + 添加心愿
|
|
|
|
|
- </button>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import {
|
|
|
|
|
- getWishes,
|
|
|
|
|
- requestWishExchange,
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ getWishes,
|
|
|
|
|
+ createWish,
|
|
|
|
|
+ requestWishExchange,
|
|
|
confirmWishReceived,
|
|
confirmWishReceived,
|
|
|
- getPointsBalance
|
|
|
|
|
|
|
+ cancelWish,
|
|
|
|
|
+ getPointsBalance
|
|
|
} from '../../utils/api.js'
|
|
} from '../../utils/api.js'
|
|
|
|
|
|
|
|
|
|
+import PlayfulCard from '../../components/PlayfulCard.vue'
|
|
|
|
|
+import PlayfulButton from '../../components/PlayfulButton.vue'
|
|
|
|
|
+import BaseBadge from '../../components/BaseBadge.vue'
|
|
|
|
|
+import BaseEmpty from '../../components/BaseEmpty.vue'
|
|
|
|
|
+import BaseLoading from '../../components/BaseLoading.vue'
|
|
|
|
|
+
|
|
|
export default {
|
|
export default {
|
|
|
|
|
+ components: {
|
|
|
|
|
+ PlayfulCard,
|
|
|
|
|
+ PlayfulButton,
|
|
|
|
|
+ BaseBadge,
|
|
|
|
|
+ BaseEmpty,
|
|
|
|
|
+ BaseLoading
|
|
|
|
|
+ },
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
wishes: [],
|
|
wishes: [],
|
|
|
currentPoints: 0,
|
|
currentPoints: 0,
|
|
|
childId: '',
|
|
childId: '',
|
|
|
- loading: true
|
|
|
|
|
|
|
+ loading: true,
|
|
|
|
|
+ activeFilter: 'all',
|
|
|
|
|
+
|
|
|
|
|
+ // 添加心愿弹窗
|
|
|
|
|
+ showAddModal: false,
|
|
|
|
|
+ addForm: {
|
|
|
|
|
+ title: '',
|
|
|
|
|
+ category: '',
|
|
|
|
|
+ emoji: '🎁',
|
|
|
|
|
+ pointsRequired: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ submittingWish: false,
|
|
|
|
|
+
|
|
|
|
|
+ // 筛选标签配置
|
|
|
|
|
+ filterTabs: [
|
|
|
|
|
+ { key: 'all', emoji: '📋', label: '全部' },
|
|
|
|
|
+ { key: 'redeemable', emoji: '🎯', label: '可兑换' },
|
|
|
|
|
+ { key: 'pending', emoji: '⏳', label: '审核中' },
|
|
|
|
|
+ { key: 'completed', emoji: '✅', label: '已兑换' },
|
|
|
|
|
+ { key: 'rejected', emoji: '❌', label: '未通过' }
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ // 分类配置
|
|
|
|
|
+ categories: [
|
|
|
|
|
+ { value: '玩具', emoji: '🎮', label: '玩具' },
|
|
|
|
|
+ { value: '图书', emoji: '📚', label: '图书' },
|
|
|
|
|
+ { value: '美食', emoji: '🍕', label: '美食' },
|
|
|
|
|
+ { value: '娱乐', emoji: '🎪', label: '娱乐' },
|
|
|
|
|
+ { value: '活动', emoji: '🏕️', label: '活动' },
|
|
|
|
|
+ { value: '其他', emoji: '🎁', label: '其他' }
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ // 状态 → badge 颜色映射
|
|
|
|
|
+ statusBadgeMap: {
|
|
|
|
|
+ draft: 'warning',
|
|
|
|
|
+ active: 'accent',
|
|
|
|
|
+ pending_exchange: 'warning',
|
|
|
|
|
+ approved: 'accent',
|
|
|
|
|
+ exchanging: 'primary',
|
|
|
|
|
+ fulfilled: 'success',
|
|
|
|
|
+ rejected: 'error'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 状态 → emoji 映射
|
|
|
|
|
+ statusEmojiMap: {
|
|
|
|
|
+ draft: '⏳',
|
|
|
|
|
+ active: '🎯',
|
|
|
|
|
+ pending_exchange: '⏳',
|
|
|
|
|
+ approved: '📦',
|
|
|
|
|
+ exchanging: '🚚',
|
|
|
|
|
+ fulfilled: '✅',
|
|
|
|
|
+ rejected: '😢'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 分类 → emoji 映射
|
|
|
|
|
+ categoryEmojiMap: {
|
|
|
|
|
+ '玩具': '🎮',
|
|
|
|
|
+ '图书': '📚',
|
|
|
|
|
+ '美食': '🍕',
|
|
|
|
|
+ '娱乐': '🎪',
|
|
|
|
|
+ '活动': '🏕️',
|
|
|
|
|
+ '其他': '🎁'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 用于触发列表重绘(筛选切换时重新触发动画)
|
|
|
|
|
+ listKey: 0
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ formattedPoints() {
|
|
|
|
|
+ if (!this.currentPoints) return '0'
|
|
|
|
|
+ return this.currentPoints.toLocaleString()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ milestoneInfo() {
|
|
|
|
|
+ const step = 200
|
|
|
|
|
+ const current = this.currentPoints || 0
|
|
|
|
|
+ const next = Math.ceil(current / step) * step
|
|
|
|
|
+ const remaining = next - current
|
|
|
|
|
+ const progress = step > 0 ? (current % step) / step * 100 : 0
|
|
|
|
|
+ return { nextMilestone: next, remaining, progress }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ filteredWishes() {
|
|
|
|
|
+ return this.getFilteredWishes(this.activeFilter)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getActiveTabLabel() {
|
|
|
|
|
+ var activeFilter = this.activeFilter
|
|
|
|
|
+ var tab = this.filterTabs.find(function(t) { return t.key === activeFilter })
|
|
|
|
|
+ return tab ? tab.label : ''
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ canSubmit() {
|
|
|
|
|
+ var title = this.addForm.title || ''
|
|
|
|
|
+ return title.trim().length > 0 && !this.submittingWish
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
onShow() {
|
|
onShow() {
|
|
@@ -123,24 +368,25 @@ export default {
|
|
|
methods: {
|
|
methods: {
|
|
|
async loadData() {
|
|
async loadData() {
|
|
|
this.loading = true
|
|
this.loading = true
|
|
|
|
|
+ var currentRole
|
|
|
|
|
+ var results
|
|
|
try {
|
|
try {
|
|
|
- // 获取当前孩子ID
|
|
|
|
|
- const currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role')
|
|
|
|
|
|
|
+ currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role')
|
|
|
if (currentRole !== 'child') {
|
|
if (currentRole !== 'child') {
|
|
|
- // 如果不是孩子模式,跳转
|
|
|
|
|
uni.reLaunch({ url: '/pages/index/index' })
|
|
uni.reLaunch({ url: '/pages/index/index' })
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
this.childId = uni.getStorageSync('currentChildId') || uni.getStorageSync('userId')
|
|
this.childId = uni.getStorageSync('currentChildId') || uni.getStorageSync('userId')
|
|
|
-
|
|
|
|
|
- // 获取心愿列表
|
|
|
|
|
- const res = await getWishes({ childId: this.childId })
|
|
|
|
|
- this.wishes = res.data || []
|
|
|
|
|
-
|
|
|
|
|
- // 获取当前积分
|
|
|
|
|
- const balanceRes = await getPointsBalance(this.childId)
|
|
|
|
|
- this.currentPoints = (balanceRes.data && balanceRes.data.balance) || 0
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 并行获取心愿列表和积分
|
|
|
|
|
+ results = await Promise.all([
|
|
|
|
|
+ getWishes({ childId: this.childId }),
|
|
|
|
|
+ getPointsBalance(this.childId)
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ this.wishes = (results[0] && results[0].data) || []
|
|
|
|
|
+ this.currentPoints = (results[1] && results[1].data && results[1].data.balance) || 0
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.error('加载心愿列表失败', e)
|
|
console.error('加载心愿列表失败', e)
|
|
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
|
@@ -148,240 +394,690 @@ export default {
|
|
|
this.loading = false
|
|
this.loading = false
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- async requestExchange(wish) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 筛选切换(触发重动画)
|
|
|
|
|
+ onFilterChange(key) {
|
|
|
|
|
+ this.activeFilter = key
|
|
|
|
|
+ this.listKey++
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getFilteredWishes(filterKey) {
|
|
|
|
|
+ var list = this.wishes || []
|
|
|
|
|
+ if (filterKey === 'all') return list
|
|
|
|
|
+ if (filterKey === 'redeemable') {
|
|
|
|
|
+ return list.filter(function(w) {
|
|
|
|
|
+ return w.status === 'active'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filterKey === 'pending') {
|
|
|
|
|
+ return list.filter(function(w) {
|
|
|
|
|
+ return w.status === 'draft' || w.status === 'pending_exchange'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filterKey === 'completed') {
|
|
|
|
|
+ return list.filter(function(w) {
|
|
|
|
|
+ return w.status === 'fulfilled' || w.status === 'exchanging' || w.status === 'approved'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filterKey === 'rejected') {
|
|
|
|
|
+ return list.filter(function(w) {
|
|
|
|
|
+ return w.status === 'rejected'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ return list
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getFilterCount(filterKey) {
|
|
|
|
|
+ return this.getFilteredWishes(filterKey).length
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ canRedeem(wish) {
|
|
|
|
|
+ return this.currentPoints >= (wish.pointsRequired || 0)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getCategoryEmoji(category) {
|
|
|
|
|
+ var map = this.categoryEmojiMap
|
|
|
|
|
+ if (category && map[category]) {
|
|
|
|
|
+ return map[category]
|
|
|
|
|
+ }
|
|
|
|
|
+ return '🎁'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getStatusBadgeVariant(status) {
|
|
|
|
|
+ var map = this.statusBadgeMap
|
|
|
|
|
+ return (status && map[status]) || 'muted'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ getStatusEmoji(status) {
|
|
|
|
|
+ var map = this.statusEmojiMap
|
|
|
|
|
+ return (status && map[status]) || '📋'
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 兑换操作
|
|
|
|
|
+ onRedeem(wish) {
|
|
|
|
|
+ var self = this
|
|
|
uni.showModal({
|
|
uni.showModal({
|
|
|
- title: '申请兑换',
|
|
|
|
|
- content: `确定要兑换"${wish.title}"吗?\n将消耗${wish.pointsRequired}积分`,
|
|
|
|
|
- success: async (res) => {
|
|
|
|
|
|
|
+ title: '兑换心愿',
|
|
|
|
|
+ content: '确定要兑换"' + wish.title + '"吗?\n将消耗 ' + wish.pointsRequired + ' 积分',
|
|
|
|
|
+ success: function(res) {
|
|
|
if (res.confirm) {
|
|
if (res.confirm) {
|
|
|
- try {
|
|
|
|
|
- await requestWishExchange(wish.id)
|
|
|
|
|
- uni.showToast({ title: '已提交申请', icon: 'success' })
|
|
|
|
|
- this.loadData()
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- uni.showToast({ title: e.message || '申请失败', icon: 'none' })
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ self.doRedeem(wish)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- async confirmReceived(wish) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ async doRedeem(wish) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await requestWishExchange(wish.id)
|
|
|
|
|
+ uni.showToast({ title: '已提交申请 🎉', icon: 'success' })
|
|
|
|
|
+ this.loadData()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ uni.showToast({ title: e.message || '申请失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 确认收货
|
|
|
|
|
+ onConfirmReceived(wish) {
|
|
|
|
|
+ var self = this
|
|
|
uni.showModal({
|
|
uni.showModal({
|
|
|
title: '确认收货',
|
|
title: '确认收货',
|
|
|
- content: `确定已收到"${wish.title}"吗?`,
|
|
|
|
|
- success: async (res) => {
|
|
|
|
|
|
|
+ content: '确定已收到"' + wish.title + '"吗?',
|
|
|
|
|
+ success: function(res) {
|
|
|
|
|
+ if (res.confirm) {
|
|
|
|
|
+ self.doConfirmReceived(wish)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ async doConfirmReceived(wish) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await confirmWishReceived(wish.id)
|
|
|
|
|
+ uni.showToast({ title: '心愿完成!🎉', icon: 'success' })
|
|
|
|
|
+ this.loadData()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ uni.showToast({ title: '确认失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 删除/取消心愿
|
|
|
|
|
+ onDeleteWish(wish) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '取消心愿',
|
|
|
|
|
+ content: '确定要取消"' + wish.title + '"吗?',
|
|
|
|
|
+ success: function(res) {
|
|
|
if (res.confirm) {
|
|
if (res.confirm) {
|
|
|
- try {
|
|
|
|
|
- await confirmWishReceived(wish.id)
|
|
|
|
|
- uni.showToast({ title: '心愿已完成!', icon: 'success' })
|
|
|
|
|
- this.loadData()
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- uni.showToast({ title: '确认失败', icon: 'none' })
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ self.doDeleteWish(wish)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- goCreate() {
|
|
|
|
|
- uni.navigateTo({ url: '/pages/wishes/create' })
|
|
|
|
|
|
|
+
|
|
|
|
|
+ async doDeleteWish(wish) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await cancelWish(wish.id, '不想兑换了')
|
|
|
|
|
+ uni.showToast({ title: '已取消', icon: 'success' })
|
|
|
|
|
+ this.loadData()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ uni.showToast({ title: '取消失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 添加心愿弹窗
|
|
|
|
|
+ closeAddModal() {
|
|
|
|
|
+ this.showAddModal = false
|
|
|
|
|
+ this.resetAddForm()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ resetAddForm() {
|
|
|
|
|
+ this.addForm = {
|
|
|
|
|
+ title: '',
|
|
|
|
|
+ category: '',
|
|
|
|
|
+ emoji: '🎁',
|
|
|
|
|
+ pointsRequired: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ onTitleInput() {
|
|
|
|
|
+ // 仅用于触发字符计数更新(vue 双向绑定已处理)
|
|
|
},
|
|
},
|
|
|
-
|
|
|
|
|
- goDetail(id) {
|
|
|
|
|
- uni.navigateTo({ url: `/pages/wishes/detail?id=${id}` })
|
|
|
|
|
|
|
+
|
|
|
|
|
+ async onAddWish() {
|
|
|
|
|
+ if (!this.canSubmit) return
|
|
|
|
|
+
|
|
|
|
|
+ var title = (this.addForm.title || '').trim()
|
|
|
|
|
+ if (!title) {
|
|
|
|
|
+ uni.showToast({ title: '请输入心愿名称', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.submittingWish = true
|
|
|
|
|
+ var data
|
|
|
|
|
+ try {
|
|
|
|
|
+ data = {
|
|
|
|
|
+ childId: this.childId,
|
|
|
|
|
+ title: title,
|
|
|
|
|
+ category: this.addForm.category || '其他'
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果填写了积分建议,一并提交
|
|
|
|
|
+ if (this.addForm.pointsRequired) {
|
|
|
|
|
+ data.pointsRequired = parseInt(this.addForm.pointsRequired, 10) || 0
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await createWish(data)
|
|
|
|
|
+ uni.showToast({ title: '心愿已许下 ✨', icon: 'success' })
|
|
|
|
|
+ this.closeAddModal()
|
|
|
|
|
+ this.loadData()
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ uni.showToast({ title: e.message || '添加失败', icon: 'none' })
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.submittingWish = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
-.container {
|
|
|
|
|
- padding: 30rpx;
|
|
|
|
|
- padding-bottom: 150rpx;
|
|
|
|
|
|
|
+/* ============================================================
|
|
|
|
|
+ 页面容器
|
|
|
|
|
+ ============================================================ */
|
|
|
|
|
+.page {
|
|
|
min-height: 100vh;
|
|
min-height: 100vh;
|
|
|
- background: #f5f5f5;
|
|
|
|
|
|
|
+ background: var(--bg, #FFF7ED);
|
|
|
|
|
+ padding-bottom: 180rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ============================================================
|
|
|
|
|
+ 积分卡片
|
|
|
|
|
+ ============================================================ */
|
|
|
|
|
+.points-card {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ margin: 24rpx 32rpx 16rpx;
|
|
|
|
|
+ border-radius: 32rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-card__glow {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: -50%;
|
|
|
|
|
+ right: -30%;
|
|
|
|
|
+ width: 300rpx;
|
|
|
|
|
+ height: 300rpx;
|
|
|
|
|
+ background: radial-gradient(circle, rgba(249, 115, 22, 0.15), transparent 70%);
|
|
|
|
|
+ pointer-events: none;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 空状态 */
|
|
|
|
|
-.empty-state {
|
|
|
|
|
|
|
+.points-card__inner {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ padding: 32rpx 36rpx 28rpx;
|
|
|
|
|
+ border-radius: 32rpx;
|
|
|
|
|
+ border: 4rpx solid var(--border, #FED7AA);
|
|
|
|
|
+ background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #FFF2E4));
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset -4rpx -4rpx 12rpx rgba(249, 115, 22, 0.05),
|
|
|
|
|
+ 6rpx 6rpx 18rpx rgba(249, 115, 22, 0.10);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-card__header {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
|
|
- padding: 150rpx 60rpx;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.empty-icon {
|
|
|
|
|
- font-size: 120rpx;
|
|
|
|
|
- margin-bottom: 30rpx;
|
|
|
|
|
|
|
+.points-card__icon {
|
|
|
|
|
+ font-size: 36rpx;
|
|
|
|
|
+ margin-right: 12rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.empty-title {
|
|
|
|
|
- font-size: 36rpx;
|
|
|
|
|
- font-weight: bold;
|
|
|
|
|
- color: #333;
|
|
|
|
|
|
|
+.points-card__label {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--text-secondary, #6B5A4A);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.points-card__value {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: 72rpx;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ color: var(--color-primary, #F97316);
|
|
|
|
|
+ line-height: 1.1;
|
|
|
margin-bottom: 20rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
|
+ letter-spacing: -2rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.empty-desc {
|
|
|
|
|
- font-size: 28rpx;
|
|
|
|
|
- color: #666;
|
|
|
|
|
- margin-bottom: 40rpx;
|
|
|
|
|
|
|
+/* 里程碑进度条 */
|
|
|
|
|
+.milestone {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.btn-add {
|
|
|
|
|
- background: linear-gradient(135deg, #FF6B6B, #FF8E53);
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- border-radius: 40rpx;
|
|
|
|
|
- padding: 20rpx 60rpx;
|
|
|
|
|
- font-size: 30rpx;
|
|
|
|
|
|
|
+.milestone__track {
|
|
|
|
|
+ height: 12rpx;
|
|
|
|
|
+ background: var(--border-light, #FFEDD5);
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 心愿卡片 */
|
|
|
|
|
-.wish-card {
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- border-radius: 20rpx;
|
|
|
|
|
- padding: 30rpx;
|
|
|
|
|
- margin-bottom: 20rpx;
|
|
|
|
|
- box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
|
|
|
|
|
|
|
+.milestone__fill {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ background: linear-gradient(90deg, var(--color-primary-light, #FB923C), var(--color-primary, #F97316));
|
|
|
|
|
+ transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.milestone__text {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: var(--text-secondary, #6B5A4A);
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ============================================================
|
|
|
|
|
+ 筛选标签
|
|
|
|
|
+ ============================================================ */
|
|
|
|
|
+.filter-tabs {
|
|
|
|
|
+ margin: 8rpx 0 16rpx;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ padding: 0 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-tabs__inner {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+ padding: 4rpx 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-tab {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 16rpx 28rpx;
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ background: var(--surface, #FFFFFF);
|
|
|
|
|
+ border: 2rpx solid var(--border-light, #FFEDD5);
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: var(--text-secondary, #6B5A4A);
|
|
|
|
|
+ transition: all 0.25s ease;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-tab--active {
|
|
|
|
|
+ background: var(--color-primary, #F97316);
|
|
|
|
|
+ border-color: var(--color-primary, #F97316);
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ box-shadow: 0 4rpx 16rpx rgba(249, 115, 22, 0.25);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-card.draft {
|
|
|
|
|
- border-left: 8rpx solid #999;
|
|
|
|
|
|
|
+.filter-tab:active {
|
|
|
|
|
+ transform: scale(0.96);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-card.active {
|
|
|
|
|
- border-left: 8rpx solid #10B981;
|
|
|
|
|
|
|
+.filter-tab__count {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ min-width: 32rpx;
|
|
|
|
|
+ height: 32rpx;
|
|
|
|
|
+ padding: 0 8rpx;
|
|
|
|
|
+ margin-left: 8rpx;
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.25);
|
|
|
|
|
+ color: inherit;
|
|
|
|
|
+ font-size: 20rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-card.pending_exchange {
|
|
|
|
|
- border-left: 8rpx solid #F59E0B;
|
|
|
|
|
|
|
+.filter-tab--active .filter-tab__count {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.3);
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-card.approved,
|
|
|
|
|
-.wish-card.exchanging {
|
|
|
|
|
- border-left: 8rpx solid #3B82F6;
|
|
|
|
|
|
|
+/* ============================================================
|
|
|
|
|
+ 心愿列表
|
|
|
|
|
+ ============================================================ */
|
|
|
|
|
+.wish-list {
|
|
|
|
|
+ padding: 8rpx 32rpx 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-card.fulfilled {
|
|
|
|
|
- border-left: 8rpx solid #8B5CF6;
|
|
|
|
|
- opacity: 0.8;
|
|
|
|
|
|
|
+.wish-item {
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-header {
|
|
|
|
|
|
|
+.wish-list__spacer {
|
|
|
|
|
+ height: 160rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ============================================================
|
|
|
|
|
+ 心愿卡片(内部布局)
|
|
|
|
|
+ ============================================================ */
|
|
|
|
|
+.wish-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: stretch;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ min-height: 160rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 左侧 emoji 区域 */
|
|
|
|
|
+.wish-card__icon-wrap {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 96rpx;
|
|
|
|
|
+ min-height: 120rpx;
|
|
|
|
|
+ margin-right: 20rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.wish-card__emoji {
|
|
|
|
|
+ font-size: 56rpx;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 中间内容区域 */
|
|
|
|
|
+.wish-card__body {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ padding-right: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.wish-card__top {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- margin-bottom: 15rpx;
|
|
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+ gap: 12rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-title {
|
|
|
|
|
|
|
+.wish-card__title {
|
|
|
font-size: 32rpx;
|
|
font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
|
|
- color: #333;
|
|
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: var(--text, #3D2E1E);
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-status {
|
|
|
|
|
|
|
+/* 积分行 */
|
|
|
|
|
+.wish-card__points-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
|
|
+ gap: 6rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.wish-card__points-label {
|
|
|
font-size: 24rpx;
|
|
font-size: 24rpx;
|
|
|
- padding: 6rpx 16rpx;
|
|
|
|
|
- border-radius: 20rpx;
|
|
|
|
|
- background: #f0f0f0;
|
|
|
|
|
- color: #666;
|
|
|
|
|
|
|
+ color: var(--text-secondary, #6B5A4A);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.wish-status.active { background: #D1FAE5; color: #059669; }
|
|
|
|
|
-.wish-status.pending_exchange { background: #FEF3C7; color: #D97706; }
|
|
|
|
|
-.wish-status.exchanging { background: #DBEAFE; color: #2563EB; }
|
|
|
|
|
-.wish-status.fulfilled { background: #EDE9FE; color: #7C3AED; }
|
|
|
|
|
|
|
+.wish-card__points-value {
|
|
|
|
|
+ font-size: 40rpx;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ color: var(--color-accent, #0EA5E9);
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-.category-tag {
|
|
|
|
|
- font-size: 22rpx;
|
|
|
|
|
- color: #666;
|
|
|
|
|
- background: #f5f5f5;
|
|
|
|
|
- padding: 4rpx 12rpx;
|
|
|
|
|
- border-radius: 10rpx;
|
|
|
|
|
|
|
+.wish-card__points-unit {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: var(--color-accent, #0EA5E9);
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 进度条 */
|
|
|
|
|
-.progress-section {
|
|
|
|
|
- margin: 20rpx 0;
|
|
|
|
|
|
|
+/* 操作区域 */
|
|
|
|
|
+.wish-card__actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-bar {
|
|
|
|
|
- height: 16rpx;
|
|
|
|
|
- background: #e5e7eb;
|
|
|
|
|
- border-radius: 8rpx;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
|
|
+/* 状态文本 */
|
|
|
|
|
+.wish-card__status-text {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-fill {
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- background: linear-gradient(90deg, #10B981, #34D399);
|
|
|
|
|
- border-radius: 8rpx;
|
|
|
|
|
- transition: width 0.3s;
|
|
|
|
|
|
|
+.wish-card__status-emoji {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-text {
|
|
|
|
|
|
|
+.wish-card__status-msg {
|
|
|
font-size: 24rpx;
|
|
font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
|
|
- margin-top: 10rpx;
|
|
|
|
|
- display: block;
|
|
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: var(--text-secondary, #6B5A4A);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.wish-card__status-text--warning .wish-card__status-msg {
|
|
|
|
|
+ color: var(--color-warning, #F59E0B);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 操作按钮 */
|
|
|
|
|
-.wish-actions {
|
|
|
|
|
- margin-top: 20rpx;
|
|
|
|
|
|
|
+.wish-card__status-text--info .wish-card__status-msg {
|
|
|
|
|
+ color: var(--color-accent, #0EA5E9);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.wish-card__status-text--success .wish-card__status-msg {
|
|
|
|
|
+ color: var(--color-success, #22C55E);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.wish-card__status-text--error .wish-card__status-msg {
|
|
|
|
|
+ color: var(--color-error, #EF4444);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 右侧删除按钮 */
|
|
|
|
|
+.wish-card__delete {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 48rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-left: 8rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.btn-exchange {
|
|
|
|
|
- background: linear-gradient(135deg, #FF6B6B, #FF8E53);
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- font-size: 26rpx;
|
|
|
|
|
- padding: 12rpx 30rpx;
|
|
|
|
|
- border-radius: 30rpx;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
|
|
+.wish-card__delete-icon {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: var(--muted, #94A3B8);
|
|
|
|
|
+ padding: 12rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.btn-waiting {
|
|
|
|
|
- background: #e5e7eb;
|
|
|
|
|
- color: #9ca3af;
|
|
|
|
|
- font-size: 26rpx;
|
|
|
|
|
- padding: 12rpx 30rpx;
|
|
|
|
|
- border-radius: 30rpx;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
|
|
+.wish-card__delete-icon:active {
|
|
|
|
|
+ opacity: 0.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ============================================================
|
|
|
|
|
+ 浮动操作按钮 (FAB)
|
|
|
|
|
+ ============================================================ */
|
|
|
|
|
+.fab {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ right: 40rpx;
|
|
|
|
|
+ bottom: 80rpx;
|
|
|
|
|
+ width: 110rpx;
|
|
|
|
|
+ height: 110rpx;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ background: linear-gradient(145deg, var(--color-primary-light, #FB923C), var(--color-primary, #F97316));
|
|
|
|
|
+ border: 4rpx solid var(--border, #FED7AA);
|
|
|
|
|
+ box-shadow:
|
|
|
|
|
+ inset -3rpx -3rpx 10rpx rgba(255, 255, 255, 0.3),
|
|
|
|
|
+ 4rpx 4rpx 16rpx rgba(249, 115, 22, 0.3);
|
|
|
|
|
+ z-index: 100;
|
|
|
|
|
+ transition: transform 0.15s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.fab:active {
|
|
|
|
|
+ transform: scale(0.92);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.fab__pulse {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: -8rpx;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: rgba(249, 115, 22, 0.15);
|
|
|
|
|
+ animation: pulseGlow 2s ease-in-out infinite;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.fab__icon {
|
|
|
|
|
+ font-size: 56rpx;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ font-weight: 300;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ margin-top: -4rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ============================================================
|
|
|
|
|
+ 添加心愿弹窗
|
|
|
|
|
+ ============================================================ */
|
|
|
|
|
+.modal-overlay {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.45);
|
|
|
|
|
+ z-index: 999;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ animation: fadeIn 0.2s ease both;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-height: 80vh;
|
|
|
|
|
+ background: var(--surface, #FFFFFF);
|
|
|
|
|
+ border-radius: 40rpx 40rpx 0 0;
|
|
|
|
|
+ padding: 40rpx 36rpx;
|
|
|
|
|
+ padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
|
|
|
|
+ animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal__header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 36rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal__title {
|
|
|
|
|
+ font-size: 36rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: var(--text, #3D2E1E);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal__close {
|
|
|
|
|
+ font-size: 36rpx;
|
|
|
|
|
+ color: var(--muted, #94A3B8);
|
|
|
|
|
+ padding: 12rpx;
|
|
|
|
|
+ min-width: 60rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal__close:active {
|
|
|
|
|
+ opacity: 0.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 表单组 */
|
|
|
|
|
+.form-group {
|
|
|
|
|
+ margin-bottom: 32rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.btn-confirm {
|
|
|
|
|
- background: linear-gradient(135deg, #3B82F6, #2563EB);
|
|
|
|
|
- color: #fff;
|
|
|
|
|
|
|
+.form-group__label {
|
|
|
|
|
+ display: block;
|
|
|
font-size: 26rpx;
|
|
font-size: 26rpx;
|
|
|
- padding: 12rpx 30rpx;
|
|
|
|
|
- border-radius: 30rpx;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--text, #3D2E1E);
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 分类/表情选择器 */
|
|
|
|
|
+.emoji-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.emoji-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 20rpx 12rpx;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ background: var(--bg, #FFF7ED);
|
|
|
|
|
+ border: 2rpx solid var(--border-light, #FFEDD5);
|
|
|
|
|
+ transition: all 0.2s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.emoji-item--active {
|
|
|
|
|
+ background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #FFF2E4));
|
|
|
|
|
+ border-color: var(--color-primary, #F97316);
|
|
|
|
|
+ box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.15);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.emoji-item:active {
|
|
|
|
|
+ transform: scale(0.95);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.emoji-item__icon {
|
|
|
|
|
+ font-size: 48rpx;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+ line-height: 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.fulfilled-text {
|
|
|
|
|
|
|
+.emoji-item__label {
|
|
|
font-size: 24rpx;
|
|
font-size: 24rpx;
|
|
|
- color: #8B5CF6;
|
|
|
|
|
|
|
+ color: var(--text-secondary, #6B5A4A);
|
|
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 固定添加按钮 */
|
|
|
|
|
-.btn-add-fixed {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- bottom: 30rpx;
|
|
|
|
|
- left: 30rpx;
|
|
|
|
|
- right: 30rpx;
|
|
|
|
|
- background: linear-gradient(135deg, #FF6B6B, #FF8E53);
|
|
|
|
|
- color: #fff;
|
|
|
|
|
|
|
+.emoji-item--active .emoji-item__label {
|
|
|
|
|
+ color: var(--color-primary, #F97316);
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 输入框 */
|
|
|
|
|
+.form-input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: var(--bg, #FFF7ED);
|
|
|
|
|
+ border: 2rpx solid var(--border-light, #FFEDD5);
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ padding: 24rpx 28rpx;
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
|
|
+ color: var(--text, #3D2E1E);
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-input--number {
|
|
|
|
|
+ padding-right: 80rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-input__wrapper {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-input__suffix {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ right: 28rpx;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ transform: translateY(-50%);
|
|
|
font-size: 32rpx;
|
|
font-size: 32rpx;
|
|
|
- padding: 24rpx;
|
|
|
|
|
- border-radius: 50rpx;
|
|
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(255,107,107,0.3);
|
|
|
|
|
|
|
+ color: var(--color-accent, #0EA5E9);
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.form-input__counter {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: var(--muted, #94A3B8);
|
|
|
|
|
+ margin-top: 8rpx;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|