Quellcode durchsuchen

feat: 心愿模块+任务管理模块更新

Ultraworked with Sisyphus
User vor 5 Monaten
Ursprung
Commit
156e1faccc

+ 1 - 1
cfc-frontend/pages/tasks/create-task.vue

@@ -325,5 +325,5 @@ this.form.needReview = e.detail.value
 .child-checkbox { display: flex; align-items: center; padding: 16rpx 0; border-bottom: 1rpx solid #f0f0f0; }
 .child-checkbox:last-child { border-bottom: none; }
 .child-name { font-size: 28rpx; color: #333; margin-left: 10rpx; }
-.btn-primary { background: #FF6B6B; color: #fff; border-radius: 20rpx; margin-top: 40rpx; }
+.btn-primary { background: #F97316; color: #fff; border-radius: 20rpx; margin-top: 40rpx; }
 </style>

+ 254 - 44
cfc-frontend/pages/tasks/review.vue

@@ -1,46 +1,96 @@
 <template>
-  <view class="container">
-    <view class="tabs">
-      <view class="tab" :class="{ active: tab === 'pending' }" @click="tab = 'pending'">
-        待审核({{ pendingCount }})
+  <view class="review-page">
+    <!-- Tab Bar -->
+    <view class="tab-bar">
+      <view class="tab-bar__item" :class="{ 'tab-bar__item--active': tab === 'pending' }" @click="switchTab('pending')">
+        <text class="tab-bar__label">📋 待审核</text>
+        <view class="tab-bar__badge" v-if="pendingCount > 0">
+          <text class="tab-bar__badge-text">{{ pendingCount > 99 ? '99+' : pendingCount }}</text>
+        </view>
       </view>
-      <view class="tab" :class="{ active: tab === 'history' }" @click="tab = 'history'">
-        审核历史
+      <view class="tab-bar__item" :class="{ 'tab-bar__item--active': tab === 'history' }" @click="switchTab('history')">
+        <text class="tab-bar__label">📜 审核历史</text>
       </view>
     </view>
-    
-    <view class="task-list">
-      <view class="task-item" v-for="task in taskList" :key="task.id">
-        <view class="task-info">
-          <text class="task-title">{{ task.title }}</text>
-          <text class="task-desc">{{ task.description }}</text>
-          <text class="task-meta">提供者: {{ task.creatorRole === 'parent' ? '家长' : '孩子' }} | 积分: {{ task.points }}</text>
-        </view>
-        <view class="task-actions" v-if="tab === 'pending'">
-          <button class="btn-pass" @click="handleApprove(task.id)">通过</button>
-          <button class="btn-reject" @click="handleReject(task.id)">拒绝</button>
-        </view>
-        <view class="task-status" v-else>
-          <text :class="'status-' + task.status">{{ task.status === 'approved' ? '已通过' : '已拒绝' }}</text>
-        </view>
+
+    <!-- Loading State -->
+    <BaseLoading :loading="loading" text="加载中..." />
+
+    <!-- Task List -->
+    <view class="task-list" v-if="!loading && taskList.length > 0">
+      <view v-for="(task, index) in taskList" :key="task.id" class="task-list__item">
+        <!-- Pending Tab - Review Cards with Actions -->
+        <PlayfulCard v-if="tab === 'pending'" variant="flat" shadow="sm"
+          :class="'animate-slide-up animate-stagger-' + ((index % 10) + 1)">
+          <view class="card-body">
+            <text class="card-title">{{ task.title }}</text>
+            <text class="card-desc">{{ task.description }}</text>
+            <view class="card-meta-row">
+              <text class="card-provider">提供者: {{ task.creatorRole === 'parent' ? '家长' : '孩子' }}</text>
+              <text class="card-points">⭐ +{{ task.points }}</text>
+            </view>
+          </view>
+          <view slot="actions" class="card-actions">
+            <PlayfulButton variant="success" size="sm" @click="handleApprove(task.id)">✓ 通过</PlayfulButton>
+            <PlayfulButton variant="error" size="sm" @click="handleReject(task.id)">✗ 拒绝</PlayfulButton>
+          </view>
+        </PlayfulCard>
+
+        <!-- History Tab - Review Cards with Status -->
+        <PlayfulCard v-if="tab === 'history'" variant="flat" shadow="sm"
+          :class="'animate-slide-up animate-stagger-' + ((index % 10) + 1)">
+          <view class="card-body">
+            <view class="card-header-row">
+              <text class="card-title card-title--flex">{{ task.title }}</text>
+              <BaseBadge v-if="task.status === 'approved'" variant="success" size="sm">✅ 已通过</BaseBadge>
+              <BaseBadge v-if="task.status === 'rejected'" variant="error" size="sm">❌ 已拒绝</BaseBadge>
+            </view>
+            <text class="card-desc">{{ task.description }}</text>
+            <view class="card-meta-row">
+              <text class="card-provider">提供者: {{ task.creatorRole === 'parent' ? '家长' : '孩子' }}</text>
+              <text class="card-points">⭐ +{{ task.points }}</text>
+            </view>
+            <text class="card-time" v-if="task.reviewTime">审核时间: {{ task.reviewTime }}</text>
+          </view>
+        </PlayfulCard>
       </view>
     </view>
-    
-    <view class="empty" v-if="taskList.length === 0">
-      <text>暂无任务</text>
-    </view>
+
+    <!-- Empty States -->
+    <BaseEmpty v-if="!loading && taskList.length === 0 && tab === 'pending'"
+      icon="🎉"
+      title="没有待审核的任务"
+      description="家长可以休息一下啦~"
+    />
+    <BaseEmpty v-if="!loading && taskList.length === 0 && tab === 'history'"
+      icon="📜"
+      title="还没有审核记录"
+    />
   </view>
 </template>
 
 <script>
 import { getPendingReviewTasks, approveTask, rejectTask, getTaskHistory } 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 {
+  components: {
+    PlayfulCard,
+    PlayfulButton,
+    BaseBadge,
+    BaseEmpty,
+    BaseLoading
+  },
   data() {
     return {
       tab: 'pending',
       taskList: [],
-      pendingCount: 0
+      pendingCount: 0,
+      loading: false
     }
   },
   watch: {
@@ -52,7 +102,13 @@ export default {
     this.loadTasks()
   },
   methods: {
+    switchTab(tabName) {
+      if (this.tab !== tabName) {
+        this.tab = tabName
+      }
+    },
     async loadTasks() {
+      this.loading = true
       try {
         if (this.tab === 'pending') {
           const res = await getPendingReviewTasks()
@@ -64,6 +120,8 @@ export default {
         }
       } catch (e) {
         console.error(e)
+      } finally {
+        this.loading = false
       }
     },
     async handleApprove(taskId) {
@@ -89,21 +147,173 @@ export default {
 </script>
 
 <style scoped>
-.container { padding: 30rpx; }
-.tabs { display: flex; background: #fff; border-radius: 20rpx; margin-bottom: 20rpx; }
-.tab { flex: 1; text-align: center; padding: 20rpx; color: #666; }
-.tab.active { color: #FF6B6B; border-bottom: 2rpx solid #FF6B6B; }
-.task-list { background: #fff; border-radius: 20rpx; }
-.task-item { display: flex; justify-content: space-between; padding: 30rpx; border-bottom: 1rpx solid #f0f0f0; }
-.task-info { flex: 1; }
-.task-title { font-size: 30rpx; font-weight: bold; color: #333; display: block; }
-.task-desc { font-size: 26rpx; color: #666; display: block; margin: 10rpx 0; }
-.task-meta { font-size: 24rpx; color: #999; }
-.task-actions { display: flex; gap: 10rpx; }
-.btn-pass, .btn-reject { font-size: 24rpx; padding: 10rpx 20rpx; border-radius: 10rpx; margin: 0; }
-.btn-pass { background: #52C41A; color: #fff; }
-.btn-reject { background: #FF4D4F; color: #fff; }
-.status-approved { color: #52C41A; }
-.status-rejected { color: #FF4D4F; }
-.empty { text-align: center; padding: 100rpx; color: #999; }
-</style>
+.review-page {
+  min-height: 100vh;
+  background: var(--bg, #FFF7ED);
+  padding: 24rpx var(--page-padding, 32rpx);
+}
+
+/* ============================================================
+   Tab Bar
+   ============================================================ */
+.tab-bar {
+  position: relative;
+  display: flex;
+  background: var(--surface, #FFFFFF);
+  border-radius: var(--radius-md, 20rpx);
+  padding: 8rpx;
+  margin-bottom: 32rpx;
+  box-shadow: var(--shadow-sm, 0 4rpx 12rpx rgba(249, 115, 22, 0.06));
+  border: 1px solid var(--border-light, #FFEDD5);
+}
+
+.tab-bar__item {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 22rpx 16rpx;
+  border-radius: var(--radius-sm, 12rpx);
+  position: relative;
+  z-index: 2;
+  transition: all var(--transition-base, 0.25s ease);
+  cursor: pointer;
+}
+
+.tab-bar__item:active {
+  transform: scale(0.97);
+}
+
+.tab-bar__item--active {
+  background: var(--bg, #FFF7ED);
+}
+
+.tab-bar__label {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: var(--text-secondary, #6B5A4A);
+  transition: color var(--transition-base, 0.25s ease);
+}
+
+.tab-bar__item--active .tab-bar__label {
+  color: var(--color-primary, #F97316);
+}
+
+.tab-bar__item--active::after {
+  content: '';
+  position: absolute;
+  bottom: 6rpx;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 40%;
+  height: 4rpx;
+  background: var(--color-primary, #F97316);
+  border-radius: 4rpx;
+}
+
+.tab-bar__badge {
+  background: var(--color-primary, #F97316);
+  border-radius: 999rpx;
+  min-width: 36rpx;
+  height: 36rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 8rpx;
+  margin-left: 8rpx;
+  box-shadow: 0 2rpx 8rpx rgba(249, 115, 22, 0.3);
+}
+
+.tab-bar__badge-text {
+  font-size: 20rpx;
+  font-weight: 700;
+  color: #FFFFFF;
+  line-height: 1;
+}
+
+/* ============================================================
+   Task List
+   ============================================================ */
+.task-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+  padding-bottom: 32rpx;
+}
+
+/* ============================================================
+   Card Body (inside PlayfulCard default slot)
+   ============================================================ */
+.card-body {
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+}
+
+.card-title {
+  font-size: 30rpx;
+  font-weight: 700;
+  color: var(--text, #3D2E1E);
+  line-height: 1.4;
+}
+
+.card-title--flex {
+  flex: 1;
+  min-width: 0;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.card-desc {
+  font-size: 26rpx;
+  color: var(--text-secondary, #6B5A4A);
+  line-height: 1.5;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.card-header-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16rpx;
+}
+
+.card-meta-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 24rpx;
+}
+
+.card-provider {
+  color: var(--text-secondary, #6B5A4A);
+}
+
+.card-points {
+  color: var(--color-primary, #F97316);
+  font-weight: 600;
+}
+
+.card-time {
+  font-size: 22rpx;
+  color: var(--muted, #94A3B8);
+  margin-top: 4rpx;
+}
+
+/* ============================================================
+   Card Actions
+   ============================================================ */
+.card-actions {
+  display: flex;
+  gap: 16rpx;
+  width: 100%;
+}
+
+/* Deep selector to style PlayfulButton inside actions slot */
+.card-actions >>> .playful-btn {
+  flex: 1;
+}
+</style>

+ 6 - 6
cfc-frontend/pages/tasks/tasks.vue

@@ -538,7 +538,7 @@ export default {
 }
 
 .btn-batch {
-  background: #FF6B6B;
+  background: #F97316;
   color: #fff;
   font-size: 24rpx;
   padding: 10rpx 30rpx;
@@ -570,7 +570,7 @@ export default {
   font-size: 24rpx;
   padding: 10rpx 30rpx;
   border-radius: 30rpx;
-  background: #FF6B6B;
+  background: #F97316;
   color: #fff;
 }
 
@@ -604,7 +604,7 @@ export default {
 
 .task-item.selected {
   background: #FFF1F0;
-  border: 2rpx solid #FF6B6B;
+  border: 2rpx solid #F97316;
 }
 
 .task-checkbox {
@@ -637,7 +637,7 @@ export default {
 }
 
 .points {
-  color: #FF6B6B;
+  color: #F97316;
   font-size: 24rpx;
 }
 
@@ -647,7 +647,7 @@ export default {
 }
 
 .btn-complete {
-  background: #FF6B6B;
+  background: #F97316;
   color: #fff;
   font-size: 26rpx;
   padding: 10rpx 30rpx;
@@ -720,7 +720,7 @@ export default {
 }
 
 .form-label .required {
-  color: #FF6B6B;
+  color: #F97316;
 }
 
 .form-row {

+ 3 - 3
cfc-frontend/pages/wishes/create.vue

@@ -141,7 +141,7 @@ export default {
 }
 
 .required {
-  color: #FF6B6B;
+  color: #F97316;
 }
 
 .form-item input,
@@ -174,7 +174,7 @@ export default {
 }
 
 .category-item.active {
-  background: linear-gradient(135deg, #FF6B6B, #FF8E53);
+  background: linear-gradient(135deg, #F97316, #FB923C);
   color: #fff;
 }
 
@@ -201,7 +201,7 @@ export default {
 
 /* 提交按钮 */
 .btn-submit {
-  background: linear-gradient(135deg, #FF6B6B, #FF8E53);
+  background: linear-gradient(135deg, #F97316, #FB923C);
   color: #fff;
   font-size: 32rpx;
   padding: 24rpx;

+ 2 - 2
cfc-frontend/pages/wishes/detail.vue

@@ -280,7 +280,7 @@ export default {
 }
 
 .points-section {
-  background: linear-gradient(135deg, #FF6B6B, #FF8E53);
+  background: linear-gradient(135deg, #F97316, #FB923C);
   border-radius: 15rpx;
   padding: 25rpx;
   color: #fff;
@@ -344,7 +344,7 @@ export default {
 }
 
 .btn-primary {
-  background: linear-gradient(135deg, #FF6B6B, #FF8E53);
+  background: linear-gradient(135deg, #F97316, #FB923C);
   color: #fff;
   font-size: 32rpx;
   padding: 24rpx;

+ 949 - 253
cfc-frontend/pages/wishes/index.vue

@@ -1,120 +1,365 @@
 <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 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>
+    </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>
 
-        <!-- 积分进度(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>
-          <text class="progress-text">
-            {{ currentPoints }}/{{ wish.pointsRequired }}积分
-          </text>
         </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 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>
-
-    <!-- 添加按钮 -->
-    <button class="btn-add-fixed" @click="goCreate" v-if="wishes.length > 0">
-      + 添加心愿
-    </button>
   </view>
 </template>
 
 <script>
-import { 
-  getWishes, 
-  requestWishExchange, 
+import {
+  getWishes,
+  createWish,
+  requestWishExchange,
   confirmWishReceived,
-  getPointsBalance 
+  cancelWish,
+  getPointsBalance
 } 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 {
+  components: {
+    PlayfulCard,
+    PlayfulButton,
+    BaseBadge,
+    BaseEmpty,
+    BaseLoading
+  },
   data() {
     return {
       wishes: [],
       currentPoints: 0,
       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() {
@@ -123,24 +368,25 @@ export default {
   methods: {
     async loadData() {
       this.loading = true
+      var currentRole
+      var results
       try {
-        // 获取当前孩子ID
-        const currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role')
+        currentRole = uni.getStorageSync('currentRole') || uni.getStorageSync('role')
         if (currentRole !== 'child') {
-          // 如果不是孩子模式,跳转
           uni.reLaunch({ url: '/pages/index/index' })
           return
         }
-        
+
         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) {
         console.error('加载心愿列表失败', e)
         uni.showToast({ title: '加载失败', icon: 'none' })
@@ -148,240 +394,690 @@ export default {
         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({
-        title: '申请兑换',
-        content: `确定要兑换"${wish.title}"吗?\n将消耗${wish.pointsRequired}积分`,
-        success: async (res) => {
+        title: '兑换心愿',
+        content: '确定要兑换"' + wish.title + '"吗?\n将消耗 ' + wish.pointsRequired + ' 积分',
+        success: function(res) {
           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({
         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) {
-            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>
 
 <style scoped>
-.container {
-  padding: 30rpx;
-  padding-bottom: 150rpx;
+/* ============================================================
+   页面容器
+   ============================================================ */
+.page {
   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;
-  flex-direction: column;
   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;
+  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;
-  justify-content: space-between;
   align-items: center;
-  margin-bottom: 15rpx;
+  justify-content: space-between;
+  margin-bottom: 8rpx;
+  gap: 12rpx;
 }
 
-.wish-title {
+.wish-card__title {
   font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
+  font-weight: 700;
+  color: var(--text, #3D2E1E);
   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;
-  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;
-  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;
-  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;
-  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;
-  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;
-  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>