feat(PointsCollector): 重构积分收集组件并添加一键收取功能
- 重构积分收集组件布局和样式 - 实现一键收取功能并优化收集动画 - 移除冗余代码并提升性能 - 添加组件高度可配置属性
Showing
3 changed files
with
190 additions
and
249 deletions
| 1 | <template> | 1 | <template> |
| 2 | - <view class="points-collector"> | 2 | + <view class="points-collector" :style="{ height: height }"> |
| 3 | <!-- 中心圆形显示总积分 --> | 3 | <!-- 中心圆形显示总积分 --> |
| 4 | <view class="center-circle"> | 4 | <view class="center-circle"> |
| 5 | <view class="total-points"> | 5 | <view class="total-points"> |
| 6 | - <text class="points-number">{{ animatedTotalPoints }}</text> | 6 | + <text class="points-number">{{ animatedTotalPoints }}分</text> |
| 7 | - <text class="points-label">总积分</text> | 7 | + <text class="points-label">去兑换</text> |
| 8 | </view> | 8 | </view> |
| 9 | </view> | 9 | </view> |
| 10 | 10 | ||
| 11 | <!-- 周围漂浮的小圆圈 --> | 11 | <!-- 周围漂浮的小圆圈 --> |
| 12 | <view | 12 | <view |
| 13 | - v-for="(item, index) in floatingItems" | 13 | + v-for="(item) in floatingItems" |
| 14 | :key="item.id" | 14 | :key="item.id" |
| 15 | class="floating-item" | 15 | class="floating-item" |
| 16 | - :class="{ 'collecting': item.collecting, 'stacked': item.stacked }" | 16 | + :style="getItemStyle(item)" |
| 17 | - :style="getItemStyle(item, index)" | 17 | + @tap="collectItem(item)" |
| 18 | - @tap="collectItem(item, index)" | ||
| 19 | > | 18 | > |
| 20 | - <view class="item-content"> | 19 | + <view v-if="item.type === 'steps'" class="item-content"> |
| 21 | - <text class="item-value">{{ item.value }}</text> | 20 | + <text class="item-value">{{ item.steps }}步</text> |
| 22 | - <text class="item-type">{{ item.type === 'steps' ? '步' : '分' }}</text> | 21 | + <text class="item-type">{{ item.value }}分</text> |
| 23 | </view> | 22 | </view> |
| 24 | - <!-- 堆叠数量显示 --> | 23 | + <view v-else class="item-content"> |
| 25 | - <view v-if="item.stackCount > 1" class="stack-count"> | 24 | + <text class="item-value">奖励</text> |
| 26 | - <text>{{ item.stackCount }}</text> | 25 | + <text class="item-type">{{ item.value }}分</text> |
| 27 | </view> | 26 | </view> |
| 28 | </view> | 27 | </view> |
| 29 | 28 | ||
| 30 | <!-- 一键收取按钮 --> | 29 | <!-- 一键收取按钮 --> |
| 31 | - <view | 30 | + <!-- <view |
| 32 | - v-if="floatingItems.length > 3" | 31 | + v-if="floatingItems.length > 0" |
| 33 | class="collect-all-btn" | 32 | class="collect-all-btn" |
| 34 | @tap="collectAll" | 33 | @tap="collectAll" |
| 35 | > | 34 | > |
| 36 | <text>一键收取</text> | 35 | <text>一键收取</text> |
| 37 | - </view> | 36 | + </view> --> |
| 38 | - | ||
| 39 | - <!-- 收集特效 --> | ||
| 40 | - <view | ||
| 41 | - v-for="effect in collectEffects" | ||
| 42 | - :key="effect.id" | ||
| 43 | - class="collect-effect" | ||
| 44 | - :style="effect.style" | ||
| 45 | - > | ||
| 46 | - <text>+{{ effect.value }}</text> | ||
| 47 | - </view> | ||
| 48 | </view> | 37 | </view> |
| 49 | </template> | 38 | </template> |
| 50 | 39 | ||
| 51 | <script setup> | 40 | <script setup> |
| 52 | -import { ref, computed, onMounted, nextTick } from 'vue' | 41 | +import { ref, onMounted, nextTick, defineProps, defineExpose } from 'vue' |
| 53 | import Taro from '@tarojs/taro' | 42 | import Taro from '@tarojs/taro' |
| 54 | 43 | ||
| 44 | +// 定义props | ||
| 45 | +const props = defineProps({ | ||
| 46 | + height: { | ||
| 47 | + type: String, | ||
| 48 | + default: '30vh' | ||
| 49 | + } | ||
| 50 | +}) | ||
| 51 | + | ||
| 55 | // 响应式数据 | 52 | // 响应式数据 |
| 56 | -const totalPoints = ref(1250) // 总积分 | 53 | +const totalPoints = ref(12500) // 总积分 |
| 57 | -const animatedTotalPoints = ref(1250) // 动画中的总积分 | 54 | +const animatedTotalPoints = ref(12500) // 动画中的总积分 |
| 58 | const floatingItems = ref([]) // 漂浮的积分项 | 55 | const floatingItems = ref([]) // 漂浮的积分项 |
| 59 | -const collectEffects = ref([]) // 收集特效数组 | 56 | +const isCollecting = ref(false) // 是否正在收集,防止重复触发 |
| 60 | -const isCollecting = ref(false) // 是否正在收集 | ||
| 61 | 57 | ||
| 62 | /** | 58 | /** |
| 63 | - * 生成模拟数据 | 59 | + * 生成模拟数据并分配随机位置 |
| 64 | */ | 60 | */ |
| 65 | const generateMockData = () => { | 61 | const generateMockData = () => { |
| 66 | const mockItems = [ | 62 | const mockItems = [ |
| 67 | - { id: 1, type: 'steps', value: 500, collected: false }, | 63 | + { id: 1, type: 'steps', value: 5000, steps: 5000 }, |
| 68 | - { id: 2, type: 'points', value: 50, collected: false }, | 64 | + { id: 2, type: 'points', value: 500 }, |
| 69 | - { id: 3, type: 'steps', value: 800, collected: false }, | 65 | + { id: 3, type: 'steps', value: 8000, steps: 5000 }, |
| 70 | - { id: 4, type: 'points', value: 30, collected: false }, | 66 | + { id: 4, type: 'points', value: 30 }, |
| 71 | - { id: 5, type: 'steps', value: 1200, collected: false }, | 67 | + { id: 5, type: 'steps', value: 12000, steps: 12000 }, |
| 72 | - { id: 6, type: 'points', value: 80, collected: false }, | 68 | + { id: 6, type: 'points', value: 800 }, |
| 73 | - { id: 7, type: 'points', value: 25, collected: false }, | 69 | + { id: 7, type: 'points', value: 250 }, |
| 74 | - { id: 8, type: 'steps', value: 600, collected: false }, | 70 | + { id: 8, type: 'steps', value: 6000, steps: 6000 }, |
| 75 | - { id: 9, type: 'points', value: 100, collected: false }, | 71 | + { id: 9, type: 'points', value: 1000 }, |
| 76 | - { id: 10, type: 'steps', value: 1000, collected: false }, | 72 | + { id: 10, type: 'steps', value: 10000, steps: 10000 }, |
| 77 | - { id: 11, type: 'points', value: 100, collected: false }, | 73 | + ].map(item => ({ ...item, collecting: false })); // 初始化collecting状态 |
| 78 | - { id: 12, type: 'steps', value: 1000, collected: false }, | 74 | + |
| 79 | - { id: 13, type: 'points', value: 100, collected: false }, | 75 | + const maxValue = Math.max(...mockItems.map(i => i.value)); |
| 80 | - { id: 14, type: 'steps', value: 1000, collected: false }, | 76 | + const baseSize = 80; |
| 81 | - { id: 15, type: 'points', value: 100, collected: false }, | 77 | + const { windowWidth, windowHeight } = Taro.getWindowInfo(); |
| 82 | - { id: 16, type: 'steps', value: 1000, collected: false }, | 78 | + const positionedItems = []; |
| 83 | - { id: 17, type: 'points', value: 100, collected: false }, | 79 | + |
| 84 | - { id: 18, type: 'steps', value: 1000, collected: false }, | 80 | + // 为每个项目分配一个不重叠的随机位置 |
| 85 | - { id: 19, type: 'points', value: 100, collected: false }, | 81 | + return mockItems.map(item => { |
| 86 | - { id: 20, type: 'steps', value: 1000, collected: false }, | 82 | + let x, y, hasCollision; |
| 87 | - ] | 83 | + const maxAttempts = 100; // 限制尝试次数以避免无限循环 |
| 88 | - | 84 | + let attempts = 0; |
| 89 | - // 处理堆叠逻辑 | 85 | + const centerNoFlyZone = 25; // 中心圆形25%的禁飞区半径 |
| 90 | - const processedItems = [] | 86 | + |
| 91 | - const itemGroups = {} | 87 | + // 计算项目大小和半径 |
| 92 | - | 88 | + const sizeRatio = item.value / maxValue; |
| 93 | - mockItems.forEach(item => { | 89 | + const size = baseSize + (sizeRatio * 40); // rpx |
| 94 | - const key = `${item.type}_${item.value}` | 90 | + const radiusXPercent = (size / 750) * 100 / 2 * (windowWidth / windowHeight) ; // 归一化为高度的百分比 |
| 95 | - if (itemGroups[key]) { | 91 | + |
| 96 | - itemGroups[key].stackCount++ | 92 | + // 定义安全区域 |
| 97 | - } else { | 93 | + const minX = radiusXPercent; |
| 98 | - itemGroups[key] = { ...item, stackCount: 1, stacked: false } | 94 | + const maxX = 100 - radiusXPercent; |
| 99 | - processedItems.push(itemGroups[key]) | 95 | + const minY = radiusXPercent + 5; |
| 96 | + const maxY = 100 - radiusXPercent - 15; | ||
| 97 | + | ||
| 98 | + do { | ||
| 99 | + attempts++; | ||
| 100 | + if (attempts > maxAttempts) { | ||
| 101 | + console.warn('无法为项目找到不重叠的位置:', item); | ||
| 102 | + break; // 超过尝试次数,接受当前位置 | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + // 在计算出的边界内生成位置 | ||
| 106 | + x = Math.random() * (maxX - minX) + minX; | ||
| 107 | + y = Math.random() * (maxY - minY) + minY; | ||
| 108 | + | ||
| 109 | + // 检查与中心禁飞区的距离 | ||
| 110 | + const dxCenter = x - 50; | ||
| 111 | + const dyCenter = y - 50; | ||
| 112 | + const distanceFromCenter = Math.sqrt(dxCenter * dxCenter + dyCenter * dyCenter); | ||
| 113 | + if (distanceFromCenter < centerNoFlyZone) { | ||
| 114 | + hasCollision = true; | ||
| 115 | + continue; | ||
| 100 | } | 116 | } |
| 101 | - }) | ||
| 102 | 117 | ||
| 103 | - // 标记堆叠项 | 118 | + // 检查与其他项目的碰撞 |
| 104 | - processedItems.forEach(item => { | 119 | + hasCollision = false; |
| 105 | - if (item.stackCount > 1) { | 120 | + for (const pItem of positionedItems) { |
| 106 | - item.stacked = true | 121 | + const dx = x - pItem.x; |
| 122 | + const dy = y - pItem.y; | ||
| 123 | + const distance = Math.sqrt(dx * dx + dy * dy); | ||
| 124 | + const combinedRadius = radiusXPercent + pItem.radiusXPercent; | ||
| 125 | + | ||
| 126 | + if (distance < combinedRadius) { | ||
| 127 | + hasCollision = true; | ||
| 128 | + break; | ||
| 129 | + } | ||
| 107 | } | 130 | } |
| 108 | - }) | 131 | + } while (hasCollision); |
| 109 | 132 | ||
| 110 | - return processedItems | 133 | + positionedItems.push({ ...item, x, y, radiusXPercent }); |
| 134 | + return { ...item, x, y }; | ||
| 135 | + }); | ||
| 111 | } | 136 | } |
| 112 | 137 | ||
| 113 | /** | 138 | /** |
| 114 | - * 获取项目样式(位置和大小) | 139 | + * 获取项目样式(位置、大小和动画) |
| 115 | */ | 140 | */ |
| 116 | -const getItemStyle = (item, index) => { | 141 | +const getItemStyle = (item) => { |
| 117 | - const centerX = 375 // 屏幕中心X | 142 | + const baseSize = 80; |
| 118 | - const centerY = 400 // 屏幕中心Y | 143 | + const maxValue = Math.max(...(floatingItems.value.map(i => i.value).length > 0 ? floatingItems.value.map(i => i.value) : [1])); |
| 119 | - const radius = 200 // 分布半径 | 144 | + const sizeRatio = item.value / maxValue; |
| 120 | - | 145 | + const size = baseSize + (sizeRatio * 40); |
| 121 | - // 根据索引计算角度 | 146 | + |
| 122 | - const angle = (index * 45) + (Math.random() * 30 - 15) // 添加随机偏移 | 147 | + const style = { |
| 123 | - const radian = (angle * Math.PI) / 180 | ||
| 124 | - | ||
| 125 | - // 计算位置 | ||
| 126 | - const x = centerX + Math.cos(radian) * (radius + Math.random() * 50) | ||
| 127 | - const y = centerY + Math.sin(radian) * (radius + Math.random() * 50) | ||
| 128 | - | ||
| 129 | - // 根据数值大小计算圆圈大小 | ||
| 130 | - const baseSize = 80 | ||
| 131 | - const maxValue = Math.max(...floatingItems.value.map(i => i.value)) | ||
| 132 | - const sizeRatio = item.value / maxValue | ||
| 133 | - const size = baseSize + (sizeRatio * 40) | ||
| 134 | - | ||
| 135 | - return { | ||
| 136 | position: 'absolute', | 148 | position: 'absolute', |
| 137 | - left: `${x - size/2}rpx`, | 149 | + left: `${item.x}%`, |
| 138 | - top: `${y - size/2}rpx`, | 150 | + top: `${item.y}%`, |
| 139 | width: `${size}rpx`, | 151 | width: `${size}rpx`, |
| 140 | height: `${size}rpx`, | 152 | height: `${size}rpx`, |
| 141 | - transform: item.collecting ? 'scale(0)' : 'scale(1)', | 153 | + transform: 'translate(-50%, -50%) scale(1)', |
| 142 | - transition: 'all 0.5s ease-in-out' | 154 | + transition: 'all 0.8s cubic-bezier(0.5, -0.5, 0.5, 1.5)', |
| 155 | + zIndex: 15, | ||
| 156 | + }; | ||
| 157 | + | ||
| 158 | + if (item.collecting) { | ||
| 159 | + style.left = '50%'; | ||
| 160 | + style.top = '50%'; | ||
| 161 | + style.transform = 'translate(-50%, -50%) scale(0)'; | ||
| 162 | + style.opacity = 0; | ||
| 163 | + style.zIndex = 20; // 飞向中心时置于顶层 | ||
| 143 | } | 164 | } |
| 165 | + | ||
| 166 | + return style; | ||
| 144 | } | 167 | } |
| 145 | 168 | ||
| 146 | /** | 169 | /** |
| 147 | * 收集单个项目 | 170 | * 收集单个项目 |
| 148 | */ | 171 | */ |
| 149 | -const collectItem = async (item, index) => { | 172 | +const collectItem = (item) => { |
| 150 | - if (item.collecting || isCollecting.value) return | 173 | + if (item.collecting) return; |
| 151 | - | 174 | + item.collecting = true; |
| 152 | - item.collecting = true | ||
| 153 | - | ||
| 154 | - // 创建收集特效 | ||
| 155 | - const effect = { | ||
| 156 | - id: Date.now(), | ||
| 157 | - value: item.type === 'steps' ? Math.floor(item.value / 10) : item.value, | ||
| 158 | - style: { | ||
| 159 | - position: 'absolute', | ||
| 160 | - left: '50%', | ||
| 161 | - top: '50%', | ||
| 162 | - transform: 'translate(-50%, -50%)', | ||
| 163 | - opacity: 1, | ||
| 164 | - transition: 'all 1s ease-out' | ||
| 165 | - } | ||
| 166 | - } | ||
| 167 | - | ||
| 168 | - collectEffects.value.push(effect) | ||
| 169 | 175 | ||
| 170 | - // 动画效果 | ||
| 171 | - await nextTick() | ||
| 172 | - effect.style.transform = 'translate(-50%, -150%)' | ||
| 173 | - effect.style.opacity = 0 | ||
| 174 | - | ||
| 175 | - // 更新总积分(步数转换为积分,比例1:10) | ||
| 176 | - const pointsToAdd = item.type === 'steps' ? Math.floor(item.value / 10) : item.value | ||
| 177 | - const stackMultiplier = item.stackCount || 1 | ||
| 178 | - const totalToAdd = pointsToAdd * stackMultiplier | ||
| 179 | - | ||
| 180 | - // 数字滚动效果 | ||
| 181 | - animateNumber(totalPoints.value, totalPoints.value + totalToAdd) | ||
| 182 | - totalPoints.value += totalToAdd | ||
| 183 | - | ||
| 184 | - // 延迟移除项目和特效 | ||
| 185 | setTimeout(() => { | 176 | setTimeout(() => { |
| 186 | - floatingItems.value.splice(index, 1) | 177 | + const totalToAdd = item.type === 'steps' ? Math.floor(item.value / 10) : item.value; |
| 187 | - collectEffects.value = collectEffects.value.filter(e => e.id !== effect.id) | 178 | + animateNumber(totalPoints.value, totalPoints.value + totalToAdd); |
| 188 | - }, 1000) | 179 | + totalPoints.value += totalToAdd; |
| 180 | + | ||
| 181 | + floatingItems.value = floatingItems.value.filter(i => i.id !== item.id); | ||
| 182 | + }, 800); // 动画时长 | ||
| 189 | } | 183 | } |
| 190 | 184 | ||
| 191 | /** | 185 | /** |
| 192 | * 一键收取所有积分 | 186 | * 一键收取所有积分 |
| 193 | */ | 187 | */ |
| 194 | const collectAll = async () => { | 188 | const collectAll = async () => { |
| 195 | - if (isCollecting.value) return | 189 | + if (isCollecting.value) return; |
| 196 | - | 190 | + isCollecting.value = true; |
| 197 | - isCollecting.value = true | ||
| 198 | 191 | ||
| 199 | - // 计算总积分 | 192 | + let totalToAdd = 0; |
| 200 | - let totalToAdd = 0 | 193 | + const itemsToCollect = [...floatingItems.value]; |
| 201 | - floatingItems.value.forEach(item => { | ||
| 202 | - const pointsToAdd = item.type === 'steps' ? Math.floor(item.value / 10) : item.value | ||
| 203 | - const stackMultiplier = item.stackCount || 1 | ||
| 204 | - totalToAdd += pointsToAdd * stackMultiplier | ||
| 205 | - }) | ||
| 206 | 194 | ||
| 207 | - // 批量收集动画 | 195 | + itemsToCollect.forEach((item, index) => { |
| 208 | - floatingItems.value.forEach((item, index) => { | ||
| 209 | setTimeout(() => { | 196 | setTimeout(() => { |
| 210 | - item.collecting = true | 197 | + item.collecting = true; |
| 211 | - }, index * 100) | 198 | + }, index * 80); // 依次触发动画 |
| 212 | - }) | 199 | + totalToAdd += item.value; |
| 213 | - | 200 | + }); |
| 214 | - // 创建总收集特效 | ||
| 215 | - const effect = { | ||
| 216 | - id: Date.now(), | ||
| 217 | - value: totalToAdd, | ||
| 218 | - style: { | ||
| 219 | - position: 'absolute', | ||
| 220 | - left: '50%', | ||
| 221 | - top: '50%', | ||
| 222 | - transform: 'translate(-50%, -50%)', | ||
| 223 | - opacity: 1, | ||
| 224 | - fontSize: '48rpx', | ||
| 225 | - color: '#ff6b35', | ||
| 226 | - fontWeight: 'bold', | ||
| 227 | - transition: 'all 1.5s ease-out' | ||
| 228 | - } | ||
| 229 | - } | ||
| 230 | - | ||
| 231 | - collectEffects.value.push(effect) | ||
| 232 | - | ||
| 233 | - await nextTick() | ||
| 234 | - effect.style.transform = 'translate(-50%, -200%)' | ||
| 235 | - effect.style.opacity = 0 | ||
| 236 | 201 | ||
| 237 | - // 数字滚动效果 | 202 | + const totalAnimationTime = itemsToCollect.length * 80 + 800; |
| 238 | - animateNumber(totalPoints.value, totalPoints.value + totalToAdd) | ||
| 239 | - totalPoints.value += totalToAdd | ||
| 240 | - | ||
| 241 | - // 清空所有项目 | ||
| 242 | setTimeout(() => { | 203 | setTimeout(() => { |
| 243 | - floatingItems.value = [] | 204 | + animateNumber(totalPoints.value, totalPoints.value + totalToAdd); |
| 244 | - collectEffects.value = [] | 205 | + totalPoints.value += totalToAdd; |
| 245 | - isCollecting.value = false | ||
| 246 | 206 | ||
| 247 | - // 重新生成数据(模拟新的积分) | 207 | + floatingItems.value = []; |
| 248 | - setTimeout(() => { | 208 | + isCollecting.value = false; |
| 249 | - floatingItems.value = generateMockData() | 209 | + |
| 250 | - }, 2000) | 210 | + // 模拟2秒后重新生成 |
| 251 | - }, 1500) | 211 | + // setTimeout(() => { |
| 212 | + // floatingItems.value = generateMockData(); | ||
| 213 | + // }, 2000); | ||
| 214 | + }, totalAnimationTime); | ||
| 252 | } | 215 | } |
| 253 | 216 | ||
| 254 | /** | 217 | /** |
| 255 | * 数字滚动动画 | 218 | * 数字滚动动画 |
| 256 | */ | 219 | */ |
| 257 | const animateNumber = (start, end) => { | 220 | const animateNumber = (start, end) => { |
| 258 | - const duration = 1000 | 221 | + const duration = 800; |
| 259 | - const startTime = Date.now() | 222 | + const startTime = Date.now(); |
| 260 | - const difference = end - start | 223 | + const difference = end - start; |
| 261 | 224 | ||
| 262 | const animate = () => { | 225 | const animate = () => { |
| 263 | - const elapsed = Date.now() - startTime | 226 | + const elapsed = Date.now() - startTime; |
| 264 | - const progress = Math.min(elapsed / duration, 1) | 227 | + const progress = Math.min(elapsed / duration, 1); |
| 265 | - | 228 | + const easeOut = 1 - Math.pow(1 - progress, 3); |
| 266 | - // 使用缓动函数 | 229 | + animatedTotalPoints.value = Math.floor(start + difference * easeOut); |
| 267 | - const easeOut = 1 - Math.pow(1 - progress, 3) | ||
| 268 | - animatedTotalPoints.value = Math.floor(start + difference * easeOut) | ||
| 269 | 230 | ||
| 270 | if (progress < 1) { | 231 | if (progress < 1) { |
| 271 | - requestAnimationFrame(animate) | 232 | + requestAnimationFrame(animate); |
| 272 | - } | ||
| 273 | } | 233 | } |
| 274 | - | 234 | + }; |
| 275 | - animate() | 235 | + animate(); |
| 276 | } | 236 | } |
| 277 | 237 | ||
| 238 | +// 暴露方法给父组件 | ||
| 239 | +defineExpose({ | ||
| 240 | + collectAll | ||
| 241 | +}) | ||
| 242 | + | ||
| 278 | // 组件挂载时初始化数据 | 243 | // 组件挂载时初始化数据 |
| 279 | onMounted(() => { | 244 | onMounted(() => { |
| 280 | - floatingItems.value = generateMockData() | 245 | + floatingItems.value = generateMockData(); |
| 281 | }) | 246 | }) |
| 282 | </script> | 247 | </script> |
| 283 | 248 | ||
| ... | @@ -286,7 +251,7 @@ onMounted(() => { | ... | @@ -286,7 +251,7 @@ onMounted(() => { |
| 286 | position: relative; | 251 | position: relative; |
| 287 | width: 100vw; | 252 | width: 100vw; |
| 288 | height: 100vh; | 253 | height: 100vh; |
| 289 | - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | 254 | + // background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| 290 | overflow: hidden; | 255 | overflow: hidden; |
| 291 | } | 256 | } |
| 292 | 257 | ||
| ... | @@ -297,12 +262,12 @@ onMounted(() => { | ... | @@ -297,12 +262,12 @@ onMounted(() => { |
| 297 | transform: translate(-50%, -50%); | 262 | transform: translate(-50%, -50%); |
| 298 | width: 200rpx; | 263 | width: 200rpx; |
| 299 | height: 200rpx; | 264 | height: 200rpx; |
| 300 | - background: linear-gradient(135deg, #ff6b35, #f7931e); | 265 | + background: linear-gradient(135deg, #4A90E2, #4d96ea); |
| 301 | border-radius: 50%; | 266 | border-radius: 50%; |
| 302 | display: flex; | 267 | display: flex; |
| 303 | align-items: center; | 268 | align-items: center; |
| 304 | justify-content: center; | 269 | justify-content: center; |
| 305 | - box-shadow: 0 8rpx 32rpx rgba(255, 107, 53, 0.3); | 270 | + box-shadow: 0 8rpx 32rpx rgba(53, 144, 255, 0.3); |
| 306 | z-index: 10; | 271 | z-index: 10; |
| 307 | } | 272 | } |
| 308 | 273 | ||
| ... | @@ -322,12 +287,12 @@ onMounted(() => { | ... | @@ -322,12 +287,12 @@ onMounted(() => { |
| 322 | display: block; | 287 | display: block; |
| 323 | font-size: 24rpx; | 288 | font-size: 24rpx; |
| 324 | margin-top: 8rpx; | 289 | margin-top: 8rpx; |
| 325 | - opacity: 0.9; | 290 | + // opacity: 0.9; |
| 326 | } | 291 | } |
| 327 | 292 | ||
| 328 | .floating-item { | 293 | .floating-item { |
| 329 | position: absolute; | 294 | position: absolute; |
| 330 | - background: rgba(255, 255, 255, 0.9); | 295 | + background: rgb(62, 144, 239); |
| 331 | border-radius: 50%; | 296 | border-radius: 50%; |
| 332 | display: flex; | 297 | display: flex; |
| 333 | align-items: center; | 298 | align-items: center; |
| ... | @@ -335,33 +300,25 @@ onMounted(() => { | ... | @@ -335,33 +300,25 @@ onMounted(() => { |
| 335 | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); | 300 | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); |
| 336 | cursor: pointer; | 301 | cursor: pointer; |
| 337 | animation: float 3s ease-in-out infinite; | 302 | animation: float 3s ease-in-out infinite; |
| 338 | - | ||
| 339 | - &.stacked { | ||
| 340 | - border: 4rpx solid #ff6b35; | ||
| 341 | - } | ||
| 342 | - | ||
| 343 | - &.collecting { | ||
| 344 | - transform: scale(0) !important; | ||
| 345 | - } | ||
| 346 | } | 303 | } |
| 347 | 304 | ||
| 348 | .item-content { | 305 | .item-content { |
| 349 | text-align: center; | 306 | text-align: center; |
| 350 | - color: #333; | 307 | + color: #FFF; |
| 351 | } | 308 | } |
| 352 | 309 | ||
| 353 | .item-value { | 310 | .item-value { |
| 354 | display: block; | 311 | display: block; |
| 355 | - font-size: 28rpx; | 312 | + font-size: 22rpx; |
| 356 | - font-weight: bold; | ||
| 357 | line-height: 1; | 313 | line-height: 1; |
| 358 | } | 314 | } |
| 359 | 315 | ||
| 360 | .item-type { | 316 | .item-type { |
| 361 | display: block; | 317 | display: block; |
| 362 | - font-size: 20rpx; | 318 | + font-size: 23rpx; |
| 363 | margin-top: 4rpx; | 319 | margin-top: 4rpx; |
| 364 | - opacity: 0.7; | 320 | + font-weight: bold; |
| 321 | + // opacity: 0.7; | ||
| 365 | } | 322 | } |
| 366 | 323 | ||
| 367 | .stack-count { | 324 | .stack-count { |
| ... | @@ -395,26 +352,18 @@ onMounted(() => { | ... | @@ -395,26 +352,18 @@ onMounted(() => { |
| 395 | z-index: 20; | 352 | z-index: 20; |
| 396 | } | 353 | } |
| 397 | 354 | ||
| 398 | -.collect-effect { | ||
| 399 | - pointer-events: none; | ||
| 400 | - color: #ff6b35; | ||
| 401 | - font-size: 36rpx; | ||
| 402 | - font-weight: bold; | ||
| 403 | - z-index: 30; | ||
| 404 | -} | ||
| 405 | - | ||
| 406 | @keyframes float { | 355 | @keyframes float { |
| 407 | 0%, 100% { | 356 | 0%, 100% { |
| 408 | - transform: translateY(0px) rotate(0deg); | 357 | + transform: translate(-50%, -50%) translateY(0px) rotate(0deg); |
| 409 | } | 358 | } |
| 410 | 25% { | 359 | 25% { |
| 411 | - transform: translateY(-10px) rotate(1deg); | 360 | + transform: translate(-50%, -50%) translateY(-10px) rotate(1deg); |
| 412 | } | 361 | } |
| 413 | 50% { | 362 | 50% { |
| 414 | - transform: translateY(-5px) rotate(-1deg); | 363 | + transform: translate(-50%, -50%) translateY(-5px) rotate(-1deg); |
| 415 | } | 364 | } |
| 416 | 75% { | 365 | 75% { |
| 417 | - transform: translateY(-15px) rotate(0.5deg); | 366 | + transform: translate(-50%, -50%) translateY(-15px) rotate(0.5deg); |
| 418 | } | 367 | } |
| 419 | } | 368 | } |
| 420 | 369 | ||
| ... | @@ -430,7 +379,7 @@ onMounted(() => { | ... | @@ -430,7 +379,7 @@ onMounted(() => { |
| 430 | } | 379 | } |
| 431 | 380 | ||
| 432 | .points-label { | 381 | .points-label { |
| 433 | - font-size: 20rpx; | 382 | + font-size: 24rpx; |
| 434 | } | 383 | } |
| 435 | } | 384 | } |
| 436 | </style> | 385 | </style> | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-06-28 10:33:00 | 2 | * @Date: 2025-06-28 10:33:00 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-08-27 15:22:35 | 4 | + * @LastEditTime: 2025-08-27 17:13:11 |
| 5 | * @FilePath: /lls_program/src/pages/index/index.vue | 5 | * @FilePath: /lls_program/src/pages/index/index.vue |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | --> | 7 | --> |
| 8 | <template> | 8 | <template> |
| 9 | <view class="index"> | 9 | <view class="index"> |
| 10 | - <nut-button type="primary" @click="onClick">按钮</nut-button> | 10 | + <PointsCollector ref="pointsCollectorRef" height="30vh" /> |
| 11 | - <nut-button type="success" @click="showPointsCollector" style="margin-left: 20rpx;">积分收集</nut-button> | 11 | + <nut-button type="success" @click="handleCollectAll" style="margin-top: 20rpx;">一键收取</nut-button> |
| 12 | - <nut-toast v-model:visible="show" msg="你成功了" /> | ||
| 13 | - <!-- <View className="text-[#acc855] text-[100px]">Hello world!</View> --> | ||
| 14 | - | ||
| 15 | - <!-- 积分收集组件 --> | ||
| 16 | - <PointsCollector v-if="showCollector" /> | ||
| 17 | </view> | 12 | </view> |
| 18 | </template> | 13 | </template> |
| 19 | 14 | ||
| ... | @@ -25,18 +20,15 @@ import { useDidShow, useReady } from '@tarojs/taro' | ... | @@ -25,18 +20,15 @@ import { useDidShow, useReady } from '@tarojs/taro' |
| 25 | import PointsCollector from '@/components/PointsCollector.vue' | 20 | import PointsCollector from '@/components/PointsCollector.vue' |
| 26 | import "./index.less"; | 21 | import "./index.less"; |
| 27 | 22 | ||
| 28 | -const show = ref(false) | 23 | +const pointsCollectorRef = ref(null) |
| 29 | -const showCollector = ref(false) | ||
| 30 | - | ||
| 31 | -const onClick = () => { | ||
| 32 | - show.value = true | ||
| 33 | -} | ||
| 34 | 24 | ||
| 35 | /** | 25 | /** |
| 36 | - * 显示积分收集组件 | 26 | + * 触发积分收集组件的一键收取 |
| 37 | */ | 27 | */ |
| 38 | -const showPointsCollector = () => { | 28 | +const handleCollectAll = () => { |
| 39 | - showCollector.value = !showCollector.value | 29 | + if (pointsCollectorRef.value) { |
| 30 | + pointsCollectorRef.value.collectAll() | ||
| 31 | + } | ||
| 40 | } | 32 | } |
| 41 | 33 | ||
| 42 | // 生命周期钩子 | 34 | // 生命周期钩子 | ... | ... |
-
Please register or login to post a comment