JoinCheckInPage.vue
9.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<!--
* @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>