refactor(CheckInList): 重构课程列表为简洁文本样式并优化点击处理
- 将课程卡片布局改为简洁的文本列表形式,显示标题和日期 - 合并处理逻辑,移除二级弹框的特殊处理 - 移除未使用的图片处理方法和mock数据 - 添加本地构造的示例任务数据
Showing
1 changed file
with
84 additions
and
73 deletions
| ... | @@ -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> | ... | ... |
-
Please register or login to post a comment