JoinCheckInPage.vue 9.5 KB
<!--
 * @Date: 2025-11-17 13:42:00
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2025-11-17 15:07:52
 * @FilePath: /mlaj/src/views/checkin/JoinCheckInPage.vue
 * @Description: 文件描述
-->
<template>
    <AppLayout :hasTitle="false">
        <div class="JoinCheckInPage">
            <!-- 头部课程信息 -->
            <div class="joinHeader bg-white rounded-lg shadow px-4 py-3">
                <div class="flex flex-col">
                    <van-image
                        height="8rem"
                        fit="cover"
                        :src="mock_task_info.course_cover_url"
                        class="rounded-lg"
                    />
                    <div class="mt-3 text-center">
                        <div class="courseTitle text-base font-semibold text-gray-800">{{ mock_task_info.course_title }}</div>
                        <div class="teacher flex items-center mt-1 justify-center">
                            <van-image
                                round
                                width="1.4rem"
                                height="1.4rem"
                                fit="cover"
                                :src="mock_task_info.teacher_avatar"
                            />
                            <span class="ml-2 text-sm text-gray-700">{{ mock_task_info.teacher_name }}</span>
                        </div>
                        <div class="schedule text-xs text-gray-500 mt-1">{{ mock_task_info.schedule_desc }}</div>
                    </div>
                </div>
            </div>

            <!-- 作业信息(接口) -->
            <div class="infoCard bg-white rounded-lg shadow mt-4">
                <div class="cardHeader px-4 py-3 border-b border-gray-100">
                    <div class="cardTitle text-sm font-semibold text-gray-800">作业信息</div>
                </div>
                <div class="cardBody px-4 py-3 space-y-2">
                    <div class="infoItem flex justify-between text-sm">
                        <div class="label text-gray-500">作业名称</div>
                        <div class="value text-gray-800">{{ task_detail.title || '-' }}</div>
                    </div>
                    <div class="infoItem flex justify-between text-sm">
                        <div class="label text-gray-500">作业周期</div>
                        <div class="value text-gray-800">{{ format_cycle(task_detail.cycle) }}</div>
                    </div>
                    <div class="infoItem flex justify-between text-sm">
                        <div class="label text-gray-500">交作业的频次</div>
                        <div class="value text-gray-800">{{ format_frequency(task_detail.frequency) }}</div>
                    </div>
                    <div class="infoItem flex justify-between text-sm">
                        <div class="label text-gray-500">提交类型</div>
                        <div class="value text-gray-800">{{ format_attachment_types(task_detail.attachment_type) }}</div>
                    </div>
                    <div class="infoItem flex justify-between text-sm">
                        <div class="label text-gray-500">开始时间</div>
                        <div class="value text-gray-800">{{ task_detail.begin_date || '-' }}</div>
                    </div>
                    <div class="infoItem flex justify-between text-sm">
                        <div class="label text-gray-500">结束时间</div>
                        <div class="value text-gray-800">{{ task_detail.end_date || '-' }}</div>
                    </div>
                </div>
            </div>

            <!-- 作业说明(接口) -->
            <div class="infoCard bg-white rounded-lg shadow mt-4">
                <div class="cardHeader px-4 py-3 border-b border-gray-100">
                    <div class="cardTitle text-sm font-semibold text-gray-800">作业说明</div>
                </div>
                <div class="cardBody px-4 py-3">
                    <div class="text-sm text-gray-700 leading-6" v-html="task_detail.note || '暂无作业说明'"></div>
                </div>
            </div>

            <!-- 加入按钮 -->
            <div class="joinAction p-4 bg-gradient-to-t from-white/95 to-white/20">
                <van-button type="primary" round block class="h-11 text-base font-semibold" @click="on_join_click">
                    加入该作业
                </van-button>
            </div>
        </div>
    </AppLayout>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTitle } from "@vueuse/core";
import { showConfirmDialog } from 'vant'
import AppLayout from '@/components/layout/AppLayout.vue'
import { getTaskDetailAPI } from "@/api/checkin"

const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);

