浏览代码

课程公告

canghailong 7 月之前
父节点
当前提交
146b5eb793

+ 28 - 0
src/api/student/classCentre.js

@@ -2,6 +2,14 @@ import { baseRequest } from '@/utils/request'
 
 const request = (url, ...arg) => baseRequest(`/api/webapp/disk/${url}`, ...arg)
 export default {
+	//课程中心-学习进度-新增
+	classPlanAdd(data) {
+		return request('coursestudentprogress/add', data)
+	},
+	//课程中心-学习进度-查询最近一次进度
+	theLastDetail(data) {
+		return request('coursestudentprogress/theLastDetail', data, 'get')
+	},
 	//课程中心-课程-详情(增加点击次数)
 	addViewCount(data) {
 		return request('coursecentry/addViewCount', data)
@@ -46,4 +54,24 @@ export default {
 	askLike(data, like = false) {
 		return request(`answer/${edit ? 'giveCancel' : 'give'}`, data, 'get')
 	},
+	//课程-收藏-添加
+	classCollectAdd(data, isLike = false) {
+		return request(`coursestudentcollect/${isLike ? 'notCollect' : 'add'}`, data)
+	},
+	//课程-收藏-列表
+	classCollectList(data) {
+		return request('coursestudentcollect/page', data, 'get')
+	},
+	//学习足迹-添加
+	footprintAdd(data) {
+		return request('footprint/add', data)
+	},
+	//学习足迹-列表
+	footprintList(data) {
+		return request('footprint/page', data, 'get')
+	},
+	//课程公告-列表
+	classNotice(data) {
+		return request('notice/page', data, 'get')
+	}
 }

+ 2 - 1
src/api/student/examPaper.js

@@ -5,5 +5,6 @@ const request = (url, ...arg) => baseRequest(`/api/webapp/` + url, ...arg)
 export default {
 	select: (id) => request('api/student/exam/paper/select/' + id, '', 'post'),
 	pageList: (query) => request('api/student/exam/paper/pageList', query, 'post'),
-	task: () => request('api/student/dashboard/task')
+	task: () => request('api/student/dashboard/task'),
+	pageExamList: (query) => request('api/admin/s_exam/page', query, 'get')
 }

+ 11 - 18
src/router/student.js

@@ -68,15 +68,20 @@ const forum = [
 			},
 			{
 				path: 'classCentre',
-				hide: true,
 				component: () => import('@/views/student/classCentre/index.vue'),
 				meta: {
 					title: '课程中心详情'
 				}
 			},
 			{
-				path: 'paper',
-				name: 'studentPaper',
+				path: 'classCollect',
+				component: () => import('@/views/student/classCollect/index.vue'),
+				meta: {
+					title: '课程收藏'
+				}
+			},
+			{
+				path: 'paper/:examType(\\d+)',
 				component: () => import('@/views/student/paper/index.vue'),
 				meta: {
 					title: '学生试卷'
@@ -84,7 +89,6 @@ const forum = [
 			},
 			{
 				path: 'questionError',
-				name: 'questionError',
 				component: () => import('@/views/student/question-error/index.vue'),
 				meta: {
 					title: '错题本'
@@ -92,7 +96,6 @@ const forum = [
 			},
 			{
 				path: 'do',
-				name: 'studentDo',
 				component: () => import('@/views/student/exam/paper/do.vue'),
 				meta: {
 					title: '试卷答题' //从学生试卷列表进入
@@ -100,27 +103,17 @@ const forum = [
 			},
 			{
 				path: 'read',
-				name: 'studentRead',
 				component: () => import('@/views/student/exam/paper/read.vue'),
 				meta: {
-					title: '试卷查看' //学生端,老师端都可用
+					title: '试卷查看'
 				}
 			},
 			{
-				path: 'exampaper',
-				name: 'exmExampaper',
-				component: () => import('@/views/exm/exampaper/index.vue'),
+				path: 'homework',
+				component: () => import('@/views/student/paper/homework.vue'),
 				meta: {
 					title: '试卷列表'
 				}
-			},
-			{
-				path: 'questionnaireManagement',
-				name: 'exmQuestionnaireManagement',
-				component: () => import('@/views/exm/questionnaireManagement/index.vue'),
-				meta: {
-					title: '问卷管理'
-				}
 			}
 		]
 	}

+ 3 - 2
src/views/exm/examinationManagement/index.vue

@@ -80,7 +80,7 @@
 </template>
 
 <script setup>
-	import { ref, reactive, onMounted } from 'vue'
+	import { ref, reactive, onMounted, computed } from 'vue'
 	import { message, Modal } from 'ant-design-vue'
 	import examManagerApi from '@/api/exam/paper/examManager.js'
 	import TaskEdit from './form.vue'
@@ -97,7 +97,8 @@
 		examName: null,
 		examStatus: null,
 		pageIndex: 1,
-		pageSize: 10
+		pageSize: 10,
+		examType: 1
 	})
 	const listLoading = ref(false)
 	const tableData = ref([])

+ 0 - 1
src/views/exm/exampaper/index.vue

@@ -9,7 +9,6 @@
 				<a-select
 					v-model:value="queryParam.paperType"
 					placeholder="请选择试卷类型"
-					@change="paperTypeChange"
 					allow-clear
 					style="width: 120px"
 				>

+ 2 - 1
src/views/exm/questionnaireManagement/index.vue

@@ -97,7 +97,8 @@
 		examName: null,
 		examStatus: null,
 		pageIndex: 1,
-		pageSize: 10
+		pageSize: 10,
+		examType: 3
 	})
 	const listLoading = ref(false)
 	const tableData = ref([])

+ 5 - 5
src/views/portal/components/Header.vue

@@ -9,10 +9,10 @@
 				<a-menu-item key="student/courseCenter">课程中心</a-menu-item>
 				<a-sub-menu key="myList">
 					<template #title>我的</template>
-					<a-menu-item key="student/paper">我的考试</a-menu-item>
-					<a-menu-item key="student/courseManagement">我的作业</a-menu-item>
-					<a-menu-item key="exm/questionnaireManagement">调查问卷</a-menu-item>
-					<a-menu-item key="exm/questionnaireManagement">课程收藏</a-menu-item>
+					<a-menu-item key="student/paper/1">我的考试</a-menu-item>
+					<a-menu-item key="student/homework">我的作业</a-menu-item>
+					<a-menu-item key="student/paper/3">调查问卷</a-menu-item>
+					<a-menu-item key="student/classCollect">课程收藏</a-menu-item>
 				</a-sub-menu>
 			</a-menu>
 		</div>
@@ -54,10 +54,10 @@
 	import { message } from 'ant-design-vue'
 	import { ref } from 'vue'
 	import { useRouter, useRoute } from 'vue-router'
+	import tool from '@/utils/tool'
 	const router = useRouter()
 	const route = useRoute()
 	const current = ref([route.path.slice(1)]) // 默认选中“资源中心”
-	import tool from '@/utils/tool'
 	const emit = defineEmits(['onChangeCurrent'])
 	const userInfo = tool.data.get('USER_INFO')
 	watch(

+ 12 - 0
src/views/resourceDetails/index.vue

@@ -21,6 +21,7 @@
 	import { addViewCount, detail, add, queryList } from '@/api/portal'
 	import resourceAuditApi from '@/api/resourceAudit.js'
 	import EventBus from '@/utils/EventBus'
+	import classCentre from '@/api/student/classCentre'
 
 	const router = useRouter()
 
@@ -68,6 +69,7 @@
 			.then((res) => {
 				if (res.code == 200) {
 					itemData.value = res.data
+					addFootprint()
 				}
 			})
 			.catch((err) => {})
@@ -80,6 +82,16 @@
 			getData({ id: id })
 		}
 	})
