This commit is contained in:
alvis
2020-06-14 16:44:45 +08:00
parent 6a10ed3e16
commit 943dae05da
2150 changed files with 986029 additions and 17 deletions

View File

@@ -0,0 +1,219 @@
<template>
<div style="margin-top: 10px">
<el-row>
<el-carousel :interval="5000" arrow="always" type="card">
<el-carousel-item >
<img src="@/assets/carousel/1.png" class="carousel-img">
</el-carousel-item>
<el-carousel-item >
<img src="@/assets/carousel/2.png" class="carousel-img">
</el-carousel-item>
<el-carousel-item >
<img src="@/assets/carousel/3.png" class="carousel-img">
</el-carousel-item>
<el-carousel-item >
<img src="@/assets/carousel/4.png" class="carousel-img">
</el-carousel-item>
</el-carousel>
</el-row>
<el-row class="app-item-contain">
<h3 class="index-title-h3" style="border-left: solid 10px #3651d4;">任务中心</h3>
<div style="padding-left: 15px">
<el-collapse v-loading="taskLoading" accordion v-if="taskList.length!==0">
<el-collapse-item :title="taskItem.title" :name="taskItem.id" :key="taskItem.id" v-for="taskItem in taskList">
<table class="index-task-table">
<tr v-for="paperItem in taskItem.paperItems" :key="paperItem.examPaperId">
<td class="index-task-table-paper">
{{paperItem.examPaperName}}
</td>
<td width="70px">
<el-tag :type="statusTagFormatter(paperItem.status)" v-if="paperItem.status !== null" size="mini">
{{ statusTextFormatter(paperItem.status) }}
</el-tag>
</td>
<td width="80px">
<router-link target="_blank" :to="{path:'/do',query:{id:paperItem.examPaperId}}" v-if="paperItem.status === null">
<el-button type="text" size="small">开始答题</el-button>
</router-link>
<router-link target="_blank" :to="{path:'/edit',query:{id:paperItem.examPaperAnswerId}}" v-else-if="paperItem.status === 1">
<el-button type="text" size="small">批改试卷</el-button>
</router-link>
<router-link target="_blank" :to="{path:'/read',query:{id:paperItem.examPaperAnswerId}}" v-else-if="paperItem.status === 2">
<el-button type="text" size="small">查看试卷</el-button>
</router-link>
</td>
</tr>
</table>
</el-collapse-item>
</el-collapse>
</div>
</el-row>
<el-row class="app-item-contain">
<h3 class="index-title-h3">固定试卷</h3>
<div style="padding-left: 15px">
<el-col :span="4" v-for="(item, index) in fixedPaper" :key="index" :offset="index > 0 ? 1 : 0">
<el-card :body-style="{ padding: '0px' }" v-loading="loading">
<img src="@/assets/exam-paper/show1.png" class="image">
<div style="padding: 14px;">
<span>{{item.name}}</span>
<div class="bottom clearfix">
<router-link target="_blank" :to="{path:'/do',query:{id:item.id}}">
<el-button type="text" class="button">开始做题</el-button>
</router-link>
</div>
</div>
</el-card>
</el-col>
</div>
</el-row>
<el-row class="app-item-contain">
<h3 class="index-title-h3" style="border-left: solid 10px rgb(220, 208, 65);">时段试卷</h3>
<div style="padding-left: 15px">
<el-col :span="4" v-for="(item, index) in timeLimitPaper" :key="index" :offset="index > 0 ? 1 : 0">
<el-card :body-style="{ padding: '0px' }" v-loading="loading">
<img src="@/assets/exam-paper/show2.png" class="image">
<div style="padding: 14px;">
<span>{{item.name}}</span>
<p class="index-limit-paper-time">
<span>{{item.startTime}}</span>
<br/>
<span>{{item.endTime}}</span>
</p>
<div class="bottom clearfix">
<router-link target="_blank" :to="{path:'/do',query:{id:item.id}}">
<el-button type="text" class="button">开始做题</el-button>
</router-link>
</div>
</div>
</el-card>
</el-col>
</div>
</el-row>
<el-row class="app-item-contain">
<h3 class="index-title-h3" style="border-left: solid 10px #e454b1;">推送试卷</h3>
<div style="padding-left: 15px">
<el-col :span="4" v-for="(o, index) in pushPaper" :key="o" :offset="index > 0 ? 1 : 0">
<el-card :body-style="{ padding: '0px' }" v-loading="loading">
<img src="@/assets/exam-paper/show3.png" class="image">
<div style="padding: 14px;">
<span>{{item.name}}</span>
<p class="index-limit-paper-time">
<span>{{item.startTime}}</span>
<br/>
<span>{{item.endTime}}</span>
</p>
<div class="bottom clearfix">
<router-link target="_blank" :to="{path:'/do',query:{id:item.id}}">
<el-button type="text" class="button">开始做题</el-button>
</router-link>
</div>
</div>
</el-card>
</el-col>
</div>
</el-row>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import indexApi from '@/api/dashboard'
export default {
data () {
return {
fixedPaper: [],
timeLimitPaper: [],
pushPaper: [],
loading: false,
taskLoading: false,
taskList: []
}
},
created () {
let _this = this
this.loading = true
indexApi.index().then(re => {
_this.fixedPaper = re.response.fixedPaper
_this.timeLimitPaper = re.response.timeLimitPaper
_this.pushPaper = re.response.pushPaper
_this.loading = false
})
this.taskLoading = true
indexApi.task().then(re => {
_this.taskList = re.response
_this.taskLoading = false
})
},
methods: {
statusTagFormatter (status) {
return this.enumFormat(this.statusTag, status)
},
statusTextFormatter (status) {
return this.enumFormat(this.statusEnum, status)
}
},
computed: {
...mapGetters('enumItem', [
'enumFormat'
]),
...mapState('enumItem', {
statusEnum: state => state.exam.examPaperAnswer.statusEnum,
statusTag: state => state.exam.examPaperAnswer.statusTag
})
}
}
</script>
<style lang="scss" scoped>
.index-title-h3 {
font-size: 22px;
font-weight: 400;
color: #1f2f3d;
border-left: solid 10px #2ce8b4;
padding-left: 10px;
}
.el-carousel__item h3 {
color: #475669;
font-size: 18px;
opacity: 0.75;
line-height: 300px;
margin: 0;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n+1) {
background-color: #d3dce6;
}
.bottom {
margin-top: 13px;
line-height: 12px;
}
.button {
padding: 0;
float: right;
}
.image {
width: 50%;
height: 80%;
display: block;
margin: 20px auto 10px auto;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
</style>

View File

@@ -0,0 +1,99 @@
<template>
<div class="errPage-container">
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
返回
</el-button>
<el-row>
<el-col :span="12">
<h1 class="text-jumbo text-ginormous">
Oops!
</h1>
gif来源<a href="https://zh.airbnb.com/" target="_blank">airbnb</a> 页面
<h2>你没有权限去该页面</h2>
<h6>如有不满请联系你领导</h6>
<ul class="list-unstyled">
<li>或者你可以去:</li>
<li class="link-type">
<router-link to="/dashboard">
回首页
</router-link>
</li>
<li class="link-type">
<a href="https://www.taobao.com/">随便看看</a>
</li>
<li><a href="#" @click.prevent="dialogVisible=true">点我看图</a></li>
</ul>
</el-col>
<el-col :span="12">
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
</el-col>
</el-row>
<el-dialog :visible.sync="dialogVisible" title="随便看">
<img :src="ewizardClap" class="pan-img">
</el-dialog>
</div>
</template>
<script>
import errGif from '@/assets/401_images/401.gif'
export default {
name: 'Page401',
data () {
return {
errGif: errGif + '?' + +new Date(),
ewizardClap: 'https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646',
dialogVisible: false
}
},
methods: {
back () {
if (this.$route.query.noGoBack) {
this.$router.push({ path: '/dashboard' })
} else {
this.$router.go(-1)
}
}
}
}
</script>
<style lang="scss" scoped>
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none!important;
}
.pan-gif {
margin: 0 auto;
display: block;
}
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
</style>