// Mock数据:课程与作业信息
const mock_task_info = ref({
    course_title: '英语口语提升营',
    course_cover_url: 'https://cdn.ipadbiz.cn/mlaj/images/cover_video_2.png?imageMogr2/thumbnail/200x/strip/quality/70',
    teacher_name: '王老师',
    teacher_avatar: 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg?imageMogr2/thumbnail/200x/strip/quality/70',
    schedule_desc: '每周二、周四晚 19:00-20:00',
    grade_name: '五年级',
    class_name: '二班',
    task_title: '本周作业:日常口语练习',
    task_desc: '请完成3次日常口语练习打卡,每次不少于60秒,可上传音频或视频。',
    target_number: 3,
    period_desc: '2025-11-01 至 2025-11-30',
    join_deadline_desc: '2025-11-20 23:59',
})

// 目标打卡页作业ID(无真实数据前默认写死
const target_checkin_id = ref($route.query.id || '833668')

/**
 * 载入页面Mock数据
 * @returns {void}
 * 注释:此处预留真实接口接入位置,当前为静态Mock。
 */
const load_mock_data = () => {
    // 预留:后续可根据 $route.query.id 拉取真实数据并映射
}

// 作业详情数据
const task_detail = ref({})

/**
 * 获取作业详情(接口)
 * @returns {Promise<void>}
 * 注释:使用 target_checkin_id 作为作业ID,调用作业详情接口并填充页面字段。
 */
const load_task_detail = async () => {
    try {
        const { code, data } = await getTaskDetailAPI({ i: target_checkin_id.value })
        if (code) {
            task_detail.value = data || {}
        }
    } catch (error) {
        // 接口异常时保持页面可用
        task_detail.value = {}
    }
}

/**
 * 格式化作业周期文案
 * @param {number|string} cycle 周期数值编码
 * @returns {string} 中文周期说明
 * 注释:0=本周期,30=每月,7=每周,1=每日。
 */
const format_cycle = (cycle) => {
    const map = {
        0: '本周期',
        30: '每月',
        7: '每周',
        1: '每日'
    }
    const key = Number(cycle)
    return map[key] || '-'
}

/**
 * 格式化频次文案
 * @param {number|string} freq 频次
 * @returns {string} 频次说明
 * 注释:显示为“X 次”。
 */
const format_frequency = (freq) => {
    if (freq === undefined || freq === null || freq === '') return '-'
    const num = Number(freq)
    return isNaN(num) ? '-' : `${num} 次`
}

/**
 * 格式化提交类型
 * @param {Array|Object|string} types 提交类型配置
 * @returns {string} 类型列表中文
 * 注释:支持数组或对象,统一转中文并以“、”连接。
 */
const format_attachment_types = (types) => {
    const type_map = {
        text: '文本',
        image: '图片',
        audio: '音频',
        video: '视频'
    }
    if (!types) return '文本、图片、音频、视频'
    if (Array.isArray(types)) {
        return types.map(k => type_map[k] || k).join('、') || '-'
    }
    if (typeof types === 'object') {
        return Object.keys(types).map(k => type_map[k] || types[k] || k).join('、') || '-'
    }
    return type_map[types] || String(types)
}

/**
 * 点击加入作业的处理
 * @returns {Promise<void>}
 * 注释:弹出确认框,确认后跳转到打卡页。
 */
const on_join_click = () => {
    try {
        showConfirmDialog({
            title: '确认加入',
            message: '是否确认加入该作业?加入后可前往打卡页进行每日打卡。',
            confirmButtonColor: '#4caf50',
        })
          .then(() => {
            // on confirm
            // 确认后跳转到打卡页
            $router.push({
                path: '/checkin/index',
                query: {
                    id: target_checkin_id.value,
                }
            })
          })
          .catch(() => {
            // on cancel
          });
    } catch (err) {
        // 用户取消
    }
}

onMounted(() => {
    load_mock_data()
    load_task_detail()
})
</script>

<style lang="less" scoped>
.JoinCheckInPage {
    min-height: 100vh;
    background: linear-gradient(to bottom right, #f0fdf4, #f0fdfa, #eff6ff);
    padding: 1rem;
    padding-bottom: 6rem;

    .joinHeader {
        .courseTitle {
            line-height: 1.4;
        }
        .teacher {
            .name {
                color: #4caf50;
            }
        }
    }

    .infoCard {
        .cardHeader {
            .cardTitle {
                color: #4caf50;
            }
        }
        .cardBody {
            .infoItem {
                .label {
                    color: #6b7280;
                }
                .value {
                    color: #111827;
                }
            }
        }
    }

    .joinAction {
        // box-shadow: 0 -6px 12px rgba(0,0,0,0.06);
    }
}
</style>