+	const addFootprint = () => {
+		classCentre.footprintAdd({
+			userFileId: route.query.id, //userfile文件id
+			extendName: itemData.value.suffix, //扩展名
+			fileId: itemData.value.fileId, //文件id
+			fileName: itemData.value.fileName, //文件名称
+			filePath: itemData.value.fileUrl, //文件路劲
+			resourceRecordId: itemData.value.id //文件记录id
+		})
+	}
 	defineExpose({
 		setData
 	})

+ 29 - 16
src/views/student/classCentre/form.vue

@@ -8,9 +8,11 @@
 		:mask="false"
 	>
 		<div v-if="itemObj.key == 2" style="height: 100%">
-			<vue-office-pdf :src="itemObj.url" style="width: 100%; height: 100%" />
+			<div v-if="!showPdf">渲染pdf失败</div>
+			<vue-office-pdf :src="itemObj.url" style="width: 100%; height: 100%" @error="errorHandler" />
 		</div>
 		<div v-if="itemObj.key == 3" style="height: 100%">
+			<div v-if="subtitle">{{ subtitle }}</div>
 			<div v-for="(item, idx) in itemObj.srtInfoList" :key="idx">
 				<div>{{ item.startTime }}~{{ item.endTime }}</div>
 				<div style="cursor: pointer; padding: 10px 0" @click="videoSpeed(item)">{{ item.text }}</div>
@@ -137,26 +139,33 @@
 	const videoSpeed = (e) => {
 		emit('videoSpeed', e)
 	}
