|
| 1 | +<template> |
| 2 | + <div class="p-4 bmi-calculator dark:bg-gray-900 transition-colors duration-300"> |
| 3 | + <!-- 结果区域和引导区域的包装容器 --> |
| 4 | + <div class="relative mb-6" style="min-height: 260px;"> |
| 5 | + <!-- 结果展示区域 --> |
| 6 | + <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 transform transition-all duration-500 absolute top-0 left-0 right-0 w-full overflow-hidden" |
| 7 | + :class="{'scale-100 opacity-100 z-10': bmi, 'scale-95 opacity-0 z-0': !bmi}"> |
| 8 | + <!-- 装饰元素 --> |
| 9 | + <div class="absolute -right-10 -top-10 w-32 h-32 bg-green-100 dark:bg-green-900/30 rounded-full opacity-50"></div> |
| 10 | + <div class="absolute -left-10 -bottom-10 w-24 h-24 bg-blue-100 dark:bg-blue-900/30 rounded-full opacity-50"></div> |
| 11 | + |
| 12 | + <!-- BMI值 --> |
| 13 | + <div class="flex items-center justify-between mb-6 relative z-10"> |
| 14 | + <div class="text-gray-600 dark:text-gray-300 font-medium">BMI值</div> |
| 15 | + <div class="text-3xl font-bold text-green-500 dark:text-green-400"> |
| 16 | + <span class="inline-block transform transition-all duration-500" |
| 17 | + :class="{'translate-y-0 opacity-100': bmi, 'translate-y-4 opacity-0': !bmi}"> |
| 18 | + {{ bmi }} |
| 19 | + </span> |
| 20 | + </div> |
| 21 | + </div> |
| 22 | + |
| 23 | + <!-- 身体状况 --> |
| 24 | + <div class="flex items-center justify-between mb-6 relative z-10"> |
| 25 | + <div class="text-gray-600 dark:text-gray-300">身体状况</div> |
| 26 | + <div class="text-lg font-medium" :class="statusColorClass"> |
| 27 | + <span class="inline-block transform transition-all duration-500 delay-100" |
| 28 | + :class="{'translate-y-0 opacity-100': status, 'translate-y-4 opacity-0': !status}"> |
| 29 | + {{ status }} |
| 30 | + </span> |
| 31 | + </div> |
| 32 | + </div> |
| 33 | + |
| 34 | + <!-- 建议体重 --> |
| 35 | + <div class="flex items-center justify-between relative z-10"> |
| 36 | + <div class="text-gray-600 dark:text-gray-300">建议体重</div> |
| 37 | + <div class="text-lg font-medium text-teal-600 dark:text-teal-400"> |
| 38 | + <span class="inline-block transform transition-all duration-500 delay-200" |
| 39 | + :class="{'translate-y-0 opacity-100': suggest, 'translate-y-4 opacity-0': !suggest}"> |
| 40 | + {{ suggest }} kg |
| 41 | + </span> |
| 42 | + </div> |
| 43 | + </div> |
| 44 | + |
| 45 | + <!-- 进度条 --> |
| 46 | + <div class="mt-6 relative z-10" v-if="bmi"> |
| 47 | + <div class="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden"> |
| 48 | + <div class="h-full transition-all duration-1000 ease-out" |
| 49 | + :class="progressColorClass" |
| 50 | + :style="{ width: progressWidth }"></div> |
| 51 | + </div> |
| 52 | + <div class="flex justify-between mt-1 text-xs text-gray-500 dark:text-gray-400"> |
| 53 | + <span>18.5</span> |
| 54 | + <span>24</span> |
| 55 | + <span>27</span> |
| 56 | + <span>30</span> |
| 57 | + <span>35</span> |
| 58 | + </div> |
| 59 | + </div> |
| 60 | + </div> |
| 61 | + |
| 62 | + <!-- 默认引导区域 --> |
| 63 | + <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 transform transition-all duration-500 absolute top-0 left-0 right-0 w-full overflow-hidden" |
| 64 | + :class="{'scale-100 opacity-100 z-10': !bmi, 'scale-95 opacity-0 z-0': bmi}"> |
| 65 | + <!-- 装饰元素 --> |
| 66 | + <div class="absolute -right-10 -top-10 w-32 h-32 bg-blue-100 dark:bg-blue-900/30 rounded-full opacity-50"></div> |
| 67 | + <div class="absolute -left-10 -bottom-10 w-24 h-24 bg-green-100 dark:bg-green-900/30 rounded-full opacity-50"></div> |
| 68 | + |
| 69 | + <div class="flex flex-col items-center justify-center text-center py-2 relative z-10"> |
| 70 | + <div class="w-16 h-16 text-green-500 dark:text-green-400"> |
| 71 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"> |
| 72 | + <path d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z"/> |
| 73 | + <path d="M11 11h2v6h-2zm0-4h2v2h-2z"/> |
| 74 | + </svg> |
| 75 | + </div> |
| 76 | + <h3 class="text-xl font-bold !mt-0 text-gray-800 dark:text-white mb-3">BMI 健康指数</h3> |
| 77 | + <p class="text-gray-600 dark:text-gray-300 mb-4">输入您的身高和体重,计算您的身体质量指数</p> |
| 78 | + <div class="flex flex-wrap justify-center gap-3 text-sm text-gray-500 dark:text-gray-400"> |
| 79 | + <div class="px-3 py-1 bg-yellow-100 dark:bg-yellow-900/30 rounded-full">体重过轻 < 18.5</div> |
| 80 | + <div class="px-3 py-1 bg-green-100 dark:bg-green-900/30 rounded-full">体重正常 18.5-24</div> |
| 81 | + <div class="px-3 py-1 bg-orange-100 dark:bg-orange-900/30 rounded-full">过重 24-27</div> |
| 82 | + <div class="px-3 py-1 bg-red-100 dark:bg-red-900/30 rounded-full">肥胖 > 27</div> |
| 83 | + </div> |
| 84 | + </div> |
| 85 | + </div> |
| 86 | + </div> |
| 87 | + |
| 88 | + <!-- 输入表单 --> |
| 89 | + <div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 relative overflow-hidden"> |
| 90 | + <!-- 装饰元素 --> |
| 91 | + <div class="absolute -left-6 -top-6 w-20 h-20 bg-green-100 dark:bg-green-900/30 rounded-full opacity-50"></div> |
| 92 | + |
| 93 | + <h2 class="text-xl font-bold text-gray-800 dark:text-white mb-6 relative z-10">BMI计算器</h2> |
| 94 | + |
| 95 | + <form @submit.prevent="formSubmit" class="space-y-6 relative z-10"> |
| 96 | + <!-- 身高输入 --> |
| 97 | + <div class="relative group"> |
| 98 | + <label class="block text-sm font-medium text-gray-600 dark:text-gray-300 mb-2">身高</label> |
| 99 | + <div class="flex items-center"> |
| 100 | + <input |
| 101 | + v-model="height" |
| 102 | + type="number" |
| 103 | + class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 dark:focus:ring-green-400 focus:border-transparent outline-none transition-all duration-300 dark:text-white" |
| 104 | + placeholder="请输入身高" |
| 105 | + min="50" |
| 106 | + max="250" |
| 107 | + /> |
| 108 | + <div class="ml-2 text-gray-500 dark:text-gray-400 font-medium">cm</div> |
| 109 | + </div> |
| 110 | + </div> |
| 111 | + |
| 112 | + <!-- 体重输入 --> |
| 113 | + <div class="relative group"> |
| 114 | + <label class="block text-sm font-medium text-gray-600 dark:text-gray-300 mb-2">体重</label> |
| 115 | + <div class="flex items-center"> |
| 116 | + <input |
| 117 | + v-model="weight" |
| 118 | + type="number" |
| 119 | + class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 dark:focus:ring-green-400 focus:border-transparent outline-none transition-all duration-300 dark:text-white" |
| 120 | + placeholder="请输入体重" |
| 121 | + min="10" |
| 122 | + max="300" |
| 123 | + step="0.1" |
| 124 | + /> |
| 125 | + <div class="ml-2 text-gray-500 dark:text-gray-400 font-medium">kg</div> |
| 126 | + </div> |
| 127 | + </div> |
| 128 | + |
| 129 | + <!-- 提交按钮 --> |
| 130 | + <button |
| 131 | + type="submit" |
| 132 | + class="w-full bg-gradient-to-r from-green-400 to-teal-500 hover:from-green-500 hover:to-teal-600 text-white font-medium py-3 px-6 rounded-lg transition-all duration-300 transform hover:scale-105 hover:shadow-lg flex items-center justify-center" |
| 133 | + :disabled="!height || !weight" |
| 134 | + :class="{'opacity-70 cursor-not-allowed': !height || !weight}" |
| 135 | + > |
| 136 | + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20" fill="currentColor"> |
| 137 | + <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /> |
| 138 | + </svg> |
| 139 | + 开始计算 |
| 140 | + </button> |
| 141 | + </form> |
| 142 | + </div> |
| 143 | + </div> |
| 144 | +</template> |
| 145 | + |
| 146 | +<script> |
| 147 | +export default { |
| 148 | + data() { |
| 149 | + return { |
| 150 | + height: '', |
| 151 | + weight: '', |
| 152 | + bmi: '', |
| 153 | + status: '', |
| 154 | + suggest: '' |
| 155 | + } |
| 156 | + }, |
| 157 | + computed: { |
| 158 | + statusColorClass() { |
| 159 | + if (!this.status) return ''; |
| 160 | +
|
| 161 | + const statusMap = { |
| 162 | + '体重过轻': 'text-yellow-500 dark:text-yellow-400', |
| 163 | + '体重正常': 'text-green-500 dark:text-green-400', |
| 164 | + '过重': 'text-orange-500 dark:text-orange-400', |
| 165 | + '轻度肥胖': 'text-red-400 dark:text-red-300', |
| 166 | + '中度肥胖': 'text-red-500 dark:text-red-400', |
| 167 | + '重度肥胖': 'text-red-600 dark:text-red-500' |
| 168 | + }; |
| 169 | +
|
| 170 | + return statusMap[this.status] || 'text-gray-600 dark:text-gray-300'; |
| 171 | + }, |
| 172 | + progressColorClass() { |
| 173 | + if (!this.bmi) return ''; |
| 174 | +
|
| 175 | + if (this.bmi < 18.5) return 'bg-yellow-400'; |
| 176 | + if (this.bmi < 24) return 'bg-green-400'; |
| 177 | + if (this.bmi < 27) return 'bg-orange-400'; |
| 178 | + if (this.bmi < 30) return 'bg-red-400'; |
| 179 | + if (this.bmi < 35) return 'bg-red-500'; |
| 180 | + return 'bg-red-600'; |
| 181 | + }, |
| 182 | + progressWidth() { |
| 183 | + if (!this.bmi) return '0%'; |
| 184 | +
|
| 185 | + // 设置进度条的最小和最大值 |
| 186 | + const min = 15; |
| 187 | + const max = 40; |
| 188 | +
|
| 189 | + // 计算百分比位置(限制在0-100%之间) |
| 190 | + let percentage = ((this.bmi - min) / (max - min)) * 100; |
| 191 | + percentage = Math.max(0, Math.min(100, percentage)); |
| 192 | +
|
| 193 | + return `${percentage}%`; |
| 194 | + } |
| 195 | + }, |
| 196 | + methods: { |
| 197 | + formatNumber(number) { |
| 198 | + return Math.round(number * 100) / 100; |
| 199 | + }, |
| 200 | + formSubmit() { |
| 201 | + if (!this.height || !this.weight) return; |
| 202 | +
|
| 203 | + const height = parseFloat(this.height); |
| 204 | + const weight = parseFloat(this.weight); |
| 205 | +
|
| 206 | + if (isNaN(height) || isNaN(weight) || height <= 0 || weight <= 0) { |
| 207 | + alert('请输入有效的身高和体重'); |
| 208 | + return; |
| 209 | + } |
| 210 | +
|
| 211 | + const bmi = this.formatNumber(weight / Math.pow(height / 100, 2)); |
| 212 | + this.bmi = bmi; |
| 213 | + this.suggest = this.formatNumber(Math.pow(height / 100, 2) * 22) + ''; |
| 214 | +
|
| 215 | + const status = ['体重过轻', '体重正常', '过重', '轻度肥胖', '中度肥胖', '重度肥胖']; |
| 216 | + let i = 0; |
| 217 | +
|
| 218 | + if (bmi < 18.5) { |
| 219 | + i = 0; |
| 220 | + } else if (bmi >= 18.5 && bmi < 24) { |
| 221 | + i = 1; |
| 222 | + } else if (bmi >= 24 && bmi < 27) { |
| 223 | + i = 2; |
| 224 | + } else if (bmi >= 27 && bmi < 30) { |
| 225 | + i = 3; |
| 226 | + } else if (bmi >= 30 && bmi < 35) { |
| 227 | + i = 4; |
| 228 | + } else if (bmi >= 35) { |
| 229 | + i = 5; |
| 230 | + } |
| 231 | +
|
| 232 | + this.status = status[i]; |
| 233 | + } |
| 234 | + } |
| 235 | +} |
| 236 | +</script> |
| 237 | + |
| 238 | +<style scoped> |
| 239 | +.bmi-calculator input::-webkit-outer-spin-button, |
| 240 | +.bmi-calculator input::-webkit-inner-spin-button { |
| 241 | + -webkit-appearance: none; |
| 242 | + margin: 0; |
| 243 | +} |
| 244 | +
|
| 245 | +.bmi-calculator input[type=number] { |
| 246 | + -moz-appearance: textfield; |
| 247 | +} |
| 248 | +</style> |
0 commit comments