fix: 修复一些问题
This commit is contained in:
parent
5ec9b711aa
commit
3e3525aee1
@ -346,6 +346,19 @@ const startClassMonitor = (msg: anyObj) => {
|
||||
monitorTimer = setInterval(() => reportMonitorSnapshot(sessionId), MONITOR_SNAPSHOT_INTERVAL)
|
||||
}
|
||||
|
||||
const getMonitorSessionId = (data: anyObj) => {
|
||||
const sessionId =
|
||||
data?.sessionId ||
|
||||
data?.monitorSessionId ||
|
||||
data?.classMonitorSessionId ||
|
||||
data?.monitorInfo?.sessionId ||
|
||||
data?.classMonitor?.sessionId
|
||||
|
||||
return typeof sessionId === 'string' || typeof sessionId === 'number'
|
||||
? String(sessionId).trim()
|
||||
: ''
|
||||
}
|
||||
|
||||
export const handleClassMonitorMessage = (data: anyObj) => {
|
||||
const monitorType = resolveMonitorWsType(data)
|
||||
monitorLog('handle monitor ws message', { monitorType, data })
|
||||
@ -372,7 +385,8 @@ export const handleClassMonitorMessage = (data: anyObj) => {
|
||||
}
|
||||
|
||||
export const syncClassMonitorState = (state: unknown) => {
|
||||
const nextState = Number(state)
|
||||
const stateInfo = state && typeof state === 'object' ? (state as anyObj) : {}
|
||||
const nextState = Number(stateInfo.monitorState ?? state)
|
||||
if (nextState !== 0 && nextState !== 1) return
|
||||
|
||||
if (nextState === 0) {
|
||||
@ -380,5 +394,15 @@ export const syncClassMonitorState = (state: unknown) => {
|
||||
return
|
||||
}
|
||||
|
||||
monitorState = 1
|
||||
const sessionId = getMonitorSessionId(stateInfo)
|
||||
if (!sessionId) {
|
||||
monitorWarn('sync monitor state ignored because sessionId is empty', stateInfo)
|
||||
monitorState = 1
|
||||
return
|
||||
}
|
||||
|
||||
startClassMonitor({
|
||||
...stateInfo,
|
||||
sessionId,
|
||||
})
|
||||
}
|
||||
|
||||
645
src/page/system/homework copy/do-work/components/Question.vue
Normal file
645
src/page/system/homework copy/do-work/components/Question.vue
Normal file
@ -0,0 +1,645 @@
|
||||
<script setup lang="ts">
|
||||
import SingleChoice from '@c/question/answer/SingleChoice.vue'
|
||||
import Judgment from '@c/question/answer/Judgment.vue'
|
||||
import JudgmentCloze from '@c/question/answer/JudgmentCloze.vue'
|
||||
import MultipleChoice from '@c/question/answer/MultipleChoice.vue'
|
||||
import ClozeTest from '@c/question/answer/ClozeTest.vue'
|
||||
import Other from '@c/question/answer/Other.vue'
|
||||
import HandDrawn from '@c/question/answer/HandDrawn.vue'
|
||||
import Input from '@c/question/answer/Input.vue'
|
||||
import { useAudio, TypeSet } from '@/hooks'
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import EchartsBar from '@/page/system/homework/reports/components/EchartsBar.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const props = defineProps<{
|
||||
data: any
|
||||
idx: number
|
||||
reportFlag: number
|
||||
uploadFile: any
|
||||
beforeUpload: any
|
||||
isDeadline: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['submit', 'handleTime', 'showPic', 'beforeUpload', 'uploadError'])
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const component = computed(() => {
|
||||
console.log(props.data)
|
||||
if (props.data?.autoMarkFlag === 0) {
|
||||
// 不可自动批改
|
||||
if (props.data?.titleChannelTypeName === '填空题') {
|
||||
// 不可自动批改的填空题
|
||||
return HandDrawn
|
||||
}
|
||||
if (props.data?.subjectId === 3 && props.data?.titleChannelTypeName === '解答题') {
|
||||
// 数学解答题
|
||||
return HandDrawn
|
||||
}
|
||||
return Other
|
||||
}
|
||||
if (props.data?.fillBlankNum) {
|
||||
// 填空题
|
||||
return Input
|
||||
}
|
||||
// 选项为多个选项组时完形填空
|
||||
if (props.data?.optionList?.length > 1) {
|
||||
if (['判断', '判断题'].includes(props.data?.titleChannelTypeName)) {
|
||||
return JudgmentCloze
|
||||
}
|
||||
return ClozeTest
|
||||
}
|
||||
|
||||
if (props.data?.titleChannelTypeName === '多选题') {
|
||||
return MultipleChoice
|
||||
}
|
||||
|
||||
if (['判断', '判断题'].includes(props.data?.titleChannelTypeName)) {
|
||||
return Judgment
|
||||
}
|
||||
|
||||
// 其他题型有optionList时为单选题,否则是应用题
|
||||
return props.data?.optionList?.[0]?.length ? SingleChoice : Other
|
||||
})
|
||||
|
||||
// 图片预览
|
||||
const imgDia = reactive({
|
||||
show: false,
|
||||
url: '',
|
||||
})
|
||||
function handleShow(url: string) {
|
||||
if (url) {
|
||||
imgDia.show = true
|
||||
imgDia.url = url
|
||||
}
|
||||
}
|
||||
|
||||
const resultClass = computed(() => {
|
||||
return (data: any) => {
|
||||
if (!data)
|
||||
return {
|
||||
class: 'info',
|
||||
text: 0 + '分',
|
||||
// icon: 'emoji_info',
|
||||
}
|
||||
if (data.titleStatus === 2) {
|
||||
return {
|
||||
class: 'wait',
|
||||
text: '待批改',
|
||||
// icon: 'emoji_info',
|
||||
}
|
||||
} else if (!data.markFlag && !data.submitAnswer && !data.submitAnswerPic) {
|
||||
return {
|
||||
class: 'empty',
|
||||
text: '未作答',
|
||||
icon: 'emoji_empty',
|
||||
}
|
||||
} else if (data.markFlag) {
|
||||
return {
|
||||
class: 'success',
|
||||
text: '答对啦',
|
||||
icon: 'emoji_success',
|
||||
}
|
||||
} else if (!data.markFlag && data.answerScore === 0) {
|
||||
return {
|
||||
class: 'error',
|
||||
text: '答错了',
|
||||
icon: 'emoji_fail',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
class: 'info',
|
||||
text: data.answerScore + '分',
|
||||
// icon: 'emoji_info',
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 给子题目添加序号
|
||||
const filterTitle = computed(() => {
|
||||
return (title: string | undefined, cidx?: number) => {
|
||||
if (title) {
|
||||
const t = title.replace(/http:\/\//gi, 'https://')
|
||||
// .replace(/contenteditable/g, 'no-contenteditable');
|
||||
if (cidx !== undefined) {
|
||||
return `(${cidx + 1})` + t
|
||||
} else {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @description: 音频
|
||||
*/
|
||||
// 音频是否正在播放
|
||||
const isAudioPlay = ref(false)
|
||||
// 音频播放器
|
||||
let audioPlayer: anyObj = {}
|
||||
// 播放音频
|
||||
function playAudio() {
|
||||
if (isAudioPlay.value) {
|
||||
audioPlayer.pause()
|
||||
} else {
|
||||
audioPlayer.src = props.data?.voiceUrl
|
||||
audioPlayer.play()
|
||||
}
|
||||
}
|
||||
|
||||
// 填空题 同步答案
|
||||
const quizList = ref<anyObj[]>([])
|
||||
function inputChange(val: string, idx: number) {
|
||||
quizList.value[idx].innerText = val
|
||||
}
|
||||
|
||||
const jobStuStatistics = ref<anyObj>([])
|
||||
|
||||
const rightRef = ref()
|
||||
const echartsRef = ref()
|
||||
const echartsData = reactive<{ xData: string[]; seriesVal: anyObj[] }>({
|
||||
xData: [],
|
||||
seriesVal: [],
|
||||
})
|
||||
watch(
|
||||
() => props.data?.jobStuTittleId,
|
||||
async nv => {
|
||||
if (nv) {
|
||||
// 切换题目时初始化音频 初始化右边区域滑动条
|
||||
audioPlayer?.pause?.()
|
||||
rightRef.value && (rightRef.value.scrollTop = 0)
|
||||
await nextTick()
|
||||
TypeSet([document.querySelector('.question-item')])
|
||||
// 题目里所有图片添加点击放大查看事件
|
||||
document.querySelectorAll('.dowork .left-title img').forEach((i: anyObj) => {
|
||||
i?.addEventListener('click', () => {
|
||||
handleShow(i?.src)
|
||||
})
|
||||
})
|
||||
document.querySelectorAll('.dowork .choice-box img').forEach((i: anyObj) => {
|
||||
i?.addEventListener('click', (e: any) => {
|
||||
handleShow(i?.src)
|
||||
e?.stopPropagation?.()
|
||||
})
|
||||
})
|
||||
document.querySelectorAll('.dowork .answer-box-main img').forEach((i: anyObj) => {
|
||||
i?.addEventListener('click', () => {
|
||||
handleShow(i?.src)
|
||||
})
|
||||
})
|
||||
document.querySelectorAll('.dowork .analyze-title img').forEach((i: anyObj) => {
|
||||
i?.addEventListener('click', () => {
|
||||
handleShow(i?.src)
|
||||
})
|
||||
})
|
||||
|
||||
if (props.reportFlag) {
|
||||
const { data: res } = await homeWorkApi.getJobStatistics({
|
||||
titleId: props.data.id,
|
||||
jobId: props.data.jobId,
|
||||
stuId: route.query.stuId || '',
|
||||
})
|
||||
|
||||
jobStuStatistics.value = res.data?.map((i: anyObj) => i.scStuList) || []
|
||||
echartsData.xData = []
|
||||
echartsData.seriesVal = []
|
||||
res.data?.forEach((i: anyObj) => {
|
||||
echartsData.xData.push(i.intervalValue + '分')
|
||||
echartsData.seriesVal.push({
|
||||
value: i.num,
|
||||
itemStyle: {
|
||||
color: i.curFlag ? '#FE8200' : '#7FADFF',
|
||||
},
|
||||
})
|
||||
})
|
||||
nextTick(() => {
|
||||
echartsRef.value?.drawGraph()
|
||||
})
|
||||
}
|
||||
|
||||
// 填空题 同步答案
|
||||
if (props.data?.fillBlankNum) {
|
||||
quizList.value = []
|
||||
document
|
||||
.querySelectorAll('.quizPutTag[contenteditable]')
|
||||
.forEach((i: anyObj, idx: number) => {
|
||||
i.setAttribute('contenteditable', false)
|
||||
i.innerText = props.data.submitAnswer?.split(',')?.[idx] || ''
|
||||
quizList.value.push(i)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
const xlDia = reactive<{ show: boolean; data: anyObj[] }>({
|
||||
show: false,
|
||||
data: [],
|
||||
})
|
||||
|
||||
function handleBarClick(params: anyObj) {
|
||||
const arr = jobStuStatistics.value[params.dataIndex]
|
||||
if (arr?.length) {
|
||||
xlDia.show = true
|
||||
xlDia.data = arr.map((i: anyObj) => ({
|
||||
id: i.id,
|
||||
name: i.stuName,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
;(() => {
|
||||
nextTick(() => {
|
||||
audioPlayer = useAudio({
|
||||
autoplay: false,
|
||||
})
|
||||
audioPlayer.on('play', () => {
|
||||
isAudioPlay.value = true
|
||||
})
|
||||
audioPlayer.on('pause', () => {
|
||||
isAudioPlay.value = false
|
||||
})
|
||||
audioPlayer.on('ended', () => {
|
||||
isAudioPlay.value = false
|
||||
})
|
||||
})
|
||||
})()
|
||||
|
||||
onUnmounted(() => {
|
||||
audioPlayer?.destroy?.()
|
||||
isAudioPlay.value = false
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="question-item">
|
||||
<div class="left hidden_scroll">
|
||||
<div class="left-subType">{{ idx }}. ({{ data?.score }}分) {{ data?.channelTypeName }}</div>
|
||||
<div v-if="data?.title" class="left-title" v-html="filterTitle(data?.title)"></div>
|
||||
<div v-if="data?.voiceUrl" class="left-voice">
|
||||
<Horn :active="isAudioPlay" @click="playAudio"></Horn>
|
||||
</div>
|
||||
<div v-if="data?.children?.length" class="left-children">
|
||||
<template v-for="(c, idx) in data?.children" :key="c.id">
|
||||
<div class="left-title" v-html="filterTitle(c?.title, idx)"></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="rightRef" class="right hidden_scroll">
|
||||
<!-- 批改结构 -->
|
||||
<div class="result-box">
|
||||
<div v-if="isDeadline && reportFlag" :class="['result', resultClass(data).class]">
|
||||
<div>我的答案</div>
|
||||
<div class="emoji-box">
|
||||
<div class="emoji">
|
||||
<Icon
|
||||
v-if="resultClass(data).icon"
|
||||
:name="resultClass(data).icon || ''"
|
||||
width="48"
|
||||
></Icon>
|
||||
</div>
|
||||
{{ resultClass(data).text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 答题区域 -->
|
||||
|
||||
<component
|
||||
:is="component"
|
||||
:reportFlag="Number(reportFlag)"
|
||||
class="answer-com"
|
||||
:titleId="data?.id"
|
||||
:optionList="data?.optionList || undefined"
|
||||
:answer="data?.answer"
|
||||
:submitAnswer="data?.submitAnswer"
|
||||
:submitAnswerPic="data?.submitAnswerPic"
|
||||
:submitAnswerPicOriginal="data?.submitAnswerPicOriginal"
|
||||
:isAndroid="data?.isAndroid"
|
||||
:markAnswerPic="data?.markAnswerPic"
|
||||
:markAnswer="data?.markAnswer"
|
||||
:fillBlankNum="data?.fillBlankNum"
|
||||
:isDeadline="isDeadline"
|
||||
:uploadLoading="data?.uploadLoading"
|
||||
:uploadFile="uploadFile"
|
||||
:beforeUpload="beforeUpload"
|
||||
@submit="
|
||||
emit('submit', {
|
||||
...$event,
|
||||
id: data?.jobStuTittleId,
|
||||
})
|
||||
"
|
||||
@handleTime="emit('handleTime', $event)"
|
||||
@showPic="handleShow"
|
||||
@inputChange="inputChange"
|
||||
>
|
||||
<template #markAnswer>
|
||||
<div v-if="isDeadline && reportFlag && data?.markAnswer" class="markAnswerPic">
|
||||
<div class="answer-box">
|
||||
<div class="answer-box-title">批改说明</div>
|
||||
</div>
|
||||
<div class="text-box">
|
||||
{{ data?.markAnswer }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<!-- 答案与解析 -->
|
||||
<template v-if="isDeadline && reportFlag">
|
||||
<div v-if="echartsData.xData.length" class="answer-box">
|
||||
<div class="answer-box-title">答题统计</div>
|
||||
<div class="answer-box-echarts">
|
||||
<EchartsBar
|
||||
ref="echartsRef"
|
||||
:data="echartsData"
|
||||
yName="(人)"
|
||||
@handleBarClick="handleBarClick"
|
||||
></EchartsBar>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="data?.answer" class="answer-box">
|
||||
<div class="answer-box-title">参考答案</div>
|
||||
<div class="answer-box-main" v-html="data?.answer"></div>
|
||||
</div>
|
||||
<!-- 暂时隐藏 data.titleAnalyzeJson -->
|
||||
<div
|
||||
v-if="data?.titleAnalyze || data?.titleAnalyzePicture"
|
||||
class="answer-box"
|
||||
style="margin-top: 60px"
|
||||
>
|
||||
<div class="answer-box-title">解析</div>
|
||||
<div v-if="data?.titleAnalyze" class="answer-box-main analyze analyze-title">
|
||||
<div class="titleAnalyze" v-html="data?.titleAnalyze"></div>
|
||||
</div>
|
||||
<div v-if="data?.titleAnalyzePicture" class="answer-box-main analyze analyze-pic">
|
||||
<div
|
||||
v-for="i in data.titleAnalyzePicture.split(',')"
|
||||
:key="i"
|
||||
class="titleAnalyzePicture"
|
||||
@click="handleShow(i)"
|
||||
>
|
||||
<img :src="i" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<ImgView v-model="imgDia.show" :url="imgDia.url"></ImgView>
|
||||
<XLDia v-model="xlDia.show" title="对应学员">
|
||||
<div class="stu_list">
|
||||
<div v-for="i in xlDia.data" :key="i.id" class="stu_list-item">
|
||||
{{ i.name }}
|
||||
</div>
|
||||
</div>
|
||||
</XLDia>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.question-item {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2.67vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 2.67vh;
|
||||
transform: translate(-50%);
|
||||
content: '';
|
||||
display: block;
|
||||
width: 6px;
|
||||
height: calc(100% - 64px);
|
||||
border-radius: 0.5vh;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
#cdd5ff,
|
||||
#cdd5ff 8px,
|
||||
transparent 10px,
|
||||
transparent 20px /* 线条间隔 */
|
||||
);
|
||||
}
|
||||
|
||||
.left {
|
||||
width: calc(50% - 3px);
|
||||
height: 100%;
|
||||
padding-right: 48px;
|
||||
|
||||
// word-break: break-all;
|
||||
word-break: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-y: auto;
|
||||
|
||||
&-subType {
|
||||
color: #666;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
&-title {
|
||||
padding-left: 40px;
|
||||
color: $font-color;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
// line-height: 48px; /* 150% */
|
||||
line-height: 1.8em;
|
||||
// overflow-x: hidden;
|
||||
}
|
||||
&-voice {
|
||||
padding-left: 40px;
|
||||
margin-top: 2vh;
|
||||
}
|
||||
&-children {
|
||||
padding-left: 40px;
|
||||
color: $font-color;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
// &-item {
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
// }
|
||||
margin-top: 1.33vh;
|
||||
.left-title {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
width: calc(50% - 3px);
|
||||
height: 100%;
|
||||
padding-left: 48px;
|
||||
padding-right: 40px;
|
||||
padding-top: 1.33vh;
|
||||
.result-box {
|
||||
width: 100%;
|
||||
height: 4vh;
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
.result {
|
||||
padding: 0 20px 0 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
color: $font-color;
|
||||
font-family: $font-special;
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
border-radius: 0.33vh;
|
||||
&::before {
|
||||
content: '';
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #669cfe;
|
||||
border-radius: 0 1vh 1vh 0;
|
||||
}
|
||||
&.error {
|
||||
background: linear-gradient(90deg, rgba(255, 191, 191, 0) 0%, #ffbfbf 100%);
|
||||
}
|
||||
&.success {
|
||||
background: linear-gradient(90deg, rgba(221, 255, 140, 0) 0%, #ddff8c 100%);
|
||||
}
|
||||
&.empty {
|
||||
background: linear-gradient(90deg, rgba(255, 191, 191, 0) 0%, #ffbfbf 100%);
|
||||
}
|
||||
&.wait {
|
||||
background: linear-gradient(90deg, rgba(190, 213, 254, 0) 0%, #bed5fe 100%);
|
||||
}
|
||||
&.info {
|
||||
background: linear-gradient(90deg, rgba(190, 213, 254, 0) 0%, #bed5fe 100%);
|
||||
}
|
||||
.emoji-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.emoji {
|
||||
width: 48px;
|
||||
height: 4vh;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.answer-com {
|
||||
// max-height: calc(100% - 100px);
|
||||
}
|
||||
.answer-box {
|
||||
margin-top: 4vh;
|
||||
&-title {
|
||||
padding: 0 20px 0 32px;
|
||||
position: relative;
|
||||
color: $font-color;
|
||||
// font-family: $font-special;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
border-radius: 0.33vh;
|
||||
margin-bottom: 2vh;
|
||||
&::before {
|
||||
content: '';
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #669cfe;
|
||||
border-radius: 0 1vh 1vh 0;
|
||||
}
|
||||
}
|
||||
&-echarts {
|
||||
height: 28.33vh;
|
||||
}
|
||||
&-main {
|
||||
color: $font-color;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
padding-left: 32px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
&.analyze {
|
||||
// padding-left: 0;
|
||||
width: 100%;
|
||||
&.analyze-pic {
|
||||
margin-top: 2vh;
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 66.67vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
.titleAnalyzePicture {
|
||||
margin-bottom: 1vh;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.analyze-title {
|
||||
margin-top: 2vh;
|
||||
|
||||
.titleAnalyze {
|
||||
color: $font-color;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.text-box {
|
||||
// color: $font-color;
|
||||
// font-family: $font-special;
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
padding-left: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.xl-dia {
|
||||
:deep(.xl-dia-main) {
|
||||
padding: 2.33vh;
|
||||
}
|
||||
.stu_list {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
flex-wrap: wrap;
|
||||
height: 100%;
|
||||
@include hidden_scroll;
|
||||
&-item {
|
||||
border-radius: 3.5vh;
|
||||
border: 0.17vh solid $font-color;
|
||||
padding: 1.33vh 32px;
|
||||
color: $font-color;
|
||||
text-align: center;
|
||||
font-family: OPPOSans;
|
||||
font-size: 2.67vh;
|
||||
margin: 1vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
584
src/page/system/homework copy/do-work/index.vue
Normal file
584
src/page/system/homework copy/do-work/index.vue
Normal file
@ -0,0 +1,584 @@
|
||||
<script setup lang="ts">
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import globalApi from '@/api/global'
|
||||
import { debounce, XMessage } from '@/hooks'
|
||||
// import Question from './components/Question.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router'
|
||||
import { $XXL, getDurationText } from '@/utils'
|
||||
import { loading } from '@/components/mj/mj-loading'
|
||||
import { userStore } from '@/stores'
|
||||
import type { IQuestion } from '@/types/question'
|
||||
import { globalBack } from '@/router'
|
||||
import { dialog } from '@/components/mj/mj-dialog'
|
||||
defineOptions({
|
||||
name: 'HomeWorkDoWork',
|
||||
})
|
||||
|
||||
type PaperInfo = {
|
||||
name: string
|
||||
costTime: number
|
||||
currentTitleId: string
|
||||
}
|
||||
|
||||
const { token } = userStore()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const pageLoading = ref(true)
|
||||
// 是否为答题页面 0 答题 1 查看作业结果
|
||||
const reportFlag = ref(0)
|
||||
|
||||
// 题目列表
|
||||
interface WorkQuestion extends IQuestion {
|
||||
/** 还未做过这道题 */
|
||||
unLook: boolean
|
||||
/** 上传中 */
|
||||
uploadLoading: boolean
|
||||
}
|
||||
const paperList = ref<WorkQuestion[]>([])
|
||||
// 试卷信息
|
||||
const paperInfo = ref<PaperInfo>({} as PaperInfo)
|
||||
const isDeadline = ref(false)
|
||||
|
||||
// 当前所在题目
|
||||
const activeIdx = ref(-1)
|
||||
const indicatorRef = ref()
|
||||
|
||||
// 切换题目
|
||||
async function switchQuestion(idx?: number) {
|
||||
if (idx === activeIdx.value) return
|
||||
// 最后一题的索引
|
||||
const lastIdx = paperList.value.length - 1
|
||||
// 是否为点击下一题
|
||||
let nextQuestion = false
|
||||
if (idx !== undefined) {
|
||||
activeIdx.value = idx
|
||||
} else if (activeIdx.value < lastIdx) {
|
||||
// 点击下一题
|
||||
activeIdx.value++
|
||||
nextQuestion = true
|
||||
} else if (activeIdx.value === lastIdx) {
|
||||
if (!reportFlag.value) {
|
||||
// 提交作业
|
||||
if (paperList.value.some(i => i.uploadLoading)) {
|
||||
return XMessage.error('上传中,请稍后~')
|
||||
}
|
||||
let tips = '请确认是否提交本次作业'
|
||||
if (paperList.value.some(i => !i.submitAnswer && !i.submitAnswerPic)) {
|
||||
tips = '还有题目未作答,确定要提交答案吗?'
|
||||
}
|
||||
dialog.show({
|
||||
content: tips,
|
||||
ok: handleSubmitPaper,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!reportFlag.value) {
|
||||
paperList.value[activeIdx.value].unLook = false
|
||||
|
||||
// 因为服务器返回的题目顺序是打乱的,所以上报当前正在查看的题目已经米有意义
|
||||
// 每次切换题目调一次接口
|
||||
// const { id, jobStuId } = paperList.value[activeIdx.value]
|
||||
|
||||
// submitCurrentTitleId({
|
||||
// jobStuId,
|
||||
// currentTitleId: id,
|
||||
// costTime: paperInfo.value.costTime * 1000
|
||||
// })
|
||||
}
|
||||
|
||||
// 点击下一题时下方往后滑动
|
||||
await nextTick()
|
||||
if (nextQuestion && indicatorRef.value) {
|
||||
indicatorRef.value.scrollLeft += 104
|
||||
}
|
||||
}
|
||||
const debounceChange = debounce(switchQuestion, 200)
|
||||
|
||||
function getResourceType() {
|
||||
return route.query.resource
|
||||
}
|
||||
|
||||
// 上传前
|
||||
function beforeUpload(rawFile: any) {
|
||||
const fileType = ['image/jpeg', 'image/png']
|
||||
if (!fileType.includes(rawFile.type)) {
|
||||
XMessage.error('上传格式不正确,只支持JPG/PNG')
|
||||
return false
|
||||
}
|
||||
if (rawFile.size / 1024 / 1024 > 5) {
|
||||
XMessage.error('图片过大')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
// 上传
|
||||
async function uploadFile(options: anyObj) {
|
||||
const currentTitleId = paperList.value[activeIdx.value].jobStuTittleId
|
||||
paperList.value[activeIdx.value].uploadLoading = true
|
||||
|
||||
try {
|
||||
let path = ''
|
||||
if (options.isAndroid) {
|
||||
if (options.submitAnswerPicOriginal) {
|
||||
const res = await $XXL.uploadFile({
|
||||
url: `${import.meta.env.VITE_SERVER_URL}/sysFileInfo/tenUploadAll`,
|
||||
header: {
|
||||
Authorization: token,
|
||||
},
|
||||
filePath: options.file,
|
||||
fileKey: 'file',
|
||||
})
|
||||
path = res.url
|
||||
}
|
||||
} else {
|
||||
const formData = new FormData()
|
||||
formData.append('file', options.file)
|
||||
const { data: res } = await globalApi.uploadFile(formData)
|
||||
path = res.data.filePath
|
||||
}
|
||||
if (options.submitAnswerPicOriginal) {
|
||||
handleSubmitSingle({
|
||||
submitAnswerPic: path,
|
||||
submitAnswerPicOriginal: options.submitAnswerPicOriginal,
|
||||
id: currentTitleId,
|
||||
isAndroid: options.isAndroid || 0,
|
||||
})
|
||||
} else {
|
||||
if (options.isAndroid) {
|
||||
handleSubmitSingle({
|
||||
submitAnswerPic: '',
|
||||
submitAnswerPicOriginal: '',
|
||||
id: currentTitleId,
|
||||
isAndroid: 1,
|
||||
})
|
||||
} else {
|
||||
handleSubmitSingle({ filePath: path, id: currentTitleId })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
XMessage.error('网络异常,请检查网络~')
|
||||
}
|
||||
|
||||
const currentIdx = paperList.value.findIndex(i => i.jobStuTittleId === currentTitleId)
|
||||
if (currentIdx !== -1) {
|
||||
paperList.value[currentIdx].uploadLoading = false
|
||||
paperList.value[currentIdx].isAndroid = options.isAndroid
|
||||
}
|
||||
}
|
||||
|
||||
// 提交每道题目
|
||||
async function handleSubmitSingle(answer: any) {
|
||||
if (reportFlag.value) return
|
||||
const params: anyObj = {
|
||||
id: answer.id,
|
||||
isAndroid: answer.isAndroid,
|
||||
}
|
||||
if (answer.filePath) {
|
||||
// 添加图片
|
||||
const idx = paperList.value.findIndex(i => i.jobStuTittleId === answer.id)
|
||||
if (idx !== -1) {
|
||||
const safeUrl = String(answer.filePath || '')
|
||||
.replace(/[`]/g, '')
|
||||
.replace(/http:\/\//gi, 'https://')
|
||||
.trim()
|
||||
const q = paperList.value[idx]
|
||||
const isHandDrawn =
|
||||
q?.autoMarkFlag === 0 &&
|
||||
(q?.titleChannelTypeName === '填空题' ||
|
||||
(q?.subjectId === 3 && q?.titleChannelTypeName === '解答题'))
|
||||
const submitPic = isHandDrawn
|
||||
? safeUrl
|
||||
: q.submitAnswerPic || ''
|
||||
? `${q.submitAnswerPic},${safeUrl}`
|
||||
: safeUrl
|
||||
|
||||
paperList.value[idx].submitAnswerPic = submitPic
|
||||
params.submitAnswerPic = submitPic
|
||||
}
|
||||
} else if (answer.submitAnswer) {
|
||||
paperList.value[activeIdx.value].submitAnswer = answer.submitAnswer
|
||||
params.submitAnswer = answer.submitAnswer
|
||||
} else if (answer.delUrl) {
|
||||
// 删除图片
|
||||
const picList = paperList.value[activeIdx.value].submitAnswerPic.split(',') || []
|
||||
const idx = picList.indexOf(answer.delUrl)
|
||||
picList.splice(idx, 1)
|
||||
params.submitAnswerPic = picList.join(',')
|
||||
paperList.value[activeIdx.value].submitAnswerPic = params.submitAnswerPic
|
||||
} else if (answer.submitAnswerPic) {
|
||||
// 替换图片
|
||||
const idx = paperList.value.findIndex(i => i.jobStuTittleId === answer.id)
|
||||
paperList.value[idx].submitAnswerPic = answer.submitAnswerPic
|
||||
paperList.value[idx].submitAnswerPicOriginal = answer.submitAnswerPicOriginal
|
||||
params.submitAnswerPic = answer.submitAnswerPic
|
||||
params.submitAnswerPicOriginal = answer.submitAnswerPicOriginal
|
||||
} else if (answer.submitAnswerPic === '' && answer.isAndroid) {
|
||||
// 清空图片
|
||||
const idx = paperList.value.findIndex(i => i.jobStuTittleId === answer.id)
|
||||
paperList.value[idx].submitAnswerPic = ''
|
||||
paperList.value[idx].submitAnswerPicOriginal = ''
|
||||
params.submitAnswerPic = ''
|
||||
params.submitAnswerPicOriginal = ''
|
||||
}
|
||||
|
||||
// 单选题直接跳转下一题
|
||||
if (answer.toNext && activeIdx.value < paperList.value.length - 1) setTimeout(switchQuestion, 50)
|
||||
|
||||
await homeWorkApi.submitAnswer(params)
|
||||
}
|
||||
|
||||
let timeout: any
|
||||
function startTime() {
|
||||
clearInterval(timeout)
|
||||
timeout = setInterval(() => {
|
||||
paperInfo.value.costTime++
|
||||
}, 1000)
|
||||
}
|
||||
const timeFilter = computed(() => getDurationText(paperInfo.value.costTime))
|
||||
|
||||
function handleTime(type: 0 | 1) {
|
||||
if (type) {
|
||||
startTime()
|
||||
} else {
|
||||
clearInterval(timeout)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmitPaper() {
|
||||
clearInterval(timeout)
|
||||
const { jobId, jobStuId } = paperList.value[activeIdx.value]
|
||||
|
||||
loading.show('正在提交中...')
|
||||
try {
|
||||
await homeWorkApi.submitPaper({
|
||||
id: jobStuId,
|
||||
jobId,
|
||||
costTime: paperInfo.value.costTime * 1000,
|
||||
})
|
||||
// 标记为可以离开
|
||||
leave.value = true
|
||||
// 成功后跳转
|
||||
router.replace('/system/homeWork/reports/' + jobId)
|
||||
} finally {
|
||||
loading.hide()
|
||||
}
|
||||
}
|
||||
|
||||
// 题目是否有提交过答案
|
||||
function hasSubmitAnswer(question: WorkQuestion) {
|
||||
return question.submitAnswer || question.submitAnswerPic || question.submitAnswerPicOriginal
|
||||
}
|
||||
|
||||
// 获取试卷信息
|
||||
async function getPaper(options: any) {
|
||||
// 请求试卷数据
|
||||
const { data } = await homeWorkApi.getJobTittle(options)
|
||||
paperInfo.value = {
|
||||
name: data.paperName,
|
||||
costTime: data.costTime / 1000,
|
||||
currentTitleId: data.currentTitleId,
|
||||
}
|
||||
const oldQuestions = data.tjobStuVoList
|
||||
// 是否到了作业的截止时间
|
||||
isDeadline.value = dayjs().isAfter(data.submitEndTime)
|
||||
if (!reportFlag.value) {
|
||||
// 将已经答过的题目放在前面
|
||||
const answeredQuestions: WorkQuestion[] = []
|
||||
const unansweredQuestions: WorkQuestion[] = []
|
||||
let idx = 0
|
||||
for (const question of oldQuestions) {
|
||||
if (hasSubmitAnswer(question)) {
|
||||
answeredQuestions.push(question)
|
||||
idx += 1
|
||||
} else {
|
||||
question.unLook = true
|
||||
unansweredQuestions.push(question)
|
||||
}
|
||||
}
|
||||
|
||||
paperList.value = [...answeredQuestions, ...unansweredQuestions]
|
||||
return Math.min(idx, paperList.value.length - 1)
|
||||
} else {
|
||||
paperList.value = oldQuestions
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const leave = ref(true)
|
||||
onBeforeRouteLeave((to, _from, next) => {
|
||||
if (leave.value || to.path === '/login' || reportFlag.value) {
|
||||
return next()
|
||||
}
|
||||
next(false)
|
||||
|
||||
dialog.show({
|
||||
content: '还没有提交作业,是否确认退出?',
|
||||
ok: () => {
|
||||
leave.value = true
|
||||
globalBack()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const options: anyObj = route.query || {}
|
||||
if (!options?.id) {
|
||||
// 没有试卷的id
|
||||
globalBack()
|
||||
return
|
||||
}
|
||||
|
||||
// 是否为报告
|
||||
reportFlag.value = Number(options.reportFlag)
|
||||
|
||||
// 请求数据
|
||||
pageLoading.value = true
|
||||
const currentIdx = await getPaper(options)
|
||||
pageLoading.value = false
|
||||
|
||||
// 数据记载完毕后,就不准直接离开
|
||||
leave.value = false
|
||||
|
||||
// 切换题目
|
||||
switchQuestion(options?.idx ? Number(options?.idx) : currentIdx)
|
||||
|
||||
if (!reportFlag.value) {
|
||||
startTime()
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
if (indicatorRef.value) {
|
||||
indicatorRef.value.scrollLeft = ((options?.idx ? Number(options?.idx) : currentIdx) - 1) * 104
|
||||
}
|
||||
|
||||
// 设置屏幕常亮
|
||||
$XXL.setScreenOn(1)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
paperInfo.value.costTime = 0
|
||||
activeIdx.value = 0
|
||||
paperList.value = []
|
||||
paperInfo.value = {} as PaperInfo
|
||||
|
||||
// showQuesImgDia.value = false;
|
||||
// quesImg.value = '';
|
||||
// showDia.value = false
|
||||
// leaveFlag = false;
|
||||
clearInterval(timeout)
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="dowork g-main-bg">
|
||||
<div class="title-box">
|
||||
<div class="title" :data-content="paperInfo.name">
|
||||
{{ paperInfo.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="top">
|
||||
<BackBar />
|
||||
|
||||
<div v-if="!pageLoading" class="top-timeout">
|
||||
<Icon name="clock" width="48"></Icon>
|
||||
{{ timeFilter }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!pageLoading" class="main">
|
||||
<div class="main-box">
|
||||
<div class="book-box">
|
||||
<div class="book">
|
||||
<Question
|
||||
:data="paperList[activeIdx]"
|
||||
:idx="activeIdx + 1"
|
||||
:is-homework="true"
|
||||
:total="paperList.length"
|
||||
:reportFlag="reportFlag"
|
||||
:isDeadline="isDeadline"
|
||||
:resource-type="getResourceType()"
|
||||
:uploadFile="uploadFile"
|
||||
:beforeUpload="beforeUpload"
|
||||
@submit="handleSubmitSingle"
|
||||
@handleTime="handleTime"
|
||||
></Question>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div ref="indicatorRef" :class="{ indicator: true, hidden_scroll: true }">
|
||||
<div
|
||||
v-for="(i, idx) in paperList"
|
||||
v-show="!i.unLook"
|
||||
:key="i.id"
|
||||
:class="{
|
||||
'indicator-item': true,
|
||||
pointer: true,
|
||||
active: activeIdx === idx,
|
||||
success: isDeadline && reportFlag && i.markFlag,
|
||||
error: isDeadline && reportFlag && !i.markFlag && i.titleStatus === 3,
|
||||
empty: !i.submitAnswer && !i.submitAnswerPic,
|
||||
}"
|
||||
@click="switchQuestion(idx)"
|
||||
>
|
||||
{{ idx + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
<Btn
|
||||
v-if="!reportFlag"
|
||||
class="btn"
|
||||
type="success"
|
||||
width="192"
|
||||
height="80"
|
||||
size="32"
|
||||
@click="debounceChange()"
|
||||
>
|
||||
{{ activeIdx < paperList.length - 1 ? '下一题' : '提交' }}
|
||||
</Btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mj-loading v-else v-model="pageLoading" :fullscreen="true" text="正在加载试卷中..." />
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
$filter: brightness(0) saturate(0) invert(0) sepia(0) hue-rotate(30deg);
|
||||
.dowork {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
.title-box {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 2.92vh;
|
||||
.title {
|
||||
color: #fff;
|
||||
text-shadow: 0 0.17vh 0.17vh rgba(0, 0, 0, 0.25);
|
||||
font-family: $font-special;
|
||||
font-size: 4vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 6vh; /* 150% */
|
||||
text-align: center;
|
||||
letter-spacing: 10px;
|
||||
@include font-stroke($font-color, 6px);
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
padding: 2.67vh 64px 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&-timeout {
|
||||
color: $font-color;
|
||||
text-align: center;
|
||||
text-shadow: 0 0.17vh 0.17vh rgba(0, 0, 0, 0.25);
|
||||
font-family: $font-special;
|
||||
font-size: 2.67vh;
|
||||
height: 4vh;
|
||||
line-height: 4vh;
|
||||
display: flex;
|
||||
.x-icon {
|
||||
margin-right: 10px;
|
||||
filter: $filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
.main {
|
||||
box-sizing: border-box;
|
||||
padding: 0 32px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
&-box {
|
||||
position: relative;
|
||||
top: 10.5vh;
|
||||
}
|
||||
.book-box {
|
||||
position: relative;
|
||||
height: 80vh;
|
||||
padding-top: 1.25vh;
|
||||
}
|
||||
.book {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: auto;
|
||||
height: 80vh;
|
||||
flex-shrink: 0;
|
||||
border-radius: 3.33vh;
|
||||
padding-bottom: 5.83vh;
|
||||
}
|
||||
}
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 32px;
|
||||
.indicator {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
padding: 0.83vh 0;
|
||||
|
||||
&-item {
|
||||
box-sizing: border-box;
|
||||
width: 80px;
|
||||
height: 6.67vh;
|
||||
background: #bed5fe;
|
||||
// border: 4px solid #fff;
|
||||
border-radius: 50%;
|
||||
margin-right: 24px;
|
||||
color: $font-color;
|
||||
text-align: center;
|
||||
font-family: $font-special;
|
||||
font-size: 3.33vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 6.67vh; /* 150% */
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0.33vh 0.33vh 0 rgba(0, 0, 0, 0.25);
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
&.error {
|
||||
background: #febebf;
|
||||
border-color: #f00809;
|
||||
color: #f00809;
|
||||
}
|
||||
&.success {
|
||||
background: #ddff8c;
|
||||
border-color: #8ecc00;
|
||||
color: #8ecc00;
|
||||
}
|
||||
&.empty {
|
||||
background: #cccccc;
|
||||
color: #ffffff;
|
||||
border-color: #aaa;
|
||||
}
|
||||
&.active {
|
||||
background: #ebf1fd;
|
||||
border: 0.33vh solid #7eacfe;
|
||||
line-height: 6vh;
|
||||
|
||||
&.empty {
|
||||
color: #7eacfe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
margin-left: 62px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
473
src/page/system/homework copy/history/index.vue
Normal file
473
src/page/system/homework copy/history/index.vue
Normal file
@ -0,0 +1,473 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import { rateType } from '@/hooks'
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { userStore } from '@/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const OSS_URL = import.meta.env.VITE_OSS_URL
|
||||
|
||||
const { userInfo } = storeToRefs(userStore())
|
||||
|
||||
const colorConfig = {
|
||||
// 字体颜色 progerss_border progress_bg progress_active
|
||||
primary: ['#2D39BC', '#2D39BC', '#6B73E5', '#90FCF7'],
|
||||
success: ['#2F8A5B', '#27A363', '#25BF6E', '#1FFF8A'],
|
||||
error: ['#BC2D37', '#B22731', '#CB3741', '#FF3542'],
|
||||
warning: ['#a13c00', '#C08129', '#CC8A33', '#FFFE36'],
|
||||
}
|
||||
|
||||
const rateConfig = computed(() => {
|
||||
return (rate: number) => {
|
||||
const r = Number(rate) * 100
|
||||
return {
|
||||
color: colorConfig[rateType.value(r) || 'primary'][0],
|
||||
border: colorConfig[rateType.value(r) || 'primary'][1],
|
||||
bg: colorConfig[rateType.value(r) || 'primary'][2],
|
||||
activeBg: colorConfig[rateType.value(r) || 'primary'][3],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 筛选状态
|
||||
const activeStatus = ref('')
|
||||
function handleChangeStatus() {
|
||||
refreshJobList()
|
||||
}
|
||||
|
||||
// 作业科目列表
|
||||
const classList = ref<any[]>([])
|
||||
async function getClassList() {
|
||||
const { data: res } = await homeWorkApi.getSubjectList({ statusList: [1, 2, 3], historyFlag: 1 })
|
||||
if (res.code === 200) {
|
||||
classList.value = res.data
|
||||
}
|
||||
}
|
||||
|
||||
// 当前选中的科目
|
||||
const activeClass = ref<number>(0)
|
||||
function handleSelectClass(_item: any) {
|
||||
refreshJobList()
|
||||
}
|
||||
|
||||
// 作业列表
|
||||
const jobList = ref<any[]>([])
|
||||
const page = reactive({
|
||||
pageSize: 4,
|
||||
pageNum: 1,
|
||||
total: -1,
|
||||
})
|
||||
const listLoading = ref(false)
|
||||
async function getJobList() {
|
||||
const { data: res } = await homeWorkApi.getJobList({
|
||||
statusList: activeStatus.value ? (activeStatus.value === '3' ? [3] : [1, 2]) : [1, 2, 3],
|
||||
...page,
|
||||
subjectId: activeClass.value,
|
||||
historyFlag: 1,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
jobList.value = res.rows
|
||||
page.total = res.total
|
||||
}
|
||||
}
|
||||
|
||||
const pageError = ref(false)
|
||||
async function changePage(type: 0 | 1) {
|
||||
pageError.value = false
|
||||
const { total, pageNum, pageSize } = page
|
||||
if (type) {
|
||||
if (pageNum * pageSize >= total) return
|
||||
page.pageNum++
|
||||
} else {
|
||||
if (pageNum <= 1) return
|
||||
page.pageNum--
|
||||
}
|
||||
try {
|
||||
listLoading.value = true
|
||||
jobList.value = []
|
||||
await getJobList()
|
||||
} catch {
|
||||
pageError.value = true
|
||||
}
|
||||
listLoading.value = false
|
||||
}
|
||||
|
||||
async function refreshJobList() {
|
||||
pageError.value = false
|
||||
listLoading.value = true
|
||||
jobList.value = []
|
||||
page.pageNum = 1
|
||||
try {
|
||||
await getJobList()
|
||||
} catch {
|
||||
pageError.value = true
|
||||
}
|
||||
listLoading.value = false
|
||||
}
|
||||
|
||||
const pageLoading = ref(false)
|
||||
onActivated(async () => {
|
||||
pageLoading.value = true
|
||||
listLoading.value = true
|
||||
jobList.value = []
|
||||
page.pageNum = 1
|
||||
try {
|
||||
await getClassList()
|
||||
if (classList.value.length) {
|
||||
if (classList.value.every(i => i.id !== activeClass.value)) {
|
||||
activeClass.value = classList.value[0]?.id
|
||||
}
|
||||
await getJobList()
|
||||
}
|
||||
} catch {}
|
||||
listLoading.value = false
|
||||
pageLoading.value = false
|
||||
})
|
||||
|
||||
function toReports(item: any) {
|
||||
router.push(`/system/homeWork/reports/${item.id}`)
|
||||
}
|
||||
|
||||
// 是否到截止时间
|
||||
const isDeadline = computed(() => (time: string) => dayjs().isAfter(time))
|
||||
</script>
|
||||
<template>
|
||||
<div class="uncomplete">
|
||||
<div class="top">
|
||||
<BackBar leftText="作业记录" />
|
||||
<Select
|
||||
v-model="activeStatus"
|
||||
:selectList="[
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '已批改', value: '3' },
|
||||
{ label: '未批改', value: '2' },
|
||||
]"
|
||||
@selectChange="handleChangeStatus"
|
||||
></Select>
|
||||
</div>
|
||||
<div v-if="classList.length" class="main">
|
||||
<div class="main-sidebar">
|
||||
<Sidebar
|
||||
v-model="activeClass"
|
||||
:config="classList"
|
||||
:prop="{ name: 'subject' }"
|
||||
@handle-change="handleSelectClass"
|
||||
></Sidebar>
|
||||
</div>
|
||||
<div class="list-box">
|
||||
<template v-if="jobList.length">
|
||||
<div class="list hidden_scroll">
|
||||
<div ref="scrollRef">
|
||||
<div v-for="i in jobList" :key="i.id" class="list-item pointer" @click="toReports(i)">
|
||||
<div class="list-item-info">
|
||||
<div class="list-item-title">{{ i.name }}</div>
|
||||
<div class="list-item-name">{{ i.paperName }}</div>
|
||||
<div class="list-item-time">
|
||||
{{
|
||||
i.createTime && `发布时间:${dayjs(i.createTime).format('YYYY/MM/DD HH:mm')}`
|
||||
}}
|
||||
|
||||
{{
|
||||
i.submitEndTime &&
|
||||
`截止时间:${dayjs(i.submitEndTime).format('YYYY/MM/DD HH:mm')}`
|
||||
}}
|
||||
|
||||
{{ i.markTime && `批改时间:${dayjs(i.markTime).format('YYYY/MM/DD HH:mm')}` }}
|
||||
</div>
|
||||
<div
|
||||
v-if="i.status === 3 && isDeadline(i.submitEndTime)"
|
||||
class="list-item-rate"
|
||||
:style="{ color: rateConfig(i.scoreRate).color }"
|
||||
>
|
||||
得分率:{{ i.scoreRate }}%
|
||||
<ProgressBar
|
||||
:rate="i.scoreRate / 100"
|
||||
width="196"
|
||||
v-bind="rateConfig(i.scoreRate)"
|
||||
></ProgressBar>
|
||||
<div class="reward-list">
|
||||
<div v-if="i.addDiamond" class="reward-list-item">
|
||||
<ItemPopover
|
||||
:data="{
|
||||
iconName: 'reward_diamond',
|
||||
rewardName: '钻石',
|
||||
personalCount: userInfo.diamond,
|
||||
}"
|
||||
>
|
||||
<Icon name="diamond_nobg" width="48" @click.stop="false"></Icon>
|
||||
</ItemPopover>
|
||||
<span class="text">+{{ i.addDiamond }}</span>
|
||||
</div>
|
||||
<div v-if="i.addExp" class="reward-list-item">
|
||||
<ItemPopover
|
||||
:data="{
|
||||
iconName: 'reward_exp',
|
||||
rewardName: '经验值',
|
||||
personalCount: userInfo.experience,
|
||||
}"
|
||||
>
|
||||
<Icon name="exp_nobg" width="48" @click.stop="false"></Icon>
|
||||
</ItemPopover>
|
||||
<span class="text">+{{ i.addExp }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="{ 'list-item-status': true, active: i.status === 3 }"
|
||||
:style="{
|
||||
'background-image':
|
||||
i.status === 3
|
||||
? `url(${OSS_URL}/urm/homeWork/corrected.svg)`
|
||||
: `url(${OSS_URL}/urm/homeWork/uncorrected.svg)`,
|
||||
}"
|
||||
>
|
||||
{{ i.status === 3 ? '已批改' : '未批改' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page">
|
||||
<Btn v-if="page.pageNum > 1" width="162" height="80" tilt @click="changePage(0)">
|
||||
上一页
|
||||
</Btn>
|
||||
<div>{{ page.pageNum }}/{{ Math.ceil(page.total / page.pageSize) }}</div>
|
||||
<Btn
|
||||
v-if="page.pageNum * page.pageSize < page.total"
|
||||
type="success"
|
||||
width="162"
|
||||
height="80"
|
||||
tilt
|
||||
@click="changePage(1)"
|
||||
>
|
||||
下一页
|
||||
</Btn>
|
||||
</div>
|
||||
</template>
|
||||
<Empty
|
||||
v-else
|
||||
class="empty"
|
||||
:type="listLoading ? 'loading' : pageError ? 'network' : 'learn'"
|
||||
:content="pageError ? '网络出问题了啦' : '暂无作业记录,快去做作业吧!'"
|
||||
></Empty>
|
||||
</div>
|
||||
</div>
|
||||
<Empty
|
||||
v-else
|
||||
class="empty"
|
||||
:type="pageLoading ? 'loading' : 'learn'"
|
||||
content="暂无作业记录,快去做作业吧!"
|
||||
></Empty>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.uncomplete {
|
||||
@include main_bg;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
.top {
|
||||
padding: 3vh 64px 0;
|
||||
// margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
// &-menu {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// align-items: center;
|
||||
// // flex: 1;
|
||||
// padding-left: 80px;
|
||||
// }
|
||||
}
|
||||
|
||||
.main {
|
||||
// display: flex;
|
||||
height: calc(100% - 130px);
|
||||
padding-left: 64px;
|
||||
padding-right: 64px;
|
||||
padding-top: 2.67vh;
|
||||
position: relative;
|
||||
&-sidebar {
|
||||
position: absolute;
|
||||
left: 64px;
|
||||
top: 2.67vh;
|
||||
height: calc(100% - 66px);
|
||||
width: 272px;
|
||||
}
|
||||
.list-box {
|
||||
height: 100%;
|
||||
margin-left: 310px;
|
||||
overflow: hidden;
|
||||
.page {
|
||||
margin-top: 0.83vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #666;
|
||||
font-family: $font-special;
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh;
|
||||
justify-content: flex-end;
|
||||
:deep(.x-button) {
|
||||
box-shadow: none;
|
||||
margin: 0 40px;
|
||||
&.primary {
|
||||
margin-right: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
height: calc(100% - 90px);
|
||||
overflow-y: auto;
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 1.67vh;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
margin-bottom: 1.33vh;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&-info {
|
||||
width: calc(100% - 230px);
|
||||
position: relative;
|
||||
}
|
||||
&-title {
|
||||
// display: inline-block;
|
||||
padding: 0 8px;
|
||||
height: 6.67vh;
|
||||
line-height: 6.67vh;
|
||||
background: #7effff;
|
||||
margin-bottom: 2vh;
|
||||
width: 500px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
// max-width: 100%;
|
||||
color: $font-color;
|
||||
font-family: $font-special;
|
||||
font-size: 2.5vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
border-top-left-radius: 20px;
|
||||
@include single_ellipsis;
|
||||
}
|
||||
&-name {
|
||||
color: #666;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2.33vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 3.5vh; /* 150% */
|
||||
padding-left: 32px;
|
||||
width: 100%;
|
||||
margin-bottom: 1.33vh;
|
||||
@include single_ellipsis;
|
||||
}
|
||||
&-time {
|
||||
color: #999;
|
||||
font-family: 'OPPO Sans';
|
||||
font-size: 2vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 3vh; /* 150% */
|
||||
padding-left: 32px;
|
||||
padding-bottom: 2vh;
|
||||
}
|
||||
&-status {
|
||||
width: 216px;
|
||||
height: 8vh;
|
||||
// background: url('~@/static/unity/schoolJob/uncorrected.svg') no-repeat;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
color: $font-color;
|
||||
font-family: $font-special;
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 8vh; /* 150% */
|
||||
text-align: center;
|
||||
// &.active {
|
||||
// background: url('~@/static/unity/schoolJob/corrected.svg') no-repeat;
|
||||
// }
|
||||
}
|
||||
&-rate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 516px;
|
||||
top: 1.83vh;
|
||||
font-family: $font-special;
|
||||
font-size: 2.33vh;
|
||||
line-height: 3vh;
|
||||
.progress-bar {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
.reward-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 12px;
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
// color: $font-color;
|
||||
font-family: $font-special;
|
||||
font-size: 2.33vh;
|
||||
.x-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
height: calc(100% - 156px);
|
||||
position: relative;
|
||||
.box {
|
||||
position: absolute;
|
||||
top: 22.08vh;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
width: 320px;
|
||||
height: 26.67vh;
|
||||
}
|
||||
.text {
|
||||
color: #afbfcf;
|
||||
text-align: center;
|
||||
font-family: $font-special;
|
||||
font-size: 2.33vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 3.5vh; /* 150% */
|
||||
}
|
||||
}
|
||||
}
|
||||
.info-time {
|
||||
color: #999;
|
||||
font-family: 'PingFang SC';
|
||||
font-size: 1.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 2.5vh; /* 150% */
|
||||
margin-top: 0.5vh;
|
||||
span {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
506
src/page/system/homework copy/index.vue
Normal file
506
src/page/system/homework copy/index.vue
Normal file
@ -0,0 +1,506 @@
|
||||
<script setup lang="ts">
|
||||
import LoadIcon from '@/page/system/mailbox/components/LoadIcon.vue'
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import { debounce } from '@/hooks'
|
||||
import dayjs from 'dayjs'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { $XXL } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const OSS_URL = import.meta.env.VITE_OSS_URL
|
||||
|
||||
// 作业科目列表
|
||||
const classList = ref<any[]>([])
|
||||
async function getClassList() {
|
||||
const { data: res } = await homeWorkApi.getSubjectList({ statusList: [0] })
|
||||
if (res.code === 200) {
|
||||
classList.value = res.data.map((i: anyObj) => ({ ...i, warning: true }))
|
||||
}
|
||||
}
|
||||
|
||||
// 当前选中的科目
|
||||
const activeClass = ref('')
|
||||
async function handleSelectClass(_item: any) {
|
||||
jobList.value = []
|
||||
await getJobList()
|
||||
handleSelectJob(jobList.value[0])
|
||||
}
|
||||
|
||||
// 作业列表
|
||||
const jobList = ref<any[]>([])
|
||||
const listLoading = ref(false)
|
||||
const page = reactive({
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
total: -1,
|
||||
})
|
||||
async function getJobList() {
|
||||
try {
|
||||
listLoading.value = true
|
||||
const res = await homeWorkApi.getJobList({
|
||||
statusList: [0],
|
||||
...page,
|
||||
subjectId: activeClass.value,
|
||||
})
|
||||
jobList.value.push(...res.rows)
|
||||
page.total = res.total
|
||||
} finally {
|
||||
listLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const bookSidebarRef = ref()
|
||||
async function handleScroll(event: any) {
|
||||
if (jobList.value.length >= page.total) return
|
||||
if (listLoading.value) return
|
||||
if (
|
||||
event.target.scrollTop + event.target.offsetHeight >=
|
||||
bookSidebarRef.value?.offsetHeight - 50
|
||||
) {
|
||||
page.pageNum++
|
||||
try {
|
||||
await getJobList()
|
||||
} catch {
|
||||
page.pageNum--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const debounceScroll = debounce(handleScroll, 200, false)
|
||||
|
||||
// 当前作业
|
||||
const jobData = ref<any>({})
|
||||
|
||||
function handleSelectJob(item: any) {
|
||||
jobData.value = item
|
||||
}
|
||||
|
||||
const pageLoading = ref(false)
|
||||
const pageError = ref(false)
|
||||
|
||||
onActivated(async () => {
|
||||
pageLoading.value = true
|
||||
listLoading.value = true
|
||||
jobList.value = []
|
||||
page.pageNum = 1
|
||||
$XXL.setScreenOn(1)
|
||||
try {
|
||||
await getClassList()
|
||||
if (classList.value.length) {
|
||||
if (classList.value.every(i => i.id !== activeClass.value)) {
|
||||
activeClass.value = classList.value[0]?.id
|
||||
}
|
||||
await getJobList()
|
||||
handleSelectJob(jobList.value[0])
|
||||
}
|
||||
} catch {
|
||||
pageError.value = true
|
||||
} finally {
|
||||
pageLoading.value = false
|
||||
listLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function to(url: string) {
|
||||
router.push(url)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="uncomplete"
|
||||
:style="{
|
||||
'background-image': `url(${OSS_URL}/urm/mail_bg.svg)`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="book"
|
||||
:style="{
|
||||
'background-image': `url(${OSS_URL}/urm/book_bg.svg)`,
|
||||
}"
|
||||
>
|
||||
<template v-if="jobList.length">
|
||||
<div class="book-sidebar-box">
|
||||
<div class="book-sidebar hidden_scroll" @scroll="debounceScroll">
|
||||
<div ref="bookSidebarRef" class="scroll-box">
|
||||
<div
|
||||
v-for="i in jobList"
|
||||
:key="i.id"
|
||||
:class="{
|
||||
'book-sidebar-item': true,
|
||||
pointer: true,
|
||||
active: jobData.id === i.id,
|
||||
}"
|
||||
:style="{
|
||||
'background-image':
|
||||
jobData.id === i.id
|
||||
? `url(${OSS_URL}/urm/mailbox_sidebar_active_bg.svg)`
|
||||
: i.readStatus
|
||||
? `url(${OSS_URL}/urm/mailbox_sidebar_ready_bg.svg)`
|
||||
: `url(${OSS_URL}/urm/mailbox_sidebar_bg.svg)`,
|
||||
}"
|
||||
@click="handleSelectJob(i)"
|
||||
>
|
||||
<!-- <div class="icon"></div> -->
|
||||
<Icon
|
||||
:name="jobData.id === i.id ? 'homeWork_job-item-active' : 'homeWork_job-item'"
|
||||
class="icon"
|
||||
width="84"
|
||||
></Icon>
|
||||
<div class="info">
|
||||
<div class="info-title">{{ i.name }}</div>
|
||||
<div class="info-time">
|
||||
{{ dayjs(i.createTime).format('YYYY/MM/DD HH:mm') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<LoadIcon v-if="listLoading" style="margin-top: 0"></LoadIcon>
|
||||
</div>
|
||||
<div class="book-main">
|
||||
<div class="info flex-end">
|
||||
<div class="info-title">{{ jobData.name }}</div>
|
||||
<div class="info-time">
|
||||
发布日期:
|
||||
{{ dayjs(jobData.createTime).format('YYYY/MM/DD HH:mm') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="info center">
|
||||
<div class="info-name">
|
||||
<span style="color: #999">来自:</span>{{ jobData.teacherName }}
|
||||
</div>
|
||||
<div class="info-time">
|
||||
截止日期:
|
||||
{{ dayjs(jobData.submitEndTime).format('YYYY/MM/DD HH:mm') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">{{ jobData.content }}</div>
|
||||
<div class="tab">
|
||||
<div
|
||||
class="tab-icon"
|
||||
:style="{
|
||||
'background-image': `url(${OSS_URL}/urm/homeWork/tab.svg)`,
|
||||
}"
|
||||
>
|
||||
<Icon class="icon" name="homeWork_paper" width="32"></Icon>
|
||||
组卷
|
||||
</div>
|
||||
</div>
|
||||
<div class="content paper">{{ jobData.paperName }}</div>
|
||||
<div class="btn-box">
|
||||
<mj-button
|
||||
width="386"
|
||||
height="108"
|
||||
size="48"
|
||||
:type="jobData.currentTitleId ? 'success' : 'primary'"
|
||||
@click="to(`/system/homeWork/doWork?id=${jobData.id}&reportFlag=${0}`)"
|
||||
>
|
||||
{{ jobData.currentTitleId ? '继续答题' : '开始答题' }}
|
||||
</mj-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<Empty
|
||||
v-else
|
||||
class="empty"
|
||||
:type="listLoading ? 'loading' : 'task'"
|
||||
content="还没有作业哦~"
|
||||
></Empty>
|
||||
</div>
|
||||
<div class="top">
|
||||
<BackBar leftText="作业" />
|
||||
<div class="top-history">
|
||||
<mj-button type="success" tilt @click="to(`/system/homeWork/statistics`)"
|
||||
>统计分析</mj-button
|
||||
>
|
||||
<mj-button
|
||||
type="success"
|
||||
tilt
|
||||
@click="to(`/system/homeWork/wrong?subjectId=${activeClass}`)"
|
||||
>错题本</mj-button
|
||||
>
|
||||
<!-- <Icon
|
||||
name="homeWork_temporary"
|
||||
width="120"
|
||||
class="pointer"
|
||||
@click="to(`/system/homeWork/temporary`)"
|
||||
></Icon> -->
|
||||
<Icon
|
||||
name="homeWork_hisJob"
|
||||
width="120"
|
||||
class="pointer"
|
||||
@click="to(`/system/homeWork/history`)"
|
||||
></Icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="classList.length" class="main">
|
||||
<div class="main-sidebar">
|
||||
<Sidebar
|
||||
v-model="activeClass"
|
||||
:config="classList"
|
||||
:prop="{ name: 'subject' }"
|
||||
@handle-change="handleSelectClass"
|
||||
></Sidebar>
|
||||
</div>
|
||||
</div>
|
||||
<Empty
|
||||
v-else
|
||||
class="empty"
|
||||
:type="pageLoading ? 'loading' : 'task'"
|
||||
content="还没有作业哦~"
|
||||
></Empty>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.uncomplete {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
|
||||
.book {
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
width: 133.5vh;
|
||||
height: 94vh;
|
||||
left: 300px;
|
||||
top: 6.67vh;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top left;
|
||||
background-size: 100% 100%;
|
||||
padding: 10vh 11.67vh 6.33vh 9vh;
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
&-sidebar {
|
||||
width: 36.67vh;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
&-item {
|
||||
padding: 0.83vh;
|
||||
display: flex;
|
||||
height: 9.67vh;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 1.33vh;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0.33vh 0.17vh 0 rgba(0, 0, 0, 0.15);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 26.17vh center;
|
||||
background-size: 11.17vh, 13vh;
|
||||
border: 0.5vh solid transparent;
|
||||
|
||||
&.unready {
|
||||
background-color: #dcdff5;
|
||||
}
|
||||
&.active {
|
||||
background-color: #fff;
|
||||
border-color: rgb(255, 240, 31);
|
||||
// background-position:
|
||||
// center,
|
||||
// 314px center;
|
||||
// background-size: 100%, 134px, 156px;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.icon {
|
||||
width: 7vh;
|
||||
height: 7vh;
|
||||
flex-shrink: 0;
|
||||
background: #1c2a3a;
|
||||
margin-right: 1.33vh;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.info {
|
||||
width: calc(100% - 8.33vh);
|
||||
&-title {
|
||||
color: $font-color;
|
||||
font-family: $font-text;
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
@include single_ellipsis;
|
||||
}
|
||||
|
||||
&-time {
|
||||
font-size: 2vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-main {
|
||||
margin-left: 5.83vh;
|
||||
padding-top: 1.33vh;
|
||||
width: calc(100% - 3.33vh);
|
||||
.info {
|
||||
padding: 1.33vh 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&.flex-end {
|
||||
align-items: end;
|
||||
border-bottom: 0.17vh solid rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
&.center {
|
||||
align-items: center;
|
||||
border-bottom: 0.08vh solid rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
&-title {
|
||||
color: $font-color;
|
||||
font-family: $font-special;
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
width: 41.67vh;
|
||||
margin-right: 1vh;
|
||||
@include single_ellipsis;
|
||||
}
|
||||
&-name {
|
||||
color: #999;
|
||||
font-family: 'PingFang SC';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.2;
|
||||
// width: calc(100% - 400px);
|
||||
width: 41.67vh;
|
||||
margin-right: 1vh;
|
||||
@include single_ellipsis;
|
||||
}
|
||||
&-time {
|
||||
font-size: 2vh;
|
||||
|
||||
line-height: 3vh; /* 150% */
|
||||
@include single_ellipsis;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
color: #333;
|
||||
font-family: $font-text;
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4vh; /* 150% */
|
||||
margin: 2.67vh 0;
|
||||
text-indent: 2em;
|
||||
height: 20vh;
|
||||
// @include multiple_ellipsis(5);
|
||||
@include hidden_scroll;
|
||||
&.paper {
|
||||
height: 8vh;
|
||||
@include multiple_ellipsis(2);
|
||||
}
|
||||
}
|
||||
.tab {
|
||||
// padding-top: 32px;
|
||||
&-icon {
|
||||
box-sizing: border-box;
|
||||
width: 19vh;
|
||||
height: 4.33vh;
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
padding: 0.17vh 0 0.17vh 4.67vh;
|
||||
color: $font-color;
|
||||
font-family: 'PingFang SC';
|
||||
font-size: 2.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 4vh; /* 150% */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.icon {
|
||||
// width: 32px;
|
||||
// height: 32px;
|
||||
margin-right: 1.33vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-box {
|
||||
box-sizing: border-box;
|
||||
padding-top: 1.33vh;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.x-button {
|
||||
font-family: $font-text;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
padding-top: 1.5vh;
|
||||
width: 32.17vh;
|
||||
height: 9vh;
|
||||
flex-shrink: 0;
|
||||
border-radius: 5vh;
|
||||
background-size: 100%;
|
||||
box-shadow: 0 0.5vh 0.17vh 0 rgba(0, 0, 0, 0.25);
|
||||
text-align: center;
|
||||
color: $font-color;
|
||||
text-align: center;
|
||||
font-family: $font-text;
|
||||
font-size: 4vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 6vh; /* 150% */
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
padding: 3vh 5.33vh 0;
|
||||
}
|
||||
.top-history {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 1.67vh;
|
||||
right: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.mj-button {
|
||||
margin: 0 16px 0.67vh;
|
||||
font-family: $font-text;
|
||||
}
|
||||
.x-icon {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
height: calc(100% - 13vh);
|
||||
padding-left: 64px;
|
||||
padding-right: 20px;
|
||||
position: relative;
|
||||
&-sidebar {
|
||||
position: absolute;
|
||||
left: 64px;
|
||||
top: 1vh;
|
||||
height: calc(100% - 2.5vh);
|
||||
width: 22.92vh;
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
height: calc(100% - 25vh);
|
||||
}
|
||||
}
|
||||
.info-time {
|
||||
color: #999;
|
||||
font-family: 'PingFang SC';
|
||||
font-size: 1.67vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 2.5vh; /* 150% */
|
||||
margin-top: 0.5vh;
|
||||
span {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
261
src/page/system/homework copy/reports/components/Analyse.vue
Normal file
261
src/page/system/homework copy/reports/components/Analyse.vue
Normal file
@ -0,0 +1,261 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import { rateType } from '@/hooks'
|
||||
import { colorConfig } from './hooks'
|
||||
import EchartsPie from './EchartsPie.vue'
|
||||
import EchartsBar from './EchartsBar.vue'
|
||||
import { dialog } from '@/components/mj/mj-dialog'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
params: anyObj
|
||||
jobAnalyse: anyObj
|
||||
}>(),
|
||||
{
|
||||
jobAnalyse: () => ({}),
|
||||
pageLoading: false,
|
||||
},
|
||||
)
|
||||
|
||||
const difficultyColor = [
|
||||
{ name: '容易', color: '#8FCC00' },
|
||||
{ name: '较易', color: '#48A3FE' },
|
||||
{ name: '普通', color: '#FFD04D' },
|
||||
{ name: '较难', color: '#FF8200' },
|
||||
{ name: '困难', color: '#FF5151' },
|
||||
]
|
||||
|
||||
const report = computed(() => props.jobAnalyse.jobReportVo)
|
||||
const analyze = computed(() =>
|
||||
props.jobAnalyse.paperChanelAnalyzeList.map((i: anyObj) => {
|
||||
return {
|
||||
value: i.titleCount,
|
||||
name: i.chanelTypeName,
|
||||
}
|
||||
}),
|
||||
)
|
||||
const difficultyPie = computed(() =>
|
||||
props.jobAnalyse.paperDifficultyAnalyzeList.map((i: anyObj) => {
|
||||
return {
|
||||
value: i.titleCount,
|
||||
name: i.difficultyName,
|
||||
itemStyle: {
|
||||
color: difficultyColor.find(item => item.name === i.difficultyName)?.color || '#8FCC00',
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const difficultyBar = computed(() => {
|
||||
const xData: string[] = []
|
||||
const seriesVal: anyObj[] = []
|
||||
props.jobAnalyse.paperDifficultyAnalyzeList.forEach((i: anyObj) => {
|
||||
xData.push(`${i?.difficultyName}\n(${i?.score}分)`)
|
||||
seriesVal.push({
|
||||
value: i.score,
|
||||
itemStyle: {
|
||||
color: difficultyColor.find(item => item.name === i.difficultyName)?.color || '#8FCC00',
|
||||
},
|
||||
})
|
||||
})
|
||||
return {
|
||||
xData,
|
||||
seriesVal,
|
||||
}
|
||||
})
|
||||
|
||||
function showWorkPoints() {
|
||||
dialog.show({
|
||||
title: '作业内容',
|
||||
content: report.value?.jobContent,
|
||||
hideCancel: true,
|
||||
hideClose: true,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="analyse">
|
||||
<div v-if="jobAnalyse.jobReportVo" class="analyse-main">
|
||||
<div class="analyse-info analyse-item">
|
||||
<div class="analyse-item-title">作业信息</div>
|
||||
<div class="analyse-info-main">
|
||||
<p>
|
||||
<span>作业名称:</span>
|
||||
<span class="job-title">{{ report?.jobName }}</span>
|
||||
<Icon name="report_detail" class="pointer" width="42" @click="showWorkPoints"></Icon>
|
||||
</p>
|
||||
<p>
|
||||
<span>发布日期:</span>
|
||||
{{ dayjs(report?.releaseTime).format('YYYY/MM/DD HH:mm') }}
|
||||
</p>
|
||||
<p>
|
||||
<span>截止日期:</span>
|
||||
{{ dayjs(report?.submitEndTime).format('YYYY/MM/DD HH:mm') }}
|
||||
</p>
|
||||
<p>
|
||||
<span>总 分:</span>
|
||||
{{ report?.totalScore }}分
|
||||
</p>
|
||||
<p>
|
||||
<span>作答时长:</span>
|
||||
{{ report?.costTimeInfo }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="analyse-structure analyse-item">
|
||||
<div class="analyse-item-title">试卷结构</div>
|
||||
<div class="echarts-pie">
|
||||
<div class="echarts-pie-item">
|
||||
<EchartsPie :data="analyze" title="题型分布"></EchartsPie>
|
||||
</div>
|
||||
<div class="echarts-pie-item">
|
||||
<EchartsPie :data="difficultyPie" title="难度分布"></EchartsPie>
|
||||
</div>
|
||||
</div>
|
||||
<div class="echarts-bar">
|
||||
<EchartsBar :data="difficultyBar" title="试卷难易度显示"></EchartsBar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="analyse-rank analyse-item">
|
||||
<el-table
|
||||
v-if="jobAnalyse.stuRankVoList?.length"
|
||||
:data="jobAnalyse.stuRankVoList"
|
||||
:row-class-name="({ row }) => (row.selfFlag ? 'self' : '')"
|
||||
>
|
||||
<el-table-column label="序号" align="center" minWidth="30%">
|
||||
<template #default="{ row }">
|
||||
{{ row.rankNum ? row.rankNum : '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stuName" label="姓名" align="center" minWidth="30%" />
|
||||
<el-table-column label="得分" align="center" minWidth="40%">
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
:style="{
|
||||
color: colorConfig[rateType((Number(row.scoreRate) || 0) * 100)],
|
||||
}"
|
||||
>{{ row.markScore }}</span
|
||||
>/{{ row.scoreRate }}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <Empty v-else class="empty" :type="pageLoading ? 'loading' : 'network'"></Empty> -->
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.analyse {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 1.5;
|
||||
&-main {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'a b'
|
||||
'c b';
|
||||
grid-template-columns: 563px 895px;
|
||||
grid-template-rows: 430px 585px;
|
||||
gap: 24px;
|
||||
}
|
||||
&-item {
|
||||
border-radius: 3.33vh;
|
||||
border: 0.67vh solid #fff;
|
||||
background: linear-gradient(179deg, #fff 0.69%, #dfebf7 99.53%);
|
||||
box-shadow: 0 0.33vh 0.67vh 0 rgba(0, 0, 0, 0.25);
|
||||
|
||||
&-title {
|
||||
color: $font-color;
|
||||
font-family: $font-special;
|
||||
font-size: 3.33vh;
|
||||
text-align: center;
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
}
|
||||
&-info {
|
||||
grid-area: a;
|
||||
padding: 1.33vh;
|
||||
&-main {
|
||||
color: $font-color;
|
||||
font-family: OPPOSans;
|
||||
font-size: 2.33vh;
|
||||
p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1.67vh;
|
||||
line-height: 3.5vh;
|
||||
.job-title {
|
||||
width: calc(100% - 185px);
|
||||
@include single_ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-structure {
|
||||
grid-area: b;
|
||||
padding: 1.33vh 44px;
|
||||
.echarts-pie {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 35vh;
|
||||
margin-bottom: 4.17vh;
|
||||
&-item {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
.echarts-bar {
|
||||
height: 37.25vh;
|
||||
}
|
||||
}
|
||||
&-rank {
|
||||
grid-area: c;
|
||||
overflow-y: hidden;
|
||||
:deep(.el-table) {
|
||||
// border-radius: 3.33vh;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
.cell {
|
||||
line-height: 1.5;
|
||||
color: $font-color;
|
||||
font-size: 2.33vh;
|
||||
padding: 0 4px;
|
||||
}
|
||||
th.el-table__cell {
|
||||
background: #ccdeff;
|
||||
font-family: $font-special;
|
||||
padding: 1.5vh 0;
|
||||
}
|
||||
.el-table__cell {
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 0.17vh;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#deedfc,
|
||||
#deedfc 8px,
|
||||
transparent 8px,
|
||||
transparent 16px /* 线条间隔 */
|
||||
);
|
||||
}
|
||||
}
|
||||
tr {
|
||||
background: transparent;
|
||||
td {
|
||||
font-family: OPPOSans;
|
||||
padding: 1.5vh 0;
|
||||
}
|
||||
}
|
||||
.el-table__row.self {
|
||||
background: #fff7e0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
116
src/page/system/homework copy/reports/components/Detail.vue
Normal file
116
src/page/system/homework copy/reports/components/Detail.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps<{
|
||||
params: anyObj
|
||||
isDeadline: boolean
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const list = ref<anyObj>([])
|
||||
async function getData() {
|
||||
const { data } = await homeWorkApi.getJobTittle({ ...props.params, reportFlag: 1 })
|
||||
list.value = data.tjobStuVoList
|
||||
}
|
||||
|
||||
function toWorkDetail(idx: string) {
|
||||
const query: anyObj = {
|
||||
reportFlag: 1,
|
||||
idx,
|
||||
...props.params,
|
||||
}
|
||||
|
||||
router.push({
|
||||
path: `/system/homeWork/doWork`,
|
||||
query,
|
||||
})
|
||||
}
|
||||
|
||||
const pageLoading = ref(false)
|
||||
onMounted(async () => {
|
||||
pageLoading.value = true
|
||||
try {
|
||||
await getData()
|
||||
} catch {}
|
||||
pageLoading.value = false
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="detail">
|
||||
<div v-if="list.length" class="detail-main">
|
||||
<div class="detail-main-title">答题情况</div>
|
||||
<div class="detail-main-list">
|
||||
<div
|
||||
v-for="(i, idx) in list"
|
||||
:key="i.jobId"
|
||||
:class="{
|
||||
'detail-main-list-item': true,
|
||||
pointer: true,
|
||||
success: isDeadline && i.titleStatus === 3 && i.markFlag,
|
||||
error: isDeadline && i.titleStatus === 3 && !i.markFlag,
|
||||
}"
|
||||
@click="toWorkDetail(idx)"
|
||||
>
|
||||
{{ idx + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Empty v-else class="empty" :type="pageLoading ? 'loading' : 'network'"></Empty>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.detail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 1.5;
|
||||
|
||||
&-main {
|
||||
border-radius: 3.33vh;
|
||||
border: 0.67vh solid #fff;
|
||||
background: linear-gradient(179deg, #fdfdf9 0.69%, #dfebf7 99.53%);
|
||||
box-shadow: 0 0.33vh 0.67vh 0 rgba(0, 0, 0, 0.25);
|
||||
height: 100%;
|
||||
&-title {
|
||||
color: $font-color;
|
||||
font-family: $font-special;
|
||||
font-size: 4vh;
|
||||
margin: 3.33vh 0;
|
||||
text-align: center;
|
||||
}
|
||||
&-list {
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 160px);
|
||||
grid-template-rows: 160px;
|
||||
grid-auto-rows: 160px;
|
||||
gap: 40px;
|
||||
justify-content: center;
|
||||
height: calc(100% - 160px);
|
||||
&-item {
|
||||
border-radius: 1vh;
|
||||
border: 0.33vh solid #9fa5aa;
|
||||
background: #e4e9ec;
|
||||
text-align: center;
|
||||
line-height: 13.33vh;
|
||||
color: #666;
|
||||
font-family: $font-special;
|
||||
font-size: 4vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
&.success {
|
||||
color: #8fcc00;
|
||||
background-color: #ddff8c;
|
||||
border-color: #8fcc00;
|
||||
}
|
||||
&.error {
|
||||
color: #f00a0a;
|
||||
background-color: #febebf;
|
||||
border-color: #f00a0a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
129
src/page/system/homework copy/reports/components/EchartsBar.vue
Normal file
129
src/page/system/homework copy/reports/components/EchartsBar.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import { $font_color } from '@/styles/variate'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: {
|
||||
xData: string[]
|
||||
seriesVal: anyObj[]
|
||||
}
|
||||
title?: string
|
||||
yName?: string
|
||||
}>(),
|
||||
{},
|
||||
)
|
||||
|
||||
const emit = defineEmits(['handleBarClick'])
|
||||
|
||||
const widthRate = window.screen.width / 1920
|
||||
|
||||
const chartContainer = ref<HTMLDivElement | null>(null)
|
||||
|
||||
let myChart = reactive<anyObj>({})
|
||||
|
||||
const option = reactive<anyObj>({
|
||||
title: {
|
||||
text: '',
|
||||
textStyle: {
|
||||
color: $font_color,
|
||||
fontSize: widthRate * 28,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
top: 0,
|
||||
left: 'center',
|
||||
},
|
||||
xAxis: {
|
||||
data: [],
|
||||
axisLabel: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: widthRate * 22, // 修改字体大小
|
||||
color: $font_color,
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#8A94FD70',
|
||||
width: widthRate * 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
// name: '(人)',
|
||||
nameLocation: 'end',
|
||||
// nameGap: widthRate * -35,
|
||||
nameTextStyle: {
|
||||
color: $font_color,
|
||||
fontSize: widthRate * 22,
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: widthRate * 22, // 修改字体大小
|
||||
color: $font_color,
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: 'rgba(138, 148, 253, 0.25)',
|
||||
width: widthRate * 4,
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: [5, 10],
|
||||
color: 'rgba(138, 148, 253, 0.25)',
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: {
|
||||
data: [],
|
||||
type: 'bar',
|
||||
barMaxWidth: widthRate * 80,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
fontSize: widthRate * 20,
|
||||
color: $font_color,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
async function drawGraph() {
|
||||
await nextTick()
|
||||
if (myChart) {
|
||||
myChart.dispose?.()
|
||||
myChart = echarts.init(chartContainer.value)
|
||||
}
|
||||
|
||||
option.xAxis.data = props.data?.xData
|
||||
option.series.data = props.data?.seriesVal
|
||||
option.title.text = props.title
|
||||
option.yAxis.name = props.yName || ''
|
||||
myChart.setOption(option)
|
||||
myChart.off('click').on('click', async (params: anyObj) => {
|
||||
emit('handleBarClick', params)
|
||||
})
|
||||
myChart.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
drawGraph()
|
||||
})
|
||||
defineExpose({ drawGraph })
|
||||
</script>
|
||||
<template>
|
||||
<div ref="chartContainer" class="echarts-bar"></div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.echarts-bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
101
src/page/system/homework copy/reports/components/EchartsPie.vue
Normal file
101
src/page/system/homework copy/reports/components/EchartsPie.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<script setup lang="ts">
|
||||
import { $font_color } from '@/styles/variate'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: anyObj[]
|
||||
title: string
|
||||
}>(),
|
||||
{},
|
||||
)
|
||||
|
||||
const widthRate = window.screen.width / 1920
|
||||
|
||||
const chartContainer = ref<HTMLDivElement | null>(null)
|
||||
|
||||
let myChart = reactive<anyObj>({})
|
||||
|
||||
const option = reactive<anyObj>({
|
||||
title: {
|
||||
text: '',
|
||||
textStyle: {
|
||||
color: $font_color,
|
||||
fontSize: widthRate * 28,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
top: 0,
|
||||
left: 'center',
|
||||
},
|
||||
legend: {
|
||||
bottom: 0,
|
||||
left: 'center',
|
||||
icon: 'circle',
|
||||
itemWidth: widthRate * 8,
|
||||
textStyle: {
|
||||
fontSize: widthRate * 20,
|
||||
color: '#666',
|
||||
},
|
||||
data: [],
|
||||
},
|
||||
|
||||
series: {
|
||||
type: 'pie',
|
||||
radius: ['30%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
legendHoverLink: false,
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
label: {
|
||||
position: 'outer',
|
||||
alignTo: 'labelLine',
|
||||
formatter: (params: anyObj) => Math.round(params.percent) + '%',
|
||||
color: 'inherit',
|
||||
fontSize: widthRate * 22,
|
||||
distanceToLabelLine: widthRate * -50,
|
||||
},
|
||||
labelLine: {
|
||||
length: widthRate * 22,
|
||||
length2: widthRate * 75,
|
||||
maxSurfaceAngle: 80,
|
||||
},
|
||||
labelLayout: {
|
||||
verticalAlign: 'bottom',
|
||||
},
|
||||
data: [],
|
||||
// width: '85%',
|
||||
// height: '85%',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
async function drawGraph() {
|
||||
await nextTick()
|
||||
if (myChart) {
|
||||
myChart.dispose?.() // 清除上一次已经画过得图,这样才能实时才到最新数据的图
|
||||
myChart = echarts.init(chartContainer.value)
|
||||
}
|
||||
option.series.data = props.data
|
||||
option.legend.data = props.data
|
||||
option.title.text = props.title
|
||||
myChart.setOption(option)
|
||||
myChart.resize()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
drawGraph()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div ref="chartContainer" class="echarts-pie"></div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.echarts-pie {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
180
src/page/system/homework copy/reports/components/Knowledge.vue
Normal file
180
src/page/system/homework copy/reports/components/Knowledge.vue
Normal file
@ -0,0 +1,180 @@
|
||||
<script setup lang="ts">
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import { colorConfig } from './hooks'
|
||||
import { rateType } from '@/hooks'
|
||||
|
||||
const props = defineProps<{
|
||||
params: anyObj
|
||||
}>()
|
||||
|
||||
// const width_rate = window.screen.width / 1920;
|
||||
|
||||
const data = ref<anyObj[]>([])
|
||||
async function getData() {
|
||||
const { data: res } = await homeWorkApi.getKnowledgeAnalyse(props.params)
|
||||
data.value = res.data
|
||||
currentType.value = res.data[0].type
|
||||
}
|
||||
const typeConfig = ['薄弱', '合格', '良好', '优秀']
|
||||
const dataList = computed(() =>
|
||||
data.value.map(({ type }) => ({
|
||||
value: type,
|
||||
label: typeConfig[type],
|
||||
})),
|
||||
)
|
||||
const tableData = computed(
|
||||
() => data.value.find(i => i.type === currentType.value)?.knowledgeAnalyseVos,
|
||||
)
|
||||
const currentType = ref(0)
|
||||
|
||||
const pageLoading = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
pageLoading.value = true
|
||||
|
||||
try {
|
||||
await getData()
|
||||
} catch {}
|
||||
pageLoading.value = false
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="knowledge">
|
||||
<div v-if="data.length" class="knowledge-main">
|
||||
<div class="knowledge-main-select">
|
||||
<SelectTab v-model="currentType" :dataList="dataList"></SelectTab>
|
||||
</div>
|
||||
<div class="knowledge-main-table">
|
||||
<el-table :data="tableData">
|
||||
<el-table-column label="知识点名称" prop="name" align="center" minWidth="53%">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="满分"
|
||||
prop="fullScore"
|
||||
align="center"
|
||||
minWidth="23.5%"
|
||||
></el-table-column>
|
||||
<el-table-column label="得分" align="center" minWidth="23.5%">
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
:style="{
|
||||
color: colorConfig[rateType((Number(row.scoreRate.replace('%', '')) || 0) * 100)],
|
||||
}"
|
||||
>{{ row?.score }}/{{ row?.scoreRate }}</span
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<Empty
|
||||
v-else
|
||||
class="empty"
|
||||
:type="pageLoading ? 'loading' : 'knowledge'"
|
||||
content="暂无知识点数据~"
|
||||
></Empty>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.knowledge {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&-main {
|
||||
border-radius: 3.33vh;
|
||||
border: 0.67vh solid #fff;
|
||||
background: linear-gradient(179deg, #fdfdf9 0.69%, #dfebf7 99.53%);
|
||||
box-shadow: 0 0.33vh 0.67vh 0 rgba(0, 0, 0, 0.25);
|
||||
padding: 4.33vh 48px;
|
||||
height: 100%;
|
||||
&-select {
|
||||
text-align: center;
|
||||
}
|
||||
:deep(.select-list) {
|
||||
display: inline-flex;
|
||||
margin-bottom: 5vh;
|
||||
.data-item {
|
||||
width: 298px;
|
||||
}
|
||||
}
|
||||
&-table {
|
||||
height: calc(100% - 100px);
|
||||
:deep(.el-table) {
|
||||
box-sizing: border-box;
|
||||
border: 0.33vh #659cfe solid;
|
||||
border-radius: 3.33vh;
|
||||
background: transparent;
|
||||
max-height: 100%;
|
||||
@include hidden_scroll;
|
||||
|
||||
.cell {
|
||||
line-height: 1.5;
|
||||
color: $font-color;
|
||||
font-size: 2.33vh;
|
||||
padding: 0 4px;
|
||||
@include single_ellipsis;
|
||||
}
|
||||
th.el-table__cell {
|
||||
background: #ccdeff;
|
||||
font-family: $font-special;
|
||||
padding: 1.5vh 0;
|
||||
}
|
||||
.el-table__cell {
|
||||
height: 7.5vh;
|
||||
color: $font-color;
|
||||
font-family: OPPOSans;
|
||||
font-size: 2.33vh;
|
||||
position: relative;
|
||||
border: none;
|
||||
.cell {
|
||||
line-height: 1.5;
|
||||
}
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 0.17vh;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#deedfc,
|
||||
#deedfc 8px,
|
||||
transparent 8px,
|
||||
transparent 16px /* 线条间隔 */
|
||||
);
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
#deedfc,
|
||||
#deedfc 8px,
|
||||
transparent 8px,
|
||||
transparent 16px /* 线条间隔 */
|
||||
);
|
||||
}
|
||||
&:last-child {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
tr {
|
||||
background: transparent;
|
||||
td {
|
||||
font-family: OPPOSans;
|
||||
padding: 1.5vh 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,6 @@
|
||||
export const colorConfig = {
|
||||
primary: '#2D39BC',
|
||||
success: '#2F8A5B',
|
||||
error: '#B22731',
|
||||
warning: '#CC8A33',
|
||||
}
|
||||
117
src/page/system/homework copy/reports/index.vue
Normal file
117
src/page/system/homework copy/reports/index.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import Analyse from './components/Analyse.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import Knowledge from './components/Knowledge.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const params = {
|
||||
id: route.params.id,
|
||||
stuId: route.query?.stuId || '',
|
||||
}
|
||||
|
||||
// 侧边栏
|
||||
const activeType = ref('1')
|
||||
const sidebarConfig = ref<anyObj[]>([])
|
||||
const isDeadline = ref(false)
|
||||
|
||||
const jobAnalyse = ref<anyObj>({})
|
||||
async function getJobAnalyse() {
|
||||
const { data: res } = await homeWorkApi.getJobAnalyse(params)
|
||||
jobAnalyse.value = res.data
|
||||
isDeadline.value = dayjs().isAfter(jobAnalyse.value.jobReportVo.submitEndTime)
|
||||
const detailCmp = { id: '1', name: '答题详情', c: markRaw(Detail) }
|
||||
if (isDeadline.value) {
|
||||
sidebarConfig.value = [
|
||||
{ id: '0', name: '整体分析', c: markRaw(Analyse) },
|
||||
detailCmp,
|
||||
{ id: '2', name: '知识点分析', c: markRaw(Knowledge) },
|
||||
]
|
||||
activeType.value = '0'
|
||||
} else {
|
||||
sidebarConfig.value = [detailCmp]
|
||||
activeType.value = '1'
|
||||
}
|
||||
}
|
||||
|
||||
const COMPONENT = computed(() => {
|
||||
return sidebarConfig.value.find(i => i.id === activeType.value)?.c
|
||||
})
|
||||
|
||||
const pageLoading = ref(false)
|
||||
onMounted(async () => {
|
||||
pageLoading.value = true
|
||||
try {
|
||||
await getJobAnalyse()
|
||||
} catch {}
|
||||
pageLoading.value = false
|
||||
})
|
||||
|
||||
defineOptions({
|
||||
name: 'HomeWorkReports',
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="reports">
|
||||
<div class="top">
|
||||
<BackBar :leftText="(route.query?.name as string) || ' '" />
|
||||
</div>
|
||||
<div v-if="!pageLoading" class="main">
|
||||
<div class="main-sidebar">
|
||||
<Sidebar v-model="activeType" :config="sidebarConfig"></Sidebar>
|
||||
</div>
|
||||
<div class="main-box">
|
||||
<KeepAlive>
|
||||
<component
|
||||
:is="COMPONENT"
|
||||
:params="params"
|
||||
:jobAnalyse="jobAnalyse"
|
||||
:isDeadline="isDeadline"
|
||||
></component>
|
||||
</KeepAlive>
|
||||
</div>
|
||||
</div>
|
||||
<Empty v-else class="empty" :type="pageLoading ? 'loading' : 'network'"></Empty>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.reports {
|
||||
@include main_bg;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
.top {
|
||||
padding: 3vh 64px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: calc(100% - 130px);
|
||||
padding-left: 64px;
|
||||
padding-right: 64px;
|
||||
position: relative;
|
||||
&-sidebar {
|
||||
position: absolute;
|
||||
left: 64px;
|
||||
top: 3.67vh;
|
||||
height: calc(100% - 66px);
|
||||
width: 272px;
|
||||
}
|
||||
&-box {
|
||||
width: 1482px;
|
||||
height: 86.67vh;
|
||||
margin-left: 310px;
|
||||
overflow: hidden;
|
||||
:deep(.empty) {
|
||||
height: calc(100% - 150px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
463
src/page/system/homework copy/statistics/SingleAnalysis.vue
Normal file
463
src/page/system/homework copy/statistics/SingleAnalysis.vue
Normal file
@ -0,0 +1,463 @@
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts'
|
||||
import dayjs from '@/plugins/dayjs'
|
||||
import Card from '@/page/subject/components/Card.vue'
|
||||
import { $font_color } from '@/styles/variate'
|
||||
|
||||
const props = defineProps<{
|
||||
data: {
|
||||
level?: number
|
||||
stability?: number
|
||||
jobScoringAbilityDataVoList?: {
|
||||
level: number
|
||||
scoreRate: number
|
||||
submitDate: string
|
||||
}[]
|
||||
}
|
||||
table?: anyObj[]
|
||||
tableLoading: boolean
|
||||
type: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:type'])
|
||||
/*
|
||||
level 级别:0-优秀 1-良 2-中 3-弱
|
||||
stability 稳定性 0-稳定 1-相对波动
|
||||
jobScoringAbilityDataVoList {
|
||||
level 级别:0-优秀 1-良 2-中 3-弱
|
||||
scoreRate 得分率
|
||||
submitDate 日期
|
||||
}
|
||||
*/
|
||||
const levelConfig: anyObj = {
|
||||
0: { label: '优秀' },
|
||||
1: { label: '良好' },
|
||||
2: { label: '合格' },
|
||||
3: { label: '薄弱' },
|
||||
}
|
||||
|
||||
const stabilityConfig: anyObj = {
|
||||
0: { label: '稳定' },
|
||||
1: { label: '相对波动' },
|
||||
}
|
||||
|
||||
const configColor = {
|
||||
success: '#2F8A5B',
|
||||
primary: '#2D39BC',
|
||||
warning: '#A13C00',
|
||||
error: '#B22731',
|
||||
}
|
||||
// 正确率的颜色
|
||||
function getColor(rate: number) {
|
||||
return rate === 100 ? 'success' : rate >= 80 ? 'primary' : rate >= 60 ? 'warning' : 'error'
|
||||
}
|
||||
|
||||
// 最近得分情况 0-零分 1-失分 2-满分
|
||||
const recentScoreType = [
|
||||
{ label: '零分', color: $font_color },
|
||||
{ label: '失分', color: '#B22731' },
|
||||
{ label: '满分', color: '#2F8A5B' },
|
||||
]
|
||||
const lossFrequency = computed(() => {
|
||||
return (item: anyObj) => {
|
||||
if (item) {
|
||||
const { lostPointsNum, jobNum } = item
|
||||
if (lostPointsNum / jobNum >= 1) {
|
||||
return '每次'
|
||||
} else if (lostPointsNum / jobNum >= 0.5) {
|
||||
return '高频'
|
||||
} else if (lostPointsNum / jobNum < 0.5) {
|
||||
return '低频'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// type 类型 0-薄弱 1-合格 2-良好 3-优秀
|
||||
const typeConfig = [
|
||||
{
|
||||
label: '薄弱',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '合格',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '良好',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '优秀',
|
||||
value: 3,
|
||||
},
|
||||
]
|
||||
|
||||
const widthRate = window.screen.width / 1920
|
||||
|
||||
const echartRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
let myChart = reactive<anyObj>({})
|
||||
|
||||
const option = reactive<anyObj>({
|
||||
legend: {
|
||||
show: true,
|
||||
data: ['我的得分能力'],
|
||||
textStyle: {
|
||||
color: '#666',
|
||||
fontSize: widthRate * 24,
|
||||
fontFamily: 'OPPOSans',
|
||||
padding: [0, 8],
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
data: [],
|
||||
boundaryGap: false,
|
||||
axisLabel: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: widthRate * 22, // 修改字体大小
|
||||
color: $font_color,
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#31BCFF',
|
||||
width: widthRate * 4,
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
interval: 0,
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
color: '#C3ECFF',
|
||||
width: widthRate * 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
|
||||
minInterval: 1,
|
||||
nameLocation: 'end',
|
||||
nameTextStyle: {
|
||||
color: $font_color,
|
||||
fontSize: widthRate * 22,
|
||||
},
|
||||
axisLabel: {
|
||||
show: true,
|
||||
textStyle: {
|
||||
fontSize: widthRate * 22, // 修改字体大小
|
||||
color: $font_color,
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
lineStyle: {
|
||||
color: '#C3ECFF',
|
||||
width: widthRate * 4,
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
color: '#C3ECFF',
|
||||
width: widthRate * 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [],
|
||||
name: '我的得分能力',
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: '#00AEFF',
|
||||
width: widthRate * 4,
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: widthRate * 12,
|
||||
itemStyle: {
|
||||
color: '#fff',
|
||||
borderColor: '#00AEFF',
|
||||
borderWidth: widthRate * 4,
|
||||
},
|
||||
markLine: {
|
||||
symbol: ['none', 'triangle'],
|
||||
data: [
|
||||
{
|
||||
yAxis: 85,
|
||||
lineStyle: {
|
||||
type: [10, 15],
|
||||
color: '#F3B0B4',
|
||||
width: widthRate * 5,
|
||||
},
|
||||
label: {
|
||||
formatter: '优秀',
|
||||
color: '#F3B0B4',
|
||||
fontSize: widthRate * 30,
|
||||
padding: [0, widthRate * 16],
|
||||
},
|
||||
},
|
||||
{
|
||||
yAxis: 70,
|
||||
lineStyle: {
|
||||
type: [10, 15],
|
||||
color: '#F3B0B4',
|
||||
width: widthRate * 5,
|
||||
},
|
||||
label: {
|
||||
formatter: '良好',
|
||||
color: '#F3B0B4',
|
||||
fontSize: widthRate * 30,
|
||||
padding: [0, widthRate * 16],
|
||||
},
|
||||
},
|
||||
{
|
||||
yAxis: 60,
|
||||
lineStyle: {
|
||||
type: [10, 15],
|
||||
color: '#F3B0B4',
|
||||
width: widthRate * 5,
|
||||
},
|
||||
label: {
|
||||
formatter: '合格',
|
||||
color: '#F3B0B4',
|
||||
fontSize: widthRate * 30,
|
||||
padding: [0, widthRate * 16],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
animation: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
function drawGraph() {
|
||||
if (myChart) {
|
||||
myChart.dispose?.() // 清除上一次已经画过得图,这样才能实时才到最新数据的图
|
||||
myChart = echarts.init(echartRef.value)
|
||||
}
|
||||
option.xAxis.data =
|
||||
props.data.jobScoringAbilityDataVoList?.map(i => dayjs(i.submitDate).format('MM/DD')) || []
|
||||
option.series[0].data = props.data.jobScoringAbilityDataVoList?.map(i => i.scoreRate / 100) || []
|
||||
myChart.setOption(option)
|
||||
myChart.resize()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
drawGraph()
|
||||
})
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<div class="single-analysis hidden_scroll">
|
||||
<div class="score-ability">
|
||||
<div class="title" data-content="得分能力">得分能力</div>
|
||||
<template v-if="data.jobScoringAbilityDataVoList?.length">
|
||||
<div ref="echartRef" class="chartContainer"></div>
|
||||
<div>
|
||||
<div class="cards">
|
||||
<Card
|
||||
width="404"
|
||||
height="160"
|
||||
title="得分能力综合评估"
|
||||
:text="levelConfig[data.level || 0].label"
|
||||
></Card>
|
||||
<Card
|
||||
type="warning"
|
||||
width="404"
|
||||
height="160"
|
||||
title="得分能力稳定性"
|
||||
:text="stabilityConfig[data.stability || 0].label"
|
||||
></Card>
|
||||
</div>
|
||||
<p class="tips">
|
||||
注:得分能力是通过均分、得分率等数据综合计算得出,反映学生对该科目知识掌握程度和答题能力
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<Empty v-else type="learn" style="height: 600px"></Empty>
|
||||
</div>
|
||||
|
||||
<div class="knowledge-table">
|
||||
<div class="title" data-content="知识点分析">知识点分析</div>
|
||||
<div style="text-align: center">
|
||||
<SelectTab
|
||||
:modelValue="type"
|
||||
:dataList="typeConfig"
|
||||
@update:modelValue="emit('update:type', $event)"
|
||||
></SelectTab>
|
||||
</div>
|
||||
<el-table v-if="table?.length" :data="table">
|
||||
<el-table-column label="知识点名称" prop="knowledgeName" align="center" minWidth="42.8%">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="失分频率"
|
||||
prop="lostFrequency"
|
||||
sortable
|
||||
align="center"
|
||||
minWidth="22%"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ lossFrequency(row) }} ({{ row.lostPointsNum }}/{{ row.jobNum }})
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="得分率" prop="scoreRate" sortable align="center" minWidth="17.6%">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: configColor[getColor(row.scoreRate / 100)] }">
|
||||
{{ row.scoreRate / 100 }}%
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最近情况" prop="recentScoreType" align="center" minWidth="17.6%">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: recentScoreType[row.recentScoreType].color }">
|
||||
{{ recentScoreType[row.recentScoreType].label }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<Empty v-else :type="tableLoading ? 'loading' : 'learn'" style="height: 600px"></Empty>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.single-analysis {
|
||||
line-height: 1.5;
|
||||
height: calc(100%);
|
||||
.title {
|
||||
color: #fff;
|
||||
font-family: $font-special;
|
||||
font-size: 3vh;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
@include font-stroke($font-color, 4px);
|
||||
}
|
||||
.score-ability {
|
||||
.chartContainer {
|
||||
width: 100%;
|
||||
margin-top: 4vh;
|
||||
height: 60.83vh;
|
||||
}
|
||||
.cards {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.xxl-card {
|
||||
margin: 0 16px;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
width: 1208px;
|
||||
height: 9.67vh;
|
||||
border-radius: 1.67vh;
|
||||
background: #fff;
|
||||
box-shadow: 0 0.33vh 0.17vh 0 rgba(0, 0, 0, 0.15);
|
||||
color: #ff8d3b;
|
||||
font-family: OPPOSans;
|
||||
font-size: 2.67vh;
|
||||
padding: 1vh 32px 0;
|
||||
margin: 5vh auto 6vh;
|
||||
}
|
||||
}
|
||||
.knowledge-table {
|
||||
min-height: calc(100% + 210px);
|
||||
:deep(.select-list) {
|
||||
display: inline-flex;
|
||||
margin: 4vh auto;
|
||||
.data-item {
|
||||
width: 298px;
|
||||
&.active {
|
||||
background: linear-gradient(269deg, #86e6ff 2.25%, #9effda 51.93%, #5fdcfd 99.65%);
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.el-table) {
|
||||
box-sizing: border-box;
|
||||
border: 0.33vh #31bcff solid;
|
||||
border-radius: 3.33vh;
|
||||
background: transparent;
|
||||
|
||||
.cell {
|
||||
line-height: 1.5;
|
||||
color: $font-color;
|
||||
font-size: 2.33vh;
|
||||
padding: 0 4px;
|
||||
// @include single_ellipsis;
|
||||
}
|
||||
th.el-table__cell {
|
||||
background: #c2ebff;
|
||||
font-family: $font-special;
|
||||
padding: 1.5vh 0;
|
||||
}
|
||||
.el-table__cell {
|
||||
height: 7.5vh;
|
||||
color: $font-color;
|
||||
font-family: OPPOSans;
|
||||
font-size: 2.33vh;
|
||||
position: relative;
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
.cell {
|
||||
line-height: 1.5;
|
||||
}
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 0.17vh;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#deedfc,
|
||||
#deedfc 8px,
|
||||
transparent 8px,
|
||||
transparent 16px /* 线条间隔 */
|
||||
);
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
#deedfc,
|
||||
#deedfc 8px,
|
||||
transparent 8px,
|
||||
transparent 16px /* 线条间隔 */
|
||||
);
|
||||
}
|
||||
&:last-child {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
tr {
|
||||
background: transparent;
|
||||
td {
|
||||
font-family: OPPOSans;
|
||||
padding: 1.5vh 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
148
src/page/system/homework copy/statistics/Whole.vue
Normal file
148
src/page/system/homework copy/statistics/Whole.vue
Normal file
@ -0,0 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
wholeAnalysis: anyObj
|
||||
}>()
|
||||
// 整体
|
||||
const wholeConfig: anyObj = {
|
||||
0: {
|
||||
label: '优秀',
|
||||
icon: 'analysisStar_0',
|
||||
},
|
||||
1: {
|
||||
label: '良好',
|
||||
icon: 'analysisStar_1',
|
||||
},
|
||||
2: {
|
||||
label: '合格',
|
||||
icon: 'analysisStar_2',
|
||||
},
|
||||
3: {
|
||||
label: '薄弱',
|
||||
icon: 'analysisStar_3',
|
||||
},
|
||||
}
|
||||
// -1-不展示 0-退步 1-稳定 2-进步
|
||||
|
||||
const statusConfig: anyObj = {
|
||||
0: {
|
||||
label: '退步',
|
||||
bg: '#FFD9D9',
|
||||
color: '#F00A0A',
|
||||
},
|
||||
1: {
|
||||
label: '稳定',
|
||||
bg: '#E6E7FF',
|
||||
color: '#7A7FF5',
|
||||
},
|
||||
2: {
|
||||
label: '进步',
|
||||
bg: '#F4FFDA',
|
||||
color: '#8FCC00',
|
||||
},
|
||||
}
|
||||
|
||||
// // 过滤掉-1
|
||||
// const analysisComputed = computed(() =>
|
||||
// props.wholeAnalysis.map((i: anyObj) => {
|
||||
// return {
|
||||
// level: i?.level || 0,
|
||||
// jobStatisticalDataVoList:
|
||||
// i?.jobStatisticalDataVoList || [],
|
||||
// };
|
||||
// }),
|
||||
// );
|
||||
</script>
|
||||
<template>
|
||||
<div class="whole hidden_scroll">
|
||||
<div v-for="i in wholeAnalysis" :key="i.level" class="whole-item">
|
||||
<Icon :name="wholeConfig[i.level || 0].icon" size="120"> </Icon>
|
||||
<div class="whole-item-right">
|
||||
<template v-if="i.jobStatisticalDataVoList?.length">
|
||||
<div
|
||||
v-for="item in i.jobStatisticalDataVoList"
|
||||
:key="item.subjectId"
|
||||
class="whole-item-right-item"
|
||||
>
|
||||
<div class="name">{{ item.subjectName }}</div>
|
||||
<div
|
||||
v-if="statusConfig[item.status]"
|
||||
class="status"
|
||||
:style="{
|
||||
color: statusConfig[item.status].color,
|
||||
background: statusConfig[item.status].bg,
|
||||
border: `3px solid ${statusConfig[item.status].color}`,
|
||||
}"
|
||||
>
|
||||
{{ statusConfig[item.status].label }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="empty">暂无</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.whole {
|
||||
height: calc(100% - 48px);
|
||||
margin-top: 4vh;
|
||||
line-height: 1.5;
|
||||
&-item {
|
||||
display: flex;
|
||||
margin: 2vh 0;
|
||||
.x-icon {
|
||||
margin-right: 40px;
|
||||
}
|
||||
&-right {
|
||||
flex: 1;
|
||||
border-radius: 1.67vh;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
min-height: 10.67vh;
|
||||
font-family: $font-special;
|
||||
font-size: 3.33vh;
|
||||
padding: 2vh;
|
||||
display: grid;
|
||||
// grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(auto-fit, minmax(546px, 1fr));
|
||||
grid-template-rows: 80px;
|
||||
grid-auto-rows: 80px;
|
||||
gap: 24px;
|
||||
justify-content: center;
|
||||
&-item {
|
||||
// width: 546px;
|
||||
display: flex;
|
||||
padding: 0.83vh 32px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 1.67vh;
|
||||
background: #fff;
|
||||
box-shadow: 0 0.33vh 0.17vh 0 rgba(0, 0, 0, 0.15);
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
color: $font-color;
|
||||
}
|
||||
.status {
|
||||
width: 100px;
|
||||
height: 3.83vh;
|
||||
border-radius: 3.33vh;
|
||||
border: 0.25vh solid black;
|
||||
font-family: OPPOSans;
|
||||
font-size: 2.17vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #fff;
|
||||
text-shadow: 0 0.17vh 0.17vh rgba(0, 0, 0, 0.45);
|
||||
// margin: 10px auto;
|
||||
text-align: center;
|
||||
line-height: 6.67vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
171
src/page/system/homework copy/statistics/index.vue
Normal file
171
src/page/system/homework copy/statistics/index.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<script setup lang="ts">
|
||||
import { userStore } from '@/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import Whole from './Whole.vue'
|
||||
import SingleAnalysis from './SingleAnalysis.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const OSS_URL = import.meta.env.VITE_OSS_URL
|
||||
|
||||
const route = useRoute()
|
||||
let paramsStuId = route.query?.stuId || ''
|
||||
const leftText = computed(() => route.query?.name || ' ')
|
||||
|
||||
const { userInfo } = storeToRefs(userStore())
|
||||
|
||||
const wholeAnalysis = ref<anyObj[]>([])
|
||||
async function getAnalysis() {
|
||||
const { data: res } = await homeWorkApi.getStatisticalAnalysis({
|
||||
stuId: paramsStuId || userInfo.value.smartUserIdentifier,
|
||||
})
|
||||
|
||||
if (res.code === 200) {
|
||||
wholeAnalysis.value = res.data
|
||||
}
|
||||
}
|
||||
// const { subjectList, getSubjectAllList } = subjectStore();
|
||||
const activeClass = ref('')
|
||||
const classList = ref([{ id: '', name: '整体' }])
|
||||
async function getClassList() {
|
||||
const { data: res } = await homeWorkApi.getAnalyseSubjectList({
|
||||
stuId: paramsStuId || userInfo.value.smartUserIdentifier,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
classList.value.push(...res.data)
|
||||
}
|
||||
}
|
||||
|
||||
const singleAnalysis = ref<any>({})
|
||||
async function getAnalysisSubject() {
|
||||
const { data: res } = await homeWorkApi.getStatisticalSubject({
|
||||
sourceType: 0,
|
||||
stuId: paramsStuId || userInfo.value.smartUserIdentifier,
|
||||
subjectId: activeClass.value,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
singleAnalysis.value = res.data
|
||||
}
|
||||
}
|
||||
|
||||
const type = ref(0)
|
||||
const knowledgeAnalysis = ref<{ loading: boolean; table: anyObj[] }[]>([])
|
||||
async function getKnowledgeSubject() {
|
||||
if (knowledgeAnalysis.value[type.value]?.loading) return
|
||||
if (Array.isArray(knowledgeAnalysis.value[type.value]?.table)) return
|
||||
const curType = type.value
|
||||
knowledgeAnalysis.value[curType] = {
|
||||
loading: true,
|
||||
table: [],
|
||||
}
|
||||
try {
|
||||
const { data: res } = await homeWorkApi.getKnowledgeSubject({
|
||||
sourceType: 0,
|
||||
stuId: paramsStuId || userInfo.value.smartUserIdentifier,
|
||||
subjectId: activeClass.value,
|
||||
size: 300,
|
||||
// orderByColumn: 'lostFrequency',
|
||||
// isAsc: 'asc',
|
||||
type: type.value,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
knowledgeAnalysis.value[curType].table = res.rows || []
|
||||
}
|
||||
} catch {}
|
||||
knowledgeAnalysis.value[curType].loading = false
|
||||
}
|
||||
watch(type, () => getKnowledgeSubject())
|
||||
watch(activeClass, () => {
|
||||
knowledgeAnalysis.value = []
|
||||
getKnowledgeSubject()
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
async function handleSelectClass(item: anyObj) {
|
||||
if (item.id === '') {
|
||||
getAnalysis()
|
||||
} else {
|
||||
loading.value = true
|
||||
try {
|
||||
await getAnalysisSubject()
|
||||
} catch {}
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onActivated(async () => {
|
||||
paramsStuId = route.query?.stuId || ''
|
||||
classList.value = [{ id: '', name: '整体' }]
|
||||
activeClass.value = ''
|
||||
getClassList()
|
||||
getAnalysis()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="homework_statistics">
|
||||
<div class="top">
|
||||
<BackBar :leftText="leftText" />
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="main_sidebar">
|
||||
<Sidebar
|
||||
v-model="activeClass"
|
||||
:config="classList"
|
||||
@handle-change="handleSelectClass"
|
||||
></Sidebar>
|
||||
</div>
|
||||
<div
|
||||
class="main_book"
|
||||
:style="{ 'background-image': `url(${OSS_URL}/urm/homeWork/wrong_bg.svg)` }"
|
||||
>
|
||||
<template v-if="!loading">
|
||||
<Whole v-if="activeClass === ''" :wholeAnalysis="wholeAnalysis"></Whole>
|
||||
<SingleAnalysis
|
||||
v-else
|
||||
v-model:type="type"
|
||||
:data="singleAnalysis"
|
||||
:table="knowledgeAnalysis[type]?.table"
|
||||
:tableLoading="knowledgeAnalysis[type]?.loading"
|
||||
></SingleAnalysis>
|
||||
</template>
|
||||
<Empty v-else type="loading" style="height: 900px"></Empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.homework_statistics {
|
||||
padding: 2.67vh 64px;
|
||||
@include main_bg;
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5;
|
||||
.top {
|
||||
position: absolute;
|
||||
// top: 32;
|
||||
// left: 64;
|
||||
}
|
||||
.main {
|
||||
// margin-top: 66px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 2.83vh;
|
||||
display: flex;
|
||||
&_sidebar {
|
||||
padding-top: 5.5vh;
|
||||
width: 275px;
|
||||
margin-right: 50px;
|
||||
}
|
||||
&_book {
|
||||
width: 1470px;
|
||||
height: 90.25vh;
|
||||
flex-shrink: 0;
|
||||
background-size: 100%;
|
||||
padding: 8.67vh 64px 3.17vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
278
src/page/system/homework copy/temporary/index.vue
Normal file
278
src/page/system/homework copy/temporary/index.vue
Normal file
@ -0,0 +1,278 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { userStore } from '@/stores'
|
||||
import { XMessage, debounce } from '@/hooks'
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import LoadIcon from '@/page/system/mailbox/components/LoadIcon.vue'
|
||||
import sass from '@/utils/sass'
|
||||
|
||||
const router = useRouter()
|
||||
const { token } = userStore()
|
||||
const OSS_URL = import.meta.env.VITE_OSS_URL
|
||||
const itemBg = `url(${OSS_URL}/icon/homeWork_temporary_item_bg.svg)`
|
||||
|
||||
const subjectList: any = ref([])
|
||||
|
||||
const page = reactive({
|
||||
pageSize: 11,
|
||||
pageNum: 1,
|
||||
total: 0,
|
||||
})
|
||||
const jobList: any = ref([])
|
||||
const uploadLoading = ref(false)
|
||||
const listLoading = ref(false) // 列表加载
|
||||
const moreLoading = ref(false) // 加载更多
|
||||
const uploadMsg = ref({
|
||||
submitUrl: '',
|
||||
subjectId: '',
|
||||
clientType: 0,
|
||||
})
|
||||
function toLook(item: any) {
|
||||
router.push(`/system/homeWork/temporaryHomeWork/${item.id}`)
|
||||
}
|
||||
function handleSelectClass() {
|
||||
initList()
|
||||
}
|
||||
|
||||
function handleBeforeUpload(rawFile: any) {
|
||||
const fileType = ['image/jpeg', 'image/png']
|
||||
if (!fileType.includes(rawFile.type)) {
|
||||
XMessage.error('上传格式不正确,只支持JPG/PNG')
|
||||
return false
|
||||
}
|
||||
if (rawFile.size / 1024 / 1024 > 10) {
|
||||
XMessage.error('图片过大')
|
||||
return false
|
||||
}
|
||||
uploadLoading.value = true
|
||||
return true
|
||||
}
|
||||
async function handleUploadSuccess(res: any, _file: any) {
|
||||
uploadMsg.value.submitUrl = res.data.filePath
|
||||
uploadLoading.value = false
|
||||
try {
|
||||
const { data } = await homeWorkApi.uploadTemporaryJob(uploadMsg.value)
|
||||
if (data.code === 200) {
|
||||
initList()
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
function handleUploadError() {
|
||||
XMessage.error('上传失败,请重新上传~')
|
||||
uploadLoading.value = false
|
||||
}
|
||||
const debounceScroll = debounce(handleScroll, 200, false)
|
||||
const scrollBoxRef = ref()
|
||||
async function handleScroll(event: any) {
|
||||
if (jobList.value.length >= page.total) return
|
||||
if (moreLoading.value) return
|
||||
if (event.target.scrollTop + event.target.offsetHeight >= scrollBoxRef.value?.offsetHeight - 50) {
|
||||
page.pageNum++
|
||||
try {
|
||||
moreLoading.value = true
|
||||
await initTemporaryJobList()
|
||||
} catch {
|
||||
page.pageNum--
|
||||
}
|
||||
moreLoading.value = false
|
||||
}
|
||||
}
|
||||
async function initList() {
|
||||
if (listLoading.value) return
|
||||
listLoading.value = true
|
||||
jobList.value = []
|
||||
page.pageNum = 1
|
||||
try {
|
||||
await initTemporaryJobList()
|
||||
} catch {
|
||||
console.log('网络出问题了啦~')
|
||||
}
|
||||
listLoading.value = false
|
||||
}
|
||||
// 获取学科对应临时作业
|
||||
async function initTemporaryJobList() {
|
||||
try {
|
||||
const { data } = await homeWorkApi.temporaryJobList({
|
||||
clientType: 0,
|
||||
subjectId: uploadMsg.value.subjectId,
|
||||
...page,
|
||||
})
|
||||
page.total = data.total
|
||||
for (let i in data.rows) {
|
||||
jobList.value.push(data.rows[i])
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
// 获取学科
|
||||
async function getSubjectList() {
|
||||
try {
|
||||
const { data } = await homeWorkApi.getTemporarySubjectList()
|
||||
subjectList.value = data.rows
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getSubjectList()
|
||||
uploadMsg.value.subjectId = subjectList.value[0].id
|
||||
initList()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="temporary">
|
||||
<div class="top">
|
||||
<BackBar leftText="临时作业" />
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="main_sidebar">
|
||||
<Sidebar
|
||||
v-model="uploadMsg.subjectId"
|
||||
:config="subjectList"
|
||||
@handle-change="handleSelectClass"
|
||||
></Sidebar>
|
||||
</div>
|
||||
<div class="main_answer">
|
||||
<Empty v-if="listLoading" class="empty" type="loading"></Empty>
|
||||
<div v-else class="main_answer_list hidden_scroll" @scroll="debounceScroll">
|
||||
<div ref="scrollBoxRef" class="content">
|
||||
<div>
|
||||
<el-upload
|
||||
class="pointer"
|
||||
action="/api/main/sysFileInfo/tenUploadAll"
|
||||
:data="{ type: 1 }"
|
||||
:headers="{
|
||||
Authorization: token,
|
||||
'X-Tenant-Id': sass.platform.tenantId,
|
||||
}"
|
||||
accept="image/*"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:show-file-list="false"
|
||||
:disabled="uploadLoading"
|
||||
>
|
||||
<Icon name="add_book" size="80" />
|
||||
<span class="text">{{ uploadLoading ? '上传中' : '上传图片' }}</span>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div v-for="(i, index) in jobList" :key="index" class="item pointer" @click="toLook(i)">
|
||||
<img :src="i.submitUrl" alt="" class="sub_answer" />
|
||||
<span class="sub_date">{{ dayjs(i.submitTime).format('YYYY/MM/DD HH:mm') }}</span>
|
||||
<div v-if="i.status === 1" class="result">已批改</div>
|
||||
</div>
|
||||
</div>
|
||||
<LoadIcon v-if="moreLoading" class="load-icon"></LoadIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.temporary {
|
||||
@include main_bg;
|
||||
padding: 2.67vh 64px;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
.top {
|
||||
position: absolute;
|
||||
}
|
||||
.main {
|
||||
display: flex;
|
||||
padding-top: 8.67vh;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&_sidebar {
|
||||
padding-top: 2vh;
|
||||
width: 214px;
|
||||
margin-right: 98px;
|
||||
}
|
||||
&_answer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 2.5vh;
|
||||
border-radius: 3.33vh;
|
||||
border: 0.67vh solid #fff;
|
||||
background: linear-gradient(179.32deg, #fdfdf9 0.69%, #dfebf7 99.53%);
|
||||
box-sizing: border-box;
|
||||
|
||||
&_list {
|
||||
height: 100%;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
:deep(.el-upload) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24px;
|
||||
width: 328px;
|
||||
height: 33.5vh;
|
||||
border-radius: 1.67vh;
|
||||
background: #f2f3ff;
|
||||
box-shadow: 0 0.33vh 0.33vh 0 #00000026;
|
||||
.text {
|
||||
font-family: $font-special;
|
||||
margin-top: 2vh;
|
||||
display: block;
|
||||
font-size: 2.33vh;
|
||||
line-height: 3.5vh;
|
||||
color: #80b5fe;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
padding: 2vh 24px 2.67vh 24px;
|
||||
width: 332px;
|
||||
height: 33.83vh;
|
||||
margin: 0 24px 2vh 0;
|
||||
border-radius: 1.67vh;
|
||||
background-image: v-bind(itemBg);
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
|
||||
.sub_answer {
|
||||
width: 284px;
|
||||
height: 23.67vh;
|
||||
border-radius: 0.83vh;
|
||||
}
|
||||
.sub_date {
|
||||
display: inline-block;
|
||||
margin-top: 2vh;
|
||||
font-size: 2.33vh;
|
||||
line-height: 3.5vh;
|
||||
color: #666;
|
||||
}
|
||||
&:nth-child(4n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
.result {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 0.67vh 14px;
|
||||
color: #2f8a5b;
|
||||
font-family: $font-special;
|
||||
font-size: 2.33vh;
|
||||
line-height: 3.5vh;
|
||||
text-align: center;
|
||||
background-color: #a2fb6e;
|
||||
border-radius: 0 1.67vh 0 1.67vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
314
src/page/system/homework copy/temporary/singleDetail.vue
Normal file
314
src/page/system/homework copy/temporary/singleDetail.vue
Normal file
@ -0,0 +1,314 @@
|
||||
<script setup lang="ts">
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const OSS_URL = import.meta.env.VITE_OSS_URL
|
||||
const activeBg = `url(${OSS_URL}/urm/homeWork/homeWork_temporary_active_bg.svg)`
|
||||
const nonactiveBg = `url(${OSS_URL}/urm/homeWork/homeWork_temporary_nonactive_bg.svg)`
|
||||
const activeNav = ref({
|
||||
name: '原图',
|
||||
type: 0,
|
||||
url: '',
|
||||
})
|
||||
const nav = ref([
|
||||
{
|
||||
name: '原图',
|
||||
type: 0,
|
||||
url: '',
|
||||
},
|
||||
])
|
||||
const loading = ref(false)
|
||||
const dragging = ref(false) // 是否处于拖拽状态
|
||||
const imgArea = ref()
|
||||
const img = ref()
|
||||
const movableData = ref({
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
})
|
||||
const scaleData = ref(1) // 另外存一份放大倍率
|
||||
const distance = ref({
|
||||
startDistance: 0,
|
||||
endDistance: 0,
|
||||
})
|
||||
function clickAnswerStatus(i: any) {
|
||||
activeNav.value = i
|
||||
movableData.value = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
}
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
onImageLoad()
|
||||
}, 150)
|
||||
}
|
||||
// 图片加载
|
||||
function onImageLoad() {
|
||||
loading.value = false
|
||||
}
|
||||
// 放大
|
||||
function tapEnlarge() {
|
||||
if (movableData.value.scale < 2) {
|
||||
movableData.value.scale += 0.1
|
||||
}
|
||||
}
|
||||
// 缩小
|
||||
function tapLessen() {
|
||||
if (movableData.value.scale > 0.6) {
|
||||
movableData.value.scale -= 0.1
|
||||
}
|
||||
}
|
||||
|
||||
function touchTwo(e: anyObj[], mode: 'startDistance' | 'endDistance') {
|
||||
// 双指缩放
|
||||
const one = {
|
||||
x: e[0].pageX, // 第一根手指的横坐标
|
||||
y: e[0].pageY, // 第一根手指的纵坐标
|
||||
} // 第一根手指的横坐标
|
||||
const two = {
|
||||
x: e[1].pageX, // 第二根手指的横坐标
|
||||
y: e[1].pageY, // 第二根手指的纵坐标
|
||||
}
|
||||
|
||||
distance.value[mode] = getDistance(one, two)
|
||||
}
|
||||
|
||||
function getDistance(start: anyObj, stop: anyObj) {
|
||||
return Math.sqrt(
|
||||
Math.pow(Math.abs(start.x - stop.x), 2) + Math.pow(Math.abs(start.y - stop.y), 2),
|
||||
)
|
||||
}
|
||||
|
||||
const startDrag = (event: anyObj) => {
|
||||
if (event.touches?.length === 2) {
|
||||
// 两个触点
|
||||
touchTwo(event.touches, 'startDistance')
|
||||
return
|
||||
}
|
||||
dragging.value = true
|
||||
const x = (event.touches ? event.touches[0].pageX : event.pageX) - movableData.value.x
|
||||
const y = (event.touches ? event.touches[0].pageY : event.pageY) - movableData.value.y
|
||||
|
||||
const move = (event: anyObj) => {
|
||||
if (event.touches?.length === 2) {
|
||||
// 两个触点
|
||||
touchTwo(event.touches, 'endDistance')
|
||||
const scale2 =
|
||||
scaleData.value + distance.value.endDistance / distance.value.startDistance - 1.01 // 减去原本的1倍 0.01是防止小数计算误差
|
||||
movableData.value.scale = scale2 > 2 ? 2 : scale2 < 0.5 ? 0.5 : scale2 // 最大2倍 最小0.5倍
|
||||
return
|
||||
}
|
||||
if (dragging.value) {
|
||||
movableData.value.x = (event.touches ? event.touches[0].pageX : event.pageX) - x
|
||||
movableData.value.y = (event.touches ? event.touches[0].pageY : event.pageY) - y
|
||||
}
|
||||
}
|
||||
const stopDrag = () => {
|
||||
distance.value.startDistance = distance.value.endDistance = 0
|
||||
scaleData.value = movableData.value.scale
|
||||
dragging.value = false
|
||||
document.removeEventListener('mousemove', move)
|
||||
document.removeEventListener('touchmove', move)
|
||||
document.removeEventListener('mouseup', stopDrag)
|
||||
document.removeEventListener('touchend', stopDrag)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', move)
|
||||
document.addEventListener('touchmove', move)
|
||||
document.addEventListener('mouseup', stopDrag)
|
||||
document.addEventListener('touchend', stopDrag)
|
||||
}
|
||||
// 获取详情
|
||||
async function getTemporaryJobDetail() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await homeWorkApi.temporaryJobDetail(route.params.id)
|
||||
if (data.data.status === 1) {
|
||||
nav.value = [
|
||||
{
|
||||
name: '原图',
|
||||
type: 0,
|
||||
url: data.data.submitUrl,
|
||||
},
|
||||
{ name: '批改图', type: 1, url: data.data.markUrl },
|
||||
]
|
||||
} else {
|
||||
nav.value = [
|
||||
{
|
||||
name: '原图',
|
||||
type: 0,
|
||||
url: data.data.submitUrl,
|
||||
},
|
||||
]
|
||||
}
|
||||
activeNav.value = nav.value[0]
|
||||
loading.value = false
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('mousemove', () => {})
|
||||
document.removeEventListener('mouseup', () => {})
|
||||
document.removeEventListener('touchmove', () => {})
|
||||
document.removeEventListener('touchend', () => {})
|
||||
})
|
||||
onActivated(() => {
|
||||
getTemporaryJobDetail()
|
||||
movableData.value = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="sinle_temporary">
|
||||
<div class="top">
|
||||
<BackBar />
|
||||
</div>
|
||||
<div class="main">
|
||||
<div
|
||||
class="main_answer"
|
||||
:style="{
|
||||
'background-image': `url(${OSS_URL}/urm/homeWork/homeWork_temporary_single_bg.svg)`,
|
||||
}"
|
||||
>
|
||||
<div class="nav">
|
||||
<div
|
||||
v-for="i in nav"
|
||||
:key="i.type"
|
||||
class="nav_item pointer"
|
||||
:style="{
|
||||
'background-image': i.type === activeNav.type ? activeBg : nonactiveBg,
|
||||
color: i.type === activeNav.type ? '' : '#666',
|
||||
}"
|
||||
@click="clickAnswerStatus(i)"
|
||||
>
|
||||
{{ i.name }}
|
||||
</div>
|
||||
</div>
|
||||
<Empty v-if="loading" class="empty" type="loading" content=""></Empty>
|
||||
<div v-else class="area">
|
||||
<div
|
||||
ref="imgArea"
|
||||
:style="{
|
||||
transform: `translate(${movableData.x}px, ${movableData.y}px) scale(${movableData.scale})`,
|
||||
}"
|
||||
class="area-content"
|
||||
@mousedown.prevent="startDrag"
|
||||
@touchstart.prevent="startDrag"
|
||||
>
|
||||
<img ref="img" :src="activeNav.url" alt="" @load="onImageLoad" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<Icon
|
||||
name="homeWork_temporary_fangda"
|
||||
width="48"
|
||||
class="pointer tool_fangda"
|
||||
@click="tapEnlarge"
|
||||
></Icon>
|
||||
<Icon
|
||||
name="homeWork_temporary_suoxiao"
|
||||
width="48"
|
||||
class="pointer"
|
||||
@click="tapLessen"
|
||||
></Icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.sinle_temporary {
|
||||
@include main_bg;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
.top {
|
||||
padding: 3vh 64px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.main {
|
||||
padding: 0 64px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
&_answer {
|
||||
position: relative;
|
||||
width: 1792px;
|
||||
height: 85.25vh;
|
||||
background-size: 100%;
|
||||
|
||||
.nav {
|
||||
position: absolute;
|
||||
top: -4.75vh;
|
||||
width: calc(100% - 394px);
|
||||
margin-left: 197px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-family: $font-special;
|
||||
font-size: 3.33vh;
|
||||
font-weight: 400;
|
||||
line-height: 5vh;
|
||||
text-align: center;
|
||||
|
||||
&_item {
|
||||
width: 300px;
|
||||
height: 7.67vh;
|
||||
line-height: 7.67vh;
|
||||
border-radius: 3.33vh 3.33vh 0 0;
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
&:first-child {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.area {
|
||||
position: absolute;
|
||||
top: 9.58vh;
|
||||
left: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: calc(100% - 96px);
|
||||
height: 71.92vh;
|
||||
overflow: hidden;
|
||||
|
||||
.tool {
|
||||
position: absolute;
|
||||
bottom: 2vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 188px;
|
||||
height: 5.33vh;
|
||||
border-radius: 4.5vh;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
&_fangda {
|
||||
margin-right: 32px;
|
||||
}
|
||||
}
|
||||
.area-content {
|
||||
width: 825px;
|
||||
height: 71.92vh;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
cursor: grab;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
557
src/page/system/homework copy/wrong/index.vue
Normal file
557
src/page/system/homework copy/wrong/index.vue
Normal file
@ -0,0 +1,557 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import RoundSplit from '@/page/subject/freeStudy/views/textbookDetail/RoundSplit.vue'
|
||||
import LoadIcon from '@/page/system/mailbox/components/LoadIcon.vue'
|
||||
import VideoFullBox from '@/page/subject/components/VideoFullBox/index.vue'
|
||||
import homeWorkApi from '@/api/system/homeWork'
|
||||
import { debounce, useAudio } from '@/hooks'
|
||||
import { subjectStore } from '@/stores'
|
||||
import { resource_type_analogy, resource_type_wrong_book } from '@/page/subject/doWork'
|
||||
// import { userStore } from '@/stores';
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
// const { userInfo } = userStore();
|
||||
|
||||
const subjectId = ref(2)
|
||||
const { getSubjectAllList } = subjectStore()
|
||||
const { subjectList } = toRefs(subjectStore())
|
||||
const subjectComputed = computed(() =>
|
||||
subjectList.value.map(({ id, name }: anyObj) => ({ value: id, label: name })),
|
||||
)
|
||||
|
||||
const subjectiveFlag = ref<0 | 1>(1)
|
||||
const subjectiveConfig = reactive([
|
||||
{ value: 1, label: '客观题' },
|
||||
{ value: 0, label: '主观题' },
|
||||
])
|
||||
|
||||
const correctFlag = ref<0 | 1>(0)
|
||||
const correctConfig = reactive([
|
||||
{ value: 0, label: '未订正' },
|
||||
{ value: 1, label: '已订正' },
|
||||
])
|
||||
const unCorrectNum = ref(0)
|
||||
async function getCorrectConfig() {
|
||||
const { data: res } = await homeWorkApi.getWrongNum({
|
||||
subjectId: subjectId.value,
|
||||
subjectiveFlag: subjectiveFlag.value,
|
||||
})
|
||||
|
||||
unCorrectNum.value = res.data.unCorrectNum
|
||||
correctConfig[0].label = `未订正 ${res.data.unCorrectNum > 999 ? '999+' : res.data.unCorrectNum}题`
|
||||
correctConfig[1].label = `已订正 ${res.data.correctNum > 999 ? '999+' : res.data.correctNum}题`
|
||||
}
|
||||
|
||||
// 列表
|
||||
const list = ref<anyObj[]>([])
|
||||
const page = reactive({
|
||||
size: 10,
|
||||
current: 1,
|
||||
total: 0,
|
||||
})
|
||||
const loading = ref(false)
|
||||
|
||||
async function getList() {
|
||||
const { data: res } = await homeWorkApi.getWrongList({
|
||||
subjectId: subjectId.value,
|
||||
subjectiveFlag: subjectiveFlag.value,
|
||||
correctFlag: correctFlag.value,
|
||||
...page,
|
||||
})
|
||||
|
||||
if (res.code !== 200) return
|
||||
list.value.push(...(res.rows || []))
|
||||
page.total = res.total
|
||||
}
|
||||
|
||||
const scrollBoxRef = ref()
|
||||
async function handleScroll(event: any) {
|
||||
if (list.value.length >= page.total) return
|
||||
if (loading.value) return
|
||||
if (event.target.scrollTop + event.target.offsetHeight >= scrollBoxRef.value?.offsetHeight - 50) {
|
||||
page.current++
|
||||
try {
|
||||
loading.value = true
|
||||
await getList()
|
||||
} catch {
|
||||
page.current--
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const debounceScroll = debounce(handleScroll, 200, false)
|
||||
|
||||
const pageLoading = ref(false)
|
||||
const emptyText = ref('暂无错题')
|
||||
|
||||
// 初始化列表
|
||||
async function initList(pageSize = 0) {
|
||||
if (pageLoading.value) return
|
||||
pageLoading.value = true
|
||||
list.value = []
|
||||
page.current = 1
|
||||
pageSize && (page.size = pageSize)
|
||||
|
||||
try {
|
||||
await getList()
|
||||
emptyText.value = correctFlag.value ? '暂无已订正错题' : '暂无未订正错题'
|
||||
} catch {
|
||||
emptyText.value = '网络出问题了啦~'
|
||||
}
|
||||
pageLoading.value = false
|
||||
}
|
||||
|
||||
async function init(size = 0) {
|
||||
getCorrectConfig()
|
||||
await initList(size)
|
||||
}
|
||||
|
||||
/*
|
||||
** 音频
|
||||
*/
|
||||
// 播放器
|
||||
let player: anyObj = {}
|
||||
const activeAudio = ref('')
|
||||
function handlePlay(item: anyObj) {
|
||||
activeAudio.value = item.subjectTitleVo.id
|
||||
player.src = item.subjectTitleVo.voiceUrl
|
||||
player.play()
|
||||
}
|
||||
|
||||
// 举一反三
|
||||
function startTrain(item: anyObj) {
|
||||
router.push({
|
||||
path: '/subject/doWork',
|
||||
query: {
|
||||
resourceType: resource_type_analogy,
|
||||
subjectId: subjectId.value,
|
||||
titleId: item.id,
|
||||
isHomework: resource_type_analogy,
|
||||
},
|
||||
})
|
||||
}
|
||||
const debounceStartTrain = debounce(startTrain, 500)
|
||||
|
||||
function handleStart() {
|
||||
if (unCorrectNum.value <= 1) {
|
||||
handleDiaOK()
|
||||
} else {
|
||||
showDia.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 订正错题弹窗
|
||||
const showDia = ref(false)
|
||||
const correctId = ref<string>('') // 被订正的主观题id
|
||||
const correctNum = ref(1)
|
||||
async function handleDiaOK() {
|
||||
if (!correctId.value) {
|
||||
router.push({
|
||||
path: '/subject/doWork',
|
||||
query: {
|
||||
resourceType: resource_type_wrong_book,
|
||||
num: correctNum.value,
|
||||
subjectId: subjectId.value,
|
||||
isHomework: resource_type_wrong_book,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await homeWorkApi.errorEdition(correctId.value)
|
||||
await init(list.value.length)
|
||||
showDia.value = false
|
||||
page.size = 10
|
||||
}
|
||||
}
|
||||
function changeCorrectNum(type: 0 | 1) {
|
||||
if (type) {
|
||||
if (correctNum.value >= 10) return
|
||||
correctNum.value++
|
||||
} else {
|
||||
if (correctNum.value <= 1) return
|
||||
correctNum.value--
|
||||
}
|
||||
}
|
||||
|
||||
// 主观题订正错题
|
||||
function handleCorrection(item: anyObj) {
|
||||
correctId.value = item.userErrorEditionId
|
||||
showDia.value = true
|
||||
}
|
||||
|
||||
function handleDiaClosed() {
|
||||
correctId.value = ''
|
||||
}
|
||||
|
||||
// 视频播放器
|
||||
const videoDia = reactive<anyObj>({
|
||||
show: false,
|
||||
knowledge: {},
|
||||
})
|
||||
function playVideo(item: anyObj) {
|
||||
// videoDia.videoList = list;
|
||||
videoDia.show = true
|
||||
videoDia.knowledge.id = item.id
|
||||
videoDia.knowledge.name = item.name
|
||||
}
|
||||
|
||||
// 图片预览弹窗
|
||||
const imgDia = reactive({
|
||||
show: false,
|
||||
url: '',
|
||||
})
|
||||
function handleShow(url: string) {
|
||||
imgDia.show = true
|
||||
imgDia.url = url
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
player = useAudio({
|
||||
autoplay: false,
|
||||
})
|
||||
if (subjectList.value?.length <= 0) {
|
||||
await getSubjectAllList()
|
||||
}
|
||||
subjectId.value = subjectComputed.value[0].value
|
||||
})
|
||||
|
||||
onActivated(async () => {
|
||||
if (route.meta.subjectId) {
|
||||
subjectId.value = Number(route.meta.subjectId as string)
|
||||
}
|
||||
|
||||
pageLoading.value = false
|
||||
loading.value = false
|
||||
activeAudio.value = ''
|
||||
correctFlag.value = 0
|
||||
subjectiveFlag.value = 1
|
||||
showDia.value = false
|
||||
correctNum.value = 1
|
||||
init()
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
showDia.value = false
|
||||
player.pause?.()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
player.destroy()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="wrong-book">
|
||||
<div class="top">
|
||||
<BackBar leftText="错题本" />
|
||||
|
||||
<div class="top-select-tab">
|
||||
<SelectTab v-model="subjectiveFlag" :dataList="subjectiveConfig" @change="init"></SelectTab>
|
||||
</div>
|
||||
<div class="top-select">
|
||||
<Select v-model="subjectId" :selectList="subjectComputed" @selectChange="init"></Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="main-top">
|
||||
<div class="main-top-select">
|
||||
<SelectTab v-model="correctFlag" :dataList="correctConfig" @change="initList"></SelectTab>
|
||||
</div>
|
||||
</div>
|
||||
<RoundSplit :length="88"></RoundSplit>
|
||||
<div class="main-box">
|
||||
<template v-if="list.length">
|
||||
<div class="main-list hidden_scroll" @scroll="debounceScroll">
|
||||
<div ref="scrollBoxRef" class="scroll-box QuesItemBox">
|
||||
<template v-for="i in list" :key="i.id">
|
||||
<WrongQuesItem
|
||||
:correctFlag="correctFlag"
|
||||
:subjectiveFlag="subjectiveFlag"
|
||||
:data="i"
|
||||
:time="i.createTime"
|
||||
:activeAudio="activeAudio"
|
||||
@startTrain="debounceStartTrain(i)"
|
||||
@play="handlePlay(i)"
|
||||
@playVideo="playVideo"
|
||||
@showPic="handleShow"
|
||||
>
|
||||
<template #customBtn>
|
||||
<Btn
|
||||
v-if="subjectiveFlag === 1"
|
||||
class="custom-btn"
|
||||
width="192"
|
||||
height="64"
|
||||
size="32"
|
||||
@click="debounceStartTrain(i)"
|
||||
>
|
||||
举一反三
|
||||
</Btn>
|
||||
<Btn
|
||||
v-else-if="subjectiveFlag === 0 && correctFlag === 0"
|
||||
class="custom-btn"
|
||||
width="192"
|
||||
height="64"
|
||||
size="32"
|
||||
@click="handleCorrection(i)"
|
||||
>
|
||||
订正错题
|
||||
</Btn>
|
||||
<div v-else></div>
|
||||
</template>
|
||||
</WrongQuesItem>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<LoadIcon v-if="loading" class="load-icon"></LoadIcon>
|
||||
</template>
|
||||
<Empty
|
||||
v-else
|
||||
class="empty"
|
||||
:type="pageLoading ? 'loading' : 'learn'"
|
||||
:content="emptyText"
|
||||
></Empty>
|
||||
</div>
|
||||
</div>
|
||||
<Btn
|
||||
v-if="unCorrectNum && subjectiveFlag === 1"
|
||||
class="start_train-btn"
|
||||
type="success"
|
||||
width="232"
|
||||
height="80"
|
||||
@click="handleStart"
|
||||
>
|
||||
订正错题
|
||||
</Btn>
|
||||
<mj-dialog
|
||||
v-model="showDia"
|
||||
hide-cancel
|
||||
:ok-text="!correctId ? '开始' : '确定'"
|
||||
title="订正错题"
|
||||
:ok="handleDiaOK"
|
||||
:closed="handleDiaClosed"
|
||||
>
|
||||
<div class="dia_body">
|
||||
<template v-if="!correctId">
|
||||
<div class="dia_body-title">系统将从您的错题本中随机选择题目</div>
|
||||
<div class="dia_body-slider">
|
||||
<div class="dia_body-slider-box">
|
||||
<Icon name="slider_btn_reduce" width="56" @click="changeCorrectNum(0)"></Icon>
|
||||
<el-slider
|
||||
v-model="correctNum"
|
||||
:show-tooltip="false"
|
||||
:step="1"
|
||||
:max="unCorrectNum > 10 ? 10 : unCorrectNum"
|
||||
:min="1"
|
||||
></el-slider>
|
||||
<Icon name="slider_btn_add" width="56" @click="changeCorrectNum(1)"></Icon>
|
||||
</div>
|
||||
|
||||
<div class="dia_body-slider-text">
|
||||
数量:{{ correctNum }}/{{ unCorrectNum > 10 ? 10 : unCorrectNum }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="dia_body-title text">请确认该题已掌握,订正后将移动至 【已订正】模块</div>
|
||||
</template>
|
||||
</div>
|
||||
</mj-dialog>
|
||||
<VideoFullBox
|
||||
v-model:show="videoDia.show"
|
||||
:knowledge="videoDia.knowledge"
|
||||
:data="{
|
||||
subjectId,
|
||||
resourceType: 2,
|
||||
origin: 2,
|
||||
}"
|
||||
></VideoFullBox>
|
||||
<ImgView v-model="imgDia.show" :url="imgDia.url"></ImgView>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.wrong-book {
|
||||
@include main_bg;
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5;
|
||||
|
||||
// @include hidden_scroll;
|
||||
.top {
|
||||
width: 100%;
|
||||
padding: 2.67vh 64px 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
margin-bottom: 2vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: auto;
|
||||
&-select-tab {
|
||||
flex: 1;
|
||||
margin-left: 62px;
|
||||
:deep(.select-list) {
|
||||
width: 968px;
|
||||
.data-item {
|
||||
min-width: 50%;
|
||||
&.active {
|
||||
background: linear-gradient(269deg, #86e6ff 2.25%, #9effda 51.93%, #5fdcfd 99.65%);
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
margin: 11.33vh auto 0;
|
||||
background-size: 100%;
|
||||
width: 1792px;
|
||||
height: 86vh;
|
||||
// padding: 91px 24px 24px;
|
||||
|
||||
&-top {
|
||||
width: 100%;
|
||||
height: 8.5vh;
|
||||
border-radius: 1.67vh;
|
||||
background: linear-gradient(269deg, #86e6ff 2.25%, #9effda 51.93%, #5fdcfd 99.65%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 32px;
|
||||
&-select {
|
||||
:deep(.select-list) {
|
||||
border-radius: 4.5vh;
|
||||
.data-item {
|
||||
min-width: 258px;
|
||||
width: 50%;
|
||||
border-radius: 4.5vh;
|
||||
&.active {
|
||||
background: linear-gradient(90deg, #86e6ff 0%, #9effda 50%, #86e6ff 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.round-split {
|
||||
position: absolute;
|
||||
width: calc(100% - 50px);
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
// z-index: 1;
|
||||
top: 7.92vh;
|
||||
:deep(.dot) {
|
||||
background: linear-gradient(180deg, #798498 0%, #a1b1cb 100%);
|
||||
}
|
||||
}
|
||||
&-box {
|
||||
height: calc(100% - 110px);
|
||||
border-radius: 1.67vh;
|
||||
background: linear-gradient(180deg, #f0faff 0%, #fafdff 100%);
|
||||
padding: 2.67vh;
|
||||
}
|
||||
&-list {
|
||||
height: 100%;
|
||||
// margin: 0 32px;
|
||||
// .scroll-box {
|
||||
// height: 100%;
|
||||
//}
|
||||
.custom-btn {
|
||||
min-height: 5.67vh;
|
||||
margin-left: 24px;
|
||||
border-radius: 4.42vh;
|
||||
border: 0.17vh solid #fff;
|
||||
box-shadow: 0 0.17vh 0.17vh 0 rgba(0, 0, 0, 0.25);
|
||||
color: $font-color;
|
||||
.x-icon {
|
||||
margin-left: 16px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.custom_load_icon {
|
||||
position: absolute;
|
||||
bottom: 3.17vh;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
.empty {
|
||||
height: 75vh;
|
||||
}
|
||||
}
|
||||
.start_train-btn {
|
||||
position: absolute;
|
||||
bottom: 13.33vh;
|
||||
right: -35px;
|
||||
z-index: 100;
|
||||
:deep(.x-button-text) {
|
||||
transform: translate(-8px);
|
||||
}
|
||||
}
|
||||
}
|
||||
.dia_body {
|
||||
// height: 392px;
|
||||
padding: 3.33vh 0;
|
||||
text-align: center;
|
||||
color: $font-color;
|
||||
line-height: 1.5;
|
||||
&-title {
|
||||
text-align: center;
|
||||
font-family: $font-special;
|
||||
font-size: 2.67vh;
|
||||
&.text {
|
||||
width: 510px;
|
||||
margin: 4.17vh auto 0;
|
||||
}
|
||||
}
|
||||
&-slider {
|
||||
margin-top: 3.33vh;
|
||||
&-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
.x-icon {
|
||||
margin: 0 32px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
:deep(.el-slider) {
|
||||
height: 4.67vh;
|
||||
.el-slider__runway {
|
||||
height: 3vh;
|
||||
background: #9a9ea0;
|
||||
border-radius: 2.5vh;
|
||||
}
|
||||
.el-slider__bar {
|
||||
height: 3vh;
|
||||
border-radius: 2.5vh;
|
||||
background: linear-gradient(249deg, #4de798 26.9%, #a2fb6e 79.86%);
|
||||
}
|
||||
.el-slider__button-wrapper {
|
||||
width: 56px;
|
||||
height: 4.67vh;
|
||||
background: radial-gradient(189.06% 189.06% at 49.22% 50.78%, #fff 0%, #becadf 100%);
|
||||
border-radius: 50%;
|
||||
filter: drop-shadow(0px 3.5px 3.5px rgba(0, 0, 0, 0.15));
|
||||
top: -0.83vh;
|
||||
}
|
||||
.el-slider__button {
|
||||
width: 56px;
|
||||
height: 4.67vh;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
&-text {
|
||||
margin-top: 1.33vh;
|
||||
font-family: OPPOSans;
|
||||
font-size: 2vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -118,7 +118,7 @@ export const userStore = defineStore('user', () => {
|
||||
}
|
||||
|
||||
if (hasMonitorState) {
|
||||
syncClassMonitorState(info.monitorState)
|
||||
syncClassMonitorState(info)
|
||||
}
|
||||
|
||||
if (typeof info.schoolClass === 'string') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user