+	const subtitle = ref()
 	//获取字幕内容,发送一个get请求
 	function GetSrtInfo(srtUrl) {
+		subtitle.value = ''
 		axios
 			.get(`${srtUrl}`)
 			.then(function (response) {
-				let textList = response.data
-					.split(/\n\s\n/)
-					.filter((item) => item != '')
-					.map((item, index) => {
-						let textItem = item.split(/\n/)
-						return {
-							index: index,
-							sort: textItem[0],
-							text: textItem[2],
-							startTime: ToSeconds(textItem[1].split(' --> ')[0]),
-							endTime: ToSeconds(textItem[1].split(' --> ')[1]),
-							timeLine: textItem[1]
-						}
-					})
-				itemObj.value.srtInfoList = textList
+				try {
+					let textList = response.data
+						.split(/\n\s\n/)
+						.filter((item) => item != '')
+						.map((item, index) => {
+							let textItem = item.split(/\n/)
+							return {
+								index: index,
+								sort: textItem[0],
+								text: textItem[2],
+								startTime: ToSeconds(textItem[1].split(' --> ')[0]),
+								endTime: ToSeconds(textItem[1].split(' --> ')[1]),
+								timeLine: textItem[1]
+							}
+						})
+					itemObj.value.srtInfoList = textList
+				} catch (error) {
+					subtitle.value = '字幕解析失败'
+				}
+
 				console.log('解析之后的字幕内容', textList)
 			})
 			.catch(function (error) {
@@ -174,6 +183,10 @@
 		}
 		return s
 	}
+	const showPdf = ref(true)
+	function errorHandler() {
+		showPdf.value = false
+	}
 	// 调用这个函数将子组件的一些数据和方法暴露出去
 	defineExpose({
 		onOpen

+ 113 - 42
src/views/student/classCentre/index.vue

@@ -11,29 +11,16 @@
 				mode="inline"
 				@click="menuClick"
 			>
-				<template v-for="(item, idx) in classTimeList" :key="item.id">
-					<template v-if="!item.classHours">
-						<a-menu-item :key="item.id" v-for="(item, idx) in classTimeList">
-							<span class="mr-2">
-								<CheckSquareOutlined v-if="selectedKeys.includes(item.id)" />
-								<BorderOutlined v-else />
-							</span>
-							<span>{{ item.name }}</span>
-						</a-menu-item>
-					</template>
-					<template v-else>
-						<a-sub-menu :key="String(idx + 1)" v-for="(item, idx) in classTimeList">
-							<template #title>{{ item.name }}</template>
-							<a-menu-item :key="itemc.id" v-for="(itemc, idxc) in item.classHours">
-								<span class="mr-2">
-									<CheckSquareOutlined v-if="selectedKeys.includes(itemc.id)" />
-									<BorderOutlined v-else />
-								</span>
-								<span>{{ itemc.name }}</span>
-							</a-menu-item>
-						</a-sub-menu>
-					</template>
-				</template>
+				<a-sub-menu :key="String(idx + 1)" v-for="(item, idx) in classTimeList">
+					<template #title>{{ item.name }}</template>
+					<a-menu-item :key="itemc.id" v-for="(itemc, idxc) in item.classHours">
+						<span class="mr-2">
+							<CheckSquareOutlined v-if="selectedKeys.includes(itemc.id)" />
+							<BorderOutlined v-else />
+						</span>
+						<span>{{ itemc.name }}</span>
+					</a-menu-item>
+				</a-sub-menu>
 			</a-menu>
 		</a-layout-sider>
 		<a-layout>
@@ -53,6 +40,11 @@
 							style="font-size: 30px; color: #fff"
 						/>
 					</div>
+					<div @click="isLike">
+						<a-tooltip title="收藏" style="cursor: pointer" :getPopupContainer="(trigger) => trigger.parentElement">
+							<heart-outlined :style="{ 'font-size': '20px', color: classDetail.isCollect ? '#ff4d4f' : '#fff' }" />
+						</a-tooltip>
+					</div>
 				</div>
 			</a-layout-header>
 			<a-layout-content style="overflow-y: auto">
@@ -76,11 +68,13 @@
 						<a-tabs v-model:activeKey="tabsActiveKey">
 							<a-tab-pane key="1" tab="讲义">
 								<div style="height: 900px">
-									<vue-office-pdf :src="itemObj.url" style="width: 100%; height: 100%" />
+									<div v-if="!showPdf">渲染pdf失败</div>
+									<vue-office-pdf :src="itemObj.url" style="width: 100%; height: 100%" @error="errorHandler" />
 								</div>
 							</a-tab-pane>
 							<a-tab-pane key="2" tab="字幕">
 								<div style="height: 900px; overflow-y: auto">
+									<div v-if="subtitle">{{ subtitle }}</div>
 									<div v-for="(item, idx) in danmuObj.srtInfoList" :key="idx">
 										<div>{{ item.startTime }}~{{ item.endTime }}</div>
 										<div style="cursor: pointer; padding: 10px 0" @click="videoSpeed(item)">{{ item.text }}</div>
@@ -155,8 +149,10 @@
 			})
 		})
 	}
