hookehuyr

refactor(CheckInList): 重构课程列表为简洁文本样式并优化点击处理

- 将课程卡片布局改为简洁的文本列表形式,显示标题和日期
- 合并处理逻辑,移除二级弹框的特殊处理
- 移除未使用的图片处理方法和mock数据
- 添加本地构造的示例任务数据
...@@ -48,24 +48,23 @@ ...@@ -48,24 +48,23 @@
48 <h3 class="font-medium">课程列表</h3> 48 <h3 class="font-medium">课程列表</h3>
49 <van-icon name="cross" @click="close_inner_popup" /> 49 <van-icon name="cross" @click="close_inner_popup" />
50 </div> 50 </div>
51 - <div class="grid grid-cols-2 gap-4"> 51 + <!-- 简单文本列表:仅显示标题与开始/结束日期 -->
52 - <div v-for="course in inner_courses" :key="course.id" class="rounded-xl overflow-hidden bg-white/80"> 52 + <div class="rounded-lg bg-white/80 divide-y">
53 - <div class="h-24 relative"> 53 + <div v-for="task in inner_courses" :key="task.id" class="taskItem bg-white rounded-lg shadow px-4 py-3 mb-3" @click="handle_click(task)">
54 - <img 54 + <div class="flex items-center justify-between">
55 - :src="format_cdn_image(course.imageUrl)" 55 + <!-- 左侧图标:垂直居中,占用较小空间 -->
56 - :alt="course.title" 56 + <div class="iconWrapper flex items-center justify-center w-8 mr-3 text-gray-500">
57 - class="w-full h-full object-cover" 57 + <van-icon name="notes-o" size="1.2rem" />
58 - />
59 - <div v-if="course.isPurchased" class="absolute top-0 left-0 bg-amber-500 text-white text-xs px-2 py-1 rounded-br-lg font-medium" style="background-color: rgba(249, 115, 22, 0.85)">
60 - 已购
61 </div> 58 </div>
62 - </div> 59 + <!-- 中间内容:占据剩余空间 -->
63 - <div class="p-2"> 60 + <div class="left flex-1">
64 - <h4 class="text-sm font-medium line-clamp-1">{{ course.title }}</h4> 61 + <div class="taskTitle text-sm font-semibold text-gray-800">{{ task.title }}</div>
65 - <p class="text-xs text-gray-500 line-clamp-1">{{ course.subtitle }}</p> 62 + <div class="taskDates text-xs text-gray-600 mt-1">开始时间:{{ dayjs(task.begin_date).format('YYYY-MM-DD') }}</div>
66 - <div class="flex justify-between items-center mt-2"> 63 + <div v-if="task.end_date" class="taskDates text-xs text-gray-600 mt-1">截止时间:{{ dayjs(task.end_date).format('YYYY-MM-DD') }}</div>
67 - <span class="text-xs text-green-600">¥{{ course.price }}</span> 64 + </div>
68 - <button class="text-xs px-2 py-1 bg-green-600 text-white rounded" @click="select_inner_course(course)">选择</button> 65 + <!-- 右侧按钮:占用较小空间,右对齐 -->
66 + <div class="right flex items-center justify-end w-20 ml-3">
67 + <van-button type="success" size="small" round class="w-full" @click="go_task_home(task.id)">查看</van-button>
69 </div> 68 </div>
70 </div> 69 </div>
71 </div> 70 </div>
...@@ -79,7 +78,7 @@ import { ref, computed, inject } from 'vue' ...@@ -79,7 +78,7 @@ import { ref, computed, inject } from 'vue'
79 import { useRouter } from 'vue-router' 78 import { useRouter } from 'vue-router'
80 import { checkinTaskAPI } from '@/api/checkin' 79 import { checkinTaskAPI } from '@/api/checkin'
81 import { showToast } from 'vant' 80 import { showToast } from 'vant'
82 -import { courses as mock_courses } from '@/utils/mockData' 81 +import dayjs from 'dayjs'
83 82
84 /** 83 /**
85 * @typedef {Object} CheckInItem 84 * @typedef {Object} CheckInItem
...@@ -143,29 +142,42 @@ const scroll_style = computed(() => { ...@@ -143,29 +142,42 @@ const scroll_style = computed(() => {
143 */ 142 */
144 const handle_select = (item) => { 143 const handle_select = (item) => {
145 // TODO: 想要判断是否有二级菜单 144 // TODO: 想要判断是否有二级菜单
146 - const has_submenu = item.children && item.children.length > 0; 145 + // const has_submenu = item.children && item.children.length > 0;
147 - // 如果有二级菜单需要特殊处理 146 + // // 如果有二级菜单需要特殊处理
148 - if (has_submenu) { 147 + // if (has_submenu) {
149 - // 不同模式下弹框的显示逻辑是不一样的 148 + // // 不同模式下弹框的显示逻辑是不一样的
150 - if (props.plain) { 149 + // if (props.plain) {
151 - // 普通模式:直接弹出本组件的popup 150 + // // 普通模式:直接弹出本组件的popup
152 - open_inner_popup() 151 + // open_inner_popup()
153 - } else { 152 + // } else {
154 - // 弹框模式:先隐藏父级弹框,再弹出本组件的popup,关闭后重新打开父级弹框 153 + // // 弹框模式:先隐藏父级弹框,再弹出本组件的popup,关闭后重新打开父级弹框
155 - if (parent_popup && typeof parent_popup.hideParent === 'function') { 154 + // if (parent_popup && typeof parent_popup.hideParent === 'function') {
156 - parent_popup.hideParent() 155 + // parent_popup.hideParent()
157 - } 156 + // }
158 - // 略微延迟以确保父弹框状态切换完成 157 + // // 略微延迟以确保父弹框状态切换完成
159 - setTimeout(() => open_inner_popup(), 50) 158 + // setTimeout(() => open_inner_popup(), 50)
160 - } 159 + // }
161 - return 160 + // return
162 - } 161 + // }
163 - // 点击已完成打卡项提示 162 + // 直接处理点击事件
163 + handle_click(item)
164 +}
165 +
166 +/**
167 + * @function handle_click
168 + * @description 点击列表项时触发:
169 + * 1) 若为内弹框任务项(含 begin_date/end_date),提示并关闭弹框;
170 + * 2) 否则按打卡项逻辑处理选择/跳转。
171 + * @param {Object} item - 列表项对象。
172 + * @returns {void}
173 + */
174 +const handle_click = (item) => {
175 + // 打卡项:已完成提示
164 if (item.is_gray && item.task_type === 'checkin') { 176 if (item.is_gray && item.task_type === 'checkin') {
165 showToast('您已经完成了今天的打卡') 177 showToast('您已经完成了今天的打卡')
166 return 178 return
167 } 179 }
168 - // 点击上传项跳转上传页 180 + // 打卡项:上传型跳转
169 if (item.task_type === 'upload') { 181 if (item.task_type === 'upload') {
170 router.push({ 182 router.push({
171 path: '/checkin/index', 183 path: '/checkin/index',
...@@ -173,6 +185,7 @@ const handle_select = (item) => { ...@@ -173,6 +185,7 @@ const handle_select = (item) => {
173 }) 185 })
174 return 186 return
175 } 187 }
188 + // 打卡项:选中后展示提交按钮
176 selected_item.value = item 189 selected_item.value = item
177 } 190 }
178 191
...@@ -203,7 +216,7 @@ const handle_submit = async () => { ...@@ -203,7 +216,7 @@ const handle_submit = async () => {
203 216
204 /** 217 /**
205 * @function open_inner_popup 218 * @function open_inner_popup
206 - * @description 打开二级弹框并填充课程列表(mock 数据)。 219 + * @description 打开二级弹框并填充示例任务列表(本地构造)。
207 * @returns {void} 220 * @returns {void}
208 */ 221 */
209 const open_inner_popup = () => { 222 const open_inner_popup = () => {
...@@ -226,45 +239,43 @@ const close_inner_popup = () => { ...@@ -226,45 +239,43 @@ const close_inner_popup = () => {
226 239
227 /** 240 /**
228 * @function build_course_list 241 * @function build_course_list
229 - * @description 构造课程列表(来源于 mock 数据)。 242 + * @description 构造示例任务数据(仅标题与开始/结束日期)。
230 - * @returns {Array} 243 + * @returns {Array<{id:string,title:string,begin_date:string,end_date?:string}>}
231 */ 244 */
232 const build_course_list = () => { 245 const build_course_list = () => {
233 - return (mock_courses || []).map(c => ({ 246 + const now = new Date()
234 - id: c.id, 247 + /**
235 - title: c.title, 248 + * @function add_days
236 - subtitle: c.subtitle, 249 + * @description 在当前日期基础上增加指定天数。
237 - imageUrl: c.imageUrl, 250 + * @param {number} days - 增加的天数(可为负数)。
238 - price: c.price, 251 + * @returns {Date}
239 - isPurchased: !!c.isPurchased 252 + */
240 - })) 253 + const add_days = (days) => {
241 -} 254 + const d = new Date(now)
242 - 255 + d.setDate(d.getDate() + days)
243 -/** 256 + return d
244 - * @function select_inner_course
245 - * @description 选择二级弹框中的课程(占位行为:提示并关闭二级弹框)。
246 - * @param {Object} course - 课程对象。
247 - * @returns {void}
248 - */
249 -const select_inner_course = (course) => {
250 - showToast(`已选择课程:${course.title}`)
251 - close_inner_popup()
252 -}
253 -
254 -/**
255 - * @function format_cdn_image
256 - * @description 若图片来自 cdn.ipadbiz.cn,则追加压缩参数;否则原样返回。
257 - * @param {string} url - 图片地址。
258 - * @returns {string}
259 - */
260 -const format_cdn_image = (url) => {
261 - if (!url) return ''
262 - const host = 'cdn.ipadbiz.cn'
263 - if (url.includes(host)) {
264 - return `${url}?imageMogr2/thumbnail/200x/strip/quality/70`
265 } 257 }
266 - return url 258 + /**
259 + * @function to_yyyy_mm_dd
260 + * @description 格式化日期为 YYYY-MM-DD 字符串。
261 + * @param {Date} date - 日期对象。
262 + * @returns {string}
263 + */
264 + const to_yyyy_mm_dd = (date) => {
265 + const y = date.getFullYear()
266 + const m = String(date.getMonth() + 1).padStart(2, '0')
267 + const d = String(date.getDate()).padStart(2, '0')
268 + return `${y}-${m}-${d}`
269 + }
270 + return [
271 + { id: 'task-1', title: '课程打卡任务一', begin_date: to_yyyy_mm_dd(add_days(-2)), end_date: to_yyyy_mm_dd(add_days(5)) },
272 + { id: 'task-2', title: '课程打卡任务二', begin_date: to_yyyy_mm_dd(add_days(-1)), end_date: to_yyyy_mm_dd(add_days(6)) },
273 + { id: 'task-3', title: '课程打卡任务三', begin_date: to_yyyy_mm_dd(add_days(0)), end_date: to_yyyy_mm_dd(add_days(7)) },
274 + { id: 'task-4', title: '课程打卡任务四', begin_date: to_yyyy_mm_dd(add_days(1)) },
275 + ]
267 } 276 }
277 +
278 +// 该组件当前不展示封面图片,移除不再使用的图片处理方法
268 </script> 279 </script>
269 280
270 <style lang="less" scoped> 281 <style lang="less" scoped>
......