hookehuyr

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

- 将课程卡片布局改为简洁的文本列表形式,显示标题和日期
- 合并处理逻辑,移除二级弹框的特殊处理
- 移除未使用的图片处理方法和mock数据
- 添加本地构造的示例任务数据
......@@ -48,24 +48,23 @@
<h3 class="font-medium">课程列表</h3>
<van-icon name="cross" @click="close_inner_popup" />
</div>
<div class="grid grid-cols-2 gap-4">
<div v-for="course in inner_courses" :key="course.id" class="rounded-xl overflow-hidden bg-white/80">
<div class="h-24 relative">
<img
:src="format_cdn_image(course.imageUrl)"
:alt="course.title"
class="w-full h-full object-cover"
/>
<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)">
已购
<!-- 简单文本列表:仅显示标题与开始/结束日期 -->
<div class="rounded-lg bg-white/80 divide-y">
<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)">
<div class="flex items-center justify-between">
<!-- 左侧图标:垂直居中,占用较小空间 -->
<div class="iconWrapper flex items-center justify-center w-8 mr-3 text-gray-500">
<van-icon name="notes-o" size="1.2rem" />
</div>
</div>
<div class="p-2">
<h4 class="text-sm font-medium line-clamp-1">{{ course.title }}</h4>
<p class="text-xs text-gray-500 line-clamp-1">{{ course.subtitle }}</p>
<div class="flex justify-between items-center mt-2">
<span class="text-xs text-green-600">¥{{ course.price }}</span>
<button class="text-xs px-2 py-1 bg-green-600 text-white rounded" @click="select_inner_course(course)">选择</button>
<!-- 中间内容:占据剩余空间 -->
<div class="left flex-1">
<div class="taskTitle text-sm font-semibold text-gray-800">{{ task.title }}</div>
<div class="taskDates text-xs text-gray-600 mt-1">开始时间:{{ dayjs(task.begin_date).format('YYYY-MM-DD') }}</div>
<div v-if="task.end_date" class="taskDates text-xs text-gray-600 mt-1">截止时间:{{ dayjs(task.end_date).format('YYYY-MM-DD') }}</div>
</div>
<!-- 右侧按钮:占用较小空间,右对齐 -->
<div class="right flex items-center justify-end w-20 ml-3">
<van-button type="success" size="small" round class="w-full" @click="go_task_home(task.id)">查看</van-button>
</div>
</div>
</div>
......@@ -79,7 +78,7 @@ import { ref, computed, inject } from 'vue'
import { useRouter } from 'vue-router'
import { checkinTaskAPI } from '@/api/checkin'
import { showToast } from 'vant'
import { courses as mock_courses } from '@/utils/mockData'
import dayjs from 'dayjs'
/**
* @typedef {Object} CheckInItem
......@@ -143,29 +142,42 @@ const scroll_style = computed(() => {
*/
const handle_select = (item) => {
// TODO: 想要判断是否有二级菜单
const has_submenu = item.children && item.children.length > 0;
// 如果有二级菜单需要特殊处理
if (has_submenu) {
// 不同模式下弹框的显示逻辑是不一样的
if (props.plain) {
// 普通模式:直接弹出本组件的popup
open_inner_popup()
} else {
// 弹框模式:先隐藏父级弹框,再弹出本组件的popup,关闭后重新打开父级弹框
if (parent_popup && typeof parent_popup.hideParent === 'function') {
parent_popup.hideParent()
}
// 略微延迟以确保父弹框状态切换完成
setTimeout(() => open_inner_popup(), 50)
}
return
}
// 点击已完成打卡项提示
// const has_submenu = item.children && item.children.length > 0;
// // 如果有二级菜单需要特殊处理
// if (has_submenu) {
// // 不同模式下弹框的显示逻辑是不一样的
// if (props.plain) {
// // 普通模式:直接弹出本组件的popup
// open_inner_popup()
// } else {
// // 弹框模式:先隐藏父级弹框,再弹出本组件的popup,关闭后重新打开父级弹框
// if (parent_popup && typeof parent_popup.hideParent === 'function') {
// parent_popup.hideParent()
// }
// // 略微延迟以确保父弹框状态切换完成
// setTimeout(() => open_inner_popup(), 50)
// }
// return
// }
// 直接处理点击事件
handle_click(item)
}
/**
* @function handle_click
* @description 点击列表项时触发:
* 1) 若为内弹框任务项(含 begin_date/end_date),提示并关闭弹框;
* 2) 否则按打卡项逻辑处理选择/跳转。
* @param {Object} item - 列表项对象。
* @returns {void}
*/
const handle_click = (item) => {
// 打卡项:已完成提示
if (item.is_gray && item.task_type === 'checkin') {
showToast('您已经完成了今天的打卡')
return
}
// 点击上传项跳转上传页
// 打卡项:上传型跳转
if (item.task_type === 'upload') {
router.push({
path: '/checkin/index',
......@@ -173,6 +185,7 @@ const handle_select = (item) => {
})
return
}
// 打卡项:选中后展示提交按钮
selected_item.value = item
}
......@@ -203,7 +216,7 @@ const handle_submit = async () => {
/**
* @function open_inner_popup
* @description 打开二级弹框并填充课程列表(mock 数据)。
* @description 打开二级弹框并填充示例任务列表(本地构造)。
* @returns {void}
*/
const open_inner_popup = () => {
......@@ -226,45 +239,43 @@ const close_inner_popup = () => {
/**
* @function build_course_list
* @description 构造课程列表(来源于 mock 数据)。
* @returns {Array}
* @description 构造示例任务数据(仅标题与开始/结束日期)。
* @returns {Array<{id:string,title:string,begin_date:string,end_date?:string}>}
*/
const build_course_list = () => {
return (mock_courses || []).map(c => ({
id: c.id,
title: c.title,
subtitle: c.subtitle,
imageUrl: c.imageUrl,
price: c.price,
isPurchased: !!c.isPurchased
}))
}
/**
* @function select_inner_course
* @description 选择二级弹框中的课程(占位行为:提示并关闭二级弹框)。
* @param {Object} course - 课程对象。
* @returns {void}
*/
const select_inner_course = (course) => {
showToast(`已选择课程:${course.title}`)
close_inner_popup()
}
/**
* @function format_cdn_image
* @description 若图片来自 cdn.ipadbiz.cn,则追加压缩参数;否则原样返回。
* @param {string} url - 图片地址。
* @returns {string}
*/
const format_cdn_image = (url) => {
if (!url) return ''
const host = 'cdn.ipadbiz.cn'
if (url.includes(host)) {
return `${url}?imageMogr2/thumbnail/200x/strip/quality/70`
const now = new Date()
/**
* @function add_days
* @description 在当前日期基础上增加指定天数。
* @param {number} days - 增加的天数(可为负数)。
* @returns {Date}
*/
const add_days = (days) => {
const d = new Date(now)
d.setDate(d.getDate() + days)
return d
}
return url
/**
* @function to_yyyy_mm_dd
* @description 格式化日期为 YYYY-MM-DD 字符串。
* @param {Date} date - 日期对象。
* @returns {string}
*/
const to_yyyy_mm_dd = (date) => {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${y}-${m}-${d}`
}
return [
{ id: 'task-1', title: '课程打卡任务一', begin_date: to_yyyy_mm_dd(add_days(-2)), end_date: to_yyyy_mm_dd(add_days(5)) },
{ id: 'task-2', title: '课程打卡任务二', begin_date: to_yyyy_mm_dd(add_days(-1)), end_date: to_yyyy_mm_dd(add_days(6)) },
{ id: 'task-3', title: '课程打卡任务三', begin_date: to_yyyy_mm_dd(add_days(0)), end_date: to_yyyy_mm_dd(add_days(7)) },
{ id: 'task-4', title: '课程打卡任务四', begin_date: to_yyyy_mm_dd(add_days(1)) },
]
}
// 该组件当前不展示封面图片,移除不再使用的图片处理方法
</script>
<style lang="less" scoped>
......