提交 d8acd0b7 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 d807da43
......@@ -16,6 +16,7 @@
"dayjs": "^1.11.4",
"element-plus": "^2.2.11",
"file-saver": "^2.0.5",
"format-duration": "^2.0.0",
"lodash-es": "^4.17.21",
"pinia": "^2.0.17",
"qs": "^6.11.0",
......@@ -2423,6 +2424,11 @@
"node": ">= 6"
}
},
"node_modules/format-duration": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/format-duration/-/format-duration-2.0.0.tgz",
"integrity": "sha512-ARqJ9qXm71pw3SGAY7bibf8lRLvltOXLjWjzzR3UrUjHu1zdeYpA/Z+u+ltdhrfRa440OjEsHNzdmuZViqqQWQ=="
},
"node_modules/formstream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/formstream/-/formstream-1.1.1.tgz",
......@@ -6489,6 +6495,11 @@
"mime-types": "^2.1.12"
}
},
"format-duration": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/format-duration/-/format-duration-2.0.0.tgz",
"integrity": "sha512-ARqJ9qXm71pw3SGAY7bibf8lRLvltOXLjWjzzR3UrUjHu1zdeYpA/Z+u+ltdhrfRa440OjEsHNzdmuZViqqQWQ=="
},
"formstream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/formstream/-/formstream-1.1.1.tgz",
......
......@@ -20,6 +20,7 @@
"dayjs": "^1.11.4",
"element-plus": "^2.2.11",
"file-saver": "^2.0.5",
"format-duration": "^2.0.0",
"lodash-es": "^4.17.21",
"pinia": "^2.0.17",
"qs": "^6.11.0",
......
......@@ -30,6 +30,12 @@ const questionList = $(inject<PaperQuestionType[]>('questionList'))
// 试题索引
let questionIndex = $ref<number>(0)
provide('questionIndex', $$(questionIndex))
watchEffect(() => {
if (route.query.index) {
questionIndex = parseInt(route.query.index as string) || 0
}
})
// 试题数量
const questionLength = $computed(() => {
return questionList.length
......
<script setup lang="ts">
import type { PaperQuestionType } from '@/types'
const router = useRouter()
const courseId = $ref<string>(inject('courseId'))
const semesterId = $ref<string>(inject('semesterId'))
const paperId = $ref<string>(inject('paperId'))
const type = $ref<string>(inject('type'))
// 所有试题
const questionList = $ref<PaperQuestionType[]>(inject('questionList'))
const questionNum = computed(() => {
return [
{ class: 'is-success', name: '答对' },
{ class: 'is-error', name: '答错' },
{ class: 'is-info', name: '未答' },
{ class: 'is-review', name: '已评价' },
{ class: 'is-unReview', name: '未评价' }
]
})
function genClass(data: PaperQuestionType) {
let hasUserAnswer = !!data.user_answer
if (data.children && data.children.length) {
!data.children.filter(item => !item.user_answer).length && (hasUserAnswer = true)
}
return {
'is-success': data.user_answer_status, // 答对
'is-error': !data.user_answer_status, // 答错
'is-info': !hasUserAnswer, // 未答
'is-review': data.is_reviewed && (data.question_type === 3 || data.child_question_type === 3), // 已评价
'is-unReview': !data.is_reviewed && (data.question_type === 3 || data.child_question_type === 3) // 未评价
}
}
function handleClick(index: number) {
router.push({
path: '/course/exam',
query: { course_id: courseId, semester_id: semesterId, paper_id: paperId, type, index }
})
}
</script>
<template>
<div class="question-numbers">
<ul class="question-num-tips">
<li v-for="(item, index) in questionNum" :key="index">
<div class="question-num-tips-item" :class="item.class"></div>
<div class="txt">{{ item.name }}</div>
</li>
</ul>
<div class="question-num">
<!-- <div v-for="item in dataList" :key="item.question_item_id">
<div class="tit">{{ item.title }}</div> -->
<ul>
<li
v-for="(item, index) in questionList"
class="question-num-item"
:class="genClass(item)"
:key="index"
@click="handleClick(index)"
>
{{ index + 1 }}
</li>
</ul>
<!-- </div> -->
</div>
<div style="text-align: center">
<el-button size="large" @click="handleClick(0)">全部解析</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
.question-numbers {
height: 100%;
display: flex;
flex-direction: column;
}
.question-num {
flex: 1;
padding-top: 20px;
.tit {
font-size: 12px;
color: #999999;
line-height: 17px;
margin-bottom: 10px;
}
ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
flex-wrap: wrap;
}
}
.question-num-item {
cursor: pointer;
position: relative;
border-radius: 50px;
width: 24px;
height: 24px;
font-size: 14px;
line-height: 24px;
margin-right: 20px;
margin-bottom: 10px;
text-align: center;
border: 2px solid #ccc;
color: #666;
&:nth-child(5n) {
margin-right: 0;
}
}
.question-num-tips {
padding: 0 20px 20px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #ccc;
.txt {
margin-top: 5px;
font-size: 12px;
color: #ccc;
line-height: 17px;
text-align: center;
}
}
.question-num-tips-item {
cursor: pointer;
position: relative;
border-radius: 50px;
width: 24px;
height: 24px;
font-size: 14px;
line-height: 24px;
text-align: center;
border: 2px solid #ccc;
color: #666;
}
.is-default {
color: #666;
border: 2px solid #ccc;
}
.is-review {
color: #fff;
background-color: blue;
border: 2px solid blue;
}
.is-unReview {
border: 2px solid pink;
}
.is-info {
color: #fff;
background-color: #999;
border: 2px solid #999;
}
.is-success {
color: #666;
border: 2px solid #0fc118;
}
.is-info.is-success {
color: #fff;
}
.is-error {
color: #666;
border: 2px solid #c01540;
}
</style>
......@@ -65,6 +65,14 @@ function downloadFile(data: CourseResourceType) {
{{ data.name }}
</a>
</p>
<router-link
:to="`/course/exam/result?course_id=${courseId}&semester_id=${semesterId}&paper_id=${data.resource_id}&type=2`"
target="_blank"
style="margin: 0 20px"
v-if="data.paper_status === 2 || data.paper_status === 3"
>
<el-button round size="small">查看报告</el-button>
</router-link>
<div class="actions">
<i class="icon-star" :class="!!data.collection_count ? 'is-active' : ''" @click="toggleCollection(data)"></i>
<i class="icon-download" @click="downloadFile(data)" v-if="canDownload(data.resource_type)"><Download /></i>
......
......@@ -2,6 +2,9 @@
<script setup lang="ts">
import type { CourseChapterType, CourseSectionType, CourseResourceType } from '../types'
import ResourceIcon from '@/components/ResourceIcon.vue'
import { Download } from '@element-plus/icons-vue'
import { saveAs } from 'file-saver'
import format from 'format-duration'
import { getChapterTreeList, collectionResource } from '../api'
let chapterList = $ref<CourseChapterType[]>([])
......@@ -20,9 +23,15 @@ function fetchList() {
onMounted(() => {
fetchList()
})
// 时长转换
function formatDuration(duration: number | undefined) {
if (!duration) return ''
return format(duration * 1000, { leading: true })
}
// 是否可以收藏
function canCollection(data: CourseResourceType) {
return [4, 2, 10, 11, 3].includes(data.resource_type)
return [4, 2, 10, 11, 3, 9].includes(data.resource_type)
}
// 收藏/取消收藏
function toggleCollection(resource: CourseResourceType, section: CourseSectionType, chapter: CourseChapterType) {
......@@ -41,6 +50,16 @@ function toggleCollection(resource: CourseResourceType, section: CourseSectionTy
resource.collection_count = resource.collection_count > 0 ? 0 : 1
})
}
// 是否可以下载
function canDownload(type: number) {
// 没有url
// return [4, 10, 11].includes(type)
return [99999].includes(type)
}
// 下载资源
function downloadFile(data: CourseResourceType) {
data.info.url && saveAs(data.info.url, data.info.name)
}
// 跳转链接
function targetUrl(resource: CourseResourceType, section: CourseSectionType, chapter: CourseChapterType) {
if (resource.resource_type === 3 || resource.resource_type === 9) {
......@@ -58,16 +77,35 @@ function targetUrl(resource: CourseResourceType, section: CourseSectionType, cha
<template #title><i class="icon-chapter"></i>{{ section.name }}</template>
<ul>
<li class="course-resource-item" v-for="resource in section.resources" :key="resource.id">
<router-link :to="targetUrl(resource, section, item)" target="_blank">
<ResourceIcon :resourceType="resource.resource_type" />
{{ resource.name }}
<p>
<router-link :to="targetUrl(resource, section, item)" target="_blank">
<ResourceIcon :resourceType="resource.resource_type" />
{{ resource.name }}
</router-link>
</p>
<router-link
:to="`/course/exam/result?course_id=${courseId}&semester_id=${semesterId}&paper_id=${resource.resource_id}&type=2`"
target="_blank"
style="margin: 0 20px"
v-if="resource.paper_status === 2 || resource.paper_status === 3"
>
<el-button round size="small">查看报告</el-button>
</router-link>
<i
class="icon-star"
:class="!!resource.collection_count ? 'is-active' : ''"
@click="toggleCollection(resource, section, item)"
v-if="canCollection(resource)"
></i>
<div class="video-duration" v-if="resource.resource_type === 2">
{{ formatDuration(resource.info.length) }}
</div>
<div class="actions">
<i
class="icon-star"
:class="!!resource.collection_count ? 'is-active' : ''"
@click="toggleCollection(resource, section, item)"
v-if="canCollection(resource)"
></i>
<i class="icon-download" @click="downloadFile(resource)" v-if="canDownload(resource.resource_type)">
<Download />
</i>
</div>
</li>
</ul>
</el-collapse-item>
......@@ -84,12 +122,25 @@ function targetUrl(resource: CourseResourceType, section: CourseSectionType, cha
height: 48px;
line-height: 48px;
border-bottom: 1px solid #e6e6e6;
a {
p {
flex: 1;
}
a {
&:hover {
color: var(--main-color);
}
}
.video-duration {
padding: 0 20px;
}
.actions {
width: 60px;
display: flex;
align-items: center;
justify-content: flex-end;
line-height: 1;
}
}
.icon-star {
display: inline-block;
......@@ -103,6 +154,12 @@ function targetUrl(resource: CourseResourceType, section: CourseSectionType, cha
background-size: contain;
}
}
.icon-download {
display: inline-block;
width: 20px;
height: 20px;
cursor: pointer;
}
.icon-chapter {
margin-right: 10px;
display: inline-block;
......
......@@ -19,6 +19,13 @@ const semesterId = $ref(query.semester_id as string)
>{{ data.paper_title }}</router-link
>
</p>
<router-link
:to="`/course/exam/result?course_id=${courseId}&semester_id=${semesterId}&paper_id=${data.id}&type=1`"
target="_blank"
v-if="data.paper_status === 2 || data.paper_status === 3"
>
<el-button round size="small">查看报告</el-button>
</router-link>
</div>
</template>
......
......@@ -50,6 +50,7 @@ export interface CourseResourceType {
info: ResourceType
depth: number
is_finished?: number
paper_status?: number
}
export interface VideoRecordType {
......
<script setup lang="ts">
import type { PaperQuestionType } from '@/types'
import CourseExamResultQuestionNumbers from '../components/CourseExamResultQuestionNumbers.vue'
import { getPaper } from '../api'
const route = useRoute()
const courseId = $ref(route.query.course_id as string)
provide('courseId', courseId)
const semesterId = $ref(route.query.semester_id as string)
provide('semesterId', semesterId)
const paperId = $ref(route.query.paper_id as string)
provide('paperId', paperId)
const type = $ref(parseInt(route.query.type as string))
const detail = reactive({ status: 1 })
provide('type', type)
const detail = reactive({ status: 2, check_time: '', commit_time: '', score: '', question: [], score_details: [] })
let questionList = $ref<PaperQuestionType[]>([])
provide('questionList', $$(questionList))
......@@ -18,10 +24,60 @@ function fetchInfo() {
getPaper({ course_id: courseId, semester_id: semesterId, paper_id: paperId, type }).then(res => {
const { question, status, score_details } = res.data
Object.assign(detail, res.data)
questionList = status === 3 ? score_details : question
questionList = status === 1 ? question : score_details
loading = false
})
}
const chart = $computed(() => {
return questionList.reduce(
(result, item) => {
// 大题
if (item.children && item.children.length) {
item.children.forEach(item => {
if (item.question_options && item.question_options.length) {
result.objectiveQuestions.score += item.user_score || 0
result.objectiveQuestions.total += item.score
} else {
result.subjectiveQuestions.score += item.user_score || 0
result.subjectiveQuestions.total += item.score
}
})
} else {
if (item.question_options && item.question_options.length) {
result.objectiveQuestions.score += item.user_score || 0
result.objectiveQuestions.total += item.score
} else {
result.subjectiveQuestions.score += item.user_score || 0
result.subjectiveQuestions.total += item.score
}
}
return result
},
{
objectiveQuestions: { score: 0, total: 0 }, // 客观题
subjectiveQuestions: { score: 0, total: 0 } // 主观题
}
)
})
// 客观题
const objectiveQuestions = computed(() => {
const { score, total } = chart.objectiveQuestions
return {
percentage: total ? Math.floor((score / total) * 100) : 0,
score,
total
}
})
// 主观题
const subjectiveQuestions = computed(() => {
const { score, total } = chart.subjectiveQuestions
return {
percentage: total ? Math.floor((score / total) * 100) : 0,
score,
total
}
})
onMounted(() => {
fetchInfo()
})
......@@ -29,9 +85,57 @@ onMounted(() => {
<template>
<div class="course-exam-result" v-loading="loading">
<div class="course-exam-card"></div>
<div class="course-exam-card">
<el-button size="large">全部解析</el-button>
<div class="chart-box">
<div class="chart-box-hd">
<h2>成绩报告</h2>
{{ detail.commit_time }}
</div>
<div class="chart-box-bd chart-wrapper">
<div class="chart-item">
<div class="chart-item__title">客观题</div>
<div class="chart-item__content">
<el-progress type="circle" color="#ba143e" :stroke-width="8" :percentage="objectiveQuestions.percentage">
<p>{{ objectiveQuestions.score }}</p>
</el-progress>
</div>
</div>
<div class="chart-item">
<div class="chart-item__title">主观题</div>
<div class="chart-item__content">
<el-progress type="circle" color="#ba143e" :stroke-width="8" :percentage="subjectiveQuestions.percentage">
<p>{{ detail.status === 3 ? subjectiveQuestions.score : '-' }}</p>
</el-progress>
</div>
</div>
</div>
</div>
<div class="chart-box">
<div class="chart-box-hd">
<h2>测试评估</h2>
</div>
<div class="chart-box-bd">
<div class="score">
<el-progress
color="#ba143e"
:stroke-width="8"
:percentage="detail.score ? parseFloat(detail.score) : 0"
style="width: 100%"
>
<img src="@/assets/images/icon_exam_result.png" width="24" />
</el-progress>
<template v-if="detail.status !== 3">
<p class="t1">请等待老师评分~</p>
</template>
<template v-else>
<p class="t2">{{ detail.score }}</p>
</template>
</div>
</div>
</div>
</div>
<div class="course-exam-card" style="flex: 0 0 500px">
<CourseExamResultQuestionNumbers />
</div>
</div>
</template>
......@@ -46,5 +150,66 @@ onMounted(() => {
height: 100%;
background-color: #fff;
border-radius: 6px;
padding: 20px;
box-sizing: border-box;
}
.course-exam-card + .course-exam-card {
margin-left: 20px;
}
.chart-box-hd {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 18px;
color: #222222;
line-height: 45px;
border-bottom: 1px solid #ccc;
}
.chart-box-bd {
padding: 20px 0;
}
.chart-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.chart-item {
padding: 40px;
display: flex;
align-items: center;
}
.chart-item__title {
padding: 20px;
font-size: 22px;
color: #333;
}
.chart-item__content {
p {
font-size: 22px;
color: #333;
}
}
.score {
width: 300px;
margin: 10px auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.t1 {
font-size: 14px;
color: #222222;
line-height: 20px;
text-align: center;
margin: 50px 0 68px 0;
}
.t2 {
font-size: 44px;
font-weight: 500;
color: #222222;
line-height: 20px;
text-align: center;
margin: 50px 0 68px 0;
}
}
</style>
......@@ -3,6 +3,8 @@ import type { CollectionType, AllCourseType } from '../types'
import ResourceIcon from '@/components/ResourceIcon.vue'
import { Download } from '@element-plus/icons-vue'
import { saveAs } from 'file-saver'
import format from 'format-duration'
import { getCollectionList, getCourseList, collectionResource } from '../api'
const tabs = $ref([
......@@ -49,6 +51,12 @@ onMounted(() => {
fetchCourseList()
})
// 时长转换
function formatDuration(duration: number | undefined) {
if (!duration) return ''
return format(duration * 1000, { leading: true })
}
// 收藏/取消收藏
function toggleCollection(data: CollectionType) {
collectionResource({
......@@ -95,6 +103,9 @@ function targetUrl(item: CollectionType) {
{{ item.info.name || item.info.paper_title }}
</router-link>
</p>
<div class="video-duration" v-if="item.type === 2">
{{ formatDuration(item.info.length) }}
</div>
<div class="actions">
<i class="icon-star" :class="!!item.status ? 'is-active' : ''" @click="toggleCollection(item)"></i>
<i class="icon-download" @click="downloadFile(item)" v-if="canDownload(item.type)"><Download /></i>
......@@ -119,11 +130,16 @@ function targetUrl(item: CollectionType) {
color: var(--main-color);
}
.video-duration {
padding: 0 20px;
}
.actions {
width: 60px;
display: flex;
align-items: center;
justify-content: space-between;
line-height: 1;
}
}
.icon-star {
......
......@@ -77,10 +77,12 @@ export interface ResourceType {
pdf?: string
url?: string
cover?: string
length?: number
size: number
source_id: string
paper_title?: string
paper_type?: number
paper_status?: number
}
// 直播类型
......@@ -115,6 +117,7 @@ export interface PaperType {
pass_score: number
permission: number
project_prefix: string
paper_status: number
}
// 试卷分类类型
......@@ -145,9 +148,11 @@ export interface PaperQuestionType {
child_question_type: number
score: number
children?: PaperQuestionType[]
user_answer: string
reviews?: string
user_answer: string
user_answer_status?: boolean
user_score?: number
is_reviewed?: boolean
}
export interface PaperQuestionOptionType {
checked_option: string
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论