View File

@@ -0,0 +1,225 @@
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查你的访问地址是否正确, 或点击下面按钮返回主页.</div>
<a href="" class="bullshit__return-home">返回主页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Page404',
computed: {
message () {
return '页面未找到...'
}
}
}
</script>
<style lang="scss" scoped>
.wscn-http404-container{
transform: translate(-50%,-50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

View File

@@ -0,0 +1,127 @@
<template>
<div v-loading="qLoading" style="line-height:1.8">
<div v-if="qType==1||qType==2||qType==3||qType==4||qType==5">
<div v-if="qType==1" >
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<el-radio-group v-model="answer.content">
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
</div>
</div>
<div v-else-if="qType==2" >
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<el-checkbox-group v-model="answer.contentArray" >
<el-checkbox v-for="item in question.items" :label="item.prefix" :key="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div v-else-if="qType==3" >
<div class="q-title" v-html="question.title" style="display: inline;margin-right: 10px"/>
<span style="padding-right: 10px;">(</span>
<el-radio-group v-model="answer.content">
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix">
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
<span style="padding-left: 10px;">)</span>
</div>
<div v-else-if="qType==4" >
<div class="q-title" v-html="question.title"/>
<div v-if="answer.contentArray!==null">
<el-form-item :label="item.prefix" :key="item.prefix" v-for="item in question.items" label-width="50px" style="margin-top: 10px;margin-bottom: 10px;">
<el-input v-model="answer.contentArray[item.prefix-1]" />
</el-form-item>
</div>
</div>
<div v-else-if="qType==5">
<div class="q-title" v-html="question.title"/>
<div>
<el-input v-model="answer.content" type="textarea" rows="5" ></el-input>
</div>
</div>
<div class="question-answer-show-item" style="margin-top: 15px">
<span class="question-show-item">结果</span>
<el-tag :type="doRightTagFormatter(answer.doRight)">
{{ doRightTextFormatter(answer.doRight) }}
</el-tag>
</div>
<div class="question-answer-show-item">
<span class="question-show-item">分数</span>
<span>{{question.score}}</span>
</div>
<div class="question-answer-show-item">
<span class="question-show-item">难度</span>
<el-rate disabled v-model="question.difficult" class="question-show-item"></el-rate>
</div>
<br/>
<div class="question-answer-show-item" style="line-height: 1.8">
<span class="question-show-item">解析</span>
<span v-html="question.analyze" class="q-item-span-content" />
</div>
<div class="question-answer-show-item">
<span class="question-show-item">正确答案</span>
<span v-if="qType==1||qType==2 ||qType==5" v-html="question.correct" class="q-item-span-content"/>
<span v-if="qType==3" v-html="trueFalseFormatter(question)" class="q-item-span-content"/>
<span v-if="qType==4">{{question.correctArray}}</span>
</div>
</div>
<div v-else>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'QuestionShow',
props: {
question: {
type: Object,
default: function () {
return {}
}
},
answer: {
type: Object,
default: function () {
return { id: null, content: '', contentArray: [], doRight: false }
}
},
qLoading: {
type: Boolean,
default: false
},
qType: {
type: Number,
default: 0
}
},
methods: {
trueFalseFormatter (question) {
return question.items.filter(d => d.prefix === question.correct)[0].content
},
doRightTagFormatter (status) {
return this.enumFormat(this.doRightTag, status)
},
doRightTextFormatter (status) {
return this.enumFormat(this.doRightEnum, status)
}
},
computed: {
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
doRightEnum: state => state.exam.question.answer.doRightEnum,
doRightTag: state => state.exam.question.answer.doRightTag
})
}
}
</script>

View File

@@ -0,0 +1,83 @@
<template>
<div style="line-height:1.8">
<div v-if="qType==1" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<el-radio-group v-model="answer.content" @change="answer.completed = true" >
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
</div>
</div>
<div v-else-if="qType==2" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<el-checkbox-group v-model="answer.contentArray" @change="answer.completed = true" >
<el-checkbox v-for="item in question.items" :label="item.prefix" :key="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div v-else-if="qType==3" v-loading="qLoading">
<div class="q-title" v-html="question.title" style="display: inline;margin-right: 10px"/>
<span style="padding-right: 10px;">(</span>
<el-radio-group v-model="answer.content" @change="answer.completed = true" >
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
<span style="padding-left: 10px;">)</span>
</div>
<div v-else-if="qType==4" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div>
<el-form-item :label="item.prefix" :key="item.prefix" v-for="item in question.items" label-width="50px" style="margin-top: 10px;margin-bottom: 10px;">
<el-input v-model="answer.contentArray[item.prefix-1]" @change="answer.completed = true" />
</el-form-item>
</div>
</div>
<div v-else-if="qType==5" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div>
<el-input v-model="answer.content" type="textarea" rows="5" @change="answer.completed = true"/>
</div>
</div>
<div v-else>
</div>
</div>
</template>
<script>
export default {
name: 'QuestionShow',
props: {
question: {
type: Object,
default: function () {
return {}
}
},
answer: {
type: Object,
default: function () {
return { id: null, content: '', contentArray: [] }
}
},
qLoading: {
type: Boolean,
default: false
},
qType: {
type: Number,
default: 0
}
},
methods: {
}
}
</script>

