hookehuyr

feat(打卡功能): 添加打卡对话框组件并集成到个人主页

- 新增 `CheckInDialog.vue` 组件,支持用户选择打卡类型并提交打卡内容
- 在 `ProfilePage.vue` 中集成打卡对话框,处理打卡成功后的逻辑
- 更新 `vite.config.js` 和 `components.d.ts` 以支持 Vant 组件的自动导入
...@@ -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']
......
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报错问题
......