feat(打卡功能): 添加打卡对话框组件并集成到个人主页
- 新增 `CheckInDialog.vue` 组件,支持用户选择打卡类型并提交打卡内容 - 在 `ProfilePage.vue` 中集成打卡对话框,处理打卡成功后的逻辑 - 更新 `vite.config.js` 和 `components.d.ts` 以支持 Vant 组件的自动导入
Showing
4 changed files
with
340 additions
and
99 deletions
| ... | @@ -11,6 +11,7 @@ declare module 'vue' { | ... | @@ -11,6 +11,7 @@ declare module 'vue' { |
| 11 | ActivityCard: typeof import('./components/ui/ActivityCard.vue')['default'] | 11 | ActivityCard: typeof import('./components/ui/ActivityCard.vue')['default'] |
| 12 | AppLayout: typeof import('./components/layout/AppLayout.vue')['default'] | 12 | AppLayout: typeof import('./components/layout/AppLayout.vue')['default'] |
| 13 | BottomNav: typeof import('./components/layout/BottomNav.vue')['default'] | 13 | BottomNav: typeof import('./components/layout/BottomNav.vue')['default'] |
| 14 | + CheckInDialog: typeof import('./components/ui/CheckInDialog.vue')['default'] | ||
| 14 | ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default'] | 15 | ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default'] |
| 15 | CourseCard: typeof import('./components/ui/CourseCard.vue')['default'] | 16 | CourseCard: typeof import('./components/ui/CourseCard.vue')['default'] |
| 16 | FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default'] | 17 | FrostedGlass: typeof import('./components/ui/FrostedGlass.vue')['default'] |
| ... | @@ -22,6 +23,7 @@ declare module 'vue' { | ... | @@ -22,6 +23,7 @@ declare module 'vue' { |
| 22 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] | 23 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] |
| 23 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] | 24 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] |
| 24 | VanDatePicker: typeof import('vant/es')['DatePicker'] | 25 | VanDatePicker: typeof import('vant/es')['DatePicker'] |
| 26 | + VanIcon: typeof import('vant/es')['Icon'] | ||
| 25 | VanList: typeof import('vant/es')['List'] | 27 | VanList: typeof import('vant/es')['List'] |
| 26 | VanPickerGroup: typeof import('vant/es')['PickerGroup'] | 28 | VanPickerGroup: typeof import('vant/es')['PickerGroup'] |
| 27 | VanPopup: typeof import('vant/es')['Popup'] | 29 | VanPopup: typeof import('vant/es')['Popup'] | ... | ... |
src/components/ui/CheckInDialog.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <van-popup | ||
| 3 | + :show="show" | ||
| 4 | + @update:show="$emit('update:show', $event)" | ||
| 5 | + round | ||
| 6 | + position="bottom" | ||
| 7 | + :style="{ minHeight: '30%', maxHeight: '80%', width: '100%' }" | ||
| 8 | + > | ||
| 9 | + <div class="p-4"> | ||
| 10 | + <div class="flex justify-between items-center mb-3"> | ||
| 11 | + <h3 class="font-medium">今日打卡</h3> | ||
| 12 | + <van-icon name="cross" @click="handleClose" /> | ||
| 13 | + </div> | ||
| 14 | + | ||
| 15 | + <div v-if="checkInSuccess" class="bg-green-50 border border-green-200 rounded-lg p-4 text-center"> | ||
| 16 | + <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-green-500 mx-auto mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| 17 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||
| 18 | + </svg> | ||
| 19 | + <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4> | ||
| 20 | + <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> | ||
| 21 | + </div> | ||
| 22 | + <template v-else> | ||
| 23 | + <div class="flex space-x-2 py-2"> | ||
| 24 | + <button | ||
| 25 | + v-for="checkInType in checkInTypes" | ||
| 26 | + :key="checkInType.id" | ||
| 27 | + :class="[ | ||
| 28 | + 'flex-1 flex flex-col items-center p-2 rounded-lg transition-colors', | ||
| 29 | + selectedCheckIn?.id === checkInType.id | ||
| 30 | + ? 'bg-green-100 border border-green-200' | ||
| 31 | + : 'bg-white/70 border border-gray-100 hover:bg-white' | ||
| 32 | + ]" | ||
| 33 | + @click="handleCheckInSelect(checkInType)" | ||
| 34 | + > | ||
| 35 | + <div :class="[ | ||
| 36 | + 'w-12 h-12 rounded-full flex items-center justify-center mb-1 transition-colors', | ||
| 37 | + selectedCheckIn?.id === checkInType.id | ||
| 38 | + ? 'bg-green-500 text-white' | ||
| 39 | + : 'bg-gray-100 text-gray-500' | ||
| 40 | + ]"> | ||
| 41 | + <svg v-if="checkInType.id === 'reading'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| 42 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /> | ||
| 43 | + </svg> | ||
| 44 | + <svg v-if="checkInType.id === 'exercise'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| 45 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> | ||
| 46 | + </svg> | ||
| 47 | + <svg v-if="checkInType.id === 'study'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| 48 | + <path d="M12 14l9-5-9-5-9 5 9 5z" /> | ||
| 49 | + <path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998a12.078 12.078 0 01.665-6.479L12 14z" /> | ||
| 50 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998a12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222" /> | ||
| 51 | + </svg> | ||
| 52 | + <svg v-if="checkInType.id === 'reflection'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| 53 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /> | ||
| 54 | + </svg> | ||
| 55 | + </div> | ||
| 56 | + <span class="text-xs">{{ checkInType.name }}</span> | ||
| 57 | + </button> | ||
| 58 | + </div> | ||
| 59 | + | ||
| 60 | + <div v-if="selectedCheckIn" class="mt-3"> | ||
| 61 | + <textarea | ||
| 62 | + :placeholder="`请输入${selectedCheckIn.name}内容...`" | ||
| 63 | + v-model="checkInContent" | ||
| 64 | + class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24" | ||
| 65 | + /> | ||
| 66 | + <button | ||
| 67 | + class="mt-2 w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-2 rounded-lg flex items-center justify-center" | ||
| 68 | + @click="handleCheckInSubmit" | ||
| 69 | + :disabled="isCheckingIn" | ||
| 70 | + > | ||
| 71 | + <template v-if="isCheckingIn"> | ||
| 72 | + <div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div> | ||
| 73 | + 提交中... | ||
| 74 | + </template> | ||
| 75 | + <template v-else>提交打卡</template> | ||
| 76 | + </button> | ||
| 77 | + </div> | ||
| 78 | + </template> | ||
| 79 | + </div> | ||
| 80 | + </van-popup> | ||
| 81 | +</template> | ||
| 82 | + | ||
| 83 | +<script setup> | ||
| 84 | +import { ref } from 'vue' | ||
| 85 | +import { showToast } from 'vant' | ||
| 86 | +import 'vant/lib/toast/style' | ||
| 87 | +import { checkInTypes } from '@/utils/mockData' | ||
| 88 | + | ||
| 89 | +const props = defineProps({ | ||
| 90 | + show: { | ||
| 91 | + type: Boolean, | ||
| 92 | + required: true, | ||
| 93 | + default: false | ||
| 94 | + } | ||
| 95 | +}) | ||
| 96 | + | ||
| 97 | +const emit = defineEmits(['update:show', 'check-in-success']) | ||
| 98 | + | ||
| 99 | +const selectedCheckIn = ref(null) | ||
| 100 | +const checkInContent = ref('') | ||
| 101 | +const isCheckingIn = ref(false) | ||
| 102 | +const checkInSuccess = ref(false) | ||
| 103 | + | ||
| 104 | +const handleCheckInSelect = (type) => { | ||
| 105 | + selectedCheckIn.value = type | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +const handleCheckInSubmit = async () => { | ||
| 109 | + if (!selectedCheckIn.value) { | ||
| 110 | + showToast('请选择打卡项目') | ||
| 111 | + return | ||
| 112 | + } | ||
| 113 | + if (!checkInContent.value.trim()) { | ||
| 114 | + showToast('请输入打卡内容') | ||
| 115 | + return | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + isCheckingIn.value = true | ||
| 119 | + try { | ||
| 120 | + // 模拟API调用 | ||
| 121 | + await new Promise(resolve => setTimeout(resolve, 1000)) | ||
| 122 | + checkInSuccess.value = true | ||
| 123 | + emit('check-in-success') | ||
| 124 | + | ||
| 125 | + // 重置表单 | ||
| 126 | + setTimeout(() => { | ||
| 127 | + checkInSuccess.value = false | ||
| 128 | + selectedCheckIn.value = null | ||
| 129 | + checkInContent.value = '' | ||
| 130 | + emit('update:show', false) | ||
| 131 | + }, 1500) | ||
| 132 | + } catch (error) { | ||
| 133 | + showToast('打卡失败,请重试') | ||
| 134 | + } finally { | ||
| 135 | + isCheckingIn.value = false | ||
| 136 | + } | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +const handleClose = () => { | ||
| 140 | + selectedCheckIn.value = null | ||
| 141 | + checkInContent.value = '' | ||
| 142 | + checkInSuccess.value = false | ||
| 143 | + emit('update:show', false) | ||
| 144 | +} | ||
| 145 | +</script> |
| 1 | <template> | 1 | <template> |
| 2 | <AppLayout title="我的" :right-content="rightContent"> | 2 | <AppLayout title="我的" :right-content="rightContent"> |
| 3 | - <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen"> | 3 | + <div |
| 4 | + class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen" | ||
| 5 | + > | ||
| 4 | <!-- User Profile Header with Enhanced Design --> | 6 | <!-- User Profile Header with Enhanced Design --> |
| 5 | <div class="pt-6 pb-8 relative"> | 7 | <div class="pt-6 pb-8 relative"> |
| 6 | - <div class="absolute inset-0 bg-gradient-to-r from-green-500 to-blue-500 opacity-15"></div> | 8 | + <div |
| 9 | + class="absolute inset-0 bg-gradient-to-r from-green-500 to-blue-500 opacity-15" | ||
| 10 | + ></div> | ||
| 7 | <div class="relative z-10 flex flex-col items-center"> | 11 | <div class="relative z-10 flex flex-col items-center"> |
| 8 | - <div class="w-24 h-24 rounded-full overflow-hidden border-4 border-white shadow-lg mb-4"> | 12 | + <div |
| 13 | + class="w-24 h-24 rounded-full overflow-hidden border-4 border-white shadow-lg mb-4" | ||
| 14 | + > | ||
| 9 | <img | 15 | <img |
| 10 | :src="profile.avatar || '/assets/images/user-avatar-1.jpg'" | 16 | :src="profile.avatar || '/assets/images/user-avatar-1.jpg'" |
| 11 | :alt="profile.name" | 17 | :alt="profile.name" |
| ... | @@ -32,23 +38,33 @@ | ... | @@ -32,23 +38,33 @@ |
| 32 | 38 | ||
| 33 | <div class="flex justify-between"> | 39 | <div class="flex justify-between"> |
| 34 | <div class="flex flex-col items-center"> | 40 | <div class="flex flex-col items-center"> |
| 35 | - <div class="text-2xl font-bold text-gray-800">{{ profile.checkIns?.totalDays || 0 }}</div> | 41 | + <div class="text-2xl font-bold text-gray-800"> |
| 42 | + {{ profile.checkIns?.totalDays || 0 }} | ||
| 43 | + </div> | ||
| 36 | <div class="text-xs text-gray-500 mt-1">累计打卡</div> | 44 | <div class="text-xs text-gray-500 mt-1">累计打卡</div> |
| 37 | </div> | 45 | </div> |
| 38 | <div class="flex flex-col items-center"> | 46 | <div class="flex flex-col items-center"> |
| 39 | - <div class="text-2xl font-bold text-green-600">{{ profile.checkIns?.currentStreak || 0 }}</div> | 47 | + <div class="text-2xl font-bold text-green-600"> |
| 48 | + {{ profile.checkIns?.currentStreak || 0 }} | ||
| 49 | + </div> | ||
| 40 | <div class="text-xs text-gray-500 mt-1">连续打卡</div> | 50 | <div class="text-xs text-gray-500 mt-1">连续打卡</div> |
| 41 | </div> | 51 | </div> |
| 42 | <div class="flex flex-col items-center"> | 52 | <div class="flex flex-col items-center"> |
| 43 | - <div class="text-2xl font-bold text-blue-600">{{ profile.checkIns?.longestStreak || 0 }}</div> | 53 | + <div class="text-2xl font-bold text-blue-600"> |
| 54 | + {{ profile.checkIns?.longestStreak || 0 }} | ||
| 55 | + </div> | ||
| 44 | <div class="text-xs text-gray-500 mt-1">最长连续</div> | 56 | <div class="text-xs text-gray-500 mt-1">最长连续</div> |
| 45 | </div> | 57 | </div> |
| 46 | <div> | 58 | <div> |
| 47 | - <button class="bg-gradient-to-r from-green-500 to-green-600 text-white py-2 px-6 rounded-full text-sm shadow-sm"> | 59 | + <div @click="showCheckInDialog = true" class="cursor-pointer"> |
| 60 | + <button | ||
| 61 | + class="bg-gradient-to-r from-green-500 to-green-600 text-white py-2 px-6 rounded-full text-sm shadow-sm" | ||
| 62 | + > | ||
| 48 | 立即打卡 | 63 | 立即打卡 |
| 49 | </button> | 64 | </button> |
| 50 | </div> | 65 | </div> |
| 51 | </div> | 66 | </div> |
| 67 | + </div> | ||
| 52 | </FrostedGlass> | 68 | </FrostedGlass> |
| 53 | </div> | 69 | </div> |
| 54 | 70 | ||
| ... | @@ -57,21 +73,78 @@ | ... | @@ -57,21 +73,78 @@ |
| 57 | <FrostedGlass class="p-4 rounded-xl"> | 73 | <FrostedGlass class="p-4 rounded-xl"> |
| 58 | <h3 class="font-semibold text-base mb-4">打卡项目</h3> | 74 | <h3 class="font-semibold text-base mb-4">打卡项目</h3> |
| 59 | <div class="grid grid-cols-4 gap-2"> | 75 | <div class="grid grid-cols-4 gap-2"> |
| 60 | - <div v-for="type in checkInTypes" :key="type.id" class="flex flex-col items-center cursor-pointer" @click="handleCheckInClick(type.path)"> | 76 | + <div |
| 61 | - <div class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center mb-1"> | 77 | + v-for="type in checkInTypes" |
| 62 | - <svg v-if="type.icon === 'book'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | 78 | + :key="type.id" |
| 63 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /> | 79 | + class="flex flex-col items-center cursor-pointer" |
| 80 | + @click="handleCheckInClick(type.path)" | ||
| 81 | + > | ||
| 82 | + <div | ||
| 83 | + class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center mb-1" | ||
| 84 | + > | ||
| 85 | + <svg | ||
| 86 | + v-if="type.icon === 'book'" | ||
| 87 | + xmlns="http://www.w3.org/2000/svg" | ||
| 88 | + class="h-6 w-6 text-green-600" | ||
| 89 | + fill="none" | ||
| 90 | + viewBox="0 0 24 24" | ||
| 91 | + stroke="currentColor" | ||
| 92 | + > | ||
| 93 | + <path | ||
| 94 | + stroke-linecap="round" | ||
| 95 | + stroke-linejoin="round" | ||
| 96 | + stroke-width="2" | ||
| 97 | + d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" | ||
| 98 | + /> | ||
| 64 | </svg> | 99 | </svg> |
| 65 | - <svg v-if="type.icon === 'running'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | 100 | + <svg |
| 66 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> | 101 | + v-if="type.icon === 'running'" |
| 102 | + xmlns="http://www.w3.org/2000/svg" | ||
| 103 | + class="h-6 w-6 text-green-600" | ||
| 104 | + fill="none" | ||
| 105 | + viewBox="0 0 24 24" | ||
| 106 | + stroke="currentColor" | ||
| 107 | + > | ||
| 108 | + <path | ||
| 109 | + stroke-linecap="round" | ||
| 110 | + stroke-linejoin="round" | ||
| 111 | + stroke-width="2" | ||
| 112 | + d="M13 10V3L4 14h7v7l9-11h-7z" | ||
| 113 | + /> | ||
| 67 | </svg> | 114 | </svg> |
| 68 | - <svg v-if="type.icon === 'graduation-cap'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | 115 | + <svg |
| 116 | + v-if="type.icon === 'graduation-cap'" | ||
| 117 | + xmlns="http://www.w3.org/2000/svg" | ||
| 118 | + class="h-6 w-6 text-green-600" | ||
| 119 | + fill="none" | ||
| 120 | + viewBox="0 0 24 24" | ||
| 121 | + stroke="currentColor" | ||
| 122 | + > | ||
| 69 | <path d="M12 14l9-5-9-5-9 5 9 5z" /> | 123 | <path d="M12 14l9-5-9-5-9 5 9 5z" /> |
| 70 | - <path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" /> | 124 | + <path |
| 71 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222" /> | 125 | + d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" |
| 126 | + /> | ||
| 127 | + <path | ||
| 128 | + stroke-linecap="round" | ||
| 129 | + stroke-linejoin="round" | ||
| 130 | + stroke-width="2" | ||
| 131 | + d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222" | ||
| 132 | + /> | ||
| 72 | </svg> | 133 | </svg> |
| 73 | - <svg v-if="type.icon === 'pencil-alt'" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | 134 | + <svg |
| 74 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /> | 135 | + v-if="type.icon === 'pencil-alt'" |
| 136 | + xmlns="http://www.w3.org/2000/svg" | ||
| 137 | + class="h-6 w-6 text-green-600" | ||
| 138 | + fill="none" | ||
| 139 | + viewBox="0 0 24 24" | ||
| 140 | + stroke="currentColor" | ||
| 141 | + > | ||
| 142 | + <path | ||
| 143 | + stroke-linecap="round" | ||
| 144 | + stroke-linejoin="round" | ||
| 145 | + stroke-width="2" | ||
| 146 | + d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" | ||
| 147 | + /> | ||
| 75 | </svg> | 148 | </svg> |
| 76 | </div> | 149 | </div> |
| 77 | <span class="text-xs">{{ type.name }}</span> | 150 | <span class="text-xs">{{ type.name }}</span> |
| ... | @@ -110,9 +183,7 @@ | ... | @@ -110,9 +183,7 @@ |
| 110 | </FrostedGlass> | 183 | </FrostedGlass> |
| 111 | 184 | ||
| 112 | <!-- Version Info --> | 185 | <!-- Version Info --> |
| 113 | - <div class="text-center text-xs text-gray-400 mb-4"> | 186 | + <div class="text-center text-xs text-gray-400 mb-4">亲子教育 App v1.2.0</div> |
| 114 | - 亲子教育 App v1.2.0 | ||
| 115 | - </div> | ||
| 116 | 187 | ||
| 117 | <!-- Logout Button --> | 188 | <!-- Logout Button --> |
| 118 | <button | 189 | <button |
| ... | @@ -124,125 +195,147 @@ | ... | @@ -124,125 +195,147 @@ |
| 124 | </div> | 195 | </div> |
| 125 | </div> | 196 | </div> |
| 126 | </AppLayout> | 197 | </AppLayout> |
| 198 | + | ||
| 199 | + <CheckInDialog | ||
| 200 | + v-model:show="showCheckInDialog" | ||
| 201 | + @check-in-success="handleCheckInSuccess" | ||
| 202 | + /> | ||
| 127 | </template> | 203 | </template> |
| 128 | 204 | ||
| 129 | <script setup> | 205 | <script setup> |
| 130 | -import { ref, h } from 'vue' | 206 | +import { ref, h } from "vue"; |
| 131 | -import { useRoute, useRouter } from 'vue-router' | 207 | +import { useRoute, useRouter } from "vue-router"; |
| 132 | -import AppLayout from '@/components/layout/AppLayout.vue' | 208 | +import AppLayout from "@/components/layout/AppLayout.vue"; |
| 133 | -import FrostedGlass from '@/components/ui/FrostedGlass.vue' | 209 | +import FrostedGlass from "@/components/ui/FrostedGlass.vue"; |
| 134 | -import MenuItem from '@/components/ui/MenuItem.vue' | 210 | +import MenuItem from "@/components/ui/MenuItem.vue"; |
| 135 | -import { useAuth } from '@/contexts/auth' | 211 | +import { useAuth } from "@/contexts/auth"; |
| 136 | -import { userProfile, checkInTypes } from '@/utils/mockData' | 212 | +import { userProfile, checkInTypes } from "@/utils/mockData"; |
| 137 | -import { useTitle } from '@vueuse/core'; | 213 | +import CheckInDialog from "@/components/ui/CheckInDialog.vue"; |
| 138 | -const router = useRouter() | 214 | +import { useTitle } from "@vueuse/core"; |
| 215 | +const router = useRouter(); | ||
| 139 | const $route = useRoute(); | 216 | const $route = useRoute(); |
| 140 | const $router = useRouter(); | 217 | const $router = useRouter(); |
| 141 | useTitle($route.meta.title); | 218 | useTitle($route.meta.title); |
| 142 | -const { currentUser, logout } = useAuth() | 219 | +const { currentUser, logout } = useAuth(); |
| 143 | -const profile = ref(userProfile) | 220 | +const profile = ref(userProfile); |
| 221 | +const showCheckInDialog = ref(false); | ||
| 222 | + | ||
| 223 | +// 处理打卡成功 | ||
| 224 | +const handleCheckInSuccess = () => { | ||
| 225 | + profile.value.checkIns.totalDays++; | ||
| 226 | + profile.value.checkIns.currentStreak++; | ||
| 227 | + profile.value.checkIns.longestStreak = Math.max( | ||
| 228 | + profile.value.checkIns.longestStreak, | ||
| 229 | + profile.value.checkIns.currentStreak | ||
| 230 | + ); | ||
| 231 | +}; | ||
| 144 | 232 | ||
| 145 | // Handle logout | 233 | // Handle logout |
| 146 | const handleLogout = () => { | 234 | const handleLogout = () => { |
| 147 | - logout() | 235 | + logout(); |
| 148 | - router.push('/login') | 236 | + router.push("/login"); |
| 149 | -} | 237 | +}; |
| 150 | 238 | ||
| 151 | // Handle menu item click | 239 | // Handle menu item click |
| 152 | const handleMenuClick = (path) => { | 240 | const handleMenuClick = (path) => { |
| 153 | - router.push(path) | 241 | + router.push(path); |
| 154 | -} | 242 | +}; |
| 155 | 243 | ||
| 156 | // Handle image error | 244 | // Handle image error |
| 157 | const handleImageError = (e) => { | 245 | const handleImageError = (e) => { |
| 158 | - e.target.onerror = null | 246 | + e.target.onerror = null; |
| 159 | - e.target.src = '/assets/images/user-avatar-1.jpg' | 247 | + e.target.src = "/assets/images/user-avatar-1.jpg"; |
| 160 | -} | 248 | +}; |
| 161 | 249 | ||
| 162 | // Handle check-in type click | 250 | // Handle check-in type click |
| 163 | const handleCheckInClick = (path) => { | 251 | const handleCheckInClick = (path) => { |
| 164 | - router.push(path) | 252 | + router.push(path); |
| 165 | -} | 253 | +}; |
| 166 | 254 | ||
| 167 | // Right content component | 255 | // Right content component |
| 168 | -const rightContent = h('div', { class: 'flex items-center' }, [ | 256 | +const rightContent = h("div", { class: "flex items-center" }, [ |
| 169 | - h('button', { class: 'p-2' }, [ | 257 | + h("button", { class: "p-2" }, [ |
| 170 | - h('svg', { | 258 | + h( |
| 171 | - xmlns: 'http://www.w3.org/2000/svg', | 259 | + "svg", |
| 172 | - class: 'h-6 w-6 text-gray-700', | 260 | + { |
| 173 | - fill: 'none', | 261 | + xmlns: "http://www.w3.org/2000/svg", |
| 174 | - viewBox: '0 0 24 24', | 262 | + class: "h-6 w-6 text-gray-700", |
| 175 | - stroke: 'currentColor' | 263 | + fill: "none", |
| 176 | - }, [ | 264 | + viewBox: "0 0 24 24", |
| 177 | - h('path', { | 265 | + stroke: "currentColor", |
| 178 | - 'stroke-linecap': 'round', | 266 | + }, |
| 179 | - 'stroke-linejoin': 'round', | 267 | + [ |
| 180 | - 'stroke-width': '2', | 268 | + h("path", { |
| 181 | - d: 'M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9' | 269 | + "stroke-linecap": "round", |
| 182 | - }) | 270 | + "stroke-linejoin": "round", |
| 183 | - ]) | 271 | + "stroke-width": "2", |
| 184 | - ]) | 272 | + d: |
| 185 | -]) | 273 | + "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9", |
| 274 | + }), | ||
| 275 | + ] | ||
| 276 | + ), | ||
| 277 | + ]), | ||
| 278 | +]); | ||
| 186 | 279 | ||
| 187 | // Menu items | 280 | // Menu items |
| 188 | const menuItems1 = [ | 281 | const menuItems1 = [ |
| 189 | { | 282 | { |
| 190 | - icon: 'clock', | 283 | + icon: "clock", |
| 191 | - title: '学习记录', | 284 | + title: "学习记录", |
| 192 | - path: '/profile/learning-records', | 285 | + path: "/profile/learning-records", |
| 193 | - badge: 'NEW' | 286 | + badge: "NEW", |
| 194 | }, | 287 | }, |
| 195 | { | 288 | { |
| 196 | - icon: 'user', | 289 | + icon: "user", |
| 197 | - title: '我的活动', | 290 | + title: "我的活动", |
| 198 | - path: '/profile/activities' | 291 | + path: "/profile/activities", |
| 199 | }, | 292 | }, |
| 200 | { | 293 | { |
| 201 | - icon: 'book', | 294 | + icon: "book", |
| 202 | - title: '我的课程', | 295 | + title: "我的课程", |
| 203 | - path: '/profile/courses' | 296 | + path: "/profile/courses", |
| 204 | }, | 297 | }, |
| 205 | { | 298 | { |
| 206 | - icon: 'heart', | 299 | + icon: "heart", |
| 207 | - title: '我的收藏', | 300 | + title: "我的收藏", |
| 208 | - path: '/profile/favorites' | 301 | + path: "/profile/favorites", |
| 209 | - } | 302 | + }, |
| 210 | -] | 303 | +]; |
| 211 | 304 | ||
| 212 | const menuItems2 = [ | 305 | const menuItems2 = [ |
| 213 | { | 306 | { |
| 214 | - icon: 'wallet', | 307 | + icon: "wallet", |
| 215 | - title: '我的钱包', | 308 | + title: "我的钱包", |
| 216 | - path: '/profile/wallet' | 309 | + path: "/profile/wallet", |
| 217 | }, | 310 | }, |
| 218 | { | 311 | { |
| 219 | - icon: 'document', | 312 | + icon: "document", |
| 220 | - title: '我的订单', | 313 | + title: "我的订单", |
| 221 | - path: '/profile/orders' | 314 | + path: "/profile/orders", |
| 222 | }, | 315 | }, |
| 223 | { | 316 | { |
| 224 | - icon: 'chat', | 317 | + icon: "chat", |
| 225 | - title: '我的圈子', | 318 | + title: "我的圈子", |
| 226 | - path: '/profile/community' | 319 | + path: "/profile/community", |
| 227 | - } | 320 | + }, |
| 228 | -] | 321 | +]; |
| 229 | 322 | ||
| 230 | const menuItems3 = [ | 323 | const menuItems3 = [ |
| 231 | { | 324 | { |
| 232 | - icon: 'email', | 325 | + icon: "email", |
| 233 | - title: '消息中心', | 326 | + title: "消息中心", |
| 234 | - path: '/profile/messages', | 327 | + path: "/profile/messages", |
| 235 | - badge: '3' | 328 | + badge: "3", |
| 236 | }, | 329 | }, |
| 237 | { | 330 | { |
| 238 | - icon: 'question', | 331 | + icon: "question", |
| 239 | - title: '帮助中心', | 332 | + title: "帮助中心", |
| 240 | - path: '/profile/help' | 333 | + path: "/profile/help", |
| 241 | }, | 334 | }, |
| 242 | { | 335 | { |
| 243 | - icon: 'settings', | 336 | + icon: "settings", |
| 244 | - title: '设置', | 337 | + title: "设置", |
| 245 | - path: '/profile/settings' | 338 | + path: "/profile/settings", |
| 246 | - } | 339 | + }, |
| 247 | -] | 340 | +]; |
| 248 | </script> | 341 | </script> | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-03-20 19:53:12 | 2 | * @Date: 2025-03-20 19:53:12 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-03-20 23:44:51 | 4 | + * @LastEditTime: 2025-03-21 14:21:42 |
| 5 | * @FilePath: /mlaj/vite.config.js | 5 | * @FilePath: /mlaj/vite.config.js |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| ... | @@ -31,6 +31,7 @@ export default ({ command, mode }) => { | ... | @@ -31,6 +31,7 @@ export default ({ command, mode }) => { |
| 31 | vue(), | 31 | vue(), |
| 32 | vueJsx(), | 32 | vueJsx(), |
| 33 | AutoImport({ | 33 | AutoImport({ |
| 34 | + resolvers: [VantResolver()], | ||
| 34 | imports: ['vue', 'vue-router'], | 35 | imports: ['vue', 'vue-router'], |
| 35 | dts: 'src/auto-imports.d.ts', | 36 | dts: 'src/auto-imports.d.ts', |
| 36 | // 解决eslint报错问题 | 37 | // 解决eslint报错问题 | ... | ... |
-
Please register or login to post a comment