feat(打卡功能): 添加打卡对话框组件并集成到个人主页
- 新增 `CheckInDialog.vue` 组件,支持用户选择打卡类型并提交打卡内容 - 在 `ProfilePage.vue` 中集成打卡对话框,处理打卡成功后的逻辑 - 更新 `vite.config.js` 和 `components.d.ts` 以支持 Vant 组件的自动导入
Showing
4 changed files
with
149 additions
and
1 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> |
This diff is collapsed. Click to expand it.
| 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