Commit fd8c285d by tangyi

优化

parent 2e1b3811
Version v3.7.0 (2020-03-39)
--------------------------
改进:
* 部分样式优化
Version v3.7.0 (2020-03-15)
--------------------------
新功能:
* 支持二维码v2,支持计算每道题的答题时间
改进:
* 部分样式优化
Version v3.7.0 (2020-03-15)
--------------------------
......
......@@ -11,9 +11,9 @@
> 硕果云,基于Spring Cloud搭建的新一代微服务教学管理平台,提供多租户、权限管理、在线考试、练习等功能
>
> 题型支持单选题、多选题、不定项选择题、判断题、简答题
> 题型支持单选题、多选题、不定项选择题、判断题、简答题
>
> 支持二维码分享,移动端答题
> 支持PC、H5、微信小程序(小程序后面开源)
### 🏠 [主页](https://gitee.com/wells2333/spring-microservice-exam)
......@@ -45,9 +45,10 @@
- 构建工具:`Maven`
- 后台 API 文档:`Swagger`
- 消息队列:`RabbitMQ`
- 文件系统:`七牛云`
- 文件系统:`七牛云``FastDfs`
- 缓存:`Redis`
- 前端:`vue`
- 小程序:`wepy`
## 核心依赖
......@@ -110,18 +111,14 @@
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_exams.png" alt="考试列表"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_courses.png" alt="热门课程"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_exam.png" alt="PC端考试"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_mobile.jpeg" alt="手机端考试"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_exam.png" alt="考试"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_record.png" alt="考试记录"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_incorrect_answer.png" alt="错题列表"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_register.png" alt="注册"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_courses.png" alt="热门课程"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_login.png" alt="登录"/></td>
</tr>
</table>
......
......@@ -36,19 +36,19 @@ public class SysProperties {
private String gatewaySecret;
/**
* web端配置的静态封面路径
* logo路径
*/
private String webAvatar;
private String logoUrl;
/**
* web端配置的静态封面数量
* logo数量
*/
private Integer webAvatarCount;
private Integer logoCount;
/**
* web端配置的静态封面的后缀名
* logo的后缀名
*/
private String webAvatarSuffix;
private String logoSuffix;
/**
* 二维码生成链接
......
......@@ -140,5 +140,10 @@ public class UserVo extends BaseEntity<UserVo> {
*/
private Integer familyRole;
/**
* 个性签名
*/
private String signature;
}
......@@ -59,4 +59,10 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
ResponseBean<String> responseBean = new ResponseBean<>(e.getMessage(), ApiMsg.KEY_SERVICE, ApiMsg.ERROR);
return new ResponseEntity<>(responseBean, HttpStatus.OK);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ResponseBean<String>> handleException(Exception e) {
ResponseBean<String> responseBean = new ResponseBean<>(e.getMessage(), ApiMsg.KEY_ERROR, ApiMsg.ERROR);
return new ResponseEntity<>(responseBean, HttpStatus.OK);
}
}
\ No newline at end of file
......@@ -127,6 +127,6 @@ sys:
# 微信配置
wx:
appId: test
appSecret: test
grantType: authorization_code
\ No newline at end of file
appId: ${WX_APP_ID:test}
appSecret: ${WX_APP_SECRET:test}
grantType: ${WX_GRANT_TYPE:authorization_code}
\ No newline at end of file
......@@ -79,10 +79,10 @@ pagehelper:
# 系统配置
sys:
cacheExpire: 86400 # 缓存失效时间,单位秒,默认一天
webAvatar: /static/img/exam
webAvatarCount: 22
webAvatarSuffix: .jpeg
qrCodeUrl: http://${QR_CODE_URL:localhost}:${QR_CODE_PORT:8080}/#/mobile?id=
logoUrl: /static/img/exam
logoCount: 22
logoSuffix: .jpeg
qrCodeUrl: http://${QR_CODE_URL:localhost}:${QR_CODE_PORT:8080}/#/mobile
# feign相关配置
feign:
......@@ -128,6 +128,10 @@ ignore:
- /v1/menu/anonymousUser/**
- /v1/examination/anonymousUser/**
- /v1/answer/anonymousUser/**
- /v1/examRecord/anonymousUser/**
- /v1/examRecord/currentTime
- /v1/subject/anonymousUser/**
- /v1/answer/anonymousUser/**
- /v1/code/**
- /v1/attachment/download
- /v1/log/**
......
......@@ -78,4 +78,9 @@ CLUSTER_DATA_CENTER_ID=1
TZ=Asia/Shanghai
# elk配置
LOGSTASH_HOST=localhost:5044
\ No newline at end of file
LOGSTASH_HOST=localhost:5044
# 微信配置
WX_APP_ID=test
WX_APP_SECRET=test
WX_GRANT_TYPE=authorization_code
\ No newline at end of file
ALTER TABLE `microservice-user`.`sys_user`
ADD COLUMN `signature` varchar(255) NULL COMMENT '个性签名' AFTER `wechat`;
\ No newline at end of file
......@@ -196,6 +196,7 @@ export default {
modifyDate: '修改时间',
modifier: '修改人',
share: '分享',
shareV2: '分享(v2)',
subject: {
serialNumber: '序号',
type: '类型',
......
......@@ -107,10 +107,10 @@ export default {
}
},
legend: {
data: ['考试记录数']
data: ['考试数']
},
series: [{
name: '考试记录数',
name: '考试数',
smooth: true,
type: 'line',
itemStyle: {
......
......@@ -5,7 +5,7 @@
<el-row class="chart-wrapper" style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<div class="chart-wrapper-header">
<div>考试记录</div>
<div>考试</div>
<div class="chart-wrapper-header-select">
<el-select v-model="query.pastDays" placeholder="请选择" size="mini" @change="getExamRecordData">
<el-option
......
......@@ -47,10 +47,25 @@
<span>{{ scope.row.modifyDate | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100">
<template slot-scope="scope">
<el-button v-if="course_btn_edit" type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="course_btn_del" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="course_btn_edit">
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="course_btn_del">
<a @click="handleDelete(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -17,9 +17,9 @@
@selection-change="handleSelectionChange"
@sort-change="sortChange">
<el-table-column type="selection" width="55"/>
<el-table-column :label="$t('table.examinationName')">
<el-table-column :label="$t('table.examinationName')" min-width="100">
<template slot-scope="scope">
<span>{{ scope.row.examinationName | simpleStrFilter }}</span>
<span>{{ scope.row.examinationName }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.examinationType')">
......@@ -42,13 +42,55 @@
<el-tag :type="scope.row.status | statusTypeFilter " effect="dark" size="small">{{ scope.row.status | publicStatusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="400">
<el-table-column :label="$t('table.modifier')">
<template slot-scope="scope">
<el-button v-if="exam_btn_edit" type="success" size="mini" @click="handleShare(scope.row)">{{ $t('table.share') }}</el-button>
<el-button v-if="exam_btn_edit" type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="exam_btn_edit && scope.row.status == 1" type="success" size="mini" @click="handlePublic(scope.row, 0)">{{ $t('table.public') }}</el-button>
<el-button v-if="exam_btn_edit && scope.row.status == 0" type="info" size="mini" @click="handlePublic(scope.row, 1)">{{ $t('table.withdraw') }}</el-button>
<el-button v-if="exam_btn_subject" type="success" size="mini" @click="handleSubjectManagement(scope.row)">{{ $t('table.subjectManagement') }}</el-button>
<span>{{ scope.row.modifier }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.modifyDate')">
<template slot-scope="scope">
<span>{{ scope.row.modifyDate | fmtDate('yyyy-MM-dd hh:mm')}}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100">
<template slot-scope="scope">
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="exam_btn_edit">
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="exam_btn_edit && scope.row.status == 1">
<a @click="handlePublic(scope.row, 0)">
<span><i class="el-icon-check"></i>{{ $t('table.public') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="exam_btn_edit && scope.row.status == 0">
<a @click="handlePublic(scope.row, 1)">
<span><i class="el-icon-close"></i>{{ $t('table.withdraw') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="exam_btn_subject">
<a @click="handleSubjectManagement(scope.row)">
<span><i class="el-icon-document"></i>{{ $t('table.subjectManagement') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a @click="handleShare(scope.row)">
<span><i class="el-icon-share"></i>{{ $t('table.share') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a @click="handleShareV2(scope.row)">
<span><i class="el-icon-share"></i>{{ $t('table.shareV2') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......@@ -106,7 +148,7 @@
<el-form-item :label="$t('table.status')">
<el-radio-group v-model="temp.status">
<el-radio :label="0">已发布</el-radio>
<el-radio :label="1">未发布</el-radio>
<el-radio :label="1">草稿</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
......@@ -257,7 +299,7 @@ export default {
duration: '',
totalScore: '',
totalSubject: '0',
status: 0,
status: 1,
avatarId: null,
collegeId: '',
majorId: '',
......@@ -417,6 +459,10 @@ export default {
this.qrCodeUrl = apiList.exam + 'anonymousUser/generateQrCode/' + row.id
this.dialogQrCodeVisible = true
},
handleShareV2 (row) {
this.qrCodeUrl = apiList.exam + 'anonymousUser/generateQrCode/v2/' + row.id
this.dialogQrCodeVisible = true
},
handleUpdate (row) {
this.temp = Object.assign({}, row)
if (!isNotEmpty(this.temp.course)) {
......
......@@ -46,10 +46,25 @@
<el-tag :type="scope.row.submitStatus | simpleTagStatusFilter(3)" effect="dark" size="small">{{ scope.row.submitStatus | submitStatusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300px">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleDetails(scope.row)">{{ $t('table.examRecord.details') }}</el-button>
<el-button type="success" size="mini" @click="handleMarking(scope.row)">{{ $t('table.examRecord.marking') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<a @click="handleDetails(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.examRecord.details') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a @click="handleMarking(scope.row)">
<span><i class="el-icon-check"></i>{{ $t('table.examRecord.marking') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -17,7 +17,7 @@
<el-table-column type="selection" width="55"/>
<el-table-column :label="$t('table.subjectName')" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subjectName | simpleStrFilter }}</span>
<span v-html="scope.row.subjectName"></span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')" width="120">
......@@ -40,11 +40,30 @@
<span>{{ scope.row.modifier }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button v-if="exam_btn_subject" type="primary" size="mini" @click="handleUpdateSubject(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button type="success" size="mini" @click="handleViewSubject(scope.row)">{{ $t('table.preview') }}</el-button>
<el-button v-if="exam_btn_del" type="danger" size="mini" @click="handleDeleteSubject(scope.row)">{{ $t('table.delete') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="exam_btn_subject">
<a @click="handleUpdateSubject(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a @click="handleViewSubject(scope.row)">
<span><i class="el-icon-view"></i>{{ $t('table.preview') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="exam_btn_del">
<a @click="handleDeleteSubject(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -32,22 +32,40 @@
<el-tag :type="scope.row.status | statusTypeFilter" effect="dark" size="small">{{ scope.row.status | publicStatusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.status')" min-width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusTypeFilter" effect="dark" size="small">{{ scope.row.status | statusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.modifyDate')" min-width="80">
<template slot-scope="scope">
<span>{{ scope.row.modifyDate | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300px">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="scope.row.status == 1" type="success" size="mini" @click="handlePublic(scope.row, 0)">{{ $t('table.public') }}</el-button>
<el-button v-if="scope.row.status == 0" type="info" size="mini" @click="handlePublic(scope.row, 1)">{{ $t('table.withdraw') }}</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="scope.row.status == 1">
<a @click="handlePublic(scope.row, 0)">
<span><i class="el-icon-check"></i>{{ $t('table.public') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="scope.row.status == 0">
<a @click="handlePublic(scope.row, 1)">
<span><i class="el-icon-close"></i>{{ $t('table.withdraw') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a @click="handleDelete(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -15,10 +15,11 @@
<br>
<span class="user-info-item" v-show="examRecord.deptName !== undefined && examRecord.deptName !== ''">所属部门:{{ examRecord.deptName }}</span>
<br>
<span class="user-info-item" v-show="examRecord.startTime !== undefined && examRecord.startTime !== ''">考试时间:
<el-tag type="info" >{{ examRecord.startTime | fmtDate('yyyy.MM.dd hh:mm') }}</el-tag>
~
<el-tag type="info">{{ examRecord.endTime | fmtDate('yyyy.MM.dd hh:mm') }}</el-tag>
<span class="user-info-item" v-show="examRecord.startTime !== undefined">开始时间:
{{ examRecord.startTime | fmtDate('yyyy.MM.dd hh:mm') }}
</span>
<span class="user-info-item" v-show="examRecord.endTime !== undefined">结束时间:
{{ examRecord.endTime | fmtDate('yyyy.MM.dd hh:mm') }}
</span>
<br>
</div>
......@@ -31,7 +32,7 @@
</el-col>
<el-col :span="3">
<div class="description">
<div>耗时</div>
<div>耗时</div>
<div class="description-score">{{ examRecord.duration }}</div>
</div>
</el-col>
......@@ -56,7 +57,7 @@
style="width: 100%;">
<el-table-column :label="$t('table.subjectName')" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subject.subjectName | simpleStrFilter(15) }}</span>
<span v-html="scope.row.subject.subjectName"></span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')" min-width="90">
......
......@@ -51,7 +51,7 @@
<el-table-column type="selection" width="55"/>
<el-table-column :label="$t('table.subjectName')" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subjectName | simpleStrFilter }}</span>
<span v-html="scope.row.subjectName"></span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')" width="120">
......@@ -74,11 +74,26 @@
<span>{{ scope.row.modifyDate | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button v-if="subject_bank_btn_edit" type="primary" size="mini" @click="handleUpdateSubject(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="subject_bank_btn_del" type="danger" size="mini" @click="handleDeleteSubject(scope.row)">{{ $t('table.delete') }}</el-button>
</template>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="subject_bank_btn_edit">
<a @click="handleUpdateSubject(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="subject_bank_btn_del">
<a @click="handleDeleteSubject(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
......
......@@ -54,8 +54,8 @@ export default {
return {
loginForm: {
tenantCode: 'gitee',
identifier: '',
credential: '',
identifier: 'preview',
credential: '123456',
code: '',
randomStr: '',
rememberMe: false
......@@ -149,7 +149,7 @@ export default {
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(-45deg, #3a82ce 0%, #2f8cec 33%, #19ceb5 100%);
background: url('../../../static/img/login_bg.jpg') -20% 10%;
background-size: cover;
}
.light-font {
......
......@@ -36,10 +36,25 @@
<span>{{ scope.row.refreshTokenValidity }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300px">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button v-if="client_btn_edit" type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="client_btn_del" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="client_btn_edit">
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="client_btn_del">
<a @click="handleDelete(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -42,12 +42,31 @@
<el-tag :type="scope.row.status | statusTypeFilter" effect="dark" size="small">{{ scope.row.status | statusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300px">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button v-if="role_btn_edit" type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="role_btn_auth" type="success" size="mini" @click="handlePermission(scope.row)">{{ $t('table.permission') }}</el-button>
<el-button v-if="role_btn_del" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
</template>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="role_btn_edit">
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="role_btn_auth">
<a @click="handlePermission(scope.row)">
<span><i class="el-icon-plus"></i>{{ $t('table.permission') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="role_btn_del">
<a @click="handleDelete(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -42,10 +42,25 @@
<el-tag :type="scope.row.status | statusTypeFilter" effect="dark" size="small">{{ scope.row.status | statusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button v-if="route_btn_edit" type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="route_btn_del" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="route_btn_edit">
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="route_btn_del">
<a @click="handleDelete(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -53,12 +53,35 @@
<span>{{ scope.row.loginTime | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100">
<template slot-scope="scope">
<el-button v-if="user_btn_edit" type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="user_btn_edit" type="warning" size="mini" @click="handleResetPassword(scope.row)">{{ $t('table.resetPassword') }}</el-button>
<el-button v-if="user_btn_edit && scope.row.status === 0" type="danger" size="mini" @click="handleEnableOrDisable(scope.row, 1)">{{ $t('table.disable') }}</el-button>
<el-button v-if="user_btn_edit && scope.row.status === 1" type="success" size="mini" @click="handleEnableOrDisable(scope.row, 0)">{{ $t('table.enable') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="user_btn_edit">
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="user_btn_edit">
<a @click="handleResetPassword(scope.row)">
<span><i class="el-icon-refresh-left"></i>{{ $t('table.resetPassword') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="user_btn_edit && scope.row.status === 0">
<a @click="handleEnableOrDisable(scope.row, 1)">
<span><i class="el-icon-close"></i>{{ $t('table.disable') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="user_btn_edit && scope.row.status === 1" @click="handleEnableOrDisable(scope.row, 0)">
<a @click="handleEnableOrDisable(scope.row, 0)">
<span><i class="el-icon-check"></i>{{ $t('table.enable') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -36,10 +36,25 @@
<el-tag :type="scope.row.status | statusTypeFilter" effect="dark" size="small">{{ scope.row.status | statusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="100px">
<template slot-scope="scope">
<el-button v-if="tenant_btn_edit" type="primary" size="mini" @click="handleUpdate(scope.row)">{{ $t('table.edit') }}</el-button>
<el-button v-if="tenant_btn_del" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
<el-dropdown>
<span class="el-dropdown-link">
操作<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-if="tenant_btn_edit">
<a @click="handleUpdate(scope.row)">
<span><i class="el-icon-edit"></i>{{ $t('table.edit') }}</span>
</a>
</el-dropdown-item>
<el-dropdown-item v-if="tenant_btn_del">
<a @click="handleDelete(scope.row)">
<span><i class="el-icon-delete"></i>{{ $t('table.delete') }}</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
......
......@@ -3,7 +3,7 @@ import { getRefreshToken } from '@/utils/auth'
const baseAuthenticationUrl = '/api/auth/v1/authentication/'
const basicAuthorization = 'Basic ' + btoa('web_app:spring-microservice-exam-secret')
const basicAuthorization = 'Basic d2ViX2FwcDpzcHJpbmctbWljcm9zZXJ2aWNlLWV4YW0tc2VjcmV0'
export function loginByUsername (identifier, credential, code, randomStr) {
const grantType = 'password'
......
......@@ -79,6 +79,21 @@ export function saveAndNext (obj, nextType, nextSubjectId, nextSubjectType) {
})
}
export function anonymousUserSaveAndNext (obj, nextType, nextSubjectId, nextSubjectType) {
let url = baseAnswerUrl + 'anonymousUser/saveAndNext?nextType=' + nextType
if (nextSubjectId !== undefined) {
url += '&nextSubjectId=' + nextSubjectId
}
if (nextSubjectType !== undefined) {
url += '&nextSubjectType=' + nextSubjectType
}
return request({
url: url,
method: 'post',
data: obj
})
}
export function submit (obj) {
return request({
url: baseAnswerUrl + 'submit',
......@@ -87,9 +102,17 @@ export function submit (obj) {
})
}
export function anonymousUserSubmit (obj, examinationId, identifier) {
export function anonymousUserSubmit (obj) {
return request({
url: baseAnswerUrl + 'anonymousUser/submit',
method: 'post',
data: obj
})
}
export function anonymousUserSubmitAll (obj, examinationId, identifier) {
return request({
url: baseAnswerUrl + 'anonymousUser/submit/' + examinationId + '?identifier=' + identifier,
url: baseAnswerUrl + 'anonymousUser/submitAll/' + examinationId + '?identifier=' + identifier,
method: 'post',
data: obj
})
......
......@@ -42,6 +42,14 @@ export function getSubjectIds (id, query) {
})
}
export function anonymousUserGetSubjectIds (id, query) {
return request({
url: baseExaminationUrl + 'anonymousUser/' + id + '/subjectIds',
method: 'get',
params: query
})
}
export function addObj (obj) {
return request({
url: baseExaminationUrl,
......
......@@ -34,6 +34,14 @@ export function start (obj) {
})
}
export function anonymousUserStart (obj) {
return request({
url: baseExamRecordUrl + 'anonymousUser/start',
method: 'post',
params: obj
})
}
export function getCurrentTime () {
return request({
url: baseExamRecordUrl + 'currentTime',
......
......@@ -63,3 +63,11 @@ export function getSubjectAnswer (obj) {
params: obj
})
}
export function anonymousUserGetSubjectAnswer (obj) {
return request({
url: baseSubjectUrl + 'anonymousUser/subjectAnswer',
method: 'get',
params: obj
})
}
......@@ -6,7 +6,7 @@
<span class="subject-title-content" v-html="subjectInfo.subjectName"/>
<span class="subject-title-content" v-if="subjectInfo.score !== undefined && subjectInfo.score !== 0">&nbsp;({{subjectInfo.score}})分</span>
<div class="subject-tinymce">
<tinymce ref="editor" :height="300" v-model="userAnswer"/>
<tinymce ref="editor" :height="height" v-model="userAnswer"/>
</div>
</div>
</div>
......@@ -21,6 +21,13 @@ export default {
components: {
Tinymce
},
props: {
height: {
type: Number,
required: false,
default: 300
}
},
data () {
return {
subjectCount: 0,
......
......@@ -66,6 +66,11 @@ export const constantRouterMap = [
component: () => import('@/views/exam/courses')
},
{
path: '/course-details',
name: 'course-details',
component: () => import('@/views/exam/courseDetails')
},
{
path: '/account',
name: 'account',
component: () => import('@/views/personal/account')
......@@ -93,6 +98,11 @@ export const constantRouterMap = [
component: () => import('@/views/mobile/Index')
},
{
path: '/mobile-v2',
name: 'MobileV2',
component: () => import('@/views/mobileV2/Index')
},
{
path: '*',
redirect: '/home'
}
......
<template>
<div>
<o-header @handleSubmitExam="handleSubmitExam"></o-header>
<fixed-header>
<div class="header-area">
<div class="clever-main-menu">
<div class="classy-nav-container breakpoint-off">
<nav class="classy-navbar justify-content-between" id="cleverNav">
<a class="nav-brand hidden-sm-only" href="/">硕果云</a>
<div class="classy-menu">
<div class="classynav">
<div class="search-area hidden-sm-only">
<el-input type="search" prefix-icon="el-icon-search" v-model="query" name="search" id="search" placeholder="搜索" @keyup.enter="search()"/>
</div>
<el-menu :default-active="activeIndex"
mode="horizontal"
text-color="rgba(0, 0, 0, 0.45)"
active-text-color="#232323"
:unique-opened=true
@select="handleSelect">
<el-menu-item index="/index" @click="open('/home')">首页</el-menu-item>
<el-menu-item index="/exams" @click="open('/exams')">考试</el-menu-item>
<el-menu-item index="/courses" @click="open('/courses')">课程</el-menu-item>
<el-submenu index="/other">
<template slot="title">记录</template>
<el-menu-item index="exam-record" @click="open('/exam-record')">考试记录</el-menu-item>
<el-menu-item index="incorrect" @click="todo">错题本</el-menu-item>
</el-submenu>
<el-submenu index="/u">
<template slot="title">帮助</template>
<el-menu-item index="u-source" @click="open('https://gitee.com/wells2333/spring-microservice-exam')">
源码地址
</el-menu-item>
<el-menu-item index="u-deploy" @click="open('https://www.kancloud.cn/tangyi/spring-microservice-exam/1322870')">
部署文档
</el-menu-item>
<el-menu-item index="c-log" @click="open('https://gitee.com/wells2333/spring-microservice-exam/blob/master/CHANGELOG.md')">
更新日志
</el-menu-item>
<el-menu-item index="c-overview" @click="open('https://www.kancloud.cn/tangyi/spring-microservice-exam/1322864#6__112')">
规划总览
</el-menu-item>
<el-menu-item index="u-admin" @click="open('http://118.25.138.130:81')">
管理后台
</el-menu-item>
</el-submenu>
<el-submenu v-if="login" index="/user-info">
<template slot="title">
<img src="https://colorlib.com/preview/theme/clever/img/bg-img/t1.png" style="height: 30px;border-radius: 50%;margin-right: 6px;"/>
{{userInfo.identifier}}
</template>
<el-menu-item index="account" @click="open('/account')">个人中心</el-menu-item>
<el-menu-item index="password" @click="open('/password')">修改密码</el-menu-item>
<el-menu-item index="logOut" @click="logOut">退出</el-menu-item>
</el-submenu>
</el-menu>
<div class="register-login-area" v-if="!login">
<a class="btn" target="_blank" @click="open('/register')">注册</a>
<a class="btn" target="_blank" @click="open('/login')">登录</a>
</div>
</div>
</div>
</nav>
</div>
</div>
</div>
</fixed-header>
<o-main ref="mainRef"></o-main>
</div>
</template>
<script>
import OHeader from './common/header'
import OMain from './common/main'
import { mapState } from 'vuex'
import { isNotEmpty, messageWarn } from '@/utils/util'
import FixedHeader from 'vue-fixed-header'
export default {
data () {
return {}
},
components: {
OHeader,
FixedHeader,
OMain
},
computed: {
// 获取用户信息
...mapState({
userInfo: state => state.user.userInfo
})
},
created () {
this.checkLogin()
},
// 检测路由变化
watch: {
$route () {
this.checkLogin()
}
},
data () {
return {
activeIndex: '/index',
login: false,
input: '',
query: ''
}
},
methods: {
// 提交考试
handleSubmitExam () {
this.$refs.mainRef.handleSubmitExam()
},
// 导航栏切换
open (path) {
if (path.startsWith('http')) {
window.open(path)
return
}
if (path !== this.$route.fullPath) {
if (this.$route.fullPath === '/start') {
this.$confirm('是否要结束当前考试?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// TODO 提交当前考试
this.$emit('handleSubmitExam')
this.$router.push({
path: path,
query: {}
})
}).catch(() => {})
} else {
this.$router.push({
path: path,
query: {}
})
}
}
},
// 选择事件
handleSelect (item) {
},
// 注册
handleRegister () {
// 先退出
// this.logOut()
this.$router.push('/register')
},
// 登录
handleLogin () {
this.$router.push('/login')
},
// 登出
logOut () {
this.$store.dispatch('LogOut').then(() => {
this.login = false
this.$router.push('/home')
}).catch(() => {
this.login = false
this.$router.push('/home')
})
},
// 检测登录
checkLogin () {
if (this.userInfo.id !== undefined) {
this.login = true
}
},
search () {
if (isNotEmpty(this.query)) {
this.$router.push({name: 'exams', query: {query: this.query}})
}
},
todo () {
messageWarn(this, '功能正在开发中!')
}
}
}
......
......@@ -44,7 +44,7 @@
</el-submenu>
<el-submenu v-if="login" index="/user-info">
<template slot="title">
<img :src="userInfo.avatarUrl" style="height: 30px;border-radius: 50%;margin-right: 6px;"/>
<img src="https://colorlib.com/preview/theme/clever/img/bg-img/t1.png" style="height: 30px;border-radius: 50%;margin-right: 6px;"/>
{{userInfo.identifier}}
</template>
<el-menu-item index="account" @click="open('/account')">个人中心</el-menu-item>
......
<template>
<div>
<transition name="fade-transform" mode="out-in">
<div v-show="!loading">
<div class="single-course-intro d-flex align-items-center justify-content-center" :style="'background-image: url(' + course.logoUrl + ');'">
<div class="single-course-intro-content text-center">
<div class="rate">
<el-rate
v-model="value"
disabled
text-color="#ff9900"
score-template="{value}">
</el-rate>
</div>
<h3>{{ course.courseName }}</h3>
<div class="meta d-flex align-items-center justify-content-center">
<a href="#">{{ course.teacher }}</a>
<span><i class="fa fa-circle" aria-hidden="true"></i></span>
<a href="#">{{ course.college }} &amp; {{ course.major }}</a>
</div>
<div class="price">免费</div>
</div>
</div>
<div class="single-course-content padding-80">
<el-row class="my-content-container ml-100 mr-100">
<el-col :span="18" style="padding-right: 40px;">
<el-tabs>
<el-tab-pane>
<span slot="label">
<el-button type="default" class="course-content-btn">课程介绍</el-button>
</span>
<div class="clever-description">
<div class="about-course mb-30">
<h4>课程介绍</h4>
<p>{{ course.courseDescription }}</p>
</div>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label">
<el-button type="default" class="course-content-btn">课程安排</el-button>
</span>
<div class="about-curriculum mb-30">
<h4>课程安排</h4>
<p>{{ course.courseDescription }}</p>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label">
<el-button type="default" class="course-content-btn">课程评价</el-button>
</span>
<div class="about-review mb-30">
<h4>课程评价</h4>
<p>{{ course.courseDescription }}</p>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label">
<el-button type="default" class="course-content-btn">报名学员</el-button>
</span>
<div class="about-members mb-30">
<h4>报名学员</h4>
<p>{{ course.courseDescription }}</p>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label">
<el-button type="default" class="course-content-btn">学习交流</el-button>
</span>
<div class="about-review mb-30">
<h4>学习交流</h4>
<p>{{ course.courseDescription }}</p>
</div>
</el-tab-pane>
</el-tabs>
</el-col>
<el-col :span="6">
<div class="course-sidebar">
<el-button type="primary" class="clever-btn mb-30 w-100" @click="buyCourse">购买课程</el-button>
<div class="sidebar-widget">
<h4>课程特色</h4>
<ul class="features-list">
<li>
<h6><i class="el-icon-alarm-clock"></i>课时</h6>
<h6>2 周</h6>
</li>
<li>
<h6><i class="el-icon-bell"></i>课程</h6>
<h6>10</h6>
</li>
<li>
<h6><i class="el-icon-files"></i>问答</h6>
<h6>3</h6>
</li>
<li>
<h6><i class="el-icon-arrow-up"></i>通过率</h6>
<h6>60</h6>
</li>
</ul>
</div>
<div class="sidebar-widget">
<h4>猜你喜欢</h4>
<div class="single--courses d-flex align-items-center" v-for="course in likes" :key="course.id">
<div class="thumb">
<img src="static/img/bg-img/yml.jpg" alt="">
</div>
<div class="content">
<h5>{{ course.courseName }}</h5>
<h6>{{ course.price }}</h6>
</div>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</div>
</transition>
</div>
</template>
<script>
import { getObj } from '@/api/exam/course'
import { messageWarn } from '@/utils/util'
export default {
data () {
return {
loading: true,
courseId: '',
course: {},
value: 3.7,
likes: [{
id: 1,
courseName: '应用文写作',
price: '$20'
}]
}
},
created () {
this.courseId = this.$route.query.courseId
this.getCourseInfo()
},
methods: {
getCourseInfo () {
this.loading = true
getObj(this.courseId).then(response => {
this.course = response.data.data
setTimeout(() => {
this.loading = false
}, 500)
}).catch(error => {
console.error(error)
})
},
buyCourse () {
messageWarn(this, '功能正在开发中')
}
}
}
</script>
<style lang="scss" rel="stylesheet/scss" scoped>
.rate {
margin-bottom: 12px;
}
.course-content-btn {
display: inline-block;
height: 40px;
background-color: transparent;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 14px;
color: rgba(0, 0, 0, 0.25);
border: 1px solid #ebebeb;
border-radius: 6px;
padding: 0 25px;
line-height: 40px;
-webkit-transition-duration: 800ms;
transition-duration: 800ms;
text-align: center;
margin-right: 10px;
margin-bottom: 10px;
}
.clever-btn {
display: inline-block;
min-width: 160px;
height: 40px;
background-color: #3762f0;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 14px;
color: #ffffff;
border: 1px solid transparent;
border-radius: 6px;
padding: 0 30px;
line-height: 40px;
text-align: center;
-webkit-transition-duration: 300ms;
transition-duration: 300ms;
}
.my-content-container {
margin-top: 0;
}
</style>
......@@ -6,8 +6,8 @@
<div class="content-container">
<div class="course-card-list">
<transition name="fade-transform" mode="out-in" v-for="course in courseList" :key="course.id">
<div class="single-popular-course mb-100" v-show="course.show">
<img src="static/img/bg-img/c2.jpg" alt="">
<div class="single-popular-course mb-80" v-show="course.show">
<img :src="course.logoUrl" alt="">
<div class="course-content">
<h4>{{ course.courseName }}</h4>
<div class="meta d-flex align-items-center">
......@@ -27,7 +27,7 @@
</div>
</div>
<div class="course-fee h-100">
<a href="#" @click="startCourse(course)">免费</a>
<a href="#" @click="handleStartCourse(course)">免费</a>
</div>
</div>
</div>
......@@ -37,7 +37,7 @@
</div>
<el-row style="text-align: center; margin-bottom: 50px;">
<el-col :span="24">
<el-button type="primary" @click="scrollList" :loading="loading" style="margin-bottom: 100px;">加载更多</el-button>
<el-button type="default" @click="scrollList" :loading="loading" style="margin-bottom: 100px;">加载更多</el-button>
</el-col>
</el-row>
</div>
......@@ -82,8 +82,9 @@ export default {
this.loading = false
})
},
startCourse (course) {
messageWarn(this, '功能正在开发中!')
handleStartCourse (course) {
// messageWarn(this, '功能正在开发中!')
this.$router.push({name: 'course-details', query: {courseId: course.id}})
},
scrollList () {
if (this.isLastPage) {
......
......@@ -43,7 +43,7 @@
</el-table>
<el-row style="text-align: center; margin-bottom: 50px;">
<el-col :span="24">
<el-button type="primary" @click="scrollList" :loading="listLoading" style="margin-top:20px; margin-bottom: 100px;">加载更多</el-button>
<el-button type="default" @click="scrollList" :loading="listLoading" style="margin-top:20px; margin-bottom: 100px;">加载更多</el-button>
</el-col>
</el-row>
</el-col>
......
......@@ -28,7 +28,7 @@
<div class="card-item" v-show="exam.show">
<div>
<a href="javascript: void(-1);" class="card-item-snapshoot"
:style="'background-image: url(' + exam.avatarUrl + ');'"
:style="'background-image: url(' + exam.logoUrl + ');'"
@click="startExam(exam)">
</a>
</div>
......@@ -36,17 +36,17 @@
<div>
<a href="javascript:void(-1);" @click="startExam(exam)"></a>
<h3>
<div class="card-item-name">
<div class="card-item-name mb-12">
{{ exam.examinationName | simpleStrFilter }}
</div>
</h3>
</div>
<div class="card-item-course" v-if="exam.course !== undefined && exam.course !== null">
<div class="card-item-course-detail">
{{ exam.course.courseName }}
<div class="card-item-course-detail mb-12">
<a href="#">{{ exam.course.courseName }}</a>
</div>
<div class="card-item-course-detail">
{{ exam.startTime | timeFilter }}~{{ exam.endTime | timeFilter }}
<div class="card-item-course-detail mb-12">
<a href="#">{{ exam.startTime | timeFilter }}~{{ exam.endTime | timeFilter }}</a>
</div>
</div>
</div>
......@@ -57,7 +57,7 @@
</div>
<el-row style="text-align: center; margin-bottom: 50px;">
<el-col :span="24">
<el-button type="primary" @click="scrollList" :loading="loading" style="margin-bottom: 100px;">加载更多</el-button>
<el-button type="default" @click="scrollList" :loading="loading" style="margin-bottom: 100px;">加载更多</el-button>
</el-col>
</el-row>
</div>
......@@ -346,6 +346,16 @@ export default {
.card-item-course-detail {
color: rgba(0,0,0,.54);
fill: rgba(0,0,0,.54);
a {
color: rgba(0, 0, 0, 0.4);
display: inline-block;
font-size: 14px;
font-weight: 400;
margin-right: 10px;
&:hover {
color: #000;
}
}
}
}
}
......
<template>
<div class="app-container">
<transition name="fade-transform" mode="out-in">
<el-card v-show="!listLoading">
<el-row>
<el-col :span="20" :offset="2">
<el-divider>成绩详情</el-divider>
</el-col>
</el-row>
<el-row>
<el-col :span="20" :offset="2">
<el-card>
<el-row>
<el-col :span="20" :offset="2">
<el-divider>成绩详情</el-divider>
</el-col>
</el-row>
<el-row>
<transition name="fade-transform" mode="out-in">
<el-col :span="20" :offset="2" v-show="!examRecordLoading">
<el-row>
<el-col :span="8">
<label class="el-form-item__label">考试名称: {{ examRecordDetail.examinationName }}</label>
......@@ -43,15 +43,17 @@
</el-col>
</el-row>
</el-col>
</el-row>
<el-row>
<el-col :span="20" :offset="2">
<el-divider>错题列表</el-divider>
</el-col>
</el-row>
<el-row>
<el-col :span="20" :offset="2">
<div class="subject-content" v-for="(tempIncorrectAnswer, index) in list" :key="tempIncorrectAnswer.id">
</transition>
</el-row>
<el-row>
<el-col :span="20" :offset="2">
<el-divider>错题列表</el-divider>
</el-col>
</el-row>
<el-row>
<el-col :span="20" :offset="2">
<transition name="fade-transform" mode="out-in" v-for="(tempIncorrectAnswer, index) in list" :key="tempIncorrectAnswer.id">
<div class="subject-content" v-show="tempIncorrectAnswer.show">
<div class="subject-content-option">
<div class="subject-title">
<span class="subject-title-number">{{index + 1}} .</span>
......@@ -72,39 +74,39 @@
<span v-html="tempIncorrectAnswer.subject.answer.answer"></span>
</p>
</div>
<p class="subject-content-answer">
参考答案:{{tempIncorrectAnswer.subject.answer.answer}}
<p class="subject-content-answer" v-if="tempIncorrectAnswer.subject.answer.answer !== ''">
参考答案:<span v-html="tempIncorrectAnswer.subject.answer.answer"></span>
</p>
<p class="subject-content-analysis">
解析:{{tempIncorrectAnswer.subject.analysis}}
<p class="subject-content-analysis" v-if="tempIncorrectAnswer.subject.analysis !== ''">
解析:<span v-html="tempIncorrectAnswer.subject.analysis"></span>
</p>
</div>
<div class="pagination-container">
<el-pagination v-show="total > 0" :current-page="listQuery.pageNum" :page-sizes="[10,20,30, 50]" :page-size="listQuery.pageSize" :total="total" background layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange"/>
</div>
</el-col>
</el-row>
</el-card>
</transition>
</transition>
</el-col>
</el-row>
<el-row style="text-align: center; margin-bottom: 50px;">
<el-col :span="24">
<el-button type="default" @click="scrollList" :loading="loading" style="margin-bottom: 100px;">加载更多</el-button>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { getAnswerListInfo } from '@/api/exam/answer'
import { examRecordDetails } from '@/api/exam/examRecord'
import { notifyFail } from '@/utils/util'
import { notifyFail, messageWarn } from '@/utils/util'
import { answerType } from '@/const/constant'
import SpinnerLoading from '@/components/SpinnerLoading'
export default {
name: 'Incorrect',
components: {
SpinnerLoading
},
data () {
return {
listLoading: true,
loading: true,
examRecordLoading: true,
total: 0,
isLastPage: false,
tableKey: 0,
list: [],
listQuery: {
......@@ -139,25 +141,77 @@ export default {
},
methods: {
getRecordDetail () {
this.listLoading = true
this.loading = true
this.examRecordLoading = true
examRecordDetails(this.incorrectRecord.id).then(response => {
this.examRecordDetail = response.data.data
setTimeout(() => {
this.examRecordDetail = response.data.data
this.examRecordLoading = false
}, 500)
getAnswerListInfo(this.incorrectRecord.id, this.listQuery).then(response => {
this.list = response.data.list
this.total = response.data.total
this.listLoading = false
}).catch(() => {
const { total, isLastPage, list } = response.data
this.updateList(list)
this.total = total
this.isLastPage = isLastPage
this.loading = false
}).catch(error => {
console.error(error)
notifyFail(this, '加载错题失败')
this.loading = false
})
})
},
handleSizeChange (val) {
this.listQuery.limit = val
this.getList()
scrollList () {
if (this.isLastPage) {
messageWarn(this, '暂无更多数据!')
return
}
if (this.loading) {
messageWarn(this, '正在拼命加载!')
return
}
this.loading = true
setTimeout(() => {
this.listQuery.pageNum++
getAnswerListInfo(this.incorrectRecord.id, this.listQuery).then(response => {
const { total, isLastPage, list } = response.data
this.updateList(list)
this.total = total
this.isLastPage = isLastPage
this.loading = false
}).catch(() => {
messageWarn(this, '加载数据失败!')
this.loading = false
})
}, 500)
},
handleCurrentChange (val) {
this.listQuery.pageNum = val
this.getList()
updateList (list) {
if (list === undefined || list.length === 0) {
return list
}
list.forEach(item => {
item.show = false
})
if (this.list.length === 0) {
this.list = list
} else {
list.forEach(item => {
let exist = false
for (let i = 0; i < this.list.length; i++) {
if (this.list[i].id === item.id) {
exist = true
}
}
if (!exist) {
this.list.push(item)
}
})
}
for (let i = 0; i < list.length; i++) {
setTimeout(() => {
list[i].show = true
}, 250 + (100 * i))
}
},
getClass (right) {
return answerType[right]
......
......@@ -227,7 +227,10 @@ export default {
store.dispatch('SetSubjectInfo', subject).then(() => {})
this.updateSubjectIndex()
}
this.subjectStartTime = new Date()
// 更新时间
getCurrentTime().then(response => {
this.subjectStartTime = moment(response.data.data)
})
this.endLoading(nextType)
}).catch((error) => {
console.log(error)
......
......@@ -10,11 +10,11 @@
</div>
</el-col>
</el-row>
<div class="cool-facts-area padding-100-0">
<div class="cool-facts-area padding-80-0">
<el-row type="flex" justify="center" :gutter="50">
<el-col :span="4">
<transition name="fade-transform" mode="out-in">
<div class="single-cool-facts-area mb-100" v-show="showFacts">
<div class="single-cool-facts-area mb-80" v-show="showFacts">
<div class="icon">
<img src="static/img/core-img/star.png" alt="">
</div>
......@@ -27,7 +27,7 @@
</el-col>
<el-col :span="4">
<transition name="fade-transform" mode="out-in">
<div class="single-cool-facts-area mb-100" v-show="showFacts">
<div class="single-cool-facts-area mb-80" v-show="showFacts">
<div class="icon">
<img src="static/img/core-img/star.png" alt="">
</div>
......@@ -40,7 +40,7 @@
</el-col>
<el-col :span="4">
<transition name="fade-transform" mode="out-in">
<div class="single-cool-facts-area mb-100" v-show="showFacts">
<div class="single-cool-facts-area mb-80" v-show="showFacts">
<div class="icon">
<img src="static/img/core-img/star.png" alt="">
</div>
......@@ -53,7 +53,7 @@
</el-col>
<el-col :span="4">
<transition name="fade-transform" mode="out-in">
<div class="single-cool-facts-area mb-100" v-show="showFacts">
<div class="single-cool-facts-area mb-80" v-show="showFacts">
<div class="icon">
<img src="static/img/core-img/star.png" alt="">
</div>
......@@ -67,7 +67,7 @@
</el-row>
</div>
<div class="popular-courses-area padding-100-0">
<div class="popular-courses-area padding-80-0">
<el-row>
<el-col :span="24">
<div class="section-heading">
......@@ -107,7 +107,7 @@
</el-col>
<el-col :span="6">
<transition name="fade-transform" mode="out-in">
<div class="single-popular-course mb-100" v-show="showCourses">
<div class="single-popular-course mb-80" v-show="showCourses">
<img src="static/img/bg-img/c2.jpg" alt="">
<div class="course-content">
<h4>词汇</h4>
......@@ -136,7 +136,7 @@
</el-col>
<el-col :span="6">
<transition name="fade-transform" mode="out-in">
<div class="single-popular-course mb-100" v-show="showCourses">
<div class="single-popular-course mb-80" v-show="showCourses">
<img src="static/img/bg-img/c3.jpg" alt="">
<div class="course-content">
<h4>说明文写作</h4>
......@@ -166,7 +166,7 @@
</el-row>
</div>
<div class="blog-area padding-100-0">
<div class="blog-area padding-80-0">
<el-row>
<el-col :span="24">
<div class="section-heading">
......@@ -177,7 +177,7 @@
<el-row type="flex" justify="center" :gutter="50">
<el-col :span="10">
<transition name="fade-transform" mode="out-in">
<div class="single-blog-area mb-100" v-show="showBlog">
<div class="single-blog-area mb-80" v-show="showBlog">
<img src="static/img/blog-img/1.jpg" alt="">
<div class="blog-content">
<a href="#" class="blog-headline">
......@@ -195,7 +195,7 @@
</el-col>
<el-col :span="10">
<transition name="fade-transform" mode="out-in">
<div class="single-blog-area mb-100" v-show="showBlog">
<div class="single-blog-area mb-80" v-show="showBlog">
<img src="static/img/blog-img/2.jpg" alt="">
<div class="blog-content">
<a href="#" class="blog-headline">
......@@ -256,13 +256,13 @@ export default {
let vm = this
window.onscroll = function () {
vm.isActive = document.documentElement.scrollTop > 60
if (document.documentElement.scrollTop > 300) {
if (document.documentElement.scrollTop > 250) {
setTimeout(() => {
vm.showCourses = true
}, 350)
}
if (document.documentElement.scrollTop > 800) {
if (document.documentElement.scrollTop > 650) {
setTimeout(() => {
vm.showBlog = true
}, 350)
......
......@@ -13,7 +13,7 @@
<el-col :span="24">
<!-- 题目内容 -->
<choices :ref="`choices${subject.id}`" v-if="subject.type === 0"/>
<short-answer :ref="`shortAnswer${subject.id}`" v-if="subject.type === 1"/>
<short-answer :ref="`shortAnswer${subject.id}`" :height="100" v-if="subject.type === 1"/>
<judgement :ref="`judgement${subject.id}`" v-if="subject.type === 2"/>
<multiple-choices :ref="`multipleChoices${subject.id}`" v-if="subject.type === 3"/>
</el-col>
......@@ -27,7 +27,7 @@
<script>
import { anonymousUserGetObj, fetchAllSubjectList } from '@/api/exam/exam'
import { anonymousUserSubmit } from '@/api/exam/answer'
import { anonymousUserSubmitAll } from '@/api/exam/answer'
import { isNotEmpty, messageWarn, messageFail, messageSuccess } from '@/utils/util'
import Choices from '@/components/Subjects/Choices'
import MultipleChoices from '@/components/Subjects/MultipleChoices'
......@@ -64,7 +64,7 @@ export default {
this.$prompt('请输入考生账号', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPlaceholder: '注册或联系管理员创建账号',
inputPlaceholder: '自行注册或联系管理员创建账号',
inputValidator: function (value) {
if (value.length < 4) {
return false
......@@ -148,6 +148,16 @@ export default {
},
// 提交考试
handleSubmitExam () {
this.$confirm('确定要提交吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
center: true
}).then(() => {
this.doSubmitExam()
}).catch(() => {})
},
doSubmitExam () {
if (this.subjectList.length > 0) {
for (let i = 0; i < this.subjectList.length; i++) {
const subject = this.subjectList[i]
......@@ -159,7 +169,7 @@ export default {
}
}
// 提交到后台
anonymousUserSubmit(this.subjectList, this.exam.id, this.identifier).then(response => {
anonymousUserSubmitAll(this.subjectList, this.exam.id, this.identifier).then(response => {
if (response.data.data) {
messageSuccess(this, '提交成功,5s后自动退出')
setTimeout(() => {
......
......@@ -88,7 +88,6 @@
</template>
<script>
import { updateObjInfo, updateAvatar } from '@/api/admin/user'
import OHeader from '../common/header'
import OFooter from '../common/footer'
import { getToken } from '@/utils/auth'
import { mapState } from 'vuex'
......@@ -113,7 +112,6 @@ export default {
}
},
components: {
OHeader,
OFooter
},
created () {
......
......@@ -37,7 +37,6 @@
</div>
</template>
<script>
import OHeader from '../common/header'
import OFooter from '../common/footer'
import { updatePassword } from '@/api/admin/user'
import { mapState } from 'vuex'
......@@ -82,7 +81,6 @@ export default {
}
},
components: {
OHeader,
OFooter
},
computed: {
......
......@@ -51,7 +51,6 @@
</div>
</template>
<script>
import OHeader from '../common/header'
import OFooter from '../common/footer'
import { updatePassword } from '@/api/admin/user'
import { mapState } from 'vuex'
......@@ -97,7 +96,6 @@ export default {
}
},
components: {
OHeader,
OFooter
},
computed: {
......
......@@ -44,7 +44,6 @@
</div>
</template>
<script>
import OHeader from '../common/header'
import OFooter from '../common/footer'
export default {
data () {
......@@ -57,7 +56,6 @@ export default {
}
},
components: {
OHeader,
OFooter
},
methods: {
......
......@@ -46,7 +46,7 @@
</appender>-->
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="warn">
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
......
......@@ -46,7 +46,7 @@
</appender>-->
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="warn">
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
......
......@@ -30,5 +30,11 @@
<groupId>com.github.tangyi</groupId>
<artifactId>common-feign</artifactId>
</dependency>
<!-- user-service-api -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>user-service-api</artifactId>
</dependency>
</dependencies>
</project>
......@@ -18,6 +18,6 @@ public class ExaminationDto extends Examination {
/**
* 封面地址
*/
private String avatarUrl;
private String logoUrl;
}
......@@ -39,4 +39,14 @@ public class Course extends BaseEntity<Course> {
* 课程描述
*/
private String courseDescription;
/**
* logoId
*/
private Long logoId;
/**
* logo URL
*/
private String logoUrl;
}
......@@ -43,12 +43,6 @@
<artifactId>common-feign</artifactId>
</dependency>
<!-- user-service-api -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>user-service-api</artifactId>
</dependency>
<!-- exam-service-api -->
<dependency>
<groupId>com.github.tangyi</groupId>
......
......@@ -200,6 +200,27 @@ public class AnswerController extends BaseController {
return new ResponseBean<>(answerService.saveAndNext(answer, nextType, nextSubjectId, nextSubjectType));
}
/**
* 保存答题,返回下一题信息
*
* @param answer answer
* @param nextType 0:下一题,1:上一题,2:提交
* @param nextSubjectId nextSubjectId
* @param nextSubjectType 下一题的类型,选择题、判断题
* @return ResponseBean
* @author tangyi
* @date 2019/04/30 18:06
*/
@PostMapping("anonymousUser/saveAndNext")
@ApiOperation(value = "保存答题", notes = "保存答题")
@ApiImplicitParam(name = "answer", value = "答题信息", dataType = "Answer")
public ResponseBean<SubjectDto> anonymousUserSaveAndNext(@RequestBody AnswerDto answer,
@RequestParam Integer nextType,
@RequestParam(required = false) Long nextSubjectId,
@RequestParam(required = false) Integer nextSubjectType) {
return new ResponseBean<>(answerService.saveAndNext(answer, nextType, nextSubjectId, nextSubjectType));
}
/**
* 保存答题
*
......@@ -250,6 +271,22 @@ public class AnswerController extends BaseController {
}
/**
* 提交答卷
*
* @param answer answer
* @return ResponseBean
* @author tangyi
* @date 2018/12/24 20:44
*/
@PostMapping("anonymousUser/submit")
@ApiOperation(value = "提交答卷", notes = "提交答卷")
@ApiImplicitParam(name = "answer", value = "答卷信息", dataType = "Answer")
@Log("提交答题")
public ResponseBean<Boolean> anonymousUserSubmit(@RequestBody Answer answer) {
return new ResponseBean<>(answerService.submitAsync(answer));
}
/**
* 答题列表,包括题目的详情
* 支持查询正确、错误类型的题目
*
......@@ -320,13 +357,13 @@ public class AnswerController extends BaseController {
* @author tangyi
* @date 2020/03/15 16:08
*/
@PostMapping("anonymousUser/submit/{examinationId}")
@PostMapping("anonymousUser/submitAll/{examinationId}")
@ApiOperation(value = "提交答题", notes = "提交答题")
@ApiImplicitParams({
@ApiImplicitParam(name = "examinationId", value = "考试id", dataType = "Long"),
@ApiImplicitParam(name = "identifier", value = "考生账号", dataType = "String")
})
public ResponseBean<Boolean> anonymousUserSubmit(@PathVariable Long examinationId, @RequestParam String identifier, @RequestBody List<SubjectDto> subjectDtos) {
public ResponseBean<Boolean> anonymousUserSubmitAll(@PathVariable Long examinationId, @RequestParam String identifier, @RequestBody List<SubjectDto> subjectDtos) {
return new ResponseBean<>(answerService.anonymousUserSubmit(examinationId, identifier, subjectDtos));
}
}
......@@ -78,7 +78,7 @@ public class CourseController extends BaseController {
@RequestParam(value = CommonConstant.ORDER, required = false, defaultValue = CommonConstant.PAGE_ORDER_DEFAULT) String order,
Course course) {
course.setTenantCode(SysUtil.getTenantCode());
return courseService.findPage(PageUtil.pageInfo(pageNum, pageSize, sort, order), course);
return courseService.findPage(PageUtil.pageInfo(pageNum, pageSize, sort, order), course);
}
/**
......
......@@ -249,4 +249,19 @@ public class ExamRecordController extends BaseController {
public ResponseBean<ExaminationRecordDto> details(@PathVariable Long id) {
return new ResponseBean<>(examRecordService.details(id));
}
/**
* 开始考试
*
* @param examinationId examinationId
* @param identifier identifier
* @return ResponseBean
* @author tangyi
* @date 2020/3/21 5:51 下午
*/
@PostMapping("anonymousUser/start")
@Log("开始考试(匿名)")
public ResponseBean<StartExamDto> anonymousUserStart(Long examinationId, String identifier) {
return new ResponseBean<>(answerService.anonymousUserStart(examinationId, identifier));
}
}
......@@ -249,23 +249,60 @@ public class ExaminationController extends BaseController {
}
/**
* 根据考试ID查询题目id列表
*
* @param examinationId examinationId
* @return ResponseBean
* @author tangyi
* @date 2019/06/18 14:31
*/
@ApiImplicitParam(name = "examinationId", value = "考试ID", required = true, paramType = "path")
@GetMapping("/anonymousUser/{examinationId}/subjectIds")
public ResponseBean<List<ExaminationSubject>> anonymousUserFindExaminationSubjectIds(@PathVariable Long examinationId) {
List<ExaminationSubject> subjects = examinationService.findListByExaminationId(examinationId);
subjects.forEach(BaseEntity::clearCommonValue);
return new ResponseBean<>(subjects);
}
/**
* 根据考试ID生成二维码
* @param examinationId examinationId
* @param response response
* @author tangyi
* @date 2020/3/15 1:16 下午
*/
@ApiOperation(value = "生成二维码", notes = "生成二维码")
@ApiImplicitParams({
@ApiImplicitParam(name = "examinationId", value = "考试ID", required = true, dataType = "Long", paramType = "path")
})
@GetMapping("anonymousUser/generateQrCode/{examinationId}")
public void produceCode(@PathVariable Long examinationId, HttpServletResponse response) throws Exception {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(examinationService.share(examinationId)); ServletOutputStream out = response.getOutputStream()) {
BufferedImage image = ImageIO.read(inputStream);
ImageIO.write(image, "PNG", out);
}
}
@ApiOperation(value = "生成二维码", notes = "生成二维码")
@ApiImplicitParams({
@ApiImplicitParam(name = "examinationId", value = "考试ID", required = true, dataType = "Long", paramType = "path")
})
@GetMapping("anonymousUser/generateQrCode/{examinationId}")
public void produceCode(@PathVariable Long examinationId, HttpServletResponse response) throws Exception {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(examinationService.produceCode(examinationId)); ServletOutputStream out = response.getOutputStream()) {
BufferedImage image = ImageIO.read(inputStream);
ImageIO.write(image, "PNG", out);
}
}
/**
* 根据考试ID生成二维码
* @param examinationId examinationId
* @param response response
* @author tangyi
* @date 2020/3/21 5:38 下午
*/
@ApiOperation(value = "生成二维码(v2)", notes = "生成二维码(v2)")
@ApiImplicitParams({
@ApiImplicitParam(name = "examinationId", value = "考试ID", required = true, dataType = "Long", paramType = "path")
})
@GetMapping("anonymousUser/generateQrCode/v2/{examinationId}")
public void produceCodeV2(@PathVariable Long examinationId, HttpServletResponse response) throws Exception {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(examinationService.produceCodeV2(examinationId)); ServletOutputStream out = response.getOutputStream()) {
BufferedImage image = ImageIO.read(inputStream);
ImageIO.write(image, "PNG", out);
}
}
}
......@@ -262,4 +262,33 @@ public class SubjectController extends BaseController {
return new ResponseBean<>(answerService
.subjectAnswer(subjectId, examRecordId, nextType, nextSubjectId, nextSubjectType));
}
/**
* 查询题目和答题
*
* @param subjectId subjectId
* @param examRecordId examRecordId
* @param userId userId
* @param nextType -1:当前题目,0:下一题,1:上一题
* @param nextSubjectId nextSubjectId
* @param nextSubjectType 下一题的类型,选择题、判断题
* @return ResponseBean
* @author tangyi
* @date 2019/01/16 22:25
*/
@GetMapping("anonymousUser/subjectAnswer")
@ApiOperation(value = "查询题目和答题", notes = "根据题目id查询题目和答题")
@ApiImplicitParams({@ApiImplicitParam(name = "subjectId", value = "题目ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "examRecordId", value = "考试记录ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "userId", value = "用户ID", dataType = "String"),
@ApiImplicitParam(name = "nextType", value = "-1:当前题目,0:下一题,1:上一题", dataType = "Integer")})
public ResponseBean<SubjectDto> anonymousUserSubjectAnswer(@RequestParam("subjectId") @NotBlank Long subjectId,
@RequestParam("examRecordId") @NotBlank Long examRecordId,
@RequestParam(value = "userId", required = false) String userId,
@RequestParam Integer nextType,
@RequestParam(required = false) Long nextSubjectId,
@RequestParam(required = false) Integer nextSubjectType) {
return new ResponseBean<>(answerService
.subjectAnswer(subjectId, examRecordId, nextType, nextSubjectId, nextSubjectType));
}
}
......@@ -358,42 +358,13 @@ public class AnswerService extends CrudService<AnswerMapper, Answer> {
*/
@Transactional
public StartExamDto start(ExaminationRecord examRecord) {
StartExamDto startExamDto = new StartExamDto();
String currentUsername = SysUtil.getUser();
String applicationCode = SysUtil.getSysCode();
String tenantCode = SysUtil.getTenantCode();
// 创建考试记录
if (examRecord.getExaminationId() == null)
throw new CommonException("参数校验失败,考试id为空!");
if (examRecord.getUserId() == null)
throw new CommonException("参数校验失败,用户id为空!");
// 查找考试信息
Examination examination = examinationService.get(examRecord.getExaminationId());
examRecord.setCommonValue(currentUsername, applicationCode, tenantCode);
examRecord.setStartTime(examRecord.getCreateDate());
// 默认未提交状态
examRecord.setSubmitStatus(SubmitStatusEnum.NOT_SUBMITTED.getValue());
// 保存考试记录
if (examRecordService.insert(examRecord) > 0) {
startExamDto.setExamination(examination);
startExamDto.setExamRecord(examRecord);
// 根据题目ID,类型获取第一题的详细信息
SubjectDto subjectDto = subjectService.findFirstSubjectByExaminationId(examRecord.getExaminationId());
startExamDto.setSubjectDto(subjectDto);
// 创建第一题的答题
Answer answer = new Answer();
answer.setCommonValue(currentUsername, applicationCode, tenantCode);
answer.setExamRecordId(examRecord.getId());
answer.setSubjectId(subjectDto.getId());
// 默认待批改状态
answer.setMarkStatus(AnswerConstant.TO_BE_MARKED);
answer.setAnswerType(AnswerConstant.WRONG);
answer.setStartTime(answer.getCreateDate());
// 保存答题
this.save(answer);
subjectDto.setAnswer(answer);
}
return startExamDto;
return this.start(examRecord.getUserId(), currentUsername, examRecord.getExaminationId(), SysUtil.getSysCode(), SysUtil.getTenantCode());
}
/**
......@@ -778,4 +749,78 @@ public class AnswerService extends CrudService<AnswerMapper, Answer> {
AnswerHandleResult shortAnswerResult = shortAnswerHandler.handle(distinctAnswer.get(SubjectTypeEnum.SHORT_ANSWER.name()));
return AnswerHandlerUtil.addAll(Arrays.asList(choiceResult, multipleResult, judgementResult, shortAnswerResult));
}
/**
* 开始考试
*
* @param examinationId examinationId
* @param identifier identifier
* @return StartExamDto
* @author tangyi
* @date 2020/3/21 5:51 下午
*/
@Transactional
public StartExamDto anonymousUserStart(Long examinationId, String identifier) {
String applicationCode = SysUtil.getSysCode();
String tenantCode = SysUtil.getTenantCode();
// 创建考试记录
if (examinationId == null)
throw new CommonException("参数校验失败,考试id为空!");
if (identifier == null)
throw new CommonException("参数校验失败,用户identifier为空!");
// 查询用户信息
ResponseBean<UserVo> userVoResponseBean = userServiceClient.findUserByIdentifier(identifier, tenantCode);
if (!ResponseUtil.isSuccess(userVoResponseBean)) {
throw new CommonException("获取用户" + identifier + "信息失败!");
}
return this.start(userVoResponseBean.getData().getUserId(), identifier, examinationId, applicationCode, tenantCode);
}
/**
* 开始考试
*
* @param userId userId
* @param identifier identifier
* @param examinationId examinationId
* @param applicationCode applicationCode
* @param tenantCode tenantCode
* @return StartExamDto
* @author tangyi
* @date 2019/04/30 23:06
*/
@Transactional
public StartExamDto start(Long userId, String identifier, Long examinationId, String applicationCode, String tenantCode) {
StartExamDto startExamDto = new StartExamDto();
// 查找考试信息
Examination examination = examinationService.get(examinationId);
ExaminationRecord examRecord = new ExaminationRecord();
examRecord.setCommonValue(identifier, applicationCode, tenantCode);
examRecord.setUserId(userId);
examRecord.setExaminationId(examinationId);
examRecord.setStartTime(examRecord.getCreateDate());
// 默认未提交状态
examRecord.setSubmitStatus(SubmitStatusEnum.NOT_SUBMITTED.getValue());
// 保存考试记录
if (examRecordService.insert(examRecord) > 0) {
startExamDto.setExamination(examination);
startExamDto.setExamRecord(examRecord);
// 根据题目ID,类型获取第一题的详细信息
SubjectDto subjectDto = subjectService.findFirstSubjectByExaminationId(examRecord.getExaminationId());
startExamDto.setSubjectDto(subjectDto);
// 创建第一题的答题
Answer answer = new Answer();
answer.setCommonValue(identifier, applicationCode, tenantCode);
answer.setExamRecordId(examRecord.getId());
answer.setSubjectId(subjectDto.getId());
// 默认待批改状态
answer.setMarkStatus(AnswerConstant.TO_BE_MARKED);
answer.setAnswerType(AnswerConstant.WRONG);
answer.setStartTime(answer.getCreateDate());
// 保存答题
this.save(answer);
subjectDto.setAnswer(answer);
}
return startExamDto;
}
}
package com.github.tangyi.exam.service;
import com.github.pagehelper.PageInfo;
import com.github.tangyi.common.basic.properties.SysProperties;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.service.CrudService;
import com.github.tangyi.exam.api.module.Course;
import com.github.tangyi.exam.mapper.CourseMapper;
import com.github.tangyi.user.api.module.Attachment;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* 课程service
*
* @author tangyi
* @date 2018/11/8 21:18
*/
@Slf4j
@Service
@AllArgsConstructor
public class CourseService extends CrudService<CourseMapper, Course> {
private final SysProperties sysProperties;
/**
* 根据id获取课程信息
*
* @param id id
* @return Course
* @author tangyi
* @date 2018/12/03 21:30
*/
@Cacheable(value = "course#" + CommonConstant.CACHE_EXPIRE, key = "#id")
@Override
public Course get(Long id) {
Course course = super.get(id);
if (course != null) {
this.initLogoUrl(Collections.singletonList(course));
}
return course;
}
/**
* 获取课程信息
*
......@@ -33,6 +65,23 @@ public class CourseService extends CrudService<CourseMapper, Course> {
}
/**
* 初始化logo
*
* @param page page
* @param course course
* @author tangyi
* @date 2020/03/18 20:38
*/
@Override
public PageInfo<Course> findPage(PageInfo<Course> page, Course course) {
PageInfo<Course> pageInfo = super.findPage(page, course);
if (CollectionUtils.isNotEmpty(pageInfo.getList())) {
this.initLogoUrl(pageInfo.getList());
}
return pageInfo;
}
/**
* 更新课程信息
*
* @param course course
......@@ -76,4 +125,33 @@ public class CourseService extends CrudService<CourseMapper, Course> {
public int deleteAll(Long[] ids) {
return super.deleteAll(ids);
}
/**
* 初始化logo
*
* @param courseList courseList
* @author tangyi
* @date 2020/03/18 20:38
*/
public void initLogoUrl(List<Course> courseList) {
try {
if (sysProperties.getLogoUrl() != null && !sysProperties.getLogoUrl().endsWith("/")) {
sysProperties.setLogoUrl(sysProperties.getLogoUrl() + "/");
}
courseList.forEach(course -> {
// 获取配置默认头像地址
if (course.getLogoId() != null && course.getLogoId() != 0L) {
Attachment attachment = new Attachment();
attachment.setId(course.getLogoId());
course.setLogoUrl(sysProperties.getLogoUrl() + course.getLogoId() + sysProperties.getLogoSuffix());
} else {
Long index = new Random().nextInt(sysProperties.getLogoCount()) + 1L;
course.setLogoUrl(sysProperties.getLogoUrl() + index + sysProperties.getLogoSuffix());
course.setLogoId(index);
}
});
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
......@@ -73,7 +73,7 @@ public class ExaminationService extends CrudService<ExaminationMapper, Examinati
* @date 2019/1/3 14:06
*/
public int insert(ExaminationDto examinationDto) {
this.initExaminationAvatarUrl(examinationDto);
this.initExaminationLogo(examinationDto);
Examination examination = new Examination();
BeanUtils.copyProperties(examinationDto, examination);
examination.setCourseId(examinationDto.getCourse().getId());
......@@ -106,7 +106,7 @@ public class ExaminationService extends CrudService<ExaminationMapper, Examinati
// 设置考试所属课程
courses.stream().filter(tempCourse -> tempCourse.getId().equals(exam.getCourseId())).findFirst().ifPresent(examinationDto::setCourse);
// 初始化封面图片
this.initExaminationAvatarUrl(examinationDto);
this.initExaminationLogo(examinationDto);
return examinationDto;
}).collect(Collectors.toList());
examinationDtoPageInfo.setList(examinationDtos);
......@@ -126,7 +126,7 @@ public class ExaminationService extends CrudService<ExaminationMapper, Examinati
@CacheEvict(value = "examinationDto", key = "#examinationDto.id")
public int update(ExaminationDto examinationDto) {
if (examinationDto.getAvatarId() == null || examinationDto.getAvatarId() == 0L) {
this.initExaminationAvatarUrl(examinationDto);
this.initExaminationLogo(examinationDto);
}
Examination examination = new Examination();
BeanUtils.copyProperties(examinationDto, examination);
......@@ -288,19 +288,19 @@ public class ExaminationService extends CrudService<ExaminationMapper, Examinati
* @author tangyi
* @date 2020/03/12 22:32:30
*/
public void initExaminationAvatarUrl(ExaminationDto examinationDto) {
public void initExaminationLogo(ExaminationDto examinationDto) {
try {
if (sysProperties.getWebAvatar() != null && !sysProperties.getWebAvatar().endsWith("/")) {
sysProperties.setWebAvatar(sysProperties.getWebAvatar() + "/");
if (sysProperties.getLogoUrl() != null && !sysProperties.getLogoUrl().endsWith("/")) {
sysProperties.setLogoUrl(sysProperties.getLogoUrl() + "/");
}
// 获取配置默认头像地址
if (examinationDto.getAvatarId() != null && examinationDto.getAvatarId() != 0L) {
Attachment attachment = new Attachment();
attachment.setId(examinationDto.getAvatarId());
examinationDto.setAvatarUrl(sysProperties.getWebAvatar() + examinationDto.getAvatarId() + sysProperties.getWebAvatarSuffix());
examinationDto.setLogoUrl(sysProperties.getLogoUrl() + examinationDto.getAvatarId() + sysProperties.getLogoSuffix());
} else {
Long index = new Random().nextInt(sysProperties.getWebAvatarCount()) + 1L;
examinationDto.setAvatarUrl(sysProperties.getWebAvatar() + index + sysProperties.getWebAvatarSuffix());
Long index = new Random().nextInt(sysProperties.getLogoCount()) + 1L;
examinationDto.setLogoUrl(sysProperties.getLogoUrl() + index + sysProperties.getLogoSuffix());
examinationDto.setAvatarId(index);
}
} catch (Exception e) {
......@@ -314,16 +314,35 @@ public class ExaminationService extends CrudService<ExaminationMapper, Examinati
* @author tangyi
* @date 2020/3/15 1:16 下午
*/
public byte[] share(Long examinationId) {
public byte[] produceCode(Long examinationId) {
Examination examination = this.get(examinationId);
// 调查问卷
if (examination == null/* || !ExaminationTypeEnum.QUESTIONNAIRE.getValue().equals(examination.getType())*/) {
return new byte[0];
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
String url = sysProperties.getQrCodeUrl() + examination.getId();
String url = sysProperties.getQrCodeUrl() + "?id=" + examination.getId();
QRCodeUtils.encoderQRCode(url, outputStream, "png");
log.info("Share examinationId: {}, url: {}", examinationId, url);
return outputStream.toByteArray();
}
/**
* 根据考试ID生成二维码
* @param examinationId examinationId
* @author tangyi
* @date 2020/3/21 5:38 下午
*/
public byte[] produceCodeV2(Long examinationId) {
Examination examination = this.get(examinationId);
// 调查问卷
if (examination == null/* || !ExaminationTypeEnum.QUESTIONNAIRE.getValue().equals(examination.getType())*/) {
return new byte[0];
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
String url = sysProperties.getQrCodeUrl() + "-v2?id=" + examination.getId();
QRCodeUtils.encoderQRCode(url, outputStream, "png");
log.info("Share v2 examinationId: {}, url: {}", examinationId, url);
return outputStream.toByteArray();
}
}
......@@ -46,7 +46,7 @@
</appender>-->
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="warn">
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
......
......@@ -46,7 +46,7 @@
</appender>-->
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="warn">
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
......
......@@ -221,4 +221,10 @@ public class UserDto implements Serializable {
*/
@ApiModelProperty(value = "家庭角色,0:爸爸,1:妈妈,2:爷爷,3:奶奶,5:外公,6:外婆", example = "0")
private Integer familyRole;
/**
* 个性签名
*/
@ApiModelProperty(value = "个性签名")
private String signature;
}
......@@ -156,4 +156,9 @@ public class UserInfoDto implements Serializable {
* 微信
*/
private String wechat;
/**
* 个性签名
*/
private String signature;
}
......@@ -126,4 +126,9 @@ public class User extends BaseEntity<User> {
* 家庭角色,参考UserStudentConstant
*/
private Integer familyRole;
/**
* 个性签名
*/
private String signature;
}
......@@ -46,7 +46,7 @@
</appender>-->
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="warn">
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
......
......@@ -20,6 +20,7 @@
<result column="login_time" property="loginTime" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="lock_time" property="lockTime" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="wechat" property="wechat"/>
<result column="signature" property="signature"/>
<result column="family_role" property="familyRole" />
<result column="creator" property="creator"/>
<result column="create_date" property="createDate" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
......@@ -49,6 +50,7 @@
a.login_time,
a.lock_time,
a.wechat,
a.signature,
a.family_role,
a.creator,
a.create_date,
......@@ -142,6 +144,7 @@
login_time,
lock_time,
wechat,
signature,
family_role,
creator,
create_date,
......@@ -169,6 +172,7 @@
#{loginTime, jdbcType=TIMESTAMP, javaType=java.util.Date},
#{lockTime, jdbcType=TIMESTAMP, javaType=java.util.Date},
#{wechat},
#{signature},
#{familyRole, jdbcType=INTEGER},
#{creator},
#{createDate, jdbcType=TIMESTAMP, javaType=java.util.Date},
......@@ -234,6 +238,9 @@
<if test="wechat != null">
wechat = #{wechat} ,
</if>
<if test="signature != null">
signature = #{signature} ,
</if>
<if test="familyRole != null">
family_role = #{familyRole, jdbcType=INTEGER} ,
</if>
......
......@@ -46,7 +46,7 @@
<!-- spring boot、spring cloud -->
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<spring-boot-admin.version>2.2.0-SNAPSHOT</spring-boot-admin.version>
<spring-boot-admin.version>2.2.0</spring-boot-admin.version>
<zipkin.version>2.11.3</zipkin.version>
<swagger.version>2.9.2</swagger.version>
<fastdfs-client.version>1.26.7</fastdfs-client.version>
......@@ -402,4 +402,32 @@
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>spring-milestone</id>
<snapshots>
<enabled>false</enabled>
</snapshots>
<url>http://repo.spring.io/milestone</url>
</repository>
<repository>
<id>spring-snapshot</id>
<snapshots>
<enabled>true</enabled>
</snapshots>
<url>http://repo.spring.io/snapshot</url>
</repository>
<repository>
<id>sonatype-nexus-snapshots</id>
<name>Sonatype Nexus Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
\ No newline at end of file
sonar.projectKey=spring-microservice-exam
sonar.projectName=spring-microservice-exam
sonar.projectVersion=3.5.0
sonar.projectVersion=3.7.0
sonar.sources=
sonar.binaries=bin
sonar.language=java
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment