hookehuyr

feat(teacher): 新增教师端作业主页功能

- 添加作业主页路由及页面组件
- 实现作业信息展示、统计数据和日历功能
- 添加学生完成情况展示组件
- 更新任务管理页跳转逻辑
- 添加相关SVG图标和组件类型声明
...@@ -7,3 +7,8 @@ https://oa-dev.onwall.cn/f/mlaj ...@@ -7,3 +7,8 @@ https://oa-dev.onwall.cn/f/mlaj
7 - 教师端新增作业管理页面:路径 `/teacher/tasks`,标题“作业管理”。 7 - 教师端新增作业管理页面:路径 `/teacher/tasks`,标题“作业管理”。
8 - 列表展示:作业名称、开始时间、截止时间。 8 - 列表展示:作业名称、开始时间、截止时间。
9 - 当前数据来源为Mock,后续可替换为真实接口数据。 9 - 当前数据来源为Mock,后续可替换为真实接口数据。
10 + - 教师端新增作业主页:路径 `/teacher/tasks/:id`,标题“作业主页”。
11 + - 头部:作业名称、介绍文案、细项信息(周期、频次、时间段、附件类型)。
12 + - 统计:出勤率与任务完成率(参考 `myClassPage.vue` 统计样式,数据Mock)。
13 + - 日历:使用 `van-calendar` 单选模式,选择日期后展示当日学生完成情况。
14 + - 学生完成情况:参考图片2样式,勾选代表已完成,未勾选代表未完成(数据Mock)。
......
1 +<svg width="36" height="36" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
2 + <defs>
3 + <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
4 + <stop offset="0%" stop-color="#10b981"/>
5 + <stop offset="100%" stop-color="#16a34a"/>
6 + </linearGradient>
7 + </defs>
8 + <!-- 右上角圆弧形底色(四分之一圆),更贴近示例图 -->
9 + <path d="M36 0 A36 36 0 0 1 0 36 L36 36 Z" fill="url(#g)" />
10 + <!-- 白色对勾 -->
11 + <path d="M22 8 L28 14 L16 26" fill="none" stroke="#ffffff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
12 +</svg>
...\ No newline at end of file ...\ No newline at end of file
...@@ -32,6 +32,7 @@ declare module 'vue' { ...@@ -32,6 +32,7 @@ declare module 'vue' {
32 RouterView: typeof import('vue-router')['RouterView'] 32 RouterView: typeof import('vue-router')['RouterView']
33 SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] 33 SearchBar: typeof import('./components/ui/SearchBar.vue')['default']
34 SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] 34 SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default']
35 + TaskCalendar: typeof import('./components/ui/TaskCalendar.vue')['default']
35 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default'] 36 TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default']
36 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default'] 37 UploadVideoPopup: typeof import('./components/ui/UploadVideoPopup.vue')['default']
37 UserAgreement: typeof import('./components/ui/UserAgreement.vue')['default'] 38 UserAgreement: typeof import('./components/ui/UserAgreement.vue')['default']
......
1 +<!--
2 + * @Date: 2025-11-19 21:20:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-11-19 21:22:29
5 + * @FilePath: /mlaj/src/components/ui/TaskCalendar.vue
6 + * @Description: 自定义轻量日历组件(单月视图,7列栅格,点击选择日期;样式参考示例图)
7 +-->
8 +<template>
9 + <div class="TaskCalendar bg-white rounded-lg shadow px-4 py-4 relative">
10 + <!-- 顶部:左右切月 + 月份标题 -->
11 + <div class="header flex items-center justify-between mb-3">
12 + <van-icon name="arrow-left" class="text-gray-500" @click="go_prev_month" />
13 + <div class="monthTitle text-xl font-bold text-gray-900 cursor-pointer" @click="open_month_picker">{{ month_title }}</div>
14 + <van-icon name="arrow" class="text-gray-500 rotate-180" @click="go_next_month" />
15 + </div>
16 +
17 + <!-- 年月选择弹窗(使用 Vant DatePicker) -->
18 + <van-popup v-model:show="show_date_picker" position="bottom">
19 + <van-picker-group
20 + title="选择年月"
21 + :tabs="['选择年月']"
22 + @confirm="on_confirm_year_month"
23 + @cancel="on_cancel_year_month"
24 + >
25 + <van-date-picker
26 + v-model="year_month_value"
27 + :min-date="min_date"
28 + :max-date="max_date"
29 + :columns-type="columns_type"
30 + />
31 + </van-picker-group>
32 + </van-popup>
33 +
34 + <!-- 星期标题 -->
35 + <div class="weekRow grid grid-cols-7 gap-3 mb-2 text-center text-gray-500 text-sm">
36 + <div v-for="w in weeks" :key="w">{{ w }}</div>
37 + </div>
38 +
39 + <!-- 日期网格:圆形按钮,选中高亮;无日期不显示圆但保留格子位置 -->
40 + <div class="daysGrid grid grid-cols-7 gap-3">
41 + <div v-for="day in grid_days" :key="day.key"
42 + class="dayItem flex items-center justify-center rounded-full" :class="[
43 + (!day.date || day.type !== 'current') ? 'invisible' : 'bg-green-100 text-gray-700',
44 + is_selected(day) ? 'bg-green-500 text-white' : ''
45 + ]" @click="on_click_day(day)">
46 + <span v-if="day.date">{{ day.date.getDate() }}</span>
47 + </div>
48 + </div>
49 + </div>
50 +
51 +</template>
52 +
53 +<script setup>
54 +import { ref, computed, watch } from 'vue'
55 +
56 +/**
57 + * 组件对外暴露的 v-model 值:选中日期(YYYY-MM-DD)
58 + */
59 +const props = defineProps({
60 + modelValue: { type: String, default: '' }
61 +})
62 +const emit = defineEmits(['update:modelValue', 'select'])
63 +
64 +//
65 +// 状态:当前面板年月与选中日期
66 +//
67 +const selected_date = ref(props.modelValue || format_date(new Date()))
68 +const panel_year = ref(Number(selected_date.value.slice(0, 4)))
69 +const panel_month = ref(Number(selected_date.value.slice(5, 7)))
70 +
71 +watch(() => props.modelValue, (val) => {
72 + if (val) selected_date.value = val
73 +})
74 +
75 +/**
76 + * 月份标题文案
77 + * @returns {string}
78 + */
79 +const month_title = computed(() => `${panel_year.value}年${panel_month.value}月`)
80 +
81 +/**
82 + * 星期标题
83 + */
84 +const weeks = ref(['日', '一', '二', '三', '四', '五', '六'])
85 +
86 +/**
87 + * 生成日历网格(含上月/下月占位)
88 + * @returns {Array<{key:string,type:string,date:Date|null}>}
89 + */
90 +const grid_days = computed(() => {
91 + const first = new Date(panel_year.value, panel_month.value - 1, 1)
92 + const first_weekday = first.getDay() // 0..6
93 + const days_in_month = new Date(panel_year.value, panel_month.value, 0).getDate()
94 + const days = []
95 + // 上月占位
96 + for (let i = 0; i < first_weekday; i++) {
97 + days.push({ key: `p-${i}`, type: 'prev', date: null })
98 + }
99 + // 当月日期
100 + for (let d = 1; d <= days_in_month; d++) {
101 + const dt = new Date(panel_year.value, panel_month.value - 1, d)
102 + days.push({ key: `c-${d}`, type: 'current', date: dt })
103 + }
104 + // 末尾占位:补至整周
105 + const tail = (7 - (days.length % 7)) % 7
106 + for (let j = 0; j < tail; j++) {
107 + days.push({ key: `n-${j}`, type: 'next', date: null })
108 + }
109 + return days
110 +})
111 +
112 +/**
113 + * 判断是否选中该日期
114 + * @param {{type:string,date:Date|null}} day
115 + * @returns {boolean}
116 + */
117 +function is_selected(day) {
118 + if (!day.date) return false
119 + return format_date(day.date) === selected_date.value
120 +}
121 +
122 +/**
123 + * 点击某日期:更新选中,并向外派发事件
124 + * @param {{type:string,date:Date|null}} day
125 + * @returns {void}
126 + */
127 +function on_click_day(day) {
128 + if (!day.date) return
129 + const val = format_date(day.date)
130 + selected_date.value = val
131 + emit('update:modelValue', val)
132 + emit('select', val)
133 +}
134 +
135 +/**
136 + * 切换至上/下月
137 + */
138 +function go_prev_month() {
139 + const d = new Date(panel_year.value, panel_month.value - 2, 1)
140 + panel_year.value = d.getFullYear()
141 + panel_month.value = d.getMonth() + 1
142 +}
143 +function go_next_month() {
144 + const d = new Date(panel_year.value, panel_month.value, 1)
145 + panel_year.value = d.getFullYear()
146 + panel_month.value = d.getMonth() + 1
147 +}
148 +
149 +/**
150 + * 日期格式化为 YYYY-MM-DD
151 + * @param {Date} d - 日期对象
152 + * @returns {string}
153 + */
154 +function format_date(d) {
155 + const y = d.getFullYear()
156 + const m = String(d.getMonth() + 1).padStart(2, '0')
157 + const day = String(d.getDate()).padStart(2, '0')
158 + return `${y}-${m}-${day}`
159 +}
160 +
161 +/**
162 + * 年月选择弹窗相关状态
163 + */
164 +const show_date_picker = ref(false)
165 +// DatePicker 的 v-model 值(仅年/月),与 Vant 用法保持一致
166 +const year_month_value = ref([String(panel_year.value), String(panel_month.value).padStart(2, '0')])
167 +/**
168 + * DatePicker 列类型(仅年份与月份)
169 + * @type {string[]}
170 + */
171 +const columns_type = ['year', 'month']
172 +// 选择范围(可按需调整)
173 +const min_date = new Date(2020, 0, 1)
174 +const max_date = new Date(2035, 11, 31)
175 +
176 +/**
177 + * 打开年月选择弹窗
178 + * @returns {void}
179 + */
180 +function open_month_picker() {
181 + // 同步当前面板年月到选择器
182 + year_month_value.value = [String(panel_year.value), String(panel_month.value).padStart(2, '0')]
183 + show_date_picker.value = true
184 +}
185 +
186 +/**
187 + * 取消选择年月
188 + * @returns {void}
189 + */
190 +function on_cancel_year_month() {
191 + show_date_picker.value = false
192 +}
193 +
194 +/**
195 + * 确认选择年月:更新面板年月并关闭弹窗
196 + * @returns {void}
197 + */
198 +function on_confirm_year_month() {
199 + const [y, m] = year_month_value.value
200 + panel_year.value = Number(y)
201 + panel_month.value = Number(m)
202 + show_date_picker.value = false
203 +}
204 +</script>
205 +
206 +<style lang="less" scoped>
207 +.TaskCalendar {
208 + // 防止内部内容溢出容器尺寸
209 + width: 100%;
210 + max-width: 100%;
211 + overflow: hidden;
212 + .header {
213 + .monthTitle {
214 + line-height: 1.3;
215 + }
216 + }
217 +
218 + .weekRow {
219 + // 兜底栅格:确保7列布局,避免类未编译导致竖排
220 + display: grid;
221 + grid-template-columns: repeat(7, 1fr);
222 + }
223 +
224 + .daysGrid {
225 + // 兜底栅格:确保7列布局,避免类未编译导致竖排
226 + display: grid;
227 + grid-template-columns: repeat(7, 1fr);
228 +
229 + .dayItem {
230 + // 自适应圆点尺寸:不超过容器列宽与2.5rem,保持正圆
231 + width: 100%;
232 + max-width: 2.5rem;
233 + aspect-ratio: 1 / 1;
234 + border-radius: 9999px;
235 + justify-self: center;
236 + transition: all 0.2s ease-in-out;
237 + }
238 + }
239 +}
240 +</style>
...@@ -43,6 +43,15 @@ export default [ ...@@ -43,6 +43,15 @@ export default [
43 }, 43 },
44 }, 44 },
45 { 45 {
46 + path: '/teacher/tasks/:id',
47 + name: 'TeacherTaskHome',
48 + component: () => import('../views/teacher/taskHomePage.vue'),
49 + meta: {
50 + title: '作业主页',
51 + requiresAuth: true
52 + },
53 + },
54 + {
46 path: '/teacher/student/:id', 55 path: '/teacher/student/:id',
47 name: 'Student', 56 name: 'Student',
48 component: () => import('../views/teacher/studentPage.vue'), 57 component: () => import('../views/teacher/studentPage.vue'),
......
1 +<!--
2 + * @Date: 2025-11-19 21:00:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-11-19 21:43:15
5 + * @FilePath: /mlaj/src/views/teacher/taskHomePage.vue
6 + * @Description: 教师端作业主页(头部介绍、统计、日历与学生完成情况;数据Mock)
7 +-->
8 +<template>
9 + <div class="TaskHomePage">
10 + <!-- 头部卡片:名称、介绍、细项(参考图片1结构) -->
11 + <div class="headCard bg-white rounded-lg shadow px-4 py-4">
12 + <div class="title text-2xl font-bold text-gray-900 mb-2">{{ task_title }}</div>
13 + <div class="intro text-base text-gray-700 leading-relaxed mb-3">{{ task_intro }}</div>
14 + <!-- 三图展示(可选),使用CDN示例地址并加压缩参数 -->
15 + <!-- <div class="images grid grid-cols-3 gap-3 mb-3">
16 + <img v-for="(img, idx) in task_images" :key="idx" :src="img"
17 + class="rounded-md object-cover w-full h-24" />
18 + </div> -->
19 + <div class="details text-sm text-gray-600">
20 + <div class="detailItem">周期:{{ task_details.cycle }}</div>
21 + <div class="detailItem">频次:{{ task_details.frequency }}</div>
22 + <div class="detailItem">时间段:{{ task_details.time_range }}</div>
23 + <div class="detailItem">附件类型:{{ task_details.attachment_type }}</div>
24 + </div>
25 + </div>
26 +
27 + <!-- 统计数据(参考 myClassPage.vue 出勤率/任务完成) -->
28 + <div class="statsCard bg-white rounded-lg shadow px-4 py-4 mt-4">
29 + <van-row gutter="16">
30 + <!-- 出勤率 -->
31 + <van-col span="12">
32 + <div class="text-center">
33 + <div class="relative w-16 h-16 mx-auto mb-2">
34 + <van-circle v-model:current-rate="checkin_count" :rate="checkin_count" :text="checkin_text"
35 + stroke-width="70" color="#10b981" size="64" layer-color="#eee" />
36 + </div>
37 + <div class="text-sm text-gray-600">出勤率</div>
38 + </div>
39 + </van-col>
40 + <!-- 任务完成 -->
41 + <van-col span="12">
42 + <div class="text-center">
43 + <div class="relative w-16 h-16 mx-auto mb-2">
44 + <van-circle v-model:current-rate="upload_count" :rate="upload_count" :text="upload_text"
45 + stroke-width="70" color="#3b82f6" size="64" layer-color="#eee" />
46 + </div>
47 + <div class="text-sm text-gray-600">任务完成</div>
48 + </div>
49 + </van-col>
50 + </van-row>
51 + </div>
52 +
53 + <!-- 日历:选择日期后展示该日期的学生完成情况 -->
54 + <div class="calendarCard bg-white rounded-lg shadow px-4 py-4 mt-4">
55 + <div class="text-base font-semibold text-gray-800 mb-2">选择日期查看完成情况</div>
56 + <TaskCalendar v-model="selected_date" @select="on_date_select" />
57 + </div>
58 +
59 + <!-- 学生完成情况(参考图片2样式) -->
60 + <div class="studentsCard bg-white rounded-lg shadow px-4 py-4 mt-4">
61 + <div class="flex items-center justify-between mb-3">
62 + <div class="text-base font-semibold text-gray-800">完成情况({{ completed_count }}/{{ students.length }})
63 + </div>
64 + <div class="text-xs text-gray-500">当前日期:{{ current_date_text }}</div>
65 + </div>
66 + <div class="grid grid-cols-5 gap-3 StudentsGrid">
67 + <div v-for="(stu, idx) in students_status" :key="stu.id"
68 + class="studentItem relative rounded-md h-16 flex flex-col items-center justify-center text-center border overflow-hidden"
69 + :class="stu.completed ? 'bg-white border-green-500 text-green-600' : 'bg-gray-100 border-gray-300 text-gray-500'">
70 + <div class="text-sm font-semibold">{{ idx + 1 }}</div>
71 + <div class="text-sm mt-1">{{ stu.name }}</div>
72 + <img v-if="stu.completed" :src="checkCorner" alt="checked" class="cornerIcon" />
73 + </div>
74 + </div>
75 + </div>
76 + </div>
77 +</template>
78 +
79 +<script setup>
80 +import { ref, computed } from 'vue'
81 +import { useRoute } from 'vue-router'
82 +import { useTitle } from '@vueuse/core'
83 +import TaskCalendar from '@/components/ui/TaskCalendar.vue'
84 +import checkCorner from '@/assets/check_corner.svg'
85 +
86 +const $route = useRoute()
87 +useTitle('作业主页')
88 +
89 +//
90 +// Mock:作业基础信息
91 +//
92 +const task_id = $route.params.id || '0'
93 +const task_title = ref('组长每日共修打卡内容')
94 +const task_intro = ref('组长与副组长以上每日语音打卡内容:《义工宣言》《万事如意祈请文》《吉祥圆满感恩文》')
95 +const task_images = ref([
96 + 'https://cdn.ipadbiz.cn/mlaj/demo-task-1.png?imageMogr2/thumbnail/200x/strip/quality/70',
97 + 'https://cdn.ipadbiz.cn/mlaj/demo-task-2.png?imageMogr2/thumbnail/200x/strip/quality/70',
98 + 'https://cdn.ipadbiz.cn/mlaj/demo-task-3.png?imageMogr2/thumbnail/200x/strip/quality/70'
99 +])
100 +const task_details = ref({
101 + cycle: '每天',
102 + frequency: '每日一次',
103 + time_range: '00:00 ~ 23:59',
104 + attachment_type: '语音/文本'
105 +})
106 +
107 +//
108 +// Mock:统计数据
109 +//
110 +const checkin_count = ref(56)
111 +const upload_count = ref(62)
112 +const checkin_text = computed(() => `${checkin_count.value}%`)
113 +const upload_text = computed(() => `${upload_count.value}%`)
114 +
115 +//
116 +// Mock:学生与完成记录
117 +// 注:每个学生给出若干已完成的日期字符串(YYYY-MM-DD)
118 +//
119 +const today = new Date()
120 +const selected_date = ref(format_date(today))
121 +const students = ref([
122 + { id: '1', name: '王菲', completed_dates: ['2025-11-18', selected_date.value] },
123 + { id: '2', name: '朱明献', completed_dates: ['2025-11-18'] },
124 + { id: '3', name: '陈小云', completed_dates: [] },
125 + { id: '4', name: '冯新虎', completed_dates: [selected_date.value] },
126 + { id: '5', name: '罗睿', completed_dates: ['2025-11-17', '2025-11-18'] },
127 + { id: '6', name: '吴绍婷', completed_dates: [] },
128 + { id: '7', name: '焦淑敏', completed_dates: [selected_date.value] },
129 + { id: '8', name: '李言斐', completed_dates: [] },
130 + { id: '9', name: '陈正统', completed_dates: [] },
131 + { id: '10', name: '杨子娟', completed_dates: [] },
132 + { id: '11', name: '方萍', completed_dates: [selected_date.value] },
133 + { id: '12', name: '冯静', completed_dates: [selected_date.value] },
134 + { id: '13', name: '尤瑞', completed_dates: [] },
135 + { id: '14', name: '鲁镇伟', completed_dates: [] },
136 + { id: '15', name: '黄润', completed_dates: [selected_date.value] },
137 + { id: '16', name: '王亚琼', completed_dates: [selected_date.value] },
138 + { id: '17', name: '高晓云', completed_dates: [selected_date.value] },
139 + { id: '18', name: '张朗', completed_dates: [] },
140 + { id: '19', name: '姚娟', completed_dates: [selected_date.value] },
141 + { id: '20', name: '李凯', completed_dates: [selected_date.value] },
142 + { id: '21', name: '李鑫', completed_dates: [] },
143 + { id: '22', name: '礼忠斌', completed_dates: [] },
144 + { id: '23', name: '谭小梅', completed_dates: [selected_date.value] },
145 + { id: '24', name: '赵红梅', completed_dates: [] }
146 +])
147 +
148 +/**
149 + * 将日期对象格式化为 YYYY-MM-DD
150 + * @param {Date} d - 日期对象
151 + * @returns {string} 格式化后的日期字符串
152 + * 注释:补零并返回标准格式,便于与完成记录匹配。
153 + */
154 +function format_date(d) {
155 + const y = d.getFullYear()
156 + const m = String(d.getMonth() + 1).padStart(2, '0')
157 + const day = String(d.getDate()).padStart(2, '0')
158 + return `${y}-${m}-${day}`
159 +}
160 +
161 +/**
162 + * 处理日历选择事件
163 + * @param {any} date - 选中的日期对象
164 + * @returns {void}
165 + * 注释:更新选中日期,并联动学生完成情况。
166 + */
167 +function on_date_select(val) {
168 + // 自定义日历组件返回 YYYY-MM-DD 字符串
169 + selected_date.value = val
170 +}
171 +
172 +/**
173 + * 计算某日期下学生完成情况列表
174 + * @returns {{id:string,name:string,completed:boolean}[]} 学生状态列表
175 + * 注释:根据 selected_date 在每个学生的 completed_dates 中判断是否完成。
176 + */
177 +const students_status = computed(() => {
178 + return students.value.map(stu => ({
179 + id: stu.id,
180 + name: stu.name,
181 + completed: stu.completed_dates.includes(selected_date.value)
182 + }))
183 +})
184 +
185 +/**
186 + * 完成人数统计文案
187 + * @returns {number} 完成人数
188 + */
189 +const completed_count = computed(() => students_status.value.filter(s => s.completed).length)
190 +
191 +/**
192 + * 当前日期的展示文本
193 + * @returns {string} 文本
194 + */
195 +const current_date_text = computed(() => selected_date.value)
196 +</script>
197 +
198 +<style lang="less" scoped>
199 +.TaskHomePage {
200 + min-height: 100vh;
201 + background: linear-gradient(to bottom right, #f0fdf4, #f0fdfa, #eff6ff);
202 + padding: 1rem;
203 + padding-bottom: 6rem;
204 +
205 + .headCard {
206 + .title {
207 + line-height: 1.3;
208 + }
209 +
210 + .intro {
211 + color: #374151;
212 + }
213 +
214 + .details {
215 + .detailItem {
216 + margin-bottom: 0.25rem;
217 + }
218 + }
219 + }
220 +
221 + .studentsCard {
222 + // 兜底:强制学生列表为5列栅格,避免一行仅1个的问题
223 + .StudentsGrid {
224 + display: grid;
225 + grid-template-columns: repeat(5, 1fr);
226 + gap: 0.75rem; // 等同于 tailwind 的 gap-3
227 + }
228 + .grid>div {
229 + transition: all 0.2s ease-in-out;
230 + }
231 + .studentItem {
232 + min-height: 4rem;
233 + // 卡片圆角裁切,配合右上角图片效果
234 + overflow: hidden;
235 + }
236 + .cornerIcon {
237 + // 右上角图片样式(对勾角标),贴边显示
238 + position: absolute;
239 + top: -1px;
240 + right: -1px;
241 + width: 28px;
242 + height: 28px;
243 + }
244 + }
245 +}
246 +</style>
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
29 </div> 29 </div>
30 <!-- 右侧按钮:占用较小空间,右对齐 --> 30 <!-- 右侧按钮:占用较小空间,右对齐 -->
31 <div class="right flex items-center justify-end w-20 ml-3"> 31 <div class="right flex items-center justify-end w-20 ml-3">
32 - <van-button type="primary" size="small" round class="w-full">查看</van-button> 32 + <van-button type="primary" size="small" round class="w-full" @click="go_task_home(task.id)">查看</van-button>
33 </div> 33 </div>
34 </div> 34 </div>
35 </div> 35 </div>
...@@ -45,10 +45,11 @@ ...@@ -45,10 +45,11 @@
45 45
46 <script setup> 46 <script setup>
47 import { ref } from 'vue' 47 import { ref } from 'vue'
48 -import { useRoute } from 'vue-router' 48 +import { useRoute, useRouter } from 'vue-router'
49 import { useTitle } from '@vueuse/core' 49 import { useTitle } from '@vueuse/core'
50 50
51 const $route = useRoute() 51 const $route = useRoute()
52 +const $router = useRouter()
52 useTitle($route.meta.title) 53 useTitle($route.meta.title)
53 54
54 // 55 //
...@@ -72,6 +73,16 @@ const format_date_range = (begin_date, end_date) => { ...@@ -72,6 +73,16 @@ const format_date_range = (begin_date, end_date) => {
72 const end = end_date || '-' 73 const end = end_date || '-'
73 return `${start} 至 ${end}` 74 return `${start} 至 ${end}`
74 } 75 }
76 +
77 +/**
78 + * 跳转到作业主页
79 + * @param {string} id - 作业ID
80 + * @returns {void}
81 + * 注释:点击列表项右侧“查看”按钮,导航到教师端作业主页。
82 + */
83 +const go_task_home = (id) => {
84 + $router.push({ name: 'TeacherTaskHome', params: { id } })
85 +}
75 </script> 86 </script>
76 87
77 <style lang="less" scoped> 88 <style lang="less" scoped>
......