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

chore: update

上级 85bb0940
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
"element-plus": "^2.2.21", "element-plus": "^2.2.21",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.0.23", "pinia": "^2.0.24",
"qs": "^6.11.0", "qs": "^6.11.0",
"ua-parser-js": "^1.0.32", "ua-parser-js": "^1.0.32",
"video.js": "^7.20.3", "video.js": "^7.20.3",
...@@ -3656,16 +3656,13 @@ ...@@ -3656,16 +3656,13 @@
} }
}, },
"node_modules/pinia": { "node_modules/pinia": {
"version": "2.0.23", "version": "2.0.24",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.24.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==", "integrity": "sha512-DDLd4Iphyc+6PYYYbx7jkb6WP9gecgu9bz9huyB5rb7CdJI3DhzYiZI+/Ih8MLewRrP9DSpslF/BgSNrJtZU7A==",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.4.4", "@vue/devtools-api": "^6.4.5",
"vue-demi": "*" "vue-demi": "*"
}, },
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": { "peerDependencies": {
"@vue/composition-api": "^1.4.0", "@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4", "typescript": ">=4.4.4",
...@@ -7761,11 +7758,11 @@ ...@@ -7761,11 +7758,11 @@
"dev": true "dev": true
}, },
"pinia": { "pinia": {
"version": "2.0.23", "version": "2.0.24",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz", "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.24.tgz",
"integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==", "integrity": "sha512-DDLd4Iphyc+6PYYYbx7jkb6WP9gecgu9bz9huyB5rb7CdJI3DhzYiZI+/Ih8MLewRrP9DSpslF/BgSNrJtZU7A==",
"requires": { "requires": {
"@vue/devtools-api": "^6.4.4", "@vue/devtools-api": "^6.4.5",
"vue-demi": "*" "vue-demi": "*"
}, },
"dependencies": { "dependencies": {
......
...@@ -102,7 +102,7 @@ onMounted(() => { ...@@ -102,7 +102,7 @@ onMounted(() => {
bottom: 0; bottom: 0;
width: 20px; width: 20px;
cursor: col-resize; cursor: col-resize;
z-index: 9999; z-index: 999;
} }
.panel-icon { .panel-icon {
position: absolute; position: absolute;
......
...@@ -103,7 +103,7 @@ const teacherText = $computed(() => { ...@@ -103,7 +103,7 @@ const teacherText = $computed(() => {
</el-dialog> </el-dialog>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.report-preview { .report-preview {
h1 { h1 {
font-size: 24px; font-size: 24px;
......
...@@ -40,3 +40,36 @@ export function uploadCheckExperimentRecord(data: { file: File }) { ...@@ -40,3 +40,36 @@ export function uploadCheckExperimentRecord(data: { file: File }) {
headers: { 'Content-Type': 'multipart/form-data' } headers: { 'Content-Type': 'multipart/form-data' }
}) })
} }
// 获取实验准备
export function getExperimentPrepare(params: { experiment_id: string; student_id: string }) {
return httpRequest.get('/api/lab/v1/teacher/experiment/pre-detail', { params })
}
// 获取实验结果
export function getExperimentResult(params: { experiment_id: string; student_id: string }) {
return httpRequest.get('/api/lab/v1/teacher/experiment/result-detail', { params })
}
// 获取实验报告
export function getExperimentReport(params: { experiment_id: string; student_id: string }) {
return httpRequest.get('/api/lab/v1/teacher/experiment/report-achievement', { params })
}
// 批改学员实验报告成绩
export function updateExperimentReport(data: { experiment_id: string; student_id: string; score_detail: string }) {
return httpRequest.post('/api/lab/v1/teacher/experiment/report-check', data)
}
// 获取实验评分模板
export function getExperimentScoreTemplate(params: { experiment_id: string }) {
return httpRequest.get('/api/lab/v1/teacher/experiment/score-template', { params })
}
// 获取实验成绩
export function getExperimentScore(params: { experiment_id: string; student_id: string }) {
return httpRequest.get('/api/lab/v1/teacher/experiment/achievement', { params })
}
// 批改学员实验成绩
export function updateExperimentScore(data: { experiment_id: string; student_id: string; score_details: string }) {
return httpRequest.post('/api/lab/v1/teacher/experiment/achievement-check', data)
}
...@@ -8,7 +8,18 @@ interface Props { ...@@ -8,7 +8,18 @@ interface Props {
} }
const props = defineProps<Props>() const props = defineProps<Props>()
let detail = $ref<RecordItem>() let detail = $ref<any>()
const dataList = $computed(() => {
return (
detail?.score_log.map((item: any) => {
const customData = item.score_details.reduce((result: any, item: any) => {
result[item.id] = item.commit_score
return result
}, {})
return { ...item, ...customData }
}) || []
)
})
function fetchInfo() { function fetchInfo() {
getExperimentRecord({ experiment_id: props.data.experiment_id, student_id: props.data.student_id }).then(res => { getExperimentRecord({ experiment_id: props.data.experiment_id, student_id: props.data.student_id }).then(res => {
detail = res.data detail = res.data
...@@ -21,19 +32,24 @@ watchEffect(() => { ...@@ -21,19 +32,24 @@ watchEffect(() => {
const appList = $ref<InstanceType<typeof AppList> | null>(null) const appList = $ref<InstanceType<typeof AppList> | null>(null)
// 列表配置 // 列表配置
const listOptions = $computed(() => { const listOptions = $computed(() => {
let customColumns: any = []
if (detail?.score_details) {
customColumns = Object.values(detail?.score_details)
.filter((item: any) => item.id)
.map((item: any) => {
return { prop: item.id, label: `${item.name}成绩` }
})
}
return { return {
columns: [ columns: [
{ label: '序号', type: 'index', width: 60 }, { label: '序号', type: 'index', width: 60 },
{ label: '姓名', prop: 'student_name', slots: 'table-name' }, { label: '姓名', prop: 'student_name', slots: 'table-name' },
{ label: '学号', prop: 'sno_number', slots: 'table-number' }, { label: '学号', prop: 'sno_number', slots: 'table-number' },
{ label: '实验操作成绩', prop: 'operate' }, ...customColumns,
{ label: '实验结果成绩', prop: 'result' },
{ label: '实验报告成绩', prop: 'file' },
{ label: '综合实验成绩', prop: 'score' },
{ label: '评分时间', prop: 'check_time' }, { label: '评分时间', prop: 'check_time' },
{ label: '评分人', prop: 'checker_id_name' } { label: '评分人', prop: 'checker_id_name' }
], ],
data: detail?.score_log data: dataList
} }
}) })
</script> </script>
......
<script setup lang="ts">
import type { RecordItem, FileItem } from '../types'
interface Props {
data: RecordItem
}
const props = defineProps<Props>()
// 实验过程截图
const pictures = $computed<FileItem[]>(() => {
try {
return props.data?.pictures ? JSON.parse(props.data.pictures) : []
} catch (error) {
console.log(error)
}
return []
})
</script>
<template>
<el-dialog title="查看实验截图" width="800px">
<ul class="picture-list">
<li v-for="item in pictures" :key="item.url">
<p class="t1">
<a :href="item.url" target="_blank">{{ item.name }}</a>
</p>
<p class="t2">截图时间:{{ item.upload_time }}</p>
</li>
</ul>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
</el-dialog>
</template>
<style lang="scss" scoped>
.picture-list {
width: 100%;
li {
display: flex;
justify-content: space-between;
line-height: 30px;
}
a {
color: var(--main-color);
}
.t1 {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
</style>
<script setup lang="ts">
import { getExperimentPrepare } from '../api'
interface Props {
experiment_id: string
student_id: string
}
const props = defineProps<Props>()
let detail = $ref<any>()
function fetchInfo() {
getExperimentPrepare({ experiment_id: props.experiment_id, student_id: props.student_id }).then(res => {
detail = res.data.detail
})
}
onMounted(() => {
fetchInfo()
})
</script>
<template>
<el-dialog title="查看实验准备" width="800px">
<div v-html="detail.detail" v-if="detail"></div>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { getExperimentResult } from '../api'
interface Props {
experiment_id: string
student_id: string
}
const props = defineProps<Props>()
let detail = $ref<any>()
function fetchInfo() {
getExperimentResult({ experiment_id: props.experiment_id, student_id: props.student_id }).then(res => {
detail = res.data.detail
})
}
onMounted(() => {
fetchInfo()
})
</script>
<template>
<el-dialog title="查看实验结果" width="800px">
<div v-html="detail.detail" v-if="detail"></div>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</template>
</el-dialog>
</template>
...@@ -5,6 +5,9 @@ export const routes: Array<RouteRecordRaw> = [ ...@@ -5,6 +5,9 @@ export const routes: Array<RouteRecordRaw> = [
{ {
path: '/admin/lab/score', path: '/admin/lab/score',
component: AppLayout, component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }] children: [
{ path: '', component: () => import('./views/Index.vue') },
{ path: 'report', component: () => import('./views/Report.vue') }
]
} }
] ]
<script setup>
import { getExperimentScore, getExperimentReport, updateExperimentReport } from '../api'
import { reportScoreRule } from '@/utils/dictionary'
const route = useRoute()
let experiment = $ref()
let report = $ref()
const form = reactive({
experiment_id: route.query.experiment_id,
student_id: route.query.student_id,
score_detail: []
})
const teacherText = $computed(() => {
if (!experiment) return ''
return experiment.teachers.map(item => item.name).join('、')
})
const classText = $computed(() => {
if (!experiment) return ''
return experiment.student.classes?.map(item => item.name).join('、')
})
function fetchInfo() {
getExperimentScore({ experiment_id: route.query.experiment_id, student_id: route.query.student_id }).then(res => {
experiment = res.data.experiment
})
}
function fetchReport() {
getExperimentReport({ experiment_id: route.query.experiment_id, student_id: route.query.student_id }).then(res => {
report = res.data.detail
let detail = []
try {
detail = JSON.parse(report.score_detail || report.detail)
} catch (error) {
console.log(error)
}
form.score_detail = detail.map(item => {
item.min_score = parseFloat(item.min_score)
item.commit_score = item.commit_score ? parseFloat(item.commit_score) : 0
item.commit = item.commit || ''
return item
})
})
}
onMounted(() => {
fetchInfo()
fetchReport()
})
const score = $computed(() => {
const result = form.score_detail.reduce((result, item) => {
return result + item.commit_score
}, 0)
return parseFloat(result.toFixed(2))
})
function handleSubmit() {
const params = { ...form, score_detail: JSON.stringify(form.score_detail) }
updateExperimentReport(params).then(res => {
console.log(res)
})
}
// 关闭
function handleClose() {
window.close()
}
</script>
<template>
<AppCard title="批改实验报告">
<h1 class="report-title">实验报告</h1>
<el-form label-suffix=":" hide-required-asterisk v-if="experiment && report">
<el-row>
<el-col :span="12">
<el-form-item label="课程名称">{{ experiment.course.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实验名称">{{ experiment.name }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="实验地点">{{ report.experiment_address }} </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实验日期"> {{ report.experiment_date }} </el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="实验类型">{{ experiment.type_level }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="指导教师">{{ teacherText }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="专业">{{ experiment.student.specialty.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班级">{{ classText }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="姓名">{{ experiment.student.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学号">{{ experiment.student.sno_number }}</el-form-item>
</el-col>
</el-row>
<el-divider />
<p class="report-score">总分:{{ score || '--' }}</p>
<el-form-item v-for="(item, index) in form.score_detail" :key="item.id" class="report-form-item">
<div class="form-hd">
<h3>{{ index + 1 }}{{ item.name }}</h3>
<div>
<span class="score">{{ item.score }}</span>
<span class="rule-mode">{{ reportScoreRule[item.rule_mode] }}</span>
</div>
<div>
<p class="score">得分:{{ item.commit_score }}</p>
<el-input-number
v-model="item.commit_score"
:min="item.min_score"
:max="item.score"
:controls="false"
:step="0.01"
step-strictly
placeholder="更新得分"
style="width: 100px"></el-input-number>
</div>
</div>
<!-- 内容 -->
<template v-if="item.type === 1">
{{ item.notice_message }}
<div class="report-form-item__content" v-html="item.content"></div>
</template>
<!-- 附件 -->
<template v-if="item.type === 2">
<el-table :data="item.files" stripe :header-cell-style="{ background: '#ededed' }" style="margin-top: 20px">
<el-table-column label="序号" type="index" width="80" align="center"></el-table-column>
<el-table-column label="附件类型" prop="type" align="center">
<template #default="{ row }">
{{ row.type === '1' ? '本地上传' : '实验截图' }}
</template>
</el-table-column>
<el-table-column label="附件名称" prop="name" align="center"></el-table-column>
<el-table-column label="更新时间" prop="updated_time" align="center"></el-table-column>
<el-table-column label="操作" width="100" align="center">
<template #default="{ row }">
<el-button text type="primary"><a :href="row.url" target="_blank">查看</a></el-button>
</template>
</el-table-column>
</el-table>
</template>
<!-- 思考题 -->
<template v-if="item.type === 3">
{{ item.question_stem }}
<div class="report-form-item__content" v-html="item.content"></div>
</template>
<p>评语:</p>
<el-input type="textarea" maxlength="200" v-model="item.comment" placeholder="请填写评语" :autosize="{ minRows: 4 }"></el-input>
</el-form-item>
<p>实验整体评语:</p>
<el-input type="textarea" maxlength="500" placeholder="请填写评语" :autosize="{ minRows: 6 }"></el-input>
</el-form>
<el-row justify="center">
<el-button type="primary" round auto-insert-space @click="handleSubmit">保存</el-button>
<el-button round auto-insert-space @click="handleClose">关闭</el-button>
</el-row>
</AppCard>
</template>
<style lang="scss" scoped>
.report-title {
padding: 20px;
font-size: 20px;
text-align: center;
}
.report-score {
text-align: right;
font-size: 24px;
font-weight: bold;
color: var(--main-color);
}
.report-form-item {
.form-hd {
display: flex;
width: 100%;
font-size: 14px;
font-weight: bold;
justify-content: space-between;
.rule-mode {
width: 100px;
text-align: right;
}
}
}
.report-form-item__content {
clear: both;
width: 100%;
padding: 10px;
background-color: #efefef;
border-radius: 6px;
box-sizing: border-box;
}
</style>
...@@ -110,3 +110,8 @@ export function getExperimentReportCache(params: { experiment_id: string }) { ...@@ -110,3 +110,8 @@ export function getExperimentReportCache(params: { experiment_id: string }) {
export function getExperimentReport(params: { experiment_id: string }) { export function getExperimentReport(params: { experiment_id: string }) {
return httpRequest.get('/api/lab/v1/student/experiment/online-report-detail', { params }) return httpRequest.get('/api/lab/v1/student/experiment/online-report-detail', { params })
} }
// 获取实验成绩
export function getExperimentScore(params: { experiment_id: string }) {
return httpRequest.get('/api/lab/v1/student/experiment/achievement', { params })
}
<script setup lang="ts">
import type { ExperimentInfo, ExperimentRecord } from '../types'
interface Props {
data: ExperimentInfo
}
const props = defineProps<Props>()
const classText = $computed(() => {
return props.data.student.classes.map(item => item.name).join('、')
})
const detail = $ref(inject<ExperimentRecord>('detail'))
</script>
<template>
<el-dialog title="查看实验报告" :close-on-click-modal="false" width="600px">
<el-form label-suffix=":" label-width="110px" label-position="right">
<el-row>
<el-col :span="12">
<el-form-item label="实验名称">{{ data.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实验课程名称">{{ data.course.name }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="学生姓名">{{ data.student.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学生学号">{{ data.student.sno_number }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="所属专业">{{ data.student.specialty.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属班级">{{ classText }}</el-form-item>
</el-col>
</el-row>
<el-form-item label="实验报告文件" v-if="detail?.file">
{{ detail.file.name }}
<el-button type="primary" text> <a :href="detail.file.url" target="_blank"> 查阅</a></el-button>
</el-form-item>
</el-form>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
</el-row>
</el-dialog>
</template>
<script setup>
import { getExperimentReport } from '../api'
const props = defineProps({ id: String })
let experiment = $ref()
let report = $ref()
const teacherText = $computed(() => {
if (!experiment) return ''
return experiment.teachers.map(item => item.name).join('、')
})
const classText = $computed(() => {
if (!experiment) return ''
return experiment.student.classes.map(item => item.name).join('、')
})
function fetchInfo() {
getExperimentReport({ experiment_id: props.id }).then(res => {
experiment = res.data.experiment
report = res.data.report
let detail = []
try {
detail = JSON.parse(report.detail)
} catch (error) {
console.log(error)
}
report = Object.assign(report, { detail })
})
}
onMounted(() => {
fetchInfo()
})
// 关闭
function handleClose() {
window.close()
}
</script>
<template>
<h1 class="report-title">实验报告</h1>
<el-form label-suffix=":" hide-required-asterisk v-if="experiment">
<el-row>
<el-col :span="12">
<el-form-item label="课程名称">{{ experiment.course.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实验名称">{{ experiment.name }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="实验地点">{{ report.experiment_address }} </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实验日期"> {{ report.experiment_date }} </el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="实验类型">{{ experiment.type_level }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="指导教师">{{ teacherText }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="专业">{{ experiment.student.specialty.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班级">{{ classText }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="姓名">{{ experiment.student.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学号">{{ experiment.student.sno_number }}</el-form-item>
</el-col>
</el-row>
<el-divider />
<el-form-item :label="item.name" v-for="item in report.detail" :key="item.id" class="report-form-item">
<!-- 内容 -->
<template v-if="item.type === 1">
{{ item.notice_message }}
<div class="report-form-item__content" v-html="item.content"></div>
</template>
<!-- 附件 -->
<template v-if="item.type === 2">
<el-table :data="item.files" stripe :header-cell-style="{ background: '#ededed' }" style="margin-top: 20px">
<el-table-column label="序号" type="index" width="80" align="center"></el-table-column>
<el-table-column label="附件类型" prop="type" align="center">
<template #default="{ row }">
{{ row.type === '1' ? '本地上传' : '实验截图' }}
</template>
</el-table-column>
<el-table-column label="附件名称" prop="name" align="center"></el-table-column>
<el-table-column label="更新时间" prop="updated_time" align="center"></el-table-column>
<el-table-column label="操作" width="100" align="center">
<template #default="{ row }">
<el-button text type="primary"><a :href="row.url" target="_blank">查看</a></el-button>
</template>
</el-table-column>
</el-table>
</template>
<!-- 思考题 -->
<template v-if="item.type === 3">
{{ item.question_stem }}
<div class="report-form-item__content" v-html="item.content"></div>
</template>
</el-form-item>
</el-form>
<el-row justify="center">
<el-button type="primary" plain auto-insert-space @click="handleClose">关闭</el-button>
</el-row>
</template>
<style lang="scss">
.report-title {
padding: 20px;
font-size: 20px;
text-align: center;
}
.report-form-item {
flex-direction: column;
.el-form-item__label {
font-weight: bold;
text-align: left;
justify-content: flex-start;
}
}
.report-form-item__content {
clear: both;
width: 100%;
padding: 10px;
background-color: #efefef;
border-radius: 6px;
box-sizing: border-box;
}
</style>
...@@ -66,7 +66,10 @@ function handleRemove(index: number) { ...@@ -66,7 +66,10 @@ function handleRemove(index: number) {
</li> </li>
</ul> </ul>
<ImageViewer v-model="imageViewerVisible" :index="imageViewerIndex" :items="detail.pictures"></ImageViewer> <ImageViewer v-model="imageViewerVisible" :index="imageViewerIndex" :items="detail.pictures"></ImageViewer>
<ResultScoreDialog v-model="dialogVisible"></ResultScoreDialog> <ResultScoreDialog
v-model="dialogVisible"
:experiment_id="experiment_id"
v-if="dialogVisible && experiment_id"></ResultScoreDialog>
</template> </template>
<el-empty description="暂无数据" v-else /> <el-empty description="暂无数据" v-else />
</template> </template>
...@@ -94,6 +97,7 @@ function handleRemove(index: number) { ...@@ -94,6 +97,7 @@ function handleRemove(index: number) {
background: url(@/assets/images/score_bg.png) no-repeat; background: url(@/assets/images/score_bg.png) no-repeat;
background-size: contain; background-size: contain;
box-sizing: border-box; box-sizing: border-box;
cursor: pointer;
} }
.t2 { .t2 {
font-size: 14px; font-size: 14px;
......
<script setup lang="ts"> <script setup lang="ts">
import type { ExperimentRecord } from '../types' import { getExperimentScore } from '../api'
import { Document } from '@element-plus/icons-vue'
const detail = $ref(inject<ExperimentRecord>('detail')) interface Props {
experiment_id: string
}
const props = defineProps<Props>()
let detail = $ref<any>()
const score = $computed<number>(() => { function fetchInfo() {
const result = getExperimentScore({ experiment_id: props.experiment_id }).then(res => {
((detail?.score_details.operate || 0) + (detail?.score_details.result || 0) + (detail?.score_details.file || 0)) / 3 detail = res.data
return parseFloat(result.toFixed(2)) })
}
onMounted(() => {
fetchInfo()
}) })
</script> </script>
...@@ -15,81 +22,49 @@ const score = $computed<number>(() => { ...@@ -15,81 +22,49 @@ const score = $computed<number>(() => {
<el-dialog title="实验成绩详情" width="600px" :close-on-click-modal="false"> <el-dialog title="实验成绩详情" width="600px" :close-on-click-modal="false">
<el-form label-width="120px" label-suffix=":" v-if="detail"> <el-form label-width="120px" label-suffix=":" v-if="detail">
<el-form-item label="实验名称">{{ detail.experiment.name }}</el-form-item> <el-form-item label="实验名称">{{ detail.experiment.name }}</el-form-item>
<el-form-item label="实验课程名称">{{ detail.course.name }}</el-form-item> <el-form-item label="实验课程名称">{{ detail.experiment.course.name }}</el-form-item>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="学生姓名">{{ detail.student.name }}</el-form-item> <el-form-item label="学生姓名">{{ detail.experiment.student.name }}</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="学生学号">{{ detail.student.sno_number }}</el-form-item> <el-form-item label="学生学号">{{ detail.experiment.student.sno_number }}</el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="所属专业">{{ detail.student.specialty.name }}</el-form-item> <el-form-item label="所属专业">{{ detail.experiment.student.specialty.name }}</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="所属班级">{{ detail.student.specialty.name }}</el-form-item> <el-form-item label="所属班级">{{ detail.experiment.student.specialty.name }}</el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="实验报告文件"> <el-row>
<div v-if="detail.file"> <el-col :span="12">
<a :href="detail.file.url" target="_blank" class="file-item"> <el-form-item label="评分教师">
<el-icon><Document /></el-icon>{{ detail.file.name }} {{
</a> detail.achievement.checker_sso_user.real_name ||
</div> detail.achievement.checker_sso_user.nickname ||
</el-form-item> detail.achievement.checker_sso_user.username
<el-form-item label="实验成绩" class="form-item-score"> }}
<el-form ref="formRef" hide-required-asterisk inline label-position="top" style="padding: 5px 0 20px">
<el-form-item label="实验操作">
<el-input-number :controls="false" disabled v-model="detail.score_details.operate" />
</el-form-item>
<el-form-item label="实验结果">
<el-input-number :controls="false" disabled v-model="detail.score_details.result" />
</el-form-item>
<el-form-item label="实验报告">
<el-input-number :controls="false" disabled v-model="detail.score_details.file" />
</el-form-item>
<el-form-item label="综合实验成绩" style="width: 364px">
<el-input-number :controls="false" disabled v-model="score" style="width: 100%" />
</el-form-item> </el-form-item>
</el-form> </el-col>
</el-form-item> <el-col :span="12">
<el-form-item label="评分时间">{{ detail.achievement.check_time }}</el-form-item>
</el-col>
</el-row>
<div>
<p>实验得分{{ detail.achievement.score }}</p>
<!-- <el-table :data="item.files" stripe :header-cell-style="{ background: '#ededed' }">
<el-table-column label="实验成绩组成项" align="center"></el-table-column>
<el-table-column label="权重" prop="type" align="center"> </el-table-column>
<el-table-column label="得分" width="100" align="center">
<template #default="{ row }">
<el-button text type="primary"><a :href="row.url" target="_blank">查看明细</a></el-button>
</template>
</el-table-column>
</el-table> -->
</div>
</el-form> </el-form>
</el-dialog> </el-dialog>
</template> </template>
<style lang="scss" scoped>
.file-item {
display: flex;
align-items: center;
color: var(--main-color);
.el-icon {
margin-right: 5px;
}
}
.picture-list {
width: 100%;
li {
display: flex;
justify-content: space-between;
}
a {
color: var(--main-color);
}
.t1 {
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.form-item-score {
:deep(.el-form-item__label) {
text-align: center;
}
.el-input-number {
width: 100px;
}
}
</style>
...@@ -8,7 +8,8 @@ export const routes: Array<RouteRecordRaw> = [ ...@@ -8,7 +8,8 @@ export const routes: Array<RouteRecordRaw> = [
props: { sidebar: false }, props: { sidebar: false },
children: [ children: [
{ path: '', component: () => import('./views/Index.vue') }, { path: '', component: () => import('./views/Index.vue') },
{ path: 'report/:id', component: () => import('./views/Report.vue'), props: true } { path: 'report/:id', component: () => import('./views/Report.vue'), props: true },
{ path: 'report/view/:id', component: () => import('./views/ReportView.vue'), props: true }
] ]
} }
] ]
...@@ -14,6 +14,7 @@ const Video = defineAsyncComponent(() => import('../components/Video.vue')) ...@@ -14,6 +14,7 @@ const Video = defineAsyncComponent(() => import('../components/Video.vue'))
const Discuss = defineAsyncComponent(() => import('../components/Discuss.vue')) const Discuss = defineAsyncComponent(() => import('../components/Discuss.vue'))
const Result = defineAsyncComponent(() => import('../components/Result.vue')) const Result = defineAsyncComponent(() => import('../components/Result.vue'))
const ReportDialog = defineAsyncComponent(() => import('../components/ReportDialog.vue')) const ReportDialog = defineAsyncComponent(() => import('../components/ReportDialog.vue'))
const ReportFilePreview = defineAsyncComponent(() => import('../components/ReportFilePreview.vue'))
const PrepareDialog = defineAsyncComponent(() => import('../components/PrepareDialog.vue')) const PrepareDialog = defineAsyncComponent(() => import('../components/PrepareDialog.vue'))
const ResultDialog = defineAsyncComponent(() => import('../components/ResultDialog.vue')) const ResultDialog = defineAsyncComponent(() => import('../components/ResultDialog.vue'))
...@@ -164,6 +165,21 @@ function handleResize() { ...@@ -164,6 +165,21 @@ function handleResize() {
const prepareDialogVisible = $ref(false) const prepareDialogVisible = $ref(false)
// 实验结果 // 实验结果
const resultDialogVisible = $ref(false) const resultDialogVisible = $ref(false)
// 查看实验报告
let reportFilePreviewVisible = $ref(false)
function handleReportView() {
if (experimentInfo?.report_upload_way === 2) {
window.open(`/student/lab/report/view/${experimentInfo?.id}`)
} else {
reportFilePreviewVisible = true
}
}
// 导出实验报告
function handleReportExport() {
console.log(1)
}
</script> </script>
<template> <template>
...@@ -216,30 +232,23 @@ const resultDialogVisible = $ref(false) ...@@ -216,30 +232,23 @@ const resultDialogVisible = $ref(false)
> >
<el-button type="primary" :disabled="disabled" @click="prepareDialogVisible = true">实验准备</el-button> <el-button type="primary" :disabled="disabled" @click="prepareDialogVisible = true">实验准备</el-button>
<el-button type="primary" :disabled="disabled" @click="resultDialogVisible = true">实验结果</el-button> <el-button type="primary" :disabled="disabled" @click="resultDialogVisible = true">实验结果</el-button>
<el-button type="primary" :disabled="disabled" v-if="experimentInfo?.report_upload_way === 2"> <el-button
type="primary"
:disabled="disabled"
v-if="experimentInfo?.report_upload_way === 2 && !experimentInfo?.is_commit_report">
<router-link :to="`/student/lab/report/${form.experiment_id}`" target="_blank">在线实验报告</router-link> <router-link :to="`/student/lab/report/${form.experiment_id}`" target="_blank">在线实验报告</router-link>
</el-button> </el-button>
<el-button <el-button
type="primary" type="primary"
:disabled="disabled" :disabled="disabled"
@click="reportDialogVisible = true" @click="reportDialogVisible = true"
v-if="experimentInfo?.report_upload_way === 1" v-if="experimentInfo?.report_upload_way === 1 && !submitted"
>上传实验报告</el-button >上传实验报告</el-button
> >
<el-button <el-button type="primary" @click="handleReportView" v-if="experimentInfo?.is_commit_report"
type="primary"
:disabled="disabled"
@click="reportDialogVisible = true"
v-if="experimentInfo?.is_commit_report"
>查看实验报告</el-button >查看实验报告</el-button
> >
<el-button <el-button type="primary" @click="handleReportExport" v-if="detail?.status === 2">导出实验报告</el-button>
type="primary"
:disabled="disabled"
@click="reportDialogVisible = true"
v-if="experimentInfo?.is_commit"
>导出实验报告</el-button
>
</div> </div>
</el-row> </el-row>
</AppCard> </AppCard>
...@@ -256,7 +265,10 @@ const resultDialogVisible = $ref(false) ...@@ -256,7 +265,10 @@ const resultDialogVisible = $ref(false)
:data="experimentInfo" :data="experimentInfo"
@update="fetchExperimentRecord" @update="fetchExperimentRecord"
v-if="reportDialogVisible && experimentInfo"></ReportDialog> v-if="reportDialogVisible && experimentInfo"></ReportDialog>
<ReportFilePreview
v-model="reportFilePreviewVisible"
:data="experimentInfo"
v-if="reportFilePreviewVisible && experimentInfo"></ReportFilePreview>
<!-- 实验准备 --> <!-- 实验准备 -->
<PrepareDialog <PrepareDialog
v-model="prepareDialogVisible" v-model="prepareDialogVisible"
......
...@@ -253,7 +253,7 @@ function handleUpdateBindCapture(files) { ...@@ -253,7 +253,7 @@ function handleUpdateBindCapture(files) {
<!-- 关联截图 --> <!-- 关联截图 -->
<BindCaptureDialog <BindCaptureDialog
v-model="bindCaptureVisible" v-model="bindCaptureVisible"
:experiment="detail" :experiment="experiment"
:data="currentRaw" :data="currentRaw"
@update="handleUpdateBindCapture" @update="handleUpdateBindCapture"
v-if="bindCaptureVisible && currentRaw"></BindCaptureDialog> v-if="bindCaptureVisible && currentRaw"></BindCaptureDialog>
......
<script setup> <script setup>
import AppEditor from '@/components/base/AppEditor.vue' import ReportPreview from '../components/ReportPreivew.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useFileDialog } from '@vueuse/core'
import { upload } from '@/utils/upload'
import { useNow, useDateFormat } from '@vueuse/core'
import {
getExperimentReport,
getExperimentReportTemplate,
getExperimentReportCache,
cacheExperimentReport,
updateExperimentReport
} from '../api'
import BindCaptureDialog from '../components/BindCaptureDialog.vue'
const props = defineProps({ id: String })
let experiment = $ref()
let report = $ref()
const disabled = $computed(() => {
return experiment?.is_commit_report
})
const teacherText = $computed(() => {
if (!experiment) return ''
return experiment.teachers.map(item => item.name).join('、')
})
const classText = $computed(() => {
if (!experiment) return ''
return experiment.student.classes.map(item => item.name).join('、')
})
const formRef = $ref()
const form = reactive({
experiment_id: props.id,
experiment_address: '',
experiment_date: useDateFormat(useNow(), 'YYYY-MM-DD').value,
detail: []
})
function fetchInfo() {
getExperimentReport({ experiment_id: props.id }).then(res => {
experiment = res.data.experiment
report = res.data.report
if (experiment.is_commit_report) {
let detail = []
try {
detail = JSON.parse(report.detail)
} catch (error) {
console.log(error)
}
Object.assign(form, report, { detail })
} else {
fetchCached()
}
})
}
function fetchTemplate() {
getExperimentReportTemplate({ experiment_id: props.id }).then(res => {
form.detail = res.data.template
})
}
function fetchCached() {
getExperimentReportCache({ experiment_id: props.id }).then(res => {
const report = res.data.report
if (!report.experiment_id) {
fetchTemplate()
return
}
let detail = []
try {
detail = JSON.parse(report.detail)
} catch (error) {
console.log(error)
}
Object.assign(form, report, { detail })
})
}
onMounted(() => {
fetchInfo()
})
// 关闭
function handleClose() {
ElMessageBox.confirm('此操作将不做任何修改,确认关闭该页面?', '提示').then(() => {
window.close()
})
}
// 暂存
function handleCache() {
const params = { ...form, detail: JSON.stringify(form.detail) }
cacheExperimentReport(params).then(() => {
ElMessage.success('暂存成功')
})
}
// 提交
function handleSubmit() {
formRef.validate().then(() => {
const params = { ...form, detail: JSON.stringify(form.detail) }
ElMessageBox.confirm('提交报告之后,您将不能进行修改报告,确认提交实验报告?', '提示').then(() => {
updateExperimentReport(params).then(() => {
ElMessage.success('提交成功')
fetchInfo()
})
})
})
}
// 附件
let currentRaw = $ref(null)
// 本地上传
const { files, open, reset } = useFileDialog()
function handleUpload(data) {
currentRaw = data
open({ multiple: false })
}
watchEffect(() => {
if (!files.value?.length) return
const [file] = files.value
upload(file).then(url => {
const formatted = useDateFormat(useNow(), 'YYYY-MM-DD HH:mm:ss')
currentRaw.files.push({
id: Date.now().toString(),
url,
name: file.name,
type: '1',
file_type: file.type,
updated_time: formatted.value,
upload_time: formatted.value
})
reset()
})
})
// 关联截图
let bindCaptureVisible = $ref(false)
function handleBindCapture(data) {
currentRaw = data
bindCaptureVisible = true
}
// 删除文件
function handleRemoveFile(data, index) {
data.files.splice(index, 1)
}
function handleUpdateBindCapture(files) {
currentRaw.files = files
}
</script> </script>
<template> <template>
<AppCard title="查看实验报告"> <AppCard title="查看实验报告">
<h1 class="report-title">查看实验报告</h1> <ReportPreview v-bind="$attrs"></ReportPreview>
<el-form ref="formRef" :model="form" label-suffix=":" hide-required-asterisk v-if="experiment">
<el-row>
<el-col :span="12">
<el-form-item label="课程名称">{{ experiment.course.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实验名称">{{ experiment.name }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="实验地点">{{ report.experiment_address }} </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实验日期"> {{ report.experiment_date }} </el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="实验类型">{{ experiment.type_level }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="指导教师">{{ teacherText }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="专业">{{ experiment.student.specialty.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班级">{{ classText }}</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="姓名">{{ experiment.student.name }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学号">{{ experiment.student.sno_number }}</el-form-item>
</el-col>
</el-row>
<el-divider />
<el-form-item :label="item.name" v-for="item in form.detail" :key="item.id" class="report-form-item">
<!-- 内容 -->
<template v-if="item.type === 1">
{{ item.notice_message }}
<AppEditor v-model="item.content" :disabled="disabled" :height="200"></AppEditor>
</template>
<!-- 附件 -->
<template v-if="item.type === 2">
<el-button type="primary" @click="handleUpload(item)" v-if="item.categories.includes(2)">上传附件</el-button>
<el-button type="primary" @click="handleBindCapture(item)" v-if="item.categories.includes(1)"
>关联截图</el-button
>
<el-table :data="item.files" stripe :header-cell-style="{ background: '#ededed' }" style="margin-top: 20px">
<el-table-column label="序号" type="index" width="80" align="center"></el-table-column>
<el-table-column label="附件类型" prop="type" align="center">
<template #default="{ row }">
{{ row.type === '1' ? '本地上传' : '实验截图' }}
</template>
</el-table-column>
<el-table-column label="附件名称" prop="name" align="center"></el-table-column>
<el-table-column label="更新时间" prop="updated_time" align="center"></el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="{ row, $index }">
<el-button text type="primary"><a :href="row.url" target="_blank">查看</a></el-button>
<el-button text type="danger" @click="handleRemoveFile(item, $index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<!-- 思考题 -->
<template v-if="item.type === 3">
{{ item.question_stem }}
<AppEditor v-model="item.content" :disabled="disabled" :height="200"></AppEditor>
</template>
</el-form-item>
<el-row justify="center">
<el-button type="primary" auto-insert-space @click="handleClose">关闭</el-button>
<el-button type="primary" auto-insert-space @click="handleCache">暂存</el-button>
<el-button type="primary" auto-insert-space @click="handleSubmit">提交报告</el-button>
</el-row>
</el-form>
<!-- 关联截图 -->
<BindCaptureDialog
v-model="bindCaptureVisible"
:experiment="detail"
:data="currentRaw"
@update="handleUpdateBindCapture"
v-if="bindCaptureVisible && currentRaw"></BindCaptureDialog>
</AppCard> </AppCard>
</template> </template>
<style lang="scss">
.report-title {
padding: 20px;
font-size: 20px;
text-align: center;
}
.report-form-item {
flex-direction: column;
.el-form-item__label {
font-weight: bold;
text-align: left;
justify-content: flex-start;
}
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论