+	const classHourData = ref()
 	const menuClick = (e) => {
 		classCentre.courseTimeDetail({ id: e?.key ? e.key : selectedKeys.value[0] }).then((data) => {
+			classHourData.value = data
 			classTimeData.value = data.courseRelates.map((r) => {
 				return {
 					...r,
@@ -210,8 +206,8 @@
 			timeStamp1.value = parseInt(e.target.currentTime) //播放进度 (秒)
 			biNum.value = Math.floor((timeStamp1.value / allTime.value) * 10000) / 100 //暂时没用到
 			currentTime.value = e.target.currentTime
-			if (newsschedule.value < 90) {
-				//newsschedule.value 请求接口获取的参数就是判断当前观看的时长是否小于90 小于90就禁止拖动进度条
+			if (newsschedule.value <= currentTime.value) {
+				//请求接口获取的参数就是判断当前观看的时长是否小于当前时间 小于当前时间就禁止拖动进度条
 
 				if (e.srcElement.currentTime - currTime.value > 3) {
 					//这里拖动进度条时间 - 当前观看的时长 > 3秒 这边判定它为拖动进度条
@@ -244,25 +240,30 @@
 	}
 	//获取字幕内容,发送一个get请求
 	function GetSrtInfo(srtUrl) {
+		subtitle.value = ''
 		axios
 			.get(`${srtUrl}`)
 			.then(function (response) {
-				let textList = response.data
-					.split(/\n\s\n/)
-					.filter((item) => item != '')
-					.map((item, index) => {
-						let textItem = item.split(/\n/)
-						return {
-							index: index,
-							sort: textItem[0],
-							text: textItem[2],
-							startTime: ToSeconds(textItem[1].split(' --> ')[0]),
-							endTime: ToSeconds(textItem[1].split(' --> ')[1]),
-							timeLine: textItem[1]
-						}
-					})
-				danmuObj.value.srtInfoList = textList
-				console.log('解析之后的字幕内容', textList)
+				try {
+					let textList = response.data
+						.split(/\n\s\n/)
+						.filter((item) => item != '')
+						.map((item, index) => {
+							let textItem = item.split(/\n/)
+							return {
+								index: index,
+								sort: textItem[0],
+								text: textItem[2],
+								startTime: ToSeconds(textItem[1].split(' --> ')[0]),
+								endTime: ToSeconds(textItem[1].split(' --> ')[1]),
+								timeLine: textItem[1]
+							}
+						})
+					danmuObj.value.srtInfoList = textList
+					console.log('解析之后的字幕内容', textList)
+				} catch (error) {
+					subtitle.value = '字幕解析失败'
+				}
 			})
 			.catch(function (error) {
 				console.log(error)
@@ -288,6 +289,76 @@
 			videoUrl: btoa(encodeURIComponent(videoRef.value?.src))
 		}
 	})
+	const showPdf = ref(true)
+	function errorHandler() {
+		showPdf.value = false
+	}
+	const subtitle = ref()
+	const getVideoTime = () => {
+		classCentre.theLastDetail().then((data) => {
+			initialtime.value = parseFloat(data.endTime)
+			currentTimenew.value = parseFloat(data.endTime)
+		})
+	}
+	getVideoTime()
+	// 毫秒转换时分秒
+	function msToHMS(ms) {
+		if (typeof ms !== 'number' || isNaN(ms)) return '00:00:00'
+		let totalSeconds = Math.floor(ms / 1000)
+		let hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
+		let minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0')
+		let seconds = String(totalSeconds % 60).padStart(2, '0')
+		return `${hours}:${minutes}:${seconds}`
+	}
+	// 时分秒转换毫秒
+	function hmsToMs(hms) {
+		if (!hms) return 0
+		const parts = hms.split(':').map((p) => parseFloat(p.replace(',', '.')))
+		// 支持 hh:mm:ss 或 mm:ss
+		let ms = 0
+		if (parts.length === 3) {
+			ms = (parts[0] * 3600 + parts[1] * 60 + parts[2]) * 1000
+		} else if (parts.length === 2) {
+			ms = (parts[0] * 60 + parts[1]) * 1000
+		} else if (parts.length === 1) {
+			ms = parts[0] * 1000
+		}
+		return ms
+	}
+	onBeforeUnmount(() => {
+		addClassPlan()
+	})
+	const addClassPlan = () => {
+		let progress = parseFloat((videoRef.value?.currentTime / videoRef.value?.duration) * 100)
+		if (
+			(progress || progress == 0) &&
+			(initialtime.value || initialtime.value == 0) &&
+			(videoRef.value.currentTime || videoRef.value.currentTime == 0)
+		) {
+			classCentre
+				.classPlanAdd({
+					startTime: parseFloat(initialtime.value),
+					endTime: Math.round(videoRef.value.currentTime),
+					progress: Math.round(progress),
+					hourId: selectedKeys.value[0]
+				})
+				.then((data) => {})
+		}
+	}
+	const isLike = () => {
+		classCentre
+			.classCollectAdd(
+				{
+					courseId: route.query.id
+				},
+				classDetail.value.isCollect
+			)
+			.then(() => {
+				classCentre.courseDetail({ courseId: route.query.id }).then((data) => {
+					classDetail.value = data
+				})
+			})
+	}
 </script>
 <style scoped lang="less">
 	.classTitle {

+ 15 - 8
src/views/student/classCentre/rightMenu.vue

@@ -16,6 +16,7 @@
 	import classCentre from '@/api/student/classCentre'
 	import rightContent from './form.vue'
 	import { useRoute, useRouter } from 'vue-router'
+	import { message } from 'ant-design-vue'
 	const route = useRoute()
 	const router = useRouter()
 	const emit = defineEmits({ videoSpeed: null })
@@ -65,14 +66,14 @@
 			key: '4',
 			icon: SnippetsOutlined,
 			type: 3,
-			routerUrl: ''
+			routerUrl: '/student/do'
 		},
 		{
 			title: '测验',
 			key: '5',
 			icon: CopyOutlined,
 			type: 3,
-			routerUrl: ''
+			routerUrl: '/student/do'
 		},
 		{
 			title: '笔记',
@@ -89,10 +90,16 @@
 	])
 	const selectBtn = (event, edit) => {
 		if (event.type == 3) {
-			router.push({
-				path: event.routerUrl,
-				query: {}
-			})
+			if (event.relateId) {
+				router.push({
+					path: event.routerUrl,
+					query: {
+						id: event.relateId
+					}
+				})
+			} else {
+				message.error(`没有${event.key == 5 ? '测试' : '作业'}`)
+			}
 		} else {
 			rightItem.value = event
 			formRef.value.onOpen(edit ? edit : '')
@@ -104,7 +111,7 @@
 			return match ? { ...r, ...match } : r
 		})
 	})
-	
+
 	const videoSpeed = (e) => {
 		emit('videoSpeed', e)
 	}
@@ -116,7 +123,7 @@
 	.rightBtn {
 		position: fixed;
 		right: 0;
-		top: 100px;
+		top: 150px;
 		z-index: 99999;
 	}
 	.fcc {

+ 52 - 0
src/views/student/classCollect/index.vue

@@ -0,0 +1,52 @@
+<template>
+	<a-card>
+		<a-list item-layout="vertical" size="large" :pagination="pagination" :data-source="listData">
+			<template #renderItem="{ item }">
+				<a-list-item key="item.title">
+					<a-list-item-meta>
+						<template #title>{{ item.courseName }}</template>
+						<template #description>
+							<div v-html="item.courseDesc"></div>
+						</template>
+					</a-list-item-meta>
+					<div class="flc">
+						<div>院系名称:{{ item.collegeAllIdName }}</div>
+						<div class="ml-4">老师姓名:{{ item.teacherIdName }}</div>
+						<div class="ml-4">观看次数:{{ item.viewCount }}</div>
+						<div class="ml-4">课时数量:{{ item.hourCount }}</div>
+					</div>
+				</a-list-item>
+			</template>
+		</a-list>
+	</a-card>
+</template>
+<script lang="ts" setup>
+	import classCentre from '@/api/student/classCentre'
+	const listData = ref([])
+	const pagination = ref({
+		current: 1,
+		onChange: (page) => {
+			getList(page)
+		},
+		pageSize: 10
+	})
+	const getList = (current) => {
+		classCentre.classCollectList({
+				current: current ? current : pagination.value.current,
+				size: pagination.value.pageSize
+			})
+			.then((data) => {
+				listData.value = data.records
+			})
+	}
+	getList()
+</script>
+<style scoped>
+	.index-message-list {
+		overflow: auto;
+	}
+	.flc {
+		display: flex;
+		justify-content: space-between;
+	}
+</style>

+ 17 - 13
src/views/student/classNotice/index.vue

@@ -13,22 +13,26 @@
 	</a-card>
 </template>
 <script lang="ts" setup>
-	const listData = []
-
-	for (let i = 0; i < 23; i++) {
-		listData.push({
-			title: `课程标题 ${i}`,
-			description: '课程说明',
-			content:'课程内容'
-		})
-	}
-
-	const pagination = {
+	import classCentre from '@/api/student/classCentre'
+	const listData = ref([])
+	const pagination = ref({
+		current: 1,
 		onChange: (page) => {
-			console.log(page)
+			getList(page)
 		},
-		pageSize: 3
+		pageSize: 10
+	})
+	const getList = (current) => {
+		classCentre
+			.classNotice({
+				current: current ? current : pagination.value.current,
+				size: pagination.value.pageSize
+			})
+			.then((data) => {
+				listData.value = data.records
+			})
 	}
+	getList()
 </script>
 <style scoped>
 	.index-message-list {

+ 12 - 2
src/views/student/forumBtn/index.vue

@@ -1,15 +1,25 @@
 <template>
 	<div class="redressBox" @click="jumpRedressUrl">
-		<a-tooltip title="纠错" v-if="props.forumData.postType==2" :getPopupContainer="(trigger) => trigger.parentElement">
+		<a-tooltip
+			title="纠错"
+			v-if="props.forumData.postType == 2"
+			:getPopupContainer="(trigger) => trigger.parentElement"
+		>
 			<SnippetsOutlined style="font-size: 40px" />
 		</a-tooltip>
-		<a-tooltip title="纠错" v-if="props.forumData.postType==1" :getPopupContainer="(trigger) => trigger.parentElement">
+		<a-tooltip
+			title="纠错"
+			v-if="props.forumData.postType == 1"
+			:getPopupContainer="(trigger) => trigger.parentElement"
+		>
 			<SnippetsOutlined style="font-size: 40px" />
 		</a-tooltip>
 	</div>
 </template>
 
 <script setup>
+	import { useRoute, useRouter } from 'vue-router'
+	const router = useRouter()
 	const props = defineProps({
 		forumData: {
 			type: Object,

+ 251 - 0
src/views/student/paper/homework.vue

@@ -0,0 +1,251 @@
+<template>
+	<a-card>
+		<div class="paper-list">
+			<!-- 任务中心开始 -->
+			<div class="task-center" style="margin-bottom: 24px">
+				<h3 style="border-left: solid 4px #3651d4; padding-left: 8px; margin-bottom: 12px; font-size: 18px">
+					<div>我的作业</div>
+				</h3>
+				<a-spin :spinning="taskLoading">
+					<a-table
+						v-if="taskList.length !== 0"
+						:dataSource="taskList"
+						:columns="taskColumns"
+						:pagination="false"
+						rowKey="id"
+						size="middle"
+					>
+						<template #bodyCell="{ column, record }">
+							<template v-if="column.key === 'examName'">
+								{{ record.examName }}
+							</template>
+							<template v-else-if="column.key === 'examType'">
+								{{ record.examType }}
+							</template>
+							<template v-else-if="column.key === 'startTime'">
+								{{ record.startTime }}
+							</template>
+							<template v-else-if="column.key === 'endTime'">
+								{{ record.endTime }}
+							</template>
+							<template v-else-if="column.key === 'examStatus'">
+								<a-tag :color="statusTagFormatter(record.examStatus)" size="small">
+									{{ statusTextFormatter(record.examStatus) }}
+								</a-tag>
+							</template>
+							<template v-else-if="column.key === 'action'">
+								<router-link
+									v-if="record.examStatus === 0"
+									:to="{ path: '/student/do', query: { id: record.paperId } }"
+									target="_blank"
+								>
+									<a-button type="link" size="small">查看问卷</a-button>
+								</router-link>
+								<router-link
+									v-else-if="record.examStatus === 1"
+									:to="{ path: '/student/do', query: { id: record.paperId } }"
+									target="_blank"
+								>
+									<a-button type="link" size="small">填写问卷</a-button>
+								</router-link>
+								<router-link
+									v-else-if="record.examStatus === 2"
+									:to="{ path: '/student/read', query: { id: record.paperId } }"
+									target="_blank"
+								>
+									<a-button type="link" size="small">查看结果</a-button>
+								</router-link>
+							</template>
+						</template>
+					</a-table>
+					<div v-else style="color: #999; padding: 16px 0">暂无任务</div>
+				</a-spin>
+			</div>
+			<!-- 任务中心结束 -->
+			<a-spin :spinning="listLoading">
+				<a-tabs tab-position="left" v-model:activeKey="tabId" @change="subjectChange" class="subject-tabs">
+					<a-tab-pane v-for="item in subjectList" :key="item.id" :tab="item.name">
+						<div class="paper-type-radio">
+							<a-radio-group v-model:value="queryParam.paperType" @change="paperTypeChange" size="small">
+								<a-radio v-for="type in paperTypeEnum" :key="type.key" :value="type.key">
+									{{ type.value }}
+								</a-radio>
+							</a-radio-group>
+						</div>
+						<a-table :dataSource="tableData" :columns="columns" :pagination="false" rowKey="id" bordered size="middle">
+							<template #bodyCell="{ column, record }">
+								<template v-if="column.key === 'action'">
+									<router-link :to="{ path: '/student/do', query: { id: record.id } }" target="_blank">
+										<a-button type="link" size="small">开始答题</a-button>
+									</router-link>
+								</template>
+							</template>
+						</a-table>
+						<a-pagination
+							v-if="total > 0"
+							:total="total"
+							:current="queryParam.pageIndex"
+							:pageSize="queryParam.pageSize"
+							@change="onPageChange"
+							@showSizeChange="onPageSizeChange"
+							:showSizeChanger="true"
+							:pageSizeOptions="['10', '20', '50', '100']"
+							style="margin-top: 20px"
+						/>
+					</a-tab-pane>
+				</a-tabs>
+			</a-spin>
+		</div>
+	</a-card>
+</template>
+
+<script setup>
+	import { useExamStore } from '@/store/exam'
+	import examPaperApi from '@/api/student/examPaper'
+	import taskApi from '@/api/student/examPaper'
+	import { useRoute } from 'vue-router'
+	const route = useRoute()
+
+	// store
+	const examStore = useExamStore()
+	const paperTypeEnum = computed(() => examStore.paperTypeEnum.filter((item) => item.key !== 6))
+
+	// 任务中心相关
+	const taskList = ref([])
+	const taskLoading = ref(false)
+	const taskColumns = [
+		{ title: '问卷名称', dataIndex: 'examName', key: 'examName' },
+		{ title: '问卷类型', dataIndex: 'examType', key: 'examType', width: 120 },
+		{ title: '开始时间', dataIndex: 'startTime', key: 'startTime', width: 180 },
+		{ title: '结束时间', dataIndex: 'endTime', key: 'endTime', width: 180 },
+		{ title: '状态', dataIndex: 'examStatus', key: 'examStatus', width: 90 },
+		{ title: '操作', key: 'action', align: 'right', width: 120 }
+	]
+
+	const statusTextFormatter = (status) => {
+		if (status === 0) return '未开始'
+		if (status === 1) return '已开始'
+		if (status === 2) return '已结束'
+		return ''
+	}
+	const statusTagFormatter = (status) => {
+		if (status === 0) return 'blue'
+		if (status === 1) return 'green'
+		if (status === 2) return 'gray'
+		return 'default'
+	}
+	const getTaskList = async () => {
+		taskLoading.value = true
+		const params = {
+			...queryParam,
+			current: queryParam.pageIndex,
+			size: queryParam.pageSize,
+			examType: examType.value,
+			paperType:"2"
+		}
+		delete params.pageIndex
+		delete params.pageSize
+		delete params.subjectId
+		const res = await examPaperApi.pageList(params)
+		taskList.value = res.records
+		taskLoading.value = false
+	}
+
+	// data
+	const queryParam = reactive({
+		paperType: 1,
+		subjectId: 0,
+		pageIndex: 1,
+		pageSize: 10
+	})
+	const tabId = ref('')
+	const listLoading = ref(true)
+	const subjectList = ref([])
+	const tableData = ref([])
+	const total = ref(0)
+	const columns = [
+		{ title: '序号', dataIndex: 'id', key: 'id', width: 90 },
+		{ title: '名称', dataIndex: 'name', key: 'name' },
+		{ title: '操作', key: 'action', align: 'right' }
+	]
+
+	// methods
+	const initSubject = async () => {
+		listLoading.value = true
+		await examStore.initSubject()
+		subjectList.value = examStore.subjects
+		if (subjectList.value.length > 0) {
+			const subjectId = subjectList.value[0].id
+			queryParam.subjectId = subjectId
+			tabId.value = subjectId
+			await search()
+		}
+		listLoading.value = false
+	}
+
+	const search = async () => {
+		listLoading.value = true
+		const params = {
+			...queryParam,
+			current: queryParam.pageIndex,
+			size: queryParam.pageSize
+		}
+		delete params.pageIndex
+		delete params.pageSize
+		const res = await examPaperApi.pageList(params)
+		const re = res
+		tableData.value = re.records
+		total.value = re.total
+		queryParam.pageIndex = re.current
+		listLoading.value = false
+	}
+
+	const paperTypeChange = () => {
+		queryParam.pageIndex = 1
+		search()
+	}
+
+	const subjectChange = (key) => {
+		queryParam.subjectId = Number(key)
+		queryParam.pageIndex = 1
+		search()
+	}
+
+	const onPageChange = (page, pageSize) => {
+		queryParam.pageIndex = page
+		queryParam.pageSize = pageSize
+		search()
+	}
+
+	const onPageSizeChange = (current, size) => {
+		queryParam.pageIndex = 1
+		queryParam.pageSize = size
+		search()
+	}
+
+	// lifecycle
+	const examType = ref()
+	onMounted(() => {
+		examType.value = route.params && route.params.examType
+		getTaskList()
+		initSubject()
+	})
+</script>
+
+<style lang="less" scoped>
+	.paper-list {
+		margin-top: 10px;
+		.task-center {
+			margin-bottom: 24px;
+		}
+		.subject-tabs {
+			.ant-tabs-nav {
+				margin-right: 20px;
+			}
+		}
+		.paper-type-radio {
+			float: right;
+			margin-bottom: 10px;
+		}
+	}
+</style>

+ 115 - 91
src/views/student/paper/index.vue

@@ -1,99 +1,113 @@
 <template>
-	<div class="paper-list">
-		<!-- 任务中心开始 -->
-		<div class="task-center" style="margin-bottom: 24px">
-			<h3 style="border-left: solid 4px #3651d4; padding-left: 8px; margin-bottom: 12px; font-size: 18px">任务中心</h3>
-			<a-spin :spinning="taskLoading">
-				<a-collapse v-if="taskList.length !== 0" accordion>
-					<a-collapse-panel v-for="taskItem in taskList" :key="taskItem.id" :header="taskItem.title">
-						<a-table
-							:dataSource="taskItem.paperItems"
-							:columns="taskColumns"
-							:pagination="false"
-							rowKey="examPaperId"
-							size="small"
-						>
+	<a-card>
+		<div class="paper-list">
+			<!-- 任务中心开始 -->
+			<div class="task-center" style="margin-bottom: 24px">
+				<h3 style="border-left: solid 4px #3651d4; padding-left: 8px; margin-bottom: 12px; font-size: 18px">
+					<div v-if="examType == 1">考试</div>
+					<div v-if="examType == 2">章节测验</div>
+					<div v-if="examType == 3">调查问卷</div>
+					<div v-if="examType == 4">我的作业</div>
+				</h3>
+				<a-spin :spinning="taskLoading">
+					<a-table
+						v-if="taskList.length !== 0"
+						:dataSource="taskList"
+						:columns="taskColumns"
+						:pagination="false"
+						rowKey="id"
+						size="middle"
+					>
+						<template #bodyCell="{ column, record }">
+							<template v-if="column.key === 'examName'">
+								{{ record.examName }}
+							</template>
+							<template v-else-if="column.key === 'examType'">
+								{{ record.examType }}
+							</template>
+							<template v-else-if="column.key === 'startTime'">
+								{{ record.startTime }}
+							</template>
+							<template v-else-if="column.key === 'endTime'">
+								{{ record.endTime }}
+							</template>
+							<template v-else-if="column.key === 'examStatus'">
+								<a-tag :color="statusTagFormatter(record.examStatus)" size="small">
+									{{ statusTextFormatter(record.examStatus) }}
+								</a-tag>
+							</template>
+							<template v-else-if="column.key === 'action'">
+								<router-link
+									v-if="record.examStatus === 0"
+									:to="{ path: '/student/do', query: { id: record.paperId } }"
+									target="_blank"
+								>
+									<a-button type="link" size="small">查看问卷</a-button>
+								</router-link>
+								<router-link
+									v-else-if="record.examStatus === 1"
+									:to="{ path: '/student/do', query: { id: record.paperId } }"
+									target="_blank"
+								>
+									<a-button type="link" size="small">填写问卷</a-button>
+								</router-link>
+								<router-link
+									v-else-if="record.examStatus === 2"
+									:to="{ path: '/student/read', query: { id: record.paperId } }"
+									target="_blank"
+								>
+									<a-button type="link" size="small">查看结果</a-button>
+								</router-link>
+							</template>
+						</template>
+					</a-table>
+					<div v-else style="color: #999; padding: 16px 0">暂无任务</div>
+				</a-spin>
+			</div>
+			<!-- 任务中心结束 -->
+			<a-spin :spinning="listLoading">
+				<a-tabs tab-position="left" v-model:activeKey="tabId" @change="subjectChange" class="subject-tabs">
+					<a-tab-pane v-for="item in subjectList" :key="item.id" :tab="item.name">
+						<div class="paper-type-radio">
+							<a-radio-group v-model:value="queryParam.paperType" @change="paperTypeChange" size="small">
+								<a-radio v-for="type in paperTypeEnum" :key="type.key" :value="type.key">
+									{{ type.value }}
+								</a-radio>
+							</a-radio-group>
+						</div>
+						<a-table :dataSource="tableData" :columns="columns" :pagination="false" rowKey="id" bordered size="middle">
 							<template #bodyCell="{ column, record }">
-								<template v-if="column.key === 'examPaperName'">
-									{{ record.examPaperName }}
-								</template>
-								<template v-else-if="column.key === 'status'">
-									<a-tag v-if="record.status !== null" :color="statusTagFormatter(record.status)" size="small">
-										{{ statusTextFormatter(record.status) }}
-									</a-tag>
-								</template>
-								<template v-else-if="column.key === 'action'">
-									<router-link
-										v-if="record.status === null"
-										:to="{ path: '/student/do', query: { id: record.examPaperId } }"
-										target="_blank"
-									>
+								<template v-if="column.key === 'action'">
+									<router-link :to="{ path: '/student/do', query: { id: record.id } }" target="_blank">
 										<a-button type="link" size="small">开始答题</a-button>
 									</router-link>
-									<router-link
-										v-else-if="record.status === 1"
-										:to="{ path: '/student/edit', query: { id: record.examPaperAnswerId } }"
-										target="_blank"
-									>
-										<a-button type="link" size="small">批改试卷</a-button>
-									</router-link>
-									<router-link
-										v-else-if="record.status === 2"
-										:to="{ path: '/student/read', query: { id: record.examPaperAnswerId } }"
-										target="_blank"
-									>
-										<a-button type="link" size="small">查看试卷</a-button>
-									</router-link>
 								</template>
 							</template>
 						</a-table>
-					</a-collapse-panel>
-				</a-collapse>
-				<div v-else style="color: #999; padding: 16px 0">暂无任务</div>
+						<a-pagination
+							v-if="total > 0"
+							:total="total"
+							:current="queryParam.pageIndex"
+							:pageSize="queryParam.pageSize"
+							@change="onPageChange"
+							@showSizeChange="onPageSizeChange"
+							:showSizeChanger="true"
+							:pageSizeOptions="['10', '20', '50', '100']"
+							style="margin-top: 20px"
+						/>
+					</a-tab-pane>
+				</a-tabs>
 			</a-spin>
 		</div>
-		<!-- 任务中心结束 -->
-		<a-spin :spinning="listLoading">
-			<a-tabs tab-position="left" v-model:activeKey="tabId" @change="subjectChange" class="subject-tabs">
-				<a-tab-pane v-for="item in subjectList" :key="item.id" :tab="item.name">
-					<div class="paper-type-radio">
-						<a-radio-group v-model:value="queryParam.paperType" @change="paperTypeChange" size="small">
-							<a-radio v-for="type in paperTypeEnum" :key="type.key" :value="type.key">
-								{{ type.value }}
-							</a-radio>
-						</a-radio-group>
-					</div>
-					<a-table :dataSource="tableData" :columns="columns" :pagination="false" rowKey="id" bordered size="middle">
-						<template #bodyCell="{ column, record }">
-							<template v-if="column.key === 'action'">
-								<router-link :to="{ path: '/student/do', query: { id: record.id } }" target="_blank">
-									<a-button type="link" size="small">开始答题</a-button>
-								</router-link>
-							</template>
-						</template>
-					</a-table>
-					<a-pagination
-						v-if="total > 0"
-						:total="total"
-						:current="queryParam.pageIndex"
-						:pageSize="queryParam.pageSize"
-						@change="onPageChange"
-						@showSizeChange="onPageSizeChange"
-						:showSizeChanger="true"
-						:pageSizeOptions="['10', '20', '50', '100']"
-						style="margin-top: 20px"
-					/>
-				</a-tab-pane>
-			</a-tabs>
-		</a-spin>
-	</div>
+	</a-card>
 </template>
 
 <script setup>
-	import { ref, reactive, onMounted, computed } from 'vue'
 	import { useExamStore } from '@/store/exam'
 	import examPaperApi from '@/api/student/examPaper'
 	import taskApi from '@/api/student/examPaper'
+	import { useRoute } from 'vue-router'
+	const route = useRoute()
 
 	// store
 	const examStore = useExamStore()
@@ -103,27 +117,35 @@
 	const taskList = ref([])
 	const taskLoading = ref(false)
 	const taskColumns = [
-		{ title: '试卷名称', dataIndex: 'examPaperName', key: 'examPaperName' },
-		{ title: '状态', dataIndex: 'status', key: 'status', width: 90 },
+		{ title: '问卷名称', dataIndex: 'examName', key: 'examName' },
+		{ title: '问卷类型', dataIndex: 'examType', key: 'examType', width: 120 },
+		{ title: '开始时间', dataIndex: 'startTime', key: 'startTime', width: 180 },
+		{ title: '结束时间', dataIndex: 'endTime', key: 'endTime', width: 180 },
+		{ title: '状态', dataIndex: 'examStatus', key: 'examStatus', width: 90 },
 		{ title: '操作', key: 'action', align: 'right', width: 120 }
 	]
 
 	const statusTextFormatter = (status) => {
-		if (status === null) return '未作答'
-		if (status === 1) return '待批改'
-		if (status === 2) return '已批改'
+		if (status === 0) return '未开始'
+		if (status === 1) return '已开始'
+		if (status === 2) return '已结束'
 		return ''
 	}
 	const statusTagFormatter = (status) => {
-		if (status === 1) return 'orange'
-		if (status === 2) return 'green'
+		if (status === 0) return 'blue'
+		if (status === 1) return 'green'
+		if (status === 2) return 'gray'
 		return 'default'
 	}
 	const getTaskList = async () => {
 		taskLoading.value = true
 		try {
-			const res = await taskApi.task()
-			taskList.value = res || []
+			const res = await taskApi.pageExamList({
+				current: 1,
+				size: 10,
+				examType: examType.value
+			})
+			taskList.value = res?.records || []
 		} catch (e) {
 			taskList.value = []
 		}
@@ -156,7 +178,7 @@
 		if (subjectList.value.length > 0) {
 			const subjectId = subjectList.value[0].id
 			queryParam.subjectId = subjectId
-			tabId.value = subjectId.toString()
+			tabId.value = subjectId
 			await search()
 		}
 		listLoading.value = false
@@ -203,7 +225,9 @@
 	}
 
 	// lifecycle
+	const examType = ref()
 	onMounted(() => {
+		examType.value = route.params && route.params.examType
 		getTaskList()
 		initSubject()
 	})

+ 2 - 2
src/views/student/style.less

@@ -1,5 +1,5 @@
 .do-exam-title {
-	position: fixed;
+	position: absolute;
 	width: 100%;
 	background: #fff6f6;
 	z-index: 999;
@@ -95,8 +95,8 @@
 	}
 }
 .app-contain {
-	height: 100vh;
 	overflow: auto;
+	position: relative;
 }
 .app-item-contain {
 	max-width: 1200px;