View File

@@ -0,0 +1,176 @@
<template>
<div>
<el-row class="do-exam-title">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag :type="questionCompleted(item.completed)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">{{item.itemOrder}}</el-tag>
</span>
<span class="do-exam-time">
<label>剩余时间</label>
<label>{{formatSeconds(remainTime)}}</label>
</span>
</el-col>
</el-row>
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag class="do-exam-title-tag" >{{item.itemOrder}}</el-tag>
</span>
<span class="do-exam-time">
<label>剩余时间</label>
</span>
</el-col>
</el-row>
<el-container class="app-item-contain">
<el-header class="align-center">
<h1>{{form.name}}</h1>
<div>
<span class="question-title-padding">试卷总分{{form.score}}</span>
<span class="question-title-padding">考试时间{{form.suggestTime}}分钟</span>
</div>
</el-header>
<el-main>
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<el-row :key="index" v-for="(titleItem,index) in form.titleItems">
<h3>{{titleItem.name}}</h3>
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length!==0">
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px" :id="'question-'+ questionItem.itemOrder">
<QuestionEdit :qType="questionItem.questionType" :question="questionItem"
:answer="answer.answerItems[questionItem.itemOrder-1]"/>
</el-form-item>
</el-card>
</el-row>
<el-row class="do-align-center">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button>取消</el-button>
</el-row>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import { formatSeconds } from '@/utils'
import QuestionEdit from '../components/QuestionEdit'
import examPaperApi from '@/api/examPaper'
import examPaperAnswerApi from '@/api/examPaperAnswer'
export default {
components: { QuestionEdit },
data () {
return {
form: {},
formLoading: false,
answer: {
questionId: null,
doTime: 0,
answerItems: []
},
timer: null,
remainTime: 0
}
},
created () {
let id = this.$route.query.id
let _this = this
if (id && parseInt(id) !== 0) {
_this.formLoading = true
examPaperApi.select(id).then(re => {
_this.form = re.response
_this.remainTime = re.response.suggestTime * 60
_this.initAnswer()
_this.timeReduce()
_this.formLoading = false
})
}
},
mounted () {
},
beforeDestroy () {
window.clearInterval(this.timer)
},
methods: {
formatSeconds (theTime) {
return formatSeconds(theTime)
},
timeReduce () {
let _this = this
this.timer = setInterval(function () {
if (_this.remainTime <= 0) {
_this.submitForm()
} else {
++_this.answer.doTime
--_this.remainTime
}
}, 1000)
},
questionCompleted (completed) {
return this.enumFormat(this.doCompletedTag, completed)
},
goAnchor (selector) {
this.$el.querySelector(selector).scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' })
},
initAnswer () {
this.answer.id = this.form.id
let titleItemArray = this.form.titleItems
for (let tIndex in titleItemArray) {
let questionArray = titleItemArray[tIndex].questionItems
for (let qIndex in questionArray) {
let question = questionArray[qIndex]
this.answer.answerItems.push({ questionId: question.id, content: null, contentArray: [], completed: false, itemOrder: question.itemOrder })
}
}
},
submitForm () {
let _this = this
window.clearInterval(_this.timer)
_this.formLoading = true
examPaperAnswerApi.answerSubmit(this.answer).then(re => {
if (re.code === 1) {
_this.$alert('试卷得分:' + re.response + '分', '考试结果', {
confirmButtonText: '返回考试记录',
callback: action => {
_this.$router.push('/record/index')
}
})
} else {
_this.$message.error(re.message)
}
_this.formLoading = false
}).catch(e => {
_this.formLoading = false
})
}
},
computed: {
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
doCompletedTag: state => state.exam.question.answer.doCompletedTag
})
}
}
</script>
<style lang="scss" scoped>
.align-center {
text-align: center
}
.exam-question-item {
padding: 10px;
.el-form-item__label {
font-size: 15px !important;
}
}
.question-title-padding {
padding-left: 25px;
padding-right: 25px;
}
</style>

View File

@@ -0,0 +1,154 @@
<template>
<div>
<el-row class="do-exam-title" style="background-color: #F5F5DC">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag :type="questionDoRightTag(item.doRight)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag class="do-exam-title-tag" >{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<el-container class="app-item-contain">
<el-header class="align-center">
<h1>{{form.name}}</h1>
<div>
<span class="question-title-padding">试卷得分{{answer.score}}</span>
<span class="question-title-padding">试卷耗时{{formatSeconds(answer.doTime)}}</span>
</div>
</el-header>
<el-main>
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<el-row :key="index" v-for="(titleItem,index) in form.titleItems">
<h3>{{titleItem.name}}</h3>
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length!==0">
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px" :id="'question-'+ questionItem.itemOrder">
<el-row>
<QuestionAnswerShow :qType="questionItem.questionType" :question="questionItem" :answer="answer.answerItems[questionItem.itemOrder-1]"/>
</el-row>
<el-row v-if="answer.answerItems[questionItem.itemOrder-1].doRight === null">
<label style="color: #e6a23c">批改</label>
<el-radio-group v-model="answer.answerItems[questionItem.itemOrder-1].score">
<el-radio v-for="item in scoreSelect(questionItem.score)" :key="item" :label="item" >
{{item}}
</el-radio>
</el-radio-group>
</el-row>
</el-form-item>
</el-card>
</el-row>
<el-row class="do-align-center">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button>取消</el-button>
</el-row>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import { formatSeconds } from '@/utils'
import QuestionAnswerShow from '../components/QuestionAnswerShow'
import examPaperAnswerApi from '@/api/examPaperAnswer'
export default {
components: { QuestionAnswerShow },
data () {
return {
form: {},
formLoading: false,
answer: {
id: null,
score: 0,
doTime: 0,
answerItems: [],
doRight: false
}
}
},
created () {
let id = this.$route.query.id
let _this = this
if (id && parseInt(id) !== 0) {
_this.formLoading = true
examPaperAnswerApi.read(id).then(re => {
_this.form = re.response.paper
_this.answer = re.response.answer
_this.formLoading = false
})
}
},
methods: {
submitForm () {
let _this = this
_this.formLoading = true
examPaperAnswerApi.edit(this.answer).then(re => {
if (re.code === 1) {
_this.$alert('试卷得分:' + re.response + '分', '考试结果', {
confirmButtonText: '返回考试记录',
callback: action => {
_this.$router.push('/record/index')
}
})
} else {
_this.$message.error(re.message)
}
_this.formLoading = false
}).catch(e => {
_this.formLoading = false
})
},
scoreSelect (score) {
let array = []
for (let i = 0; i <= parseInt(score); i++) {
array.push(i.toString())
}
if (score.indexOf('.') !== -1) {
array.push(score)
}
return array
},
formatSeconds (theTime) {
return formatSeconds(theTime)
},
questionDoRightTag (status) {
return this.enumFormat(this.doRightTag, status)
},
goAnchor (selector) {
this.$el.querySelector(selector).scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' })
}
},
computed: {
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
doRightTag: state => state.exam.question.answer.doRightTag
})
}
}
</script>
<style lang="scss" scoped>
.align-center {
text-align: center
}
.exam-question-item{
padding: 10px;
.el-form-item__label{
font-size: 15px !important;
}
}
.question-title-padding{
padding-left: 25px;
padding-right: 25px;
}
</style>

