hookehuyr

feat(components): 新增可复用打卡列表组件 CheckInList

提取首页与弹窗中重复的打卡列表 UI 与交互逻辑,封装为独立组件
新增组件支持滚动容器、紧凑布局等配置项
更新相关文档说明及类型定义
...@@ -88,3 +88,20 @@ https://oa-dev.onwall.cn/f/mlaj ...@@ -88,3 +88,20 @@ https://oa-dev.onwall.cn/f/mlaj
88 - `/src/views/profile/StudyCoursePage.vue` 88 - `/src/views/profile/StudyCoursePage.vue`
89 - `/src/views/study/StudyDetailPage.vue` 89 - `/src/views/study/StudyDetailPage.vue`
90 - 清理:上述页面已移除旧弹窗的冗余状态与方法(如 `default_list``showTaskList``showTimeoutTaskList``selectedCheckIn` 等),统一由组件内部处理。 90 - 清理:上述页面已移除旧弹窗的冗余状态与方法(如 `default_list``showTaskList``showTimeoutTaskList``selectedCheckIn` 等),统一由组件内部处理。
91 +
92 + - 打卡列表组件 CheckInList(复用)
93 + - 目的:抽取首页与弹窗内重复的“打卡类型列表 + 提交按钮”UI与交互逻辑,提升复用性与维护性。
94 + - 位置:`/src/components/ui/CheckInList.vue`,样式补充:`/src/components/ui/CheckInList.less`(使用 Less 层级嵌套)。
95 + - Props:
96 + - `items`:打卡任务数组,元素包含 `id``name``task_type``checkin`/`upload`)、`is_gray`
97 + - `dense`:是否使用更紧凑的栅格与间距。
98 + - `scroll`:是否启用滚动容器(最大高度 13rem)。
99 + - Emits:
100 + - `submit-success`:提交成功后触发,由父组件决定后续行为(轻提示、关闭弹窗等)。
101 + - 行为:
102 + - 点击置灰的 `checkin` 项时提示“您已经完成了今天的打卡”。
103 + - 点击 `upload` 类型时跳转到 `/checkin/index?id=xxx` 上传页面。
104 + - 选择 `checkin` 类型后显示提交按钮,点击后调用接口提交;成功后抛出 `submit-success` 并重置选中项。
105 + - 使用位置:
106 + - 首页:`/src/views/HomePage.vue`(替换原重复 UI,监听 `submit-success` 显示“打卡成功”)。
107 + - 弹窗:`/src/components/ui/CheckInDialog.vue`(以 `active_list` 作为数据源,监听 `submit-success` 转发为 `check-in-success` 并延时关闭)。
......
...@@ -13,6 +13,7 @@ declare module 'vue' { ...@@ -13,6 +13,7 @@ declare module 'vue' {
13 AudioPlayer: typeof import('./components/ui/AudioPlayer.vue')['default'] 13 AudioPlayer: typeof import('./components/ui/AudioPlayer.vue')['default']
14 BottomNav: typeof import('./components/layout/BottomNav.vue')['default'] 14 BottomNav: typeof import('./components/layout/BottomNav.vue')['default']
15 CheckInDialog: typeof import('./components/ui/CheckInDialog.vue')['default'] 15 CheckInDialog: typeof import('./components/ui/CheckInDialog.vue')['default']
16 + CheckInList: typeof import('./components/ui/CheckInList.vue')['default']
16 CollapsibleCalendar: typeof import('./components/ui/CollapsibleCalendar.vue')['default'] 17 CollapsibleCalendar: typeof import('./components/ui/CollapsibleCalendar.vue')['default']
17 ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default'] 18 ConfirmDialog: typeof import('./components/ui/ConfirmDialog.vue')['default']
18 CourseCard: typeof import('./components/ui/CourseCard.vue')['default'] 19 CourseCard: typeof import('./components/ui/CourseCard.vue')['default']
......
...@@ -14,67 +14,16 @@ ...@@ -14,67 +14,16 @@
14 </h3> 14 </h3>
15 <van-icon name="cross" @click="handleClose" /> 15 <van-icon name="cross" @click="handleClose" />
16 </div> 16 </div>
17 - 17 + <CheckInList :items="active_list" @submit-success="handleListSuccess" />
18 - <div v-if="checkInSuccess" class="bg-green-50 border border-green-200 rounded-lg p-4 text-center">
19 - <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">
20 - <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" />
21 - </svg>
22 - <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4>
23 - <!-- <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> -->
24 - </div>
25 - <template v-else>
26 - <div class="grid grid-cols-2 gap-4 py-2"> <!-- grid-cols-2 强制每行2列,gap控制间距 -->
27 - <button
28 - v-for="checkInType in active_list"
29 - :key="checkInType.id"
30 - class="flex flex-col items-center p-2 rounded-lg border transition-colors
31 - bg-white/70 border-gray-100 hover:bg-white"
32 - :class="{
33 - 'bg-green-100 border-green-200': selectedCheckIn?.id === checkInType.id
34 - }"
35 - @click="handleCheckInSelect(checkInType)"
36 - >
37 - <div class="w-12 h-12 rounded-full flex items-center justify-center mb-1 transition-colors
38 - bg-gray-100 text-gray-500"
39 - :class="{
40 - 'bg-green-500 text-white': selectedCheckIn?.id === checkInType.id
41 - }"
42 - >
43 - <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" :color="checkInType.is_gray ? 'gray' : ''" />
44 - <van-icon v-if="checkInType.task_type === 'upload'" name="tosend" size="1.5rem" :color="checkInType.is_gray ? 'gray' : ''" />
45 - </div>
46 - <span :class="['text-xs', checkInType.is_gray ? 'text-gray-500' : '']">{{ checkInType.name }}</span>
47 - </button>
48 - </div>
49 -
50 - <div v-if="selectedCheckIn" class="mt-3">
51 - <!-- <textarea
52 - :placeholder="`请输入${selectedCheckIn.name}内容...`"
53 - v-model="checkInContent"
54 - class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24"
55 - /> -->
56 - <button
57 - 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"
58 - @click="handleCheckInSubmit"
59 - :disabled="isCheckingIn"
60 - >
61 - <template v-if="isCheckingIn">
62 - <div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
63 - 提交中...
64 - </template>
65 - <template v-else>提交打卡</template>
66 - </button>
67 - </div>
68 - </template>
69 </div> 18 </div>
70 </van-popup> 19 </van-popup>
71 </template> 20 </template>
72 21
73 <script setup> 22 <script setup>
74 import { ref, computed, onMounted } from 'vue' 23 import { ref, computed, onMounted } from 'vue'
75 -import { showToast } from 'vant'
76 import { useRoute, useRouter } from 'vue-router' 24 import { useRoute, useRouter } from 'vue-router'
77 -import { getTaskListAPI, checkinTaskAPI } from "@/api/checkin"; 25 +import CheckInList from '@/components/ui/CheckInList.vue'
26 +import { getTaskListAPI } from "@/api/checkin";
78 27
79 // 签到列表 28 // 签到列表
80 const checkInTypes = ref([]); 29 const checkInTypes = ref([]);
...@@ -93,11 +42,6 @@ const props = defineProps({ ...@@ -93,11 +42,6 @@ const props = defineProps({
93 42
94 const emit = defineEmits(['update:show', 'check-in-success', 'check-in-data']) 43 const emit = defineEmits(['update:show', 'check-in-success', 'check-in-data'])
95 44
96 -const selectedCheckIn = ref(null)
97 -const checkInContent = ref('')
98 -const isCheckingIn = ref(false)
99 -const checkInSuccess = ref(false)
100 -
101 /** 45 /**
102 * @var {import('vue').Ref<'today'|'history'>} active_tab 46 * @var {import('vue').Ref<'today'|'history'>} active_tab
103 * @description 当前选中的任务标签页:今日或历史。 47 * @description 当前选中的任务标签页:今日或历史。
...@@ -128,77 +72,45 @@ const active_list = computed(() => { ...@@ -128,77 +72,45 @@ const active_list = computed(() => {
128 return history 72 return history
129 }) 73 })
130 74
131 -const handleCheckInSelect = (type) => { 75 +/**
132 - if (type.is_gray && type.task_type === 'checkin') { 76 + * @function refresh_checkin_list
133 - showToast('您已经完成了今天的打卡') 77 + * @description 重新获取打卡任务列表,用于提交成功后更新置灰状态;同时向父组件透出最新数据。
134 - return 78 + * @returns {Promise<void>}
135 - } 79 + */
136 - if (type.task_type === 'upload') { 80 +const refresh_checkin_list = async () => {
137 - router.push({ 81 + const task = await getTaskListAPI()
138 - path: '/checkin/index', 82 + if (task?.code) {
139 - query: { 83 + // 重建本地签到任务列表(当未传入 props.items_today 时用于展示)
140 - id: type.id 84 + checkInTypes.value = (task.data || []).map(item => ({
141 - } 85 + id: item.id,
142 - }) 86 + name: item.title,
143 - } else { 87 + task_type: item.task_type,
144 - selectedCheckIn.value = type; 88 + is_gray: item.is_gray
145 - } 89 + }))
90 + // 向父组件透出最新数据,便于父组件自行刷新其持有的数据源
91 + emit('check-in-data', task.data)
92 + }
146 } 93 }
147 94
148 -const handleCheckInSubmit = async () => { 95 +/**
149 - if (!selectedCheckIn.value) { 96 + * @function handleListSuccess
150 - showToast('请选择打卡项目') 97 + * @description 子组件提交成功后,刷新任务列表并通知外部,然后关闭弹窗。
151 - return 98 + * @returns {Promise<void>}
152 - } 99 + */
153 - // if (!checkInContent.value.trim()) { 100 +const handleListSuccess = async () => {
154 - // showToast('请输入打卡内容') 101 + await refresh_checkin_list()
155 - // return 102 + emit('check-in-success')
156 - // } 103 + setTimeout(() => emit('update:show', false), 1500)
157 -
158 - isCheckingIn.value = true
159 - try {
160 - // API调用
161 - const { code, data } = await checkinTaskAPI({ task_id: selectedCheckIn.value.id });
162 - if (code) {
163 - checkInSuccess.value = true
164 - // 重置表单
165 - setTimeout(() => {
166 - checkInSuccess.value = false
167 - selectedCheckIn.value = null
168 - checkInContent.value = ''
169 - emit('update:show', false)
170 - }, 1500)
171 - emit('check-in-success')
172 - }
173 - } catch (error) {
174 - // showToast('打卡失败,请重试')
175 - } finally {
176 - isCheckingIn.value = false
177 - }
178 } 104 }
179 105
180 const handleClose = () => { 106 const handleClose = () => {
181 - selectedCheckIn.value = null
182 - checkInContent.value = ''
183 - checkInSuccess.value = false
184 emit('update:show', false) 107 emit('update:show', false)
185 } 108 }
186 109
187 onMounted(async () => { 110 onMounted(async () => {
188 // 当未从外部传入“今日任务”时,回退为组件内部获取的通用任务列表 111 // 当未从外部传入“今日任务”时,回退为组件内部获取的通用任务列表
189 if (!Array.isArray(props.items_today) || props.items_today.length === 0) { 112 if (!Array.isArray(props.items_today) || props.items_today.length === 0) {
190 - const task = await getTaskListAPI() 113 + await refresh_checkin_list()
191 - if (task.code) {
192 - emit('check-in-data', task.data)
193 - task.data.forEach(item => {
194 - checkInTypes.value.push({
195 - id: item.id,
196 - name: item.title,
197 - task_type: item.task_type,
198 - is_gray: item.is_gray
199 - })
200 - })
201 - }
202 } 114 }
203 }) 115 })
204 </script> 116 </script>
......
1 +.CheckInListWrapper {
2 + // 列表项样式
3 + .CheckInListItem {
4 + // 选中态样式
5 + &.is-active {
6 + border-color: #bbf7d0; // 绿色边框
7 + background-color: rgba(16, 185, 129, 0.1); // 轻微绿色背景
8 + }
9 +
10 + // 图标样式
11 + .Icon {
12 + &.is-active {
13 + background-color: #10b981; // 绿色激活背景
14 + color: #ffffff; // 白色图标
15 + }
16 + }
17 + }
18 +
19 + // 提交按钮样式
20 + .SubmitBtn {
21 + &:disabled {
22 + opacity: 0.7; // 禁用态透明度
23 + }
24 + }
25 +}
1 +<template>
2 + <!-- 列表主体 -->
3 + <div :class="wrapper_class" :style="scroll_style">
4 + <button
5 + v-for="item in items"
6 + :key="item.id"
7 + class="CheckInListItem flex flex-col items-center p-2 rounded-lg border transition-colors bg-white/70 border-gray-100 hover:bg-white"
8 + :class="{ 'is-active': selected_item?.id === item.id }"
9 + @click="handle_select(item)"
10 + >
11 + <div class="Icon w-12 h-12 rounded-full flex items-center justify-center mb-1 transition-colors bg-gray-100"
12 + :class="{ 'is-active': selected_item?.id === item.id }"
13 + >
14 + <van-icon v-if="item.task_type === 'checkin'" name="edit" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
15 + <van-icon v-if="item.task_type === 'upload'" name="tosend" size="1.5rem" :color="item.is_gray ? 'gray' : ''" />
16 + </div>
17 + <span :class="['text-xs', item.is_gray ? 'text-gray-500' : '']">{{ item.name }}</span>
18 + </button>
19 + </div>
20 +
21 + <!-- 提交按钮 -->
22 + <div v-if="selected_item" class="mt-3">
23 + <button
24 + class="SubmitBtn 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"
25 + @click="handle_submit"
26 + :disabled="submitting"
27 + >
28 + <template v-if="submitting">
29 + <div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
30 + 提交中...
31 + </template>
32 + <template v-else>提交打卡</template>
33 + </button>
34 + </div>
35 +</template>
36 +
37 +<script setup>
38 +import { ref, computed } from 'vue'
39 +import { useRouter } from 'vue-router'
40 +import { checkinTaskAPI } from '@/api/checkin'
41 +import { showToast } from 'vant'
42 +
43 +/**
44 + * @typedef {Object} CheckInItem
45 + * @property {number|string} id - 任务ID。
46 + * @property {string} name - 任务名称。
47 + * @property {string} task_type - 任务类型,`checkin` 或 `upload`。
48 + * @property {boolean} [is_gray] - 是否置灰,表示今日已完成。
49 + */
50 +
51 +/**
52 + * @function props
53 + * @description 组件接收的属性定义。
54 + */
55 +const props = defineProps({
56 + items: { type: Array, default: () => [] },
57 + dense: { type: Boolean, default: false },
58 + scroll: { type: Boolean, default: false },
59 +})
60 +
61 +/**
62 + * @function emits
63 + * @description 组件对外抛出的事件。
64 + */
65 +const emit = defineEmits(['submit-success'])
66 +
67 +const router = useRouter()
68 +const selected_item = ref(null)
69 +const submitting = ref(false)
70 +
71 +/**
72 + * @function wrapper_class
73 + * @description 计算列表容器类名。
74 + * @returns {string[]}
75 + */
76 +const wrapper_class = computed(() => [
77 + 'CheckInListWrapper',
78 + props.dense ? 'grid grid-cols-2 gap-2 py-2' : 'grid grid-cols-2 gap-4 py-2',
79 +])
80 +
81 +/**
82 + * @function scroll_style
83 + * @description 当 `scroll` 为真时启用滚动区域样式。
84 + * @returns {Object}
85 + */
86 +const scroll_style = computed(() => {
87 + if (!props.scroll) return {}
88 + return { maxHeight: '13rem', overflow: 'auto' }
89 +})
90 +
91 +/**
92 + * @function handle_select
93 + * @description 处理打卡类型选择:已完成提示;上传型跳转;否则选中。
94 + * @param {CheckInItem} item - 当前点击的打卡项。
95 + * @returns {void}
96 + */
97 +const handle_select = (item) => {
98 + if (item.is_gray && item.task_type === 'checkin') {
99 + showToast('您已经完成了今天的打卡')
100 + return
101 + }
102 + if (item.task_type === 'upload') {
103 + router.push({
104 + path: '/checkin/index',
105 + query: { id: item.id },
106 + })
107 + return
108 + }
109 + selected_item.value = item
110 +}
111 +
112 +/**
113 + * @function handle_submit
114 + * @description 提交打卡调用接口,成功后抛出事件并复位。
115 + * @returns {Promise<void>}
116 + */
117 +const handle_submit = async () => {
118 + if (!selected_item.value) {
119 + showToast('请选择打卡项目')
120 + return
121 + }
122 + submitting.value = true
123 + try {
124 + const { code } = await checkinTaskAPI({ task_id: selected_item.value.id })
125 + if (code) {
126 + emit('submit-success')
127 + showToast('打卡成功')
128 + selected_item.value = null
129 + }
130 + } catch (e) {
131 + // showToast('打卡失败,请重试')
132 + } finally {
133 + submitting.value = false
134 + }
135 +}
136 +</script>
137 +
138 +<style lang="less" scoped>
139 +@import './CheckInList.less';
140 +</style>
1 <!-- 1 <!--
2 * @Date: 2025-03-20 19:55:21 2 * @Date: 2025-03-20 19:55:21
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-04 16:52:14 4 + * @LastEditTime: 2025-12-10 14:23:26
5 * @FilePath: /mlaj/src/views/HomePage.vue 5 * @FilePath: /mlaj/src/views/HomePage.vue
6 * @Description: 美乐爱觉教育首页组件 6 * @Description: 美乐爱觉教育首页组件
7 * 7 *
...@@ -79,59 +79,8 @@ ...@@ -79,59 +79,8 @@
79 <h3 class="font-medium">今日打卡</h3> 79 <h3 class="font-medium">今日打卡</h3>
80 <router-link to="/profile" class="text-green-600 text-sm">打卡记录</router-link> 80 <router-link to="/profile" class="text-green-600 text-sm">打卡记录</router-link>
81 </div> 81 </div>
82 -
83 <template v-if="checkInTypes.length"> 82 <template v-if="checkInTypes.length">
84 - <div v-if="checkInSuccess" class="bg-green-50 border border-green-200 rounded-lg p-4 text-center"> 83 + <CheckInList :items="checkInTypes" dense scroll @submit-success="handleHomeCheckInSuccess" />
85 - <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">
86 - <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" />
87 - </svg>
88 - <h4 class="text-green-700 font-medium mb-1">打卡成功!</h4>
89 - <!-- <p class="text-green-600 text-sm">+5 积分已添加到您的账户</p> -->
90 - </div>
91 - <template v-else>
92 - <div class="grid grid-cols-2 gap-2 py-2" style="max-height: 13rem; overflow: auto;">
93 - <button
94 - v-for="checkInType in checkInTypes"
95 - :key="checkInType.id"
96 - class="flex flex-col items-center p-2 rounded-lg border transition-colors
97 - bg-white/70 border-gray-100 hover:bg-white"
98 - :class="{
99 - 'bg-green-100 border-green-200': selectedCheckIn?.id === checkInType.id
100 - }"
101 - @click="handleCheckInSelect(checkInType)"
102 - >
103 - <div class="w-12 h-12 rounded-full flex items-center justify-center mb-1 transition-colors
104 - bg-gray-100"
105 - :class="{
106 - 'bg-green-500 text-white': selectedCheckIn?.id === checkInType.id
107 - }"
108 - >
109 - <van-icon v-if="checkInType.task_type === 'checkin'" name="edit" size="1.5rem" :color="checkInType.is_gray ? 'gray' : ''" />
110 - <van-icon v-if="checkInType.task_type === 'upload'" name="tosend" size="1.5rem" :color="checkInType.is_gray ? 'gray' : ''" />
111 - </div>
112 - <span :class="['text-xs', checkInType.is_gray ? 'text-gray-500' : '']">{{ checkInType.name }}</span>
113 - </button>
114 - </div>
115 -
116 - <div v-if="selectedCheckIn" class="mt-3">
117 - <!-- <textarea
118 - :placeholder="`请输入${selectedCheckIn.name}内容...`"
119 - v-model="checkInContent"
120 - class="w-full p-3 border border-gray-200 rounded-lg text-sm resize-none h-24"
121 - /> -->
122 - <button
123 - 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"
124 - @click="handleCheckInSubmit"
125 - :disabled="isCheckingIn"
126 - >
127 - <template v-if="isCheckingIn">
128 - <div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
129 - 提交中...
130 - </template>
131 - <template v-else>提交打卡</template>
132 - </button>
133 - </div>
134 - </template>
135 </template> 84 </template>
136 <template v-else> 85 <template v-else>
137 <div class="text-center"> 86 <div class="text-center">
...@@ -536,6 +485,7 @@ import LiveStreamCard from '@/components/ui/LiveStreamCard.vue' ...@@ -536,6 +485,7 @@ import LiveStreamCard from '@/components/ui/LiveStreamCard.vue'
536 import ActivityCard from '@/components/ui/ActivityCard.vue' 485 import ActivityCard from '@/components/ui/ActivityCard.vue'
537 import SummerCampCard from '@/components/ui/SummerCampCard.vue' 486 import SummerCampCard from '@/components/ui/SummerCampCard.vue'
538 import VideoPlayer from '@/components/ui/VideoPlayer.vue' 487 import VideoPlayer from '@/components/ui/VideoPlayer.vue'
488 +import CheckInList from '@/components/ui/CheckInList.vue'
539 489
540 // TODO: 导入模拟数据和工具函数 490 // TODO: 导入模拟数据和工具函数
541 import { liveStreams } from '@/utils/mockData' 491 import { liveStreams } from '@/utils/mockData'
...@@ -545,7 +495,7 @@ import { showToast } from 'vant' ...@@ -545,7 +495,7 @@ import { showToast } from 'vant'
545 495
546 // 导入接口 496 // 导入接口
547 import { getCourseListAPI } from "@/api/course"; 497 import { getCourseListAPI } from "@/api/course";
548 -import { getTaskListAPI, checkinTaskAPI } from "@/api/checkin"; 498 +import { getTaskListAPI } from "@/api/checkin";
549 499
550 // 视频播放状态管理 500 // 视频播放状态管理
551 const activeVideoIndex = ref(null); // 当前播放的视频索引 501 const activeVideoIndex = ref(null); // 当前播放的视频索引
...@@ -567,11 +517,10 @@ const { currentUser } = useAuth() ...@@ -567,11 +517,10 @@ const { currentUser } = useAuth()
567 517
568 // 响应式状态管理 518 // 响应式状态管理
569 const activeTab = ref('推荐') // 当前激活的内容标签页 519 const activeTab = ref('推荐') // 当前激活的内容标签页
570 -const selectedCheckIn = ref(null) // 选中的打卡类型 520 +// 已移除:选中项与提交逻辑由通用组件内部处理
571 -const checkInContent = ref('') // 打卡内容
572 const currentSlide = ref(0) // 当前轮播图索引 521 const currentSlide = ref(0) // 当前轮播图索引
573 -const isCheckingIn = ref(false) // 打卡提交状态 522 +// const isCheckingIn = ref(false)
574 -const checkInSuccess = ref(false) // 打卡成功状态 523 +// const checkInSuccess = ref(false)
575 const displayedRecommendations = ref([]) // 当前显示的推荐内容 524 const displayedRecommendations = ref([]) // 当前显示的推荐内容
576 525
577 // 526 //
...@@ -738,49 +687,24 @@ const scrollToSlide = (index) => { ...@@ -738,49 +687,24 @@ const scrollToSlide = (index) => {
738 } 687 }
739 } 688 }
740 689
741 -// 打卡功能:处理打卡类型选择 690 +/**
742 -const handleCheckInSelect = (checkInType) => { 691 + * @function handleHomeCheckInSuccess
743 - if (checkInType.is_gray && checkInType.task_type === 'checkin') { 692 + * @description 首页打卡成功后刷新签到任务列表,更新置灰状态,并给出轻提示。
744 - showToast('您已经完成了今天的打卡') 693 + * @returns {Promise<void>}
745 - return 694 + */
746 - } 695 +const handleHomeCheckInSuccess = async () => {
747 - if (checkInType.task_type === 'upload') { 696 + // 轻提示
748 - $router.push({ 697 + showToast('打卡成功')
749 - path: '/checkin/index', 698 + // 统一刷新:重新获取签到任务列表并更新置灰状态
750 - query: { 699 + const task = await getTaskListAPI()
751 - id: checkInType.id, 700 + if (task?.code) {
752 - }, 701 + checkInTypes.value = (task.data || []).map(item => ({
753 - }) 702 + id: item.id,
754 - } else { 703 + name: item.title,
755 - selectedCheckIn.value = checkInType // 更新选中的打卡类型 704 + task_type: item.task_type,
756 - checkInContent.value = '' // 清空打卡内容 705 + is_gray: item.is_gray
757 - } 706 + }))
758 -} 707 + }
759 -
760 -// 打卡功能:处理打卡提交
761 -const handleCheckInSubmit = async () => {
762 - // 表单验证
763 - if (!selectedCheckIn.value) {
764 - showToast('请选择打卡项目')
765 - return
766 - }
767 - // if (!checkInContent.value.trim()) {
768 - // showToast('请输入打卡内容')
769 - // return
770 - // }
771 -
772 - // API调用
773 - const { code, data } = await checkinTaskAPI({ task_id: selectedCheckIn.value.id });
774 - if (code) {
775 - isCheckingIn.value = true
776 - checkInSuccess.value = true
777 - checkInContent.value = ''
778 - setTimeout(() => {
779 - isCheckingIn.value = false
780 - selectedCheckIn.value = null
781 - checkInSuccess.value = false
782 - }, 1000);
783 - }
784 } 708 }
785 709
786 const contentRef = ref(null) // 内容区域的ref引用 710 const contentRef = ref(null) // 内容区域的ref引用
......