feat(打卡模块): 新增打卡功能相关页面和路由配置
添加打卡模块的四个页面(阅读、运动、学习、写作)及其路由配置,并在用户资料页面增加打卡项目点击跳转功能。同时更新了mock数据和组件类型声明以支持新功能。
Showing
9 changed files
with
559 additions
and
5 deletions
| ... | @@ -21,7 +21,10 @@ declare module 'vue' { | ... | @@ -21,7 +21,10 @@ declare module 'vue' { |
| 21 | RouterView: typeof import('vue-router')['RouterView'] | 21 | RouterView: typeof import('vue-router')['RouterView'] |
| 22 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] | 22 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] |
| 23 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] | 23 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] |
| 24 | + VanDatePicker: typeof import('vant/es')['DatePicker'] | ||
| 24 | VanList: typeof import('vant/es')['List'] | 25 | VanList: typeof import('vant/es')['List'] |
| 26 | + VanPickerGroup: typeof import('vant/es')['PickerGroup'] | ||
| 27 | + VanPopup: typeof import('vant/es')['Popup'] | ||
| 25 | VanRate: typeof import('vant/es')['Rate'] | 28 | VanRate: typeof import('vant/es')['Rate'] |
| 26 | VanTab: typeof import('vant/es')['Tab'] | 29 | VanTab: typeof import('vant/es')['Tab'] |
| 27 | VanTabs: typeof import('vant/es')['Tabs'] | 30 | VanTabs: typeof import('vant/es')['Tabs'] | ... | ... |
src/router/checkin.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Date: 2025-03-21 13:28:30 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-21 13:33:06 | ||
| 5 | + * @FilePath: /mlaj/src/router/checkin.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 8 | +export default [ | ||
| 9 | + { | ||
| 10 | + path: '/checkin/reading', | ||
| 11 | + name: 'ReadingCheckIn', | ||
| 12 | + component: () => import('@/views/checkin/ReadingCheckInPage.vue'), | ||
| 13 | + meta: { | ||
| 14 | + title: '阅读打卡', | ||
| 15 | + requiresAuth: true | ||
| 16 | + } | ||
| 17 | + }, | ||
| 18 | + { | ||
| 19 | + path: '/checkin/exercise', | ||
| 20 | + name: 'ExerciseCheckIn', | ||
| 21 | + component: () => import('@/views/checkin/ExerciseCheckInPage.vue'), | ||
| 22 | + meta: { | ||
| 23 | + title: '运动打卡', | ||
| 24 | + requiresAuth: true | ||
| 25 | + } | ||
| 26 | + }, | ||
| 27 | + { | ||
| 28 | + path: '/checkin/study', | ||
| 29 | + name: 'StudyCheckIn', | ||
| 30 | + component: () => import('@/views/checkin/StudyCheckInPage.vue'), | ||
| 31 | + meta: { | ||
| 32 | + title: '学习打卡', | ||
| 33 | + requiresAuth: true | ||
| 34 | + } | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + path: '/checkin/writing', | ||
| 38 | + name: 'WritingCheckIn', | ||
| 39 | + component: () => import('@/views/checkin/WritingCheckInPage.vue'), | ||
| 40 | + meta: { | ||
| 41 | + title: '反思打卡', | ||
| 42 | + requiresAuth: true | ||
| 43 | + } | ||
| 44 | + } | ||
| 45 | +] |
| ... | @@ -6,6 +6,7 @@ | ... | @@ -6,6 +6,7 @@ |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | */ | 7 | */ |
| 8 | import { createRouter, createWebHistory } from 'vue-router' | 8 | import { createRouter, createWebHistory } from 'vue-router' |
| 9 | +import checkinRoutes from './checkin' | ||
| 9 | 10 | ||
| 10 | const routes = [ | 11 | const routes = [ |
| 11 | { | 12 | { |
| ... | @@ -114,6 +115,7 @@ const routes = [ | ... | @@ -114,6 +115,7 @@ const routes = [ |
| 114 | component: () => import('../views/test.vue'), | 115 | component: () => import('../views/test.vue'), |
| 115 | meta: { title: 'test' } | 116 | meta: { title: 'test' } |
| 116 | }, | 117 | }, |
| 118 | + ...checkinRoutes | ||
| 117 | ] | 119 | ] |
| 118 | 120 | ||
| 119 | const router = createRouter({ | 121 | const router = createRouter({ | ... | ... |
| ... | @@ -185,10 +185,10 @@ export const userProfile = { | ... | @@ -185,10 +185,10 @@ export const userProfile = { |
| 185 | 185 | ||
| 186 | // Daily check-in data | 186 | // Daily check-in data |
| 187 | export const checkInTypes = [ | 187 | export const checkInTypes = [ |
| 188 | - { id: 'reading', name: '阅读打卡', icon: 'book' }, | 188 | + { id: 'reading', name: '阅读打卡', icon: 'book', path: '/checkin/reading' }, |
| 189 | - { id: 'exercise', name: '运动打卡', icon: 'running' }, | 189 | + { id: 'exercise', name: '运动打卡', icon: 'running', path: '/checkin/exercise' }, |
| 190 | - { id: 'study', name: '学习打卡', icon: 'graduation-cap' }, | 190 | + { id: 'study', name: '学习打卡', icon: 'graduation-cap', path: '/checkin/study' }, |
| 191 | - { id: 'reflection', name: '反思打卡', icon: 'pencil-alt' } | 191 | + { id: 'reflection', name: '反思打卡', icon: 'pencil-alt', path: '/checkin/writing' } |
| 192 | ]; | 192 | ]; |
| 193 | 193 | ||
| 194 | // Community posts data | 194 | // Community posts data | ... | ... |
src/views/checkin/ExerciseCheckInPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-21 13:27:50 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-21 13:53:13 | ||
| 5 | + * @FilePath: /mlaj/src/views/checkin/ExerciseCheckInPage.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title="运动打卡" :show-back="true"> | ||
| 10 | + <div | ||
| 11 | + class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen p-4" | ||
| 12 | + > | ||
| 13 | + <!-- 时间筛选 --> | ||
| 14 | + <FrostedGlass class="p-4 rounded-xl mb-4"> | ||
| 15 | + <div class="flex items-center justify-between mb-4"> | ||
| 16 | + <div class="text-sm text-gray-600">选择时间范围</div> | ||
| 17 | + <div @click="showDatePicker = true" class="text-green-600 text-sm"> | ||
| 18 | + {{ formatDateRange }} | ||
| 19 | + </div> | ||
| 20 | + </div> | ||
| 21 | + </FrostedGlass> | ||
| 22 | + | ||
| 23 | + <!-- 打卡列表 --> | ||
| 24 | + <van-list | ||
| 25 | + v-model:loading="loading" | ||
| 26 | + :finished="finished" | ||
| 27 | + finished-text="没有更多了" | ||
| 28 | + @load="onLoad" | ||
| 29 | + > | ||
| 30 | + <FrostedGlass v-for="item in list" :key="item.id" class="p-4 rounded-xl mb-4"> | ||
| 31 | + <div class="flex justify-between items-start mb-2"> | ||
| 32 | + <div class="text-gray-900 font-medium">{{ item.exerciseType }}</div> | ||
| 33 | + <div class="text-sm text-gray-500">{{ item.submitTime }}</div> | ||
| 34 | + </div> | ||
| 35 | + <div class="text-gray-600 text-sm"> | ||
| 36 | + <div class="mb-1"> | ||
| 37 | + 时长:{{ item.duration }}分钟 | 强度:{{ item.intensity }} | ||
| 38 | + </div> | ||
| 39 | + <div class="whitespace-pre-wrap">{{ item.thoughts }}</div> | ||
| 40 | + </div> | ||
| 41 | + </FrostedGlass> | ||
| 42 | + </van-list> | ||
| 43 | + | ||
| 44 | + <!-- 时间选择器 --> | ||
| 45 | + <van-popup v-model:show="showDatePicker" position="bottom"> | ||
| 46 | + <van-picker-group | ||
| 47 | + title="预约日期" | ||
| 48 | + :tabs="['开始日期', '结束日期']" | ||
| 49 | + @confirm="onConfirmDate" | ||
| 50 | + @cancel="onCancelDate" | ||
| 51 | + > | ||
| 52 | + <van-date-picker v-model="startDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 53 | + <van-date-picker v-model="endDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 54 | + </van-picker-group> | ||
| 55 | + </van-popup> | ||
| 56 | + </div> | ||
| 57 | + </AppLayout> | ||
| 58 | +</template> | ||
| 59 | + | ||
| 60 | +<script setup> | ||
| 61 | +import { ref, computed } from "vue"; | ||
| 62 | +import { DatePicker, List, Popup } from "vant"; | ||
| 63 | +import AppLayout from "@/components/layout/AppLayout.vue"; | ||
| 64 | +import FrostedGlass from "@/components/ui/FrostedGlass.vue"; | ||
| 65 | + | ||
| 66 | +// 列表数据 | ||
| 67 | +const list = ref([]); | ||
| 68 | +const loading = ref(false); | ||
| 69 | +const finished = ref(false); | ||
| 70 | +const pageSize = 10; | ||
| 71 | +const currentPage = ref(1); | ||
| 72 | + | ||
| 73 | +// 日期选择 | ||
| 74 | +const showDatePicker = ref(false); | ||
| 75 | +const startDate = ref(["2022", "06", "01"]); | ||
| 76 | +const endDate = ref(["2023", "06", "01"]); | ||
| 77 | +const minDate = new Date(2020, 0, 1); | ||
| 78 | +const maxDate = new Date(2025, 5, 1); | ||
| 79 | + | ||
| 80 | +// 格式化日期范围显示 | ||
| 81 | +const formatDateRange = computed(() => { | ||
| 82 | + return `${startDate.value.join("-")} ~ ${endDate.value.join("-")}`; | ||
| 83 | +}); | ||
| 84 | + | ||
| 85 | +// 确认日期选择 | ||
| 86 | +const onConfirmDate = (values) => { | ||
| 87 | + const [start, end] = values; | ||
| 88 | + startDate.value = start.selectedValues; | ||
| 89 | + endDate.value = end.selectedValues; | ||
| 90 | + showDatePicker.value = false; | ||
| 91 | + // 重置列表并重新加载 | ||
| 92 | + list.value = []; | ||
| 93 | + finished.value = false; | ||
| 94 | + currentPage.value = 1; | ||
| 95 | + onLoad(); | ||
| 96 | +}; | ||
| 97 | + | ||
| 98 | +// 取消日期选择 | ||
| 99 | +const onCancelDate = () => { | ||
| 100 | + showDatePicker.value = false; | ||
| 101 | +}; | ||
| 102 | + | ||
| 103 | +// 加载数据 | ||
| 104 | +const onLoad = () => { | ||
| 105 | + loading.value = true; | ||
| 106 | + // 模拟数据加载 | ||
| 107 | + setTimeout(() => { | ||
| 108 | + const newItems = Array.from({ length: pageSize }, (_, index) => ({ | ||
| 109 | + id: list.value.length + index + 1, | ||
| 110 | + exerciseType: ["跑步", "步行", "骑行", "游泳"][Math.floor(Math.random() * 4)], | ||
| 111 | + duration: Math.floor(Math.random() * 120) + 30, | ||
| 112 | + intensity: ["低强度", "中等强度", "高强度"][Math.floor(Math.random() * 3)], | ||
| 113 | + thoughts: "今天的运动很充实,感觉状态不错!", | ||
| 114 | + submitTime: "2024-03-21 14:30:00", | ||
| 115 | + })); | ||
| 116 | + | ||
| 117 | + list.value.push(...newItems); | ||
| 118 | + loading.value = false; | ||
| 119 | + currentPage.value += 1; | ||
| 120 | + | ||
| 121 | + if (list.value.length >= 30) { | ||
| 122 | + finished.value = true; | ||
| 123 | + } | ||
| 124 | + }, 1000); | ||
| 125 | +}; | ||
| 126 | +</script> |
src/views/checkin/ReadingCheckInPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-21 13:27:25 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-21 13:27:26 | ||
| 5 | + * @FilePath: /mlaj/src/views/checkin/ReadingCheckInPage.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title="阅读打卡" :show-back="true"> | ||
| 10 | + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen p-4"> | ||
| 11 | + <!-- 时间筛选 --> | ||
| 12 | + <FrostedGlass class="p-4 rounded-xl mb-4"> | ||
| 13 | + <div class="flex items-center justify-between mb-4"> | ||
| 14 | + <div class="text-sm text-gray-600">选择时间范围</div> | ||
| 15 | + <div @click="showDatePicker = true" class="text-green-600 text-sm"> | ||
| 16 | + {{ formatDateRange }} | ||
| 17 | + </div> | ||
| 18 | + </div> | ||
| 19 | + </FrostedGlass> | ||
| 20 | + | ||
| 21 | + <!-- 打卡列表 --> | ||
| 22 | + <van-list | ||
| 23 | + v-model:loading="loading" | ||
| 24 | + :finished="finished" | ||
| 25 | + finished-text="没有更多了" | ||
| 26 | + @load="onLoad" | ||
| 27 | + > | ||
| 28 | + <FrostedGlass v-for="item in list" :key="item.id" class="p-4 rounded-xl mb-4"> | ||
| 29 | + <div class="flex justify-between items-start mb-2"> | ||
| 30 | + <div class="text-gray-900 font-medium">{{ item.bookTitle }}</div> | ||
| 31 | + <div class="text-sm text-gray-500">{{ item.submitTime }}</div> | ||
| 32 | + </div> | ||
| 33 | + <div class="text-gray-600 text-sm"> | ||
| 34 | + <div class="mb-1"> | ||
| 35 | + 阅读时间:{{ item.readingTime }} | ||
| 36 | + </div> | ||
| 37 | + <div class="whitespace-pre-wrap">{{ item.thoughts }}</div> | ||
| 38 | + </div> | ||
| 39 | + </FrostedGlass> | ||
| 40 | + </van-list> | ||
| 41 | + | ||
| 42 | + <!-- 时间选择器 --> | ||
| 43 | + <van-popup v-model:show="showDatePicker" position="bottom"> | ||
| 44 | + <van-picker-group | ||
| 45 | + title="预约日期" | ||
| 46 | + :tabs="['开始日期', '结束日期']" | ||
| 47 | + @confirm="onConfirmDate" | ||
| 48 | + @cancel="onCancelDate" | ||
| 49 | + > | ||
| 50 | + <van-date-picker v-model="startDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 51 | + <van-date-picker v-model="endDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 52 | + </van-picker-group> | ||
| 53 | + </van-popup> | ||
| 54 | + </div> | ||
| 55 | + </AppLayout> | ||
| 56 | +</template> | ||
| 57 | + | ||
| 58 | +<script setup> | ||
| 59 | +import { ref, computed } from "vue"; | ||
| 60 | +import { DatePicker, List, Popup } from "vant"; | ||
| 61 | +import AppLayout from '@/components/layout/AppLayout.vue' | ||
| 62 | +import FrostedGlass from '@/components/ui/FrostedGlass.vue' | ||
| 63 | + | ||
| 64 | +// 列表数据 | ||
| 65 | +const list = ref([]); | ||
| 66 | +const loading = ref(false); | ||
| 67 | +const finished = ref(false); | ||
| 68 | +const pageSize = 10; | ||
| 69 | +const currentPage = ref(1); | ||
| 70 | + | ||
| 71 | +// 日期选择 | ||
| 72 | +const showDatePicker = ref(false); | ||
| 73 | +const startDate = ref(["2022", "06", "01"]); | ||
| 74 | +const endDate = ref(["2023", "06", "01"]); | ||
| 75 | +const minDate = new Date(2020, 0, 1); | ||
| 76 | +const maxDate = new Date(2025, 5, 1); | ||
| 77 | + | ||
| 78 | +// 格式化日期范围显示 | ||
| 79 | +const formatDateRange = computed(() => { | ||
| 80 | + return `${startDate.value.join("-")} ~ ${endDate.value.join("-")}`; | ||
| 81 | +}); | ||
| 82 | + | ||
| 83 | +// 确认日期选择 | ||
| 84 | +const onConfirmDate = (values) => { | ||
| 85 | + const [start, end] = values; | ||
| 86 | + startDate.value = start.selectedValues; | ||
| 87 | + endDate.value = end.selectedValues; | ||
| 88 | + showDatePicker.value = false; | ||
| 89 | + // 重置列表并重新加载 | ||
| 90 | + list.value = []; | ||
| 91 | + finished.value = false; | ||
| 92 | + currentPage.value = 1; | ||
| 93 | + onLoad(); | ||
| 94 | +}; | ||
| 95 | + | ||
| 96 | +// 取消日期选择 | ||
| 97 | +const onCancelDate = () => { | ||
| 98 | + showDatePicker.value = false; | ||
| 99 | +}; | ||
| 100 | + | ||
| 101 | +// 加载数据 | ||
| 102 | +const onLoad = () => { | ||
| 103 | + loading.value = true; | ||
| 104 | + // 模拟数据加载 | ||
| 105 | + setTimeout(() => { | ||
| 106 | + const newItems = Array.from({ length: pageSize }, (_, index) => ({ | ||
| 107 | + id: list.value.length + index + 1, | ||
| 108 | + bookTitle: ["深入理解计算机系统", "JavaScript高级程序设计", "算法导论", "设计模式"][Math.floor(Math.random() * 4)], | ||
| 109 | + readingTime: "1小时30分钟", | ||
| 110 | + thoughts: "今天的阅读收获很多,对这个主题有了更深的理解!", | ||
| 111 | + submitTime: "2024-03-21 14:30:00", | ||
| 112 | + })); | ||
| 113 | + | ||
| 114 | + list.value.push(...newItems); | ||
| 115 | + loading.value = false; | ||
| 116 | + currentPage.value += 1; | ||
| 117 | + | ||
| 118 | + if (list.value.length >= 30) { | ||
| 119 | + finished.value = true; | ||
| 120 | + } | ||
| 121 | + }, 1000); | ||
| 122 | +}; | ||
| 123 | +</script> |
src/views/checkin/StudyCheckInPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-21 13:28:06 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-21 13:28:08 | ||
| 5 | + * @FilePath: /mlaj/src/views/checkin/StudyCheckInPage.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title="学习打卡" :show-back="true"> | ||
| 10 | + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen p-4"> | ||
| 11 | + <!-- 时间筛选 --> | ||
| 12 | + <FrostedGlass class="p-4 rounded-xl mb-4"> | ||
| 13 | + <div class="flex items-center justify-between mb-4"> | ||
| 14 | + <div class="text-sm text-gray-600">选择时间范围</div> | ||
| 15 | + <div @click="showDatePicker = true" class="text-green-600 text-sm"> | ||
| 16 | + {{ formatDateRange }} | ||
| 17 | + </div> | ||
| 18 | + </div> | ||
| 19 | + </FrostedGlass> | ||
| 20 | + | ||
| 21 | + <!-- 打卡列表 --> | ||
| 22 | + <van-list | ||
| 23 | + v-model:loading="loading" | ||
| 24 | + :finished="finished" | ||
| 25 | + finished-text="没有更多了" | ||
| 26 | + @load="onLoad" | ||
| 27 | + > | ||
| 28 | + <FrostedGlass v-for="item in list" :key="item.id" class="p-4 rounded-xl mb-4"> | ||
| 29 | + <div class="flex justify-between items-start mb-2"> | ||
| 30 | + <div class="text-gray-900 font-medium">{{ item.subject }}</div> | ||
| 31 | + <div class="text-sm text-gray-500">{{ item.submitTime }}</div> | ||
| 32 | + </div> | ||
| 33 | + <div class="text-gray-600 text-sm"> | ||
| 34 | + <div class="mb-1"> | ||
| 35 | + 学习时长:{{ item.duration }}分钟 | ||
| 36 | + </div> | ||
| 37 | + <div class="mb-1">学习内容:{{ item.content }}</div> | ||
| 38 | + <div class="whitespace-pre-wrap">学习收获:{{ item.thoughts }}</div> | ||
| 39 | + </div> | ||
| 40 | + </FrostedGlass> | ||
| 41 | + </van-list> | ||
| 42 | + | ||
| 43 | + <!-- 时间选择器 --> | ||
| 44 | + <van-popup v-model:show="showDatePicker" position="bottom"> | ||
| 45 | + <van-picker-group | ||
| 46 | + title="预约日期" | ||
| 47 | + :tabs="['开始日期', '结束日期']" | ||
| 48 | + @confirm="onConfirmDate" | ||
| 49 | + @cancel="onCancelDate" | ||
| 50 | + > | ||
| 51 | + <van-date-picker v-model="startDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 52 | + <van-date-picker v-model="endDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 53 | + </van-picker-group> | ||
| 54 | + </van-popup> | ||
| 55 | + </div> | ||
| 56 | + </AppLayout> | ||
| 57 | +</template> | ||
| 58 | + | ||
| 59 | +<script setup> | ||
| 60 | +import { ref, computed } from "vue"; | ||
| 61 | +import { DatePicker, List, Popup } from "vant"; | ||
| 62 | +import AppLayout from "@/components/layout/AppLayout.vue"; | ||
| 63 | +import FrostedGlass from "@/components/ui/FrostedGlass.vue"; | ||
| 64 | + | ||
| 65 | +// 列表数据 | ||
| 66 | +const list = ref([]); | ||
| 67 | +const loading = ref(false); | ||
| 68 | +const finished = ref(false); | ||
| 69 | +const pageSize = 10; | ||
| 70 | +const currentPage = ref(1); | ||
| 71 | + | ||
| 72 | +// 日期选择 | ||
| 73 | +const showDatePicker = ref(false); | ||
| 74 | +const startDate = ref(["2022", "06", "01"]); | ||
| 75 | +const endDate = ref(["2023", "06", "01"]); | ||
| 76 | +const minDate = new Date(2020, 0, 1); | ||
| 77 | +const maxDate = new Date(2025, 5, 1); | ||
| 78 | + | ||
| 79 | +// 格式化日期范围显示 | ||
| 80 | +const formatDateRange = computed(() => { | ||
| 81 | + return `${startDate.value.join("-")} ~ ${endDate.value.join("-")}`; | ||
| 82 | +}); | ||
| 83 | + | ||
| 84 | +// 确认日期选择 | ||
| 85 | +const onConfirmDate = (values) => { | ||
| 86 | + const [start, end] = values; | ||
| 87 | + startDate.value = start.selectedValues; | ||
| 88 | + endDate.value = end.selectedValues; | ||
| 89 | + showDatePicker.value = false; | ||
| 90 | + // 重置列表并重新加载 | ||
| 91 | + list.value = []; | ||
| 92 | + finished.value = false; | ||
| 93 | + currentPage.value = 1; | ||
| 94 | + onLoad(); | ||
| 95 | +}; | ||
| 96 | + | ||
| 97 | +// 取消日期选择 | ||
| 98 | +const onCancelDate = () => { | ||
| 99 | + showDatePicker.value = false; | ||
| 100 | +}; | ||
| 101 | + | ||
| 102 | +// 加载数据 | ||
| 103 | +const onLoad = () => { | ||
| 104 | + loading.value = true; | ||
| 105 | + // 模拟数据加载 | ||
| 106 | + setTimeout(() => { | ||
| 107 | + const newItems = Array.from({ length: pageSize }, (_, index) => ({ | ||
| 108 | + id: list.value.length + index + 1, | ||
| 109 | + subject: ["数学", "英语", "物理", "化学"][Math.floor(Math.random() * 4)], | ||
| 110 | + duration: Math.floor(Math.random() * 120) + 30, | ||
| 111 | + content: "今天学习了很多知识点,收获颇丰。", | ||
| 112 | + thoughts: "通过今天的学习,加深了对知识的理解。", | ||
| 113 | + submitTime: "2024-03-21 14:30:00", | ||
| 114 | + })); | ||
| 115 | + | ||
| 116 | + list.value.push(...newItems); | ||
| 117 | + loading.value = false; | ||
| 118 | + currentPage.value += 1; | ||
| 119 | + | ||
| 120 | + if (list.value.length >= 30) { | ||
| 121 | + finished.value = true; | ||
| 122 | + } | ||
| 123 | + }, 1000); | ||
| 124 | +}; | ||
| 125 | +</script> |
src/views/checkin/WritingCheckInPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-21 13:28:22 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-21 13:28:24 | ||
| 5 | + * @FilePath: /mlaj/src/views/checkin/WritingCheckInPage.vue | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title="写作打卡" :show-back="true"> | ||
| 10 | + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen p-4"> | ||
| 11 | + <!-- 时间筛选 --> | ||
| 12 | + <FrostedGlass class="p-4 rounded-xl mb-4"> | ||
| 13 | + <div class="flex items-center justify-between mb-4"> | ||
| 14 | + <div class="text-sm text-gray-600">选择时间范围</div> | ||
| 15 | + <div @click="showDatePicker = true" class="text-green-600 text-sm"> | ||
| 16 | + {{ formatDateRange }} | ||
| 17 | + </div> | ||
| 18 | + </div> | ||
| 19 | + </FrostedGlass> | ||
| 20 | + | ||
| 21 | + <!-- 打卡列表 --> | ||
| 22 | + <van-list | ||
| 23 | + v-model:loading="loading" | ||
| 24 | + :finished="finished" | ||
| 25 | + finished-text="没有更多了" | ||
| 26 | + @load="onLoad" | ||
| 27 | + > | ||
| 28 | + <FrostedGlass v-for="item in list" :key="item.id" class="p-4 rounded-xl mb-4"> | ||
| 29 | + <div class="flex justify-between items-start mb-2"> | ||
| 30 | + <div class="text-gray-900 font-medium">{{ item.topic }}</div> | ||
| 31 | + <div class="text-sm text-gray-500">{{ item.submitTime }}</div> | ||
| 32 | + </div> | ||
| 33 | + <div class="text-gray-600 text-sm"> | ||
| 34 | + <div class="mb-1"> | ||
| 35 | + 写作时长:{{ item.duration }}分钟 | ||
| 36 | + </div> | ||
| 37 | + <div class="mb-1">写作内容:{{ item.content }}</div> | ||
| 38 | + <div class="whitespace-pre-wrap">感悟:{{ item.thoughts }}</div> | ||
| 39 | + </div> | ||
| 40 | + </FrostedGlass> | ||
| 41 | + </van-list> | ||
| 42 | + | ||
| 43 | + <!-- 时间选择器 --> | ||
| 44 | + <van-popup v-model:show="showDatePicker" position="bottom"> | ||
| 45 | + <van-picker-group | ||
| 46 | + title="预约日期" | ||
| 47 | + :tabs="['开始日期', '结束日期']" | ||
| 48 | + @confirm="onConfirmDate" | ||
| 49 | + @cancel="onCancelDate" | ||
| 50 | + > | ||
| 51 | + <van-date-picker v-model="startDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 52 | + <van-date-picker v-model="endDate" :min-date="minDate" :max-date="maxDate" /> | ||
| 53 | + </van-picker-group> | ||
| 54 | + </van-popup> | ||
| 55 | + </div> | ||
| 56 | + </AppLayout> | ||
| 57 | +</template> | ||
| 58 | + | ||
| 59 | +<script setup> | ||
| 60 | +import { ref, computed } from "vue"; | ||
| 61 | +import { DatePicker, List, Popup } from "vant"; | ||
| 62 | +import AppLayout from "@/components/layout/AppLayout.vue"; | ||
| 63 | +import FrostedGlass from "@/components/ui/FrostedGlass.vue"; | ||
| 64 | + | ||
| 65 | +// 列表数据 | ||
| 66 | +const list = ref([]); | ||
| 67 | +const loading = ref(false); | ||
| 68 | +const finished = ref(false); | ||
| 69 | +const pageSize = 10; | ||
| 70 | +const currentPage = ref(1); | ||
| 71 | + | ||
| 72 | +// 日期选择 | ||
| 73 | +const showDatePicker = ref(false); | ||
| 74 | +const startDate = ref(["2022", "06", "01"]); | ||
| 75 | +const endDate = ref(["2023", "06", "01"]); | ||
| 76 | +const minDate = new Date(2020, 0, 1); | ||
| 77 | +const maxDate = new Date(2025, 5, 1); | ||
| 78 | + | ||
| 79 | +// 格式化日期范围显示 | ||
| 80 | +const formatDateRange = computed(() => { | ||
| 81 | + return `${startDate.value.join("-")} ~ ${endDate.value.join("-")}`; | ||
| 82 | +}); | ||
| 83 | + | ||
| 84 | +// 确认日期选择 | ||
| 85 | +const onConfirmDate = (values) => { | ||
| 86 | + const [start, end] = values; | ||
| 87 | + startDate.value = start.selectedValues; | ||
| 88 | + endDate.value = end.selectedValues; | ||
| 89 | + showDatePicker.value = false; | ||
| 90 | + // 重置列表并重新加载 | ||
| 91 | + list.value = []; | ||
| 92 | + finished.value = false; | ||
| 93 | + currentPage.value = 1; | ||
| 94 | + onLoad(); | ||
| 95 | +}; | ||
| 96 | + | ||
| 97 | +// 取消日期选择 | ||
| 98 | +const onCancelDate = () => { | ||
| 99 | + showDatePicker.value = false; | ||
| 100 | +}; | ||
| 101 | + | ||
| 102 | +// 加载数据 | ||
| 103 | +const onLoad = () => { | ||
| 104 | + loading.value = true; | ||
| 105 | + // 模拟数据加载 | ||
| 106 | + setTimeout(() => { | ||
| 107 | + const newItems = Array.from({ length: pageSize }, (_, index) => ({ | ||
| 108 | + id: list.value.length + index + 1, | ||
| 109 | + topic: ["每日随笔", "读书感悟", "生活记录", "技术博客"][Math.floor(Math.random() * 4)], | ||
| 110 | + duration: Math.floor(Math.random() * 120) + 30, | ||
| 111 | + content: "这是一段写作内容的示例...", | ||
| 112 | + thoughts: "今天的写作让我收获颇丰,继续加油!", | ||
| 113 | + submitTime: "2024-03-21 14:30:00", | ||
| 114 | + })); | ||
| 115 | + | ||
| 116 | + list.value.push(...newItems); | ||
| 117 | + loading.value = false; | ||
| 118 | + currentPage.value += 1; | ||
| 119 | + | ||
| 120 | + if (list.value.length >= 30) { | ||
| 121 | + finished.value = true; | ||
| 122 | + } | ||
| 123 | + }, 1000); | ||
| 124 | +}; | ||
| 125 | +</script> |
| ... | @@ -57,7 +57,7 @@ | ... | @@ -57,7 +57,7 @@ |
| 57 | <FrostedGlass class="p-4 rounded-xl"> | 57 | <FrostedGlass class="p-4 rounded-xl"> |
| 58 | <h3 class="font-semibold text-base mb-4">打卡项目</h3> | 58 | <h3 class="font-semibold text-base mb-4">打卡项目</h3> |
| 59 | <div class="grid grid-cols-4 gap-2"> | 59 | <div class="grid grid-cols-4 gap-2"> |
| 60 | - <div v-for="type in checkInTypes" :key="type.id" class="flex flex-col items-center"> | 60 | + <div v-for="type in checkInTypes" :key="type.id" class="flex flex-col items-center cursor-pointer" @click="handleCheckInClick(type.path)"> |
| 61 | <div class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center mb-1"> | 61 | <div class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center mb-1"> |
| 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"> | 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"> |
| 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" /> | 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" /> |
| ... | @@ -159,6 +159,11 @@ const handleImageError = (e) => { | ... | @@ -159,6 +159,11 @@ const handleImageError = (e) => { |
| 159 | e.target.src = '/assets/images/user-avatar-1.jpg' | 159 | e.target.src = '/assets/images/user-avatar-1.jpg' |
| 160 | } | 160 | } |
| 161 | 161 | ||
| 162 | +// Handle check-in type click | ||
| 163 | +const handleCheckInClick = (path) => { | ||
| 164 | + router.push(path) | ||
| 165 | +} | ||
| 166 | + | ||
| 162 | // Right content component | 167 | // Right content component |
| 163 | const rightContent = h('div', { class: 'flex items-center' }, [ | 168 | const rightContent = h('div', { class: 'flex items-center' }, [ |
| 164 | h('button', { class: 'p-2' }, [ | 169 | h('button', { class: 'p-2' }, [ | ... | ... |
-
Please register or login to post a comment