View File

@@ -0,0 +1,111 @@
<template>
<div>
<el-row class="do-exam-title" style="background-color: #F5F5DC">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag :type="questionDoRightTag(item.doRight)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag class="do-exam-title-tag" >{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<el-container class="app-item-contain">
<el-header class="align-center">
<h1>{{form.name}}</h1>
<div>
<span class="question-title-padding">试卷得分{{answer.score}}</span>
<span class="question-title-padding">试卷耗时{{formatSeconds(answer.doTime)}}</span>
</div>
</el-header>
<el-main>
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<el-row :key="index" v-for="(titleItem,index) in form.titleItems">
<h3>{{titleItem.name}}</h3>
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length!==0">
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px" :id="'question-'+ questionItem.itemOrder">
<QuestionAnswerShow :qType="questionItem.questionType" :question="questionItem" :answer="answer.answerItems[questionItem.itemOrder-1]"/>
</el-form-item>
</el-card>
</el-row>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import { formatSeconds } from '@/utils'
import QuestionAnswerShow from '../components/QuestionAnswerShow'
import examPaperAnswerApi from '@/api/examPaperAnswer'
export default {
components: { QuestionAnswerShow },
data () {
return {
form: {},
formLoading: false,
answer: {
id: null,
score: 0,
doTime: 0,
answerItems: [],
doRight: false
}
}
},
created () {
let id = this.$route.query.id
let _this = this
if (id && parseInt(id) !== 0) {
_this.formLoading = true
examPaperAnswerApi.read(id).then(re => {
_this.form = re.response.paper
_this.answer = re.response.answer
_this.formLoading = false
})
}
},
methods: {
formatSeconds (theTime) {
return formatSeconds(theTime)
},
questionDoRightTag (status) {
return this.enumFormat(this.doRightTag, status)
},
goAnchor (selector) {
this.$el.querySelector(selector).scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' })
}
},
computed: {
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
doRightTag: state => state.exam.question.answer.doRightTag
})
}
}
</script>
<style lang="scss" scoped>
.align-center {
text-align: center
}
.exam-question-item{
padding: 10px;
.el-form-item__label{
font-size: 15px !important;
}
}
.question-title-padding{
padding-left: 25px;
padding-right: 25px;
}
</style>

View File

@@ -0,0 +1,415 @@
<template>
<div class="lowin lowin-blue">
<div class="lowin-brand">
<img src="@/assets/logo2.png" alt="logo" style="margin-top: 12px">
</div>
<div class="lowin-wrapper">
<div class="lowin-box lowin-login">
<div class="lowin-box-inner">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules">
<p>学之思考试系统</p>
<div class="lowin-group">
<label>用户名 </label>
<el-input ref="userName" v-model="loginForm.userName" class="lowin-input" placeholder="用户名" name="userName" type="text" tabindex="1" auto-complete="on"/>
</div>
<div class="lowin-group password-group">
<label>密码 <a href="#" class="forgot-link">忘记密码?</a></label>
<el-input class="lowin-input" :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType"
placeholder="密码" name="password" tabindex="2" auto-complete="on" @keyup.native="checkCapslock" @blur="capsTooltip = false" @keyup.enter.native="handleLogin"/>
</div>
<el-button :loading="loading" type="text" class="lowin-btn login-btn" @click.native.prevent="handleLogin">登录</el-button>
<div class="text-foot">
还没有账号?
<router-link to="/register" class="register-link">
注册
</router-link>
</div>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import loginApi from '@/api/login'
export default {
name: 'Login',
data () {
const validateUsername = (rule, value, callback) => {
if (value.length < 5) {
callback(new Error('用户名不能少于5个字符'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 5) {
callback(new Error('密码不能少于5个字符'))
} else {
callback()
}
}
return {
loginForm: {
userName: '',
password: '',
remember: false
},
loginRules: {
userName: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
passwordType: 'password',
capsTooltip: false,
loading: false,
showDialog: false
}
},
created () {
// window.addEventListener('storage', this.afterQRScan)
},
mounted () {
if (this.loginForm.userName === '') {
this.$refs.userName.focus()
} else if (this.loginForm.password === '') {
this.$refs.password.focus()
}
},
destroyed () {
// window.removeEventListener('storage', this.afterQRScan)
},
methods: {
checkCapslock ({ shiftKey, key } = {}) {
if (key && key.length === 1) {
// eslint-disable-next-line no-mixed-operators
if (shiftKey && (key >= 'a' && key <= 'z') || !shiftKey && (key >= 'A' && key <= 'Z')) {
this.capsTooltip = true
} else {
this.capsTooltip = false
}
}
if (key === 'CapsLock' && this.capsTooltip === true) {
this.capsTooltip = false
}
},
showPwd () {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin () {
let _this = this
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
loginApi.login(this.loginForm).then(function (result) {
if (result && result.code === 1) {
_this.setUserName(_this.loginForm.userName)
_this.$router.push({ path: '/' })
} else {
_this.loading = false
_this.$message.error(result.message)
}
}).catch(function (reason) {
_this.loading = false
})
} else {
return false
}
})
},
...mapMutations('user', ['setUserName'])
}
}
</script>
<style lang="scss">
.lowin-input{
.el-input__inner{
background-color: transparent !important;
border: 0px !important;
}
}
</style>
<style scoped>
.lowin {
/* variables */
--color-primary: #44a0b3;
--color-grey: rgba(68, 160, 179, .06);
--color-dark: rgba(68, 160, 179, .5);
--color-semidark: rgba(68, 160, 179, .5);
text-align: center;
margin: 60px 0 0 0;
font-family: 'Segoe UI';
font-size: 14px;
}
.lowin .lowin-wrapper {
-webkit-transition: all 1s;
-o-transition: all 1s;
transition: all 1s;
-webkit-perspective: 1000px;
perspective: 1000px;
position: relative;
height: 100%;
width: 360px;
margin: 0 auto;
}
.lowin.lowin-blue {
--color-primary: #0081C6;
--color-grey: rgba(0, 129, 198, .05);
--color-dark: rgba(0, 129, 198, .7);
--color-semidark: rgba(0, 129, 198, .45);
}
.lowin a {
color: var(--color-primary);
text-decoration: none;
border-bottom: 1px dashed var(--color-semidark);
margin-top: -3px;
padding-bottom: 2px;
}
.lowin * {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.lowin .lowin-brand {
overflow: hidden;
width: 100px;
height: 100px;
margin: 0 auto -50px auto;
border-radius: 50%;
-webkit-box-shadow: 0 4px 40px rgba(0, 0, 0, .07);
box-shadow: 0 4px 40px rgba(0, 0, 0, .07);
padding: 10px;
background-color: #fff;
z-index: 1;
position: relative;
}
.lowin .lowin-brand img {
width: 100%;
}
.lowin .lowin-box {
width: 100%;
position: absolute;
left: 0;
}
.lowin .lowin-box-inner {
background-color: #fff;
-webkit-box-shadow: 0 7px 25px rgba(0, 0, 0, .08);
box-shadow: 0 7px 25px rgba(0, 0, 0, .08);
padding: 60px 25px 25px 25px;
text-align: left;
border-radius: 3px;
}
.lowin .lowin-box::after {
content: ' ';
-webkit-box-shadow: 0 0 25px rgba(0, 0, 0, .1);
box-shadow: 0 0 25px rgba(0, 0, 0, .1);
-webkit-transform: translate(0, -92.6%) scale(.88);
-ms-transform: translate(0, -92.6%) scale(.88);
transform: translate(0, -92.6%) scale(.88);
border-radius: 3px;
position: absolute;
top: 100%;
left: 0;
width: 100%;
height: 100%;
background-color: #fff;
z-index: -1;
}
.lowin .lowin-box.lowin-flip {
-webkit-transform: rotate3d(0, 1, 0, -180deg);
transform: rotate3d(0, 1, 0, -180deg);
display: none;
opacity: 0;
}
.lowin .lowin-box p {
color: var(--color-semidark);
font-weight: 700;
margin-bottom: 20px;
text-align: center;
}
.lowin .lowin-box .lowin-group {
margin-bottom: 30px;
}
.lowin .lowin-box label {
margin-bottom: 5px;
display: inline-block;
width: 100%;
color: var(--color-semidark);
font-weight: 700;
}
.lowin .lowin-box label a {
float: right;
}
.lowin .lowin-box .lowin-input {
background-color: var(--color-grey);
color: var(--color-dark);
border: none;
border-radius: 3px;
padding: 5px 20px;
width: 100%;
outline: 0;
}
.lowin .lowin-box .lowin-btn {
display: inline-block;
width: 100%;
border: none;
color: #fff;
padding: 15px;
border-radius: 3px;
background-color: var(--color-primary);
-webkit-box-shadow: 0 2px 7px var(--color-semidark);
box-shadow: 0 2px 7px var(--color-semidark);
font-weight: 700;
outline: 0;
cursor: pointer;
-webkit-transition: all .5s;
-o-transition: all .5s;
transition: all .5s;
}
.lowin .lowin-box .lowin-btn:active {
-webkit-box-shadow: none;
box-shadow: none;
}
.lowin .lowin-box .lowin-btn:hover {
opacity: .9;
}
.lowin .text-foot {
text-align: center;
padding: 10px;
font-weight: 700;
margin-top: 20px;
color: var(--color-semidark);
}
/* animation */
.lowin .lowin-box.lowin-animated {
-webkit-animation-name: LowinAnimated;
animation-name: LowinAnimated;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animatedback {
-webkit-animation-name: LowinAnimatedBack;
animation-name: LowinAnimatedBack;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animated-flip {
-webkit-animation-name: LowinAnimatedFlip;
animation-name: LowinAnimatedFlip;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animated-flipback {
-webkit-animation-name: LowinAnimatedFlipBack;
animation-name: LowinAnimatedFlipBack;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-brand.lowin-animated {
-webkit-animation-name: LowinBrandAnimated;
animation-name: LowinBrandAnimated;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-group.password-group {
-webkit-transition: all 1s;
-o-transition: all 1s;
transition: all 1s;
}
.lowin .lowin-group.password-group.lowin-animated {
-webkit-animation-name: LowinPasswordAnimated;
animation-name: LowinPasswordAnimated;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.lowin .lowin-group.password-group.lowin-animated-back {
-webkit-animation-name: LowinPasswordAnimatedBack;
animation-name: LowinPasswordAnimatedBack;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
@media screen and (max-width: 320px) {
.lowin .lowin-wrapper {
width: 100%;
}
.lowin .lowin-box {
padding: 0 10px;
}
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<div style="margin-top: 10px" class="app-contain">
<el-tabs tab-position="left" v-model="tabId" @tab-click="subjectChange" >
<el-tab-pane :label="item.name" :key="item.id" :name="item.id" v-for="item in subjectList" style="margin-left: 20px;" >
<el-row style="float: right">
<el-radio-group v-model="queryParam.paperType" size="mini" @change="paperTypeChange" >
<el-radio v-for="item in paperTypeEnum" size="mini" :key="item.key" :label="item.key">{{item.value}}</el-radio>
</el-radio-group>
</el-row>
<el-table v-loading="listLoading" :data="tableData" fit highlight-current-row style="width: 100%">
<el-table-column prop="id" label="序号" width="90px"/>
<el-table-column prop="name" label="名称" />
<el-table-column align="right">
<template slot-scope="{row}">
<router-link target="_blank" :to="{path:'/do',query:{id:row.id}}">
<el-button type="text" size="small">开始答题</el-button>
</router-link>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :background="false" :page.sync="queryParam.pageIndex" :limit.sync="queryParam.pageSize"
@pagination="search" style="margin-top: 20px"/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { mapState } from 'vuex'
import Pagination from '@/components/Pagination'
import examPaperApi from '@/api/examPaper'
import subjectApi from '@/api/subject'
export default {
components: { Pagination },
data () {
return {
queryParam: {
paperType: 1,
subjectId: 0,
pageIndex: 1,
pageSize: 10
},
tabId: '',
listLoading: true,
subjectList: [],
tableData: [],
total: 0
}
},
created () {
this.initSubject()
},
methods: {
initSubject () {
let _this = this
subjectApi.list().then(re => {
_this.subjectList = re.response
let subjectId = _this.subjectList[0].id
_this.queryParam.subjectId = subjectId
_this.tabId = subjectId.toString()
_this.search()
})
},
search () {
this.listLoading = true
examPaperApi.pageList(this.queryParam).then(data => {
const re = data.response
this.tableData = re.list
this.total = re.total
this.queryParam.pageIndex = re.pageNum
this.listLoading = false
})
},
paperTypeChange (val) {
this.search()
},
subjectChange (tab, event) {
this.queryParam.subjectId = Number(this.tabId)
this.search()
}
},
computed: {
...mapState('enumItem', {
paperTypeEnum: state => state.exam.examPaper.paperTypeEnum
})
}
}
</script>

View File

@@ -0,0 +1,99 @@
<template>
<div style="margin-top: 10px" class="app-contain">
<el-row :gutter="50">
<el-col :span="14">
<el-table v-loading="listLoading" :data="tableData" fit highlight-current-row style="width: 100%" @row-click="itemSelect">
<el-table-column prop="shortTitle" label="题干" show-overflow-tooltip />
<el-table-column prop="questionType" label="题型" :formatter="questionTypeFormatter" width="70" />
<el-table-column prop="subjectName" label="学科" width="50" />
<el-table-column prop="createTime" label="做题时间" width="170" />
</el-table>
<pagination v-show="total>0" :total="total" :background="false" :page.sync="queryParam.pageIndex" :limit.sync="queryParam.pageSize"
@pagination="search" style="margin-top: 20px"/>
</el-col>
<el-col :span="10" >
<el-card class="record-answer-info">
<el-form>
<el-form-item>
<QuestionAnswerShow :qType="selectItem.questionType" :qLoading="qAnswerLoading" :question="selectItem.questionItem" :answer="selectItem.answerItem"/>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import Pagination from '@/components/Pagination'
import questionAnswerApi from '@/api/questionAnswer'
import QuestionAnswerShow from '../exam/components/QuestionAnswerShow'
export default {
components: { Pagination, QuestionAnswerShow },
data () {
return {
queryParam: {
pageIndex: 1,
pageSize: 10
},
listLoading: false,
tableData: [],
total: 0,
qAnswerLoading: false,
selectItem: {
questionType: 0,
questionItem: null,
answerItem: null
}
}
},
created () {
this.search()
},
methods: {
search () {
this.listLoading = true
let _this = this
questionAnswerApi.pageList(this.queryParam).then(data => {
const re = data.response
_this.tableData = re.list
_this.total = re.total
_this.queryParam.pageIndex = re.pageNum
_this.listLoading = false
if (re.list.length !== 0) {
_this.qAnswerShow(re.list[0].id)
}
})
},
itemSelect (row, column, event) {
this.qAnswerShow(row.id)
},
qAnswerShow (id) {
let _this = this
this.qAnswerLoading = true
questionAnswerApi.select(id).then(re => {
let response = re.response
_this.selectItem.questionType = response.questionVM.questionType
_this.selectItem.questionItem = response.questionVM
_this.selectItem.answerItem = response.questionAnswerVM
_this.qAnswerLoading = false
})
},
questionTypeFormatter (row, column, cellValue, index) {
return this.enumFormat(this.questionTypeEnum, cellValue)
}
},
computed: {
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
questionTypeEnum: state => state.exam.question.typeEnum
})
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,125 @@
<template>
<div style="margin-top: 10px" class="app-contain">
<el-row :gutter="50">
<el-col :span="18">
<el-table v-loading="listLoading" :data="tableData" fit highlight-current-row style="width: 100%" @row-click="itemSelect">
<el-table-column prop="id" label="序号" width="90px"/>
<el-table-column prop="paperName" label="名称" />
<el-table-column prop="subjectName" label="学科" width="70" />
<el-table-column label="状态" prop="status" width="100px">
<template slot-scope="{row}">
<el-tag :type="statusTagFormatter(row.status)">
{{ statusTextFormatter(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="做题时间" width="170" />
<el-table-column align="right" width="70">
<template slot-scope="{row}">
<router-link target="_blank" :to="{path:'/edit',query:{id:row.id}}" v-if="row.status === 1 ">
<el-button type="text" size="small">批改</el-button>
</router-link>
<router-link target="_blank" :to="{path:'/read',query:{id:row.id}}" v-if="row.status === 2 ">
<el-button type="text" size="small">查看试卷</el-button>
</router-link>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :background="false" :page.sync="queryParam.pageIndex" :limit.sync="queryParam.pageSize"
@pagination="search" style="margin-top: 20px"/>
</el-col>
<el-col :span="6" >
<el-card class="record-answer-info">
<el-form label-width="50%" >
<el-form-item label="系统判分:">
<span>{{selectItem.systemScore}}</span>
</el-form-item>
<el-form-item label="最终得分:">
<span>{{selectItem.userScore}}</span>
</el-form-item>
<el-form-item label="试卷总分:">
<span>{{selectItem.paperScore}}</span>
</el-form-item>
<el-form-item label="正确题数:">
<span>{{selectItem.questionCorrect}}</span>
</el-form-item>
<el-form-item label="总题数:">
<span>{{selectItem.questionCount}}</span>
</el-form-item>
<el-form-item label="用时:">
<span>{{selectItem.doTime}}</span>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import Pagination from '@/components/Pagination'
import examPaperAnswerApi from '@/api/examPaperAnswer'
import { scrollTo } from '@/utils/scroll-to'
export default {
components: { Pagination },
data () {
return {
queryParam: {
pageIndex: 1,
pageSize: 10
},
listLoading: false,
tableData: [],
total: 0,
selectItem: {
systemScore: '0',
userScore: '0',
doTime: '0',
paperScore: '0',
questionCorrect: 0,
questionCount: 0
}
}
},
created () {
this.search()
scrollTo(0, 800)
},
methods: {
search () {
this.listLoading = true
let _this = this
examPaperAnswerApi.pageList(this.queryParam).then(data => {
const re = data.response
_this.tableData = re.list
_this.total = re.total
_this.queryParam.pageIndex = re.pageNum
_this.listLoading = false
})
},
itemSelect (row, column, event) {
this.selectItem = row
},
statusTagFormatter (status) {
return this.enumFormat(this.statusTag, status)
},
statusTextFormatter (status) {
return this.enumFormat(this.statusEnum, status)
}
},
computed: {
...mapGetters('enumItem', [
'enumFormat'
]),
...mapState('enumItem', {
statusEnum: state => state.exam.examPaperAnswer.statusEnum,
statusTag: state => state.exam.examPaperAnswer.statusTag
})
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,358 @@
<template>
<div class="lowin lowin-blue">
<div class="lowin-brand">
<img src="@/assets/logo2.png" alt="logo" style="margin-top: 12px">
</div>
<div class="lowin-wrapper">
<div class="lowin-box lowin-register">
<div class="lowin-box-inner">
<el-form ref="loginForm" :model="loginForm">
<p>学之思考试系统</p>
<div class="lowin-group">
<label>用户名 </label>
<el-input ref="userName" v-model="loginForm.userName" class="lowin-input" placeholder="用户名"
name="userName" type="text" tabindex="1" auto-complete="on"/>
</div>
<div class="lowin-group password-group">
<label>密码</label>
<el-input class="lowin-input" ref="password" v-model="loginForm.password"
placeholder="密码" name="password" tabindex="2" auto-complete="on"
@keyup.enter.native="handleLogin"/>
</div>
<div class="lowin-group">
<label>年级 </label>
<el-select class="lowin-input" v-model="loginForm.userLevel" placeholder="年级">
<el-option v-for="item in levelEnum" :key="item.key" :value="item.key" :label="item.value"></el-option>
</el-select>
</div>
<el-button type="text" class="lowin-btn login-btn" @click.native.prevent="handleRegister">注册</el-button>
<div class="text-foot">
已有账号?
<router-link to="/login" class="login-link">
登录
</router-link>
</div>
</el-form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapMutations, mapState } from 'vuex'
import registerApi from '@/api/register'
export default {
name: 'Login',
data () {
return {
loginForm: {
userName: '',
password: '',
userLevel: 1
}
}
},
methods: {
handleRegister () {
let _this = this
registerApi.register(this.loginForm).then(function (result) {
if (result && result.code === 1) {
_this.$router.push({ path: '/login' })
} else {
_this.$message.error(result.message)
}
})
},
...mapMutations('user', ['setUserName'])
},
computed: {
...mapState('enumItem', {
levelEnum: state => state.user.levelEnum
})
}
}
</script>
<style lang="scss">
.lowin-input {
.el-input__inner {
background-color: transparent !important;
border: 0px !important;
}
}
</style>
<style scoped>
.lowin {
/* variables */
--color-primary: #44a0b3;
--color-grey: rgba(68, 160, 179, .06);
--color-dark: rgba(68, 160, 179, .5);
--color-semidark: rgba(68, 160, 179, .5);
text-align: center;
margin: 20px 0 0 0;
font-family: 'Segoe UI';
font-size: 14px;
}
.lowin .lowin-wrapper {
-webkit-transition: all 1s;
-o-transition: all 1s;
transition: all 1s;
-webkit-perspective: 1000px;
perspective: 1000px;
position: relative;
height: 100%;
width: 360px;
margin: 0 auto;
}
.lowin.lowin-blue {
--color-primary: #0081C6;
--color-grey: rgba(0, 129, 198, .05);
--color-dark: rgba(0, 129, 198, .7);
--color-semidark: rgba(0, 129, 198, .45);
}
.lowin a {
color: var(--color-primary);
text-decoration: none;
border-bottom: 1px dashed var(--color-semidark);
margin-top: -3px;
padding-bottom: 2px;
}
.lowin * {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.lowin .lowin-brand {
overflow: hidden;
width: 100px;
height: 100px;
margin: 0 auto -50px auto;
border-radius: 50%;
-webkit-box-shadow: 0 4px 40px rgba(0, 0, 0, .07);
box-shadow: 0 4px 40px rgba(0, 0, 0, .07);
padding: 10px;
background-color: #fff;
z-index: 1;
position: relative;
}
.lowin .lowin-brand img {
width: 100%;
}
.lowin .lowin-box {
width: 100%;
position: absolute;
left: 0;
}
.lowin .lowin-box-inner {
background-color: #fff;
-webkit-box-shadow: 0 7px 25px rgba(0, 0, 0, .08);
box-shadow: 0 7px 25px rgba(0, 0, 0, .08);
padding: 60px 25px 25px 25px;
text-align: left;
border-radius: 3px;
}
.lowin .lowin-box::after {
content: ' ';
-webkit-box-shadow: 0 0 25px rgba(0, 0, 0, .1);
box-shadow: 0 0 25px rgba(0, 0, 0, .1);
-webkit-transform: translate(0, -92.6%) scale(.88);
-ms-transform: translate(0, -92.6%) scale(.88);
transform: translate(0, -92.6%) scale(.88);
border-radius: 3px;
position: absolute;
top: 100%;
left: 0;
width: 100%;
height: 100%;
background-color: #fff;
z-index: -1;
}
.lowin .lowin-box.lowin-flip {
-webkit-transform: rotate3d(0, 1, 0, -180deg);
transform: rotate3d(0, 1, 0, -180deg);
display: none;
opacity: 0;
}
.lowin .lowin-box p {
color: var(--color-semidark);
font-weight: 700;
margin-bottom: 20px;
text-align: center;
}
.lowin .lowin-box .lowin-group {
margin-bottom: 30px;
}
.lowin .lowin-box label {
margin-bottom: 5px;
display: inline-block;
width: 100%;
color: var(--color-semidark);
font-weight: 700;
}
.lowin .lowin-box label a {
float: right;
}
.lowin .lowin-box .lowin-input {
background-color: var(--color-grey);
color: var(--color-dark);
border: none;
border-radius: 3px;
padding: 5px 20px;
width: 100%;
outline: 0;
}
.lowin .lowin-box .lowin-btn {
display: inline-block;
width: 100%;
border: none;
color: #fff;
padding: 15px;
border-radius: 3px;
background-color: var(--color-primary);
-webkit-box-shadow: 0 2px 7px var(--color-semidark);
box-shadow: 0 2px 7px var(--color-semidark);
font-weight: 700;
outline: 0;
cursor: pointer;
-webkit-transition: all .5s;
-o-transition: all .5s;
transition: all .5s;
}
.lowin .lowin-box .lowin-btn:active {
-webkit-box-shadow: none;
box-shadow: none;
}
.lowin .lowin-box .lowin-btn:hover {
opacity: .9;
}
.lowin .text-foot {
text-align: center;
padding: 10px;
font-weight: 700;
margin-top: 20px;
color: var(--color-semidark);
}
/* animation */
.lowin .lowin-box.lowin-animated {
-webkit-animation-name: LowinAnimated;
animation-name: LowinAnimated;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animatedback {
-webkit-animation-name: LowinAnimatedBack;
animation-name: LowinAnimatedBack;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animated-flip {
-webkit-animation-name: LowinAnimatedFlip;
animation-name: LowinAnimatedFlip;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-box.lowin-animated-flipback {
-webkit-animation-name: LowinAnimatedFlipBack;
animation-name: LowinAnimatedFlipBack;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-brand.lowin-animated {
-webkit-animation-name: LowinBrandAnimated;
animation-name: LowinBrandAnimated;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
.lowin .lowin-group.password-group {
-webkit-transition: all 1s;
-o-transition: all 1s;
transition: all 1s;
}
.lowin .lowin-group.password-group.lowin-animated {
-webkit-animation-name: LowinPasswordAnimated;
animation-name: LowinPasswordAnimated;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.lowin .lowin-group.password-group.lowin-animated-back {
-webkit-animation-name: LowinPasswordAnimatedBack;
animation-name: LowinPasswordAnimatedBack;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
@media screen and (max-width: 320px) {
.lowin .lowin-wrapper {
width: 100%;
}
.lowin .lowin-box {
padding: 0 10px;
}
}
</style>

View File

@@ -0,0 +1,162 @@
<template>
<div style="margin-top: 10px" class="app-contain">
<el-row :gutter="50">
<el-col :span="7">
<el-card>
<div slot="header" class="clearfix">
<span>个人信息</span>
</div>
<el-row style="text-align: center">
<el-upload action="/api/student/upload/image" accept=".jpg,.png" :show-file-list="false" :on-success="uploadSuccess">
<el-avatar class="el-dropdown-avatar" :size="100" :src="form.imagePath === null ? require('@/assets/avatar.png') : form.imagePath"></el-avatar>
</el-upload>
</el-row>
<el-row class="user-info-userName">
<label>{{form.userName}}</label>
</el-row>
<el-divider/>
<el-row class="user-info-fullInfo">
<label>姓名{{form.realName}}</label><br/>
<label>年级{{levelFormatter(form.userLevel)}}</label><br/>
<label>注册时间{{form.createTime}}</label><br/>
</el-row>
</el-card>
</el-col>
<el-col :span="17">
<el-card shadow="hover">
<el-tabs active-name="event" type="card">
<el-tab-pane label="用户动态" name="event">
<div class="block">
<el-timeline>
<el-timeline-item :timestamp="item.createTime" placement="top" :key="item.id" v-for="item in event">
<el-card>
<p>{{item.content}}</p>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
<el-tab-pane label="个人资料修改" name="update">
<el-form :model="form" ref="form" label-width="100px" v-loading="formLoading" :rules="rules">
<el-form-item label="真实姓名:" prop="realName" required>
<el-input v-model="form.realName"></el-input>
</el-form-item>
<el-form-item label="年龄:">
<el-input v-model="form.age"></el-input>
</el-form-item>
<el-form-item label="性别:">
<el-select v-model="form.sex" placeholder="性别" clearable>
<el-option v-for="item in sexEnum" :key="item.key" :value="item.key"
:label="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="出生日期:">
<el-date-picker v-model="form.birthDay" type="date" placeholder="选择日期"/>
</el-form-item>
<el-form-item label="手机:">
<el-input v-model="form.phone"></el-input>
</el-form-item>
<el-form-item label="年级:" prop="userLevel" required>
<el-select v-model="form.userLevel" placeholder="年级">
<el-option v-for="item in levelEnum" :key="item.key" :value="item.key"
:label="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">更新</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import userApi from '@/api/user'
import { mapGetters, mapState } from 'vuex'
export default {
data () {
return {
event: [],
form: {
userName: '',
realName: '',
age: '',
sex: '',
birthDay: null,
phone: null,
userLevel: null,
createTime: null,
imagePath: null
},
formLoading: false,
rules: {
realName: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' }
],
userLevel: [
{ required: true, message: '请选择年级', trigger: 'change' }
]
}
}
},
created () {
let _this = this
userApi.getUserEvent().then(re => {
_this.event = re.response
})
userApi.getCurrentUser().then(re => {
_this.form = re.response
})
},
methods: {
uploadSuccess (re, file) {
if (re.code === 1) {
window.location.reload()
} else {
this.$message.error(re.message)
}
},
submitForm () {
let _this = this
this.$refs.form.validate((valid) => {
if (valid) {
this.formLoading = true
userApi.update(this.form).then(data => {
if (data.code === 1) {
_this.$message.success(data.message)
} else {
_this.$message.error(data.message)
}
_this.formLoading = false
}).catch(e => {
_this.formLoading = false
})
} else {
return false
}
})
},
levelFormatter (level) {
return this.enumFormat(this.levelEnum, level)
}
},
computed: {
...mapGetters('enumItem', [
'enumFormat'
]),
...mapState('enumItem', {
sexEnum: state => state.user.sexEnum,
levelEnum: state => state.user.levelEnum
})
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div style="margin-top: 10px" class="app-contain">
<el-card style="padding-top: 50px;padding-bottom: 50px">
<div class="el-table__empty-text" style="text-align: center;width: 100%" v-if="total ===0">
<span>暂无消息</span>
</div>
<el-collapse @change="handleChange" class="student-message-list" v-if="total !==0 " accordion>
<el-collapse-item :name="item.id" :key="item.id" v-for="item in tableData">
<template slot="title">
{{item.title}}
<el-tag style=" margin: 0 8px 0 auto;" :type="readTagFormat(item.readed)">{{readTextFormat(item.readed)}}</el-tag>
</template>
<el-row>
<label>发送人{{item.sendUserName}}</label>
</el-row>
<el-row>
<label>发送时间{{item.createTime}}</label>
</el-row>
<el-row>
<label>发送内容{{item.content}}</label>
</el-row>
</el-collapse-item>
</el-collapse>
<pagination v-show="total>0" :total="total" :background="false" :page.sync="queryParam.pageIndex"
:limit.sync="queryParam.pageSize"
@pagination="search" style="margin-top: 20px;"/>
</el-card>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex'
import Pagination from '@/components/Pagination'
import userApi from '@/api/user'
export default {
components: { Pagination },
data () {
return {
queryParam: {
pageIndex: 1,
pageSize: 10
},
listLoading: true,
tableData: [],
total: 0
}
},
created () {
this.search()
},
methods: {
handleChange (val) {
if (val === '') {
return
}
let _this = this
let selectItem = this.tableData.filter(d => d.id === val)[0]
if (!selectItem.readed) {
userApi.read(val).then(re => {
selectItem.readed = true
_this.messageCountSubtract(1)
})
}
},
search () {
this.listLoading = true
userApi.messagePageList(this.queryParam).then(data => {
const re = data.response
this.tableData = re.list
this.total = re.total
this.queryParam.pageIndex = re.pageNum
this.listLoading = false
})
},
readTagFormat (status) {
return this.enumFormat(this.readTag, status)
},
readTextFormat (status) {
return this.enumFormat(this.readText, status)
},
...mapMutations('user', ['messageCountSubtract'])
},
computed: {
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
readTag: state => state.user.message.readTag,
readText: state => state.user.message.readText
})
}
}
</script>
<style lang="scss">
</style>