Commit dccb2593 by tangyi

Merge branch 'dev'

parents 04aa2e85 a67357e5
Version v3.6.0 (2020-02-22)
--------------------------
新功能:
* 支持多选题、不定项选择题、判断题
改进:
* 完善题目管理、成绩批改
* 优化后台样式,如:侧边栏、按钮等样色
* 新用户注册支持默认头像
Version v3.5.0 (2020-02-03)
--------------------------
新功能:
* 后台首页dashboard,支持监控考试记录数
改进:
* 优化题目管理
* 优化后台样式、路由管理等
* 升级spring boot版本为2.2.2.RELEASE、spring cloud版本为Hoxton.SR1
Version v3.5.0 (2020-01-19)
--------------------------
......
......@@ -9,7 +9,9 @@
</a>
</p>
> 硕果云,基于Spring Cloud搭建的新一代微服务教学管理平台,提供多租户、权限管理、考试、练习等功能。
> 硕果云,基于Spring Cloud搭建的新一代微服务教学管理平台,提供多租户、权限管理、在线考试、练习等功能
>
> 题型支持单选题、多选题、不定项选择题、判断题、简答题等
### 🏠 [主页](https://gitee.com/wells2333/spring-microservice-exam)
......@@ -49,8 +51,8 @@
| 名称 | 版本 |
| --------- | -------- |
| `Spring Boot` | `2.1.11.RELEASE` |
| `Spring Cloud` | `Greenwich.SR4` |
| `Spring Boot` | `2.2.2.RELEASE` |
| `Spring Cloud` | `Hoxton.SR1` |
## 系统架构
......@@ -62,7 +64,9 @@
前台主要提供在线考试、在线学习功能
后台管理分为:系统管理、系统监控、考务管理、附件管理、个人管理
后台管理分为:首页监控、系统管理、系统监控、考务管理、附件管理、个人管理
首页监控:提供系统租户数、用户数、考试数、近七天考试记录数等监控
系统管理:提供用户、部门、角色、权限等基础管理
- 用户管理:用户信息增删改查、导入导出
......@@ -81,9 +85,9 @@
考务管理:提供课程、考试、题库、成绩等管理
- 课程管理:课程信息增删改查
- 考试管理:考试信息增删改查、题目管理、发布回收,题目管理支持简单文本、富文本输入、从题库添加等
- 题库管理:题目分类增删改查、题目信息增删改查
- 成绩管理:查看成绩、导出成绩
- 考试管理:考试信息增删改查、题目管理、发布回收,题目管理支持简单文本、富文本输入、从题库添加等,题型支持单选题、多选题、不定项选择题、判断题、简答题
- 题库管理:题目分类增删改查、题目信息增删改查,题型支持单选题、多选题、不定项选择题、判断题、简答题
- 成绩管理:查看成绩、成绩批改、导出等功能
- 知识库:知识库增删改查、上传附件
附件管理:项目的所有附件存储在`fastDfs`里,提供统一的管理入口
......@@ -93,26 +97,53 @@
- 个人资料:姓名、头像等基本信息的修改
- 修改密码:修改密码
## 系统截图
## 系统截图(点击查看大图)
### 前台功能
1. 首页
![image](docs/images/image_web.png)
2. 考试
![image](docs/images/image_web_exam.png)
<table>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web_login.png" alt="登录"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_web.png" alt="首页"/></td>
</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_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>
</table>
### 后台功能
1. 总体功能
![image](docs/images/image_ui_menu.png)
2. 考试管理
![image](docs/images/image_ui_exam.png)
3. 题目管理
![image](docs/images/image_ui_subjects_rich_edit.png)
<table>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_login.png" alt="登录"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_exam.png" alt="首页"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_menu.png" alt="菜单"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_menu_manage.png" alt="菜单管理"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_menu_role_manage.png" alt="角色管理"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_route_manage.png" alt="路由管理"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_subjects_manage.png" alt="题库管理"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_subjects_rich_edit.png" alt="题目编辑"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_score_manage.png" alt="成绩管理"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_score_detail.png" alt="成绩详情"/></td>
</tr>
<tr>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_log_manage.png" alt="日志监控"/></td>
<td><img src="https://gitee.com/wells2333/spring-microservice-exam/raw/master/docs/images/image_ui_consul.png" alt="服务监控"/></td>
</tr>
</table>
## 部署文档
......
......@@ -186,5 +186,10 @@ public class CommonConstant {
* 女
*/
public static final Integer GENDER_WOMEN = 1;
/**
* 逗号
*/
public static final String COMMA = ",";
}
......@@ -67,6 +67,11 @@ public class BaseEntity<T> implements Serializable {
*/
protected boolean isNewRecord;
/**
* 扩展字段
*/
protected String ext;
public BaseEntity(Long id) {
this();
this.id = id;
......@@ -112,5 +117,18 @@ public class BaseEntity<T> implements Serializable {
this.applicationCode = applicationCode;
this.tenantCode = tenantCode;
}
/**
* 置空属性
*/
public void clearCommonValue() {
this.creator = null;
this.createDate = null;
this.modifier = null;
this.modifyDate = null;
this.delFlag = null;
this.applicationCode = null;
this.tenantCode = null;
}
}
......@@ -2,11 +2,9 @@ package com.github.tangyi.common.core.utils;
import lombok.extern.slf4j.Slf4j;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
/**
......@@ -18,9 +16,11 @@ import java.util.Date;
@Slf4j
public class DateUtils {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter FORMATTER_MILLIS = DateTimeFormatter.ofPattern("yyyyMMddhhmmssSSS");
public static final DateTimeFormatter FORMATTER_DAY = DateTimeFormatter.ofPattern("MM-dd");
public static final DateTimeFormatter FORMATTER_MILLIS = DateTimeFormatter.ofPattern("yyyyMMddhhmmssSSS");
/**
* 日期转string
......@@ -131,4 +131,24 @@ public class DateUtils {
}
return seconds;
}
/**
* 获得周几日期,上一周或下一周,依此类推
* @param week 指定周几
* @param whichWeek 那一周
* @return string 日期 年-月-日
*/
public static String getDayOfWhichWeek(DayOfWeek week, int whichWeek) {
LocalDate day = LocalDate.now().with(TemporalAdjusters.previous(week)).minusWeeks(whichWeek -1);
return day.format(FORMATTER_DAY);
}
/**
* 天数累加
* @param plusDay plusDay
* @return LocalDateTime
*/
public static LocalDateTime plusDay(int plusDay) {
return LocalDateTime.now().plusDays(plusDay);
}
}
......@@ -109,7 +109,7 @@ qiniu:
sys:
adminUser: ${ADMIN_USER:admin} # 管理员账号,默认是admin
uploadUrl: api/user/v1/attachment/upload
defaultAvatar: https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80
defaultAvatar: /static/img/avatar/
key: '1234567887654321'
cacheExpire: 86400 # 缓存失效时间,单位秒,默认一天
......
docs/images/image_ui_exam.png

245 KB | W: | H:

docs/images/image_ui_exam.png

210 KB | W: | H:

docs/images/image_ui_exam.png
docs/images/image_ui_exam.png
docs/images/image_ui_exam.png
docs/images/image_ui_exam.png
  • 2-up
  • Swipe
  • Onion skin
docs/images/image_ui_menu.png

273 KB | W: | H:

docs/images/image_ui_menu.png

279 KB | W: | H:

docs/images/image_ui_menu.png
docs/images/image_ui_menu.png
docs/images/image_ui_menu.png
docs/images/image_ui_menu.png
  • 2-up
  • Swipe
  • Onion skin
docs/images/image_ui_msg.png

332 KB | W: | H:

docs/images/image_ui_msg.png

388 KB | W: | H:

docs/images/image_ui_msg.png
docs/images/image_ui_msg.png
docs/images/image_ui_msg.png
docs/images/image_ui_msg.png
  • 2-up
  • Swipe
  • Onion skin
docs/images/image_web_exam.png

248 KB | W: | H:

docs/images/image_web_exam.png

176 KB | W: | H:

docs/images/image_web_exam.png
docs/images/image_web_exam.png
docs/images/image_web_exam.png
docs/images/image_web_exam.png
  • 2-up
  • Swipe
  • Onion skin
docs/images/image_web_exams.png

297 KB | W: | H:

docs/images/image_web_exams.png

302 KB | W: | H:

docs/images/image_web_exams.png
docs/images/image_web_exams.png
docs/images/image_web_exams.png
docs/images/image_web_exams.png
  • 2-up
  • Swipe
  • Onion skin
docs/images/image_web_record.png

248 KB | W: | H:

docs/images/image_web_record.png

196 KB | W: | H:

docs/images/image_web_record.png
docs/images/image_web_record.png
docs/images/image_web_record.png
docs/images/image_web_record.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>系统演示</title>
<title>硕果云-sg-admin</title>
</head>
<body>
<script src=/static/tinymce4.7.5/tinymce.min.js></script>
......
......@@ -44,7 +44,7 @@
"driver.js": "0.5.2",
"dropzone": "5.2.0",
"echarts": "4.1.0",
"element-ui": "2.4.6",
"element-ui": "2.13.0",
"es6-promise": "^4.1.1",
"eslint-plugin-html": "^5.0.0",
"express": "^4.14.1",
......@@ -56,6 +56,7 @@
"jsonlint": "1.6.3",
"jszip": "3.1.5",
"mockjs": "1.0.1-beta3",
"moment": "^2.24.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"screenfull": "3.3.3",
......
......@@ -35,7 +35,7 @@ export function getDownloadUrl (id) {
return request({
url: baseAttachmentUrl + '/download',
method: 'get',
params: {id: id}
params: { id: id }
})
}
......
......@@ -16,3 +16,14 @@ export function getDashboard () {
method: 'get'
})
}
/**
* 过去一周考试记录数据
*/
export function getExamRecordTendency (query) {
return request({
url: '/api/user/v1/dashboard/examRecordTendency',
method: 'get',
params: query
})
}
......@@ -66,6 +66,14 @@ export function putAnswer (obj) {
})
}
export function markAnswer (obj) {
return request({
url: baseAnswerUrl + 'mark',
method: 'put',
data: obj
})
}
export function delAnswer (id) {
return request({
url: baseAnswerUrl + id,
......
......@@ -12,7 +12,7 @@ export function fetchList (query) {
export function fetchSubjectListById (query) {
return request({
url: baseExaminationUrl + query.examinationId + '/subjectList',
url: baseExaminationUrl + 'subjectList',
method: 'get',
params: query
})
......
......@@ -73,3 +73,11 @@ export function exportObj (obj) {
data: obj
})
}
// 查询成绩详情
export function examRecordDetails (id) {
return request({
url: baseExamRecordUrl + id + '/details',
method: 'get'
})
}
<template>
<el-row>
<el-col :span="4">
<el-card class="tree-box-card" style="margin-right: 5px;">
<div slot="header">
<span>题目分类</span>
</div>
<el-row>
<div class="tree-container">
<el-tree
:data="treeData"
:props="defaultProps"
class="filter-tree"
node-key="id"
highlight-current
accordion
@node-click="getNodeData"
/>
</div>
</el-row>
</el-card>
</el-col>
<el-col :span="20">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>题目列表</span>
</div>
<el-table
v-loading="listLoading"
:data="list"
:default-sort="{ prop: 'id', order: 'ascending' }"
highlight-current-row
style="width: 100%;">
<el-table-column :label="$t('table.subjectName')" prop="subject_name" property="subjectName" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subjectName | simpleStrFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')">
<template slot-scope="scope">
<el-tag type="success">{{ scope.row.type | subjectTypeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.score')">
<template slot-scope="scope">
<span>{{ scope.row.score }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" width="100">
<template slot-scope="scope">
<el-button type="text" @click="handleSelectSubject(scope.row)" icon="el-icon-check">{{ $t('table.select') }}</el-button>
</template>
</el-table-column>
</el-table>
<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-card>
</el-col>
</el-row>
</template>
<script>
import Tinymce from '@/components/Tinymce'
import { fetchCategoryTree } from '@/api/exam/subjectCategory'
import { fetchSubjectListById } from '@/api/exam/exam'
import { getSubject } from '@/api/exam/subject'
export default {
name: 'CategorySubjects',
components: {
Tinymce
},
data () {
return {
listLoading: false,
list: [],
total: 0,
dialogVisible: false,
// 题目列表查询参数
listQuery: {
subjectName: undefined,
categoryId: undefined,
examinationId: undefined,
sort: 'id',
order: 'ascending'
},
// 分类树数据
treeData: [],
// 题目分类数据
defaultProps: {
children: 'children',
label: 'categoryName'
},
tempSubject: undefined
}
},
created () {
this.handleCreateSubjectFromSubjectBank()
},
methods: {
getList () {
this.listLoading = true
fetchSubjectListById(this.listQuery).then(response => {
if (response.data.list.length > 0) {
response.data.list.map(subject => {
subject.type = parseInt(subject.type)
subject.level = parseInt(subject.level)
})
this.list = response.data.list
} else {
this.list = []
}
this.total = parseInt(response.data.total)
setTimeout(() => {
this.listLoading = false
}, 500)
})
},
handleSizeChange (val) {
this.limit = val
this.handleSubjectManagement()
},
handleCurrentChange (val) {
this.pageNum = val
this.handleSubjectManagement()
},
// 从题库新增
handleCreateSubjectFromSubjectBank () {
// 加载分类树
fetchCategoryTree(this.listQuery).then(response => {
this.treeData = response.data
})
this.dialogVisible = true
// 加载题目列表
},
// 点击分类
getNodeData (data) {
// 获取分类ID
this.listQuery.categoryId = data.id
// 获取题目信息
this.getList()
},
// 选择题目
handleSelectSubject (selected) {
// 加载题目信息
getSubject(selected.id, { type: selected.type }).then(response => {
this.tempSubject = response.data.data
this.$emit('selected', this.tempSubject)
})
},
getSubjectInfo () {
return this.tempSubject
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../styles/subject.scss";
</style>
......@@ -31,43 +31,32 @@
</el-form-item>
</el-col>
</el-row>
<el-collapse v-model="optionCollapseActives">
<el-collapse-item title="选项列表" name="1">
<el-row class="collapse-top">
<el-row>
<el-col :span="24">
<el-divider>选项列表</el-divider>
<el-form-item v-for="(option, index) in options" :label="option.optionName" :key="option.optionName"
:prop="'options.' + index + '.optionContent'">
<el-row :gutter="5">
<el-col :span="2">
<el-input v-model="option.optionName"></el-input>
<el-col :span="4">
<el-input v-model="option.optionName"/>
</el-col>
<el-col :span="20">
<el-col :span="18">
<el-input v-model="option.optionContent" @input="updateTinymceContent(option.optionContent, index, '1')">
<el-button slot="append" @click.prevent="removeOption(option)">删除</el-button>
</el-input>
</el-col>
</el-row>
</el-form-item>
<el-button type="success" @click.prevent="addOption()" style="display:block;margin:0 auto">新增选项</el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-button @click.prevent="addOption()" style="display:block;margin:0 auto">新增选项</el-button>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<el-collapse v-model="analysisCollapseActives">
<el-collapse-item title="解析" name="2">
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis" class="analysis-form-item">
<el-input v-model="subjectInfo.analysis" @input="updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)"/>
</el-form-item>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
</div>
</el-col>
<el-col :span="14">
......@@ -95,16 +84,16 @@ export default {
default: function () {
return {
id: '',
examinationId: '',
categoryId: 0,
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 0,
choicesType: 0,
options: [
{subjectChoicesId: '', optionName: 'A', optionContent: ''},
{subjectChoicesId: '', optionName: 'B', optionContent: ''},
{subjectChoicesId: '', optionName: 'C', optionContent: ''},
{subjectChoicesId: '', optionName: 'D', optionContent: ''}
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
{ subjectChoicesId: '', optionName: 'B', optionContent: '' },
{ subjectChoicesId: '', optionName: 'C', optionContent: '' },
{ subjectChoicesId: '', optionName: 'D', optionContent: '' }
],
answer: {
subjectId: '',
......@@ -139,7 +128,7 @@ export default {
type: 1, // 类型 0:题目名称,1:选项
dialogTinymceVisible: false,
tempValue: '',
currentEdit: -1,
currentEdit: -1
},
// 编辑对象
tinymceEdit: {
......@@ -168,10 +157,10 @@ export default {
methods: {
initDefaultOptions () {
this.options = [
{subjectChoicesId: '', optionName: 'A', optionContent: ''},
{subjectChoicesId: '', optionName: 'B', optionContent: ''},
{subjectChoicesId: '', optionName: 'C', optionContent: ''},
{subjectChoicesId: '', optionName: 'D', optionContent: ''}
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
{ subjectChoicesId: '', optionName: 'B', optionContent: '' },
{ subjectChoicesId: '', optionName: 'C', optionContent: '' },
{ subjectChoicesId: '', optionName: 'D', optionContent: '' }
]
},
setSubjectInfo (subject) {
......@@ -236,8 +225,8 @@ export default {
resetTempSubject (score) {
this.subjectInfo = {
id: '',
examinationId: '',
categoryId: 0,
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 0,
choicesType: 0,
......@@ -263,7 +252,7 @@ export default {
}
this.initDefaultOptions()
},
addOption() {
addOption () {
// 校验
if (this.options.length > 0) {
let option = this.options[this.options.length - 1]
......@@ -271,25 +260,24 @@ export default {
message(this, '请先输入选项再添加', 'warning')
return
}
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' })
} else {
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' })
}
},
removeOption(item) {
removeOption (item) {
let index = this.options.indexOf(item)
if (index !== -1) {
this.options.splice(index, 1)
}
},
// 点击事件回调
hasClick(hasClick) {
hasClick (hasClick) {
this.editType = 1
}
}
}
</script>
<style lang="scss" scoped>
......@@ -297,4 +285,7 @@ export default {
.el-rate {
margin-top: 8px;
}
.analysis-form-item {
margin-top: 20px;
}
</style>
<template>
<el-form ref="dataSubjectForm" :rules="subjectRules" :model="subjectInfo" :label-position="labelPosition" label-width="100px">
<el-row>
<el-col :span="10">
<div class="subject-info">
<el-row>
<el-col :span="12">
<el-form-item :label="$t('table.subject.score')" prop="score">
<el-input v-model="subjectInfo.score"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('table.subject.level')" prop="level">
<el-rate v-model="subjectInfo.level" :max="4" :texts="['简单', '一般', '略难', '非常难']" show-text/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subjectName')" prop="subjectName">
<el-input v-model="subjectInfo.subjectName" @focus="updateTinymceContent(subjectInfo.subjectName, tinymceEdit.subjectName)"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.answer')" prop="answer">
<el-radio-group v-model="subjectInfo.answer.answer">
<el-radio v-for="(option) in options" :label="option.optionName" :key="option.optionName">{{ option.optionName }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis" class="analysis-form-item">
<el-input v-model="subjectInfo.analysis" @input="updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)"/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
<el-col :span="14">
<div class="subject-tinymce">
<tinymce ref="choicesEditor" :height="350" v-model="choicesContent" @hasClick="hasClick"/>
</div>
</el-col>
</el-row>
</el-form>
</template>
<script>
import Tinymce from '@/components/Tinymce'
import { isNotEmpty, message } from '@/utils/util'
export default {
name: 'Judgement',
components: {
Tinymce
},
props: {
subject: {
type: Object,
default: function () {
return {
id: '',
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 0,
choicesType: 0,
options: [
{ subjectChoicesId: '', optionName: '正确', optionContent: '正确' },
{ subjectChoicesId: '', optionName: '错误', optionContent: '错误' }
],
answer: {
subjectId: '',
answer: '',
answerType: '',
score: ''
},
score: 5,
analysis: '',
level: 2,
editType: 0 // 0: 输入框,1:富文本
}
}
},
choices: {
type: String,
default: ''
}
},
data () {
return {
subjectInfo: this.subject,
choicesContent: this.choices,
labelPosition: 'right',
// 表单校验规则
subjectRules: {
subjectName: [{ required: true, message: '请输入题目名称', trigger: 'change' }],
score: [{ required: true, message: '请输入题目分值', trigger: 'change' }],
answer: [{ required: true, message: '请输入答案', trigger: 'change' }]
},
tinymce: {
type: 1, // 类型 0:题目名称,1:选项
dialogTinymceVisible: false,
tempValue: '',
currentEdit: -1
},
// 编辑对象
tinymceEdit: {
subjectName: -1,
answer: 4,
analysis: 5
},
options: []
}
},
watch: {
// 监听富文本编辑器的输入
choicesContent: {
handler: function (choicesContent) {
if (isNotEmpty(this.$refs.choicesEditor)) {
if (this.editType === 1 && this.$refs.choicesEditor.getHasClick()) {
this.saveTinymceContent(choicesContent)
}
}
},
immediate: true
}
},
methods: {
initDefaultOptions () {
this.options = [
{ subjectChoicesId: '', optionName: '正确', optionContent: '正确' },
{ subjectChoicesId: '', optionName: '错误', optionContent: '错误' }
]
},
setSubjectInfo (subject) {
this.subjectInfo = subject
this.initDefaultOptions()
},
getSubjectInfo () {
return this.subjectInfo
},
// 绑定富文本的内容
updateTinymceContent (content, currentEdit, type) {
// 重置富文本
this.choicesContent = ''
// 绑定当前编辑的对象
this.tinymce.currentEdit = currentEdit
this.tinymce.type = type
this.$refs.choicesEditor.setContent(content || '')
this.editType = 0
this.$refs.choicesEditor.setHashClick(false)
},
saveTinymceContent (content) {
switch (this.tinymce.currentEdit) {
case this.tinymceEdit.subjectName:
this.subjectInfo.subjectName = content
break
case this.tinymceEdit.answer:
this.subjectInfo.answer.answer = content
break
case this.tinymceEdit.analysis:
this.subjectInfo.analysis = content
break
}
},
// 表单校验
validate () {
let valid = false
this.$refs['dataSubjectForm'].validate((validate) => {
valid = validate
})
return valid
},
clearValidate () {
this.$refs['dataSubjectForm'].clearValidate()
},
resetTempSubject (score) {
this.subjectInfo = {
id: '',
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 0,
choicesType: 0,
answer: {
subjectId: '',
answer: '',
answerType: '',
score: ''
},
score: 5,
analysis: '',
level: 2
}
// 默认分数
if (isNotEmpty(score)) {
this.subjectInfo.score = score
}
this.initDefaultOptions()
},
// 点击事件回调
hasClick (hasClick) {
this.editType = 1
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../styles/subject.scss";
.el-rate {
margin-top: 8px;
}
.analysis-form-item {
margin-top: 20px;
}
</style>
......@@ -32,6 +32,8 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-collapse v-model="optionCollapseActives">
<el-collapse-item title="选项列表" name="1">
<el-row class="collapse-top">
......@@ -39,10 +41,10 @@
<el-form-item v-for="(option, index) in options" :label="option.optionName" :key="option.optionName"
:prop="'options.' + index + '.optionContent'">
<el-row :gutter="5">
<el-col :span="2">
<el-input v-model="option.optionName"></el-input>
<el-col :span="4">
<el-input v-model="option.optionName"/>
</el-col>
<el-col :span="20">
<el-col :span="18">
<el-input v-model="option.optionContent" @input="updateTinymceContent(option.optionContent, index, '1')">
<el-button slot="append" @click.prevent="removeOption(option)">删除</el-button>
</el-input>
......@@ -53,22 +55,20 @@
</el-row>
<el-row>
<el-col :span="24">
<el-button @click.prevent="addOption()" style="display:block;margin:0 auto">新增选项</el-button>
<el-button type="success" @click.prevent="addOption()" style="display:block;margin:0 auto">新增选项</el-button>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<el-collapse v-model="analysisCollapseActives">
<el-collapse-item title="解析" name="2">
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis">
<el-form-item :label="$t('table.subject.analysis')" prop="analysis" class="analysis-form-item">
<el-input v-model="subjectInfo.analysis" @input="updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)"/>
</el-form-item>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
</div>
</el-col>
<el-col :span="14">
......@@ -82,7 +82,7 @@
<script>
import Tinymce from '@/components/Tinymce'
import { isNotEmpty } from '@/utils/util'
import { isNotEmpty, message } from '@/utils/util'
export default {
name: 'MultipleChoices',
......@@ -95,8 +95,8 @@ export default {
default: function () {
return {
id: '',
examinationId: '',
categoryId: 0,
examinationId: undefined,
categoryId: undefined,
subjectName: '',
choicesContent: this.choices,
answer: {
......@@ -131,7 +131,7 @@ export default {
type: 1, // 类型 0:题目名称,1:选项
dialogTinymceVisible: false,
tempValue: '',
currentEdit: -1,
currentEdit: -1
},
// 编辑对象
tinymceEdit: {
......@@ -161,10 +161,10 @@ export default {
methods: {
initDefaultOptions () {
this.options = [
{subjectChoicesId: '', optionName: 'A', optionContent: ''},
{subjectChoicesId: '', optionName: 'B', optionContent: ''},
{subjectChoicesId: '', optionName: 'C', optionContent: ''},
{subjectChoicesId: '', optionName: 'D', optionContent: ''}
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
{ subjectChoicesId: '', optionName: 'B', optionContent: '' },
{ subjectChoicesId: '', optionName: 'C', optionContent: '' },
{ subjectChoicesId: '', optionName: 'D', optionContent: '' }
]
},
setSubjectInfo (subject) {
......@@ -231,8 +231,8 @@ export default {
resetTempSubject (serialNumber, score) {
this.subjectInfo = {
id: '',
examinationId: '',
categoryId: 0,
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 0,
choicesType: 0,
......@@ -258,7 +258,7 @@ export default {
}
this.initDefaultOptions()
},
addOption() {
addOption () {
// 校验
if (this.options.length > 0) {
let option = this.options[this.options.length - 1]
......@@ -266,27 +266,27 @@ export default {
message(this, '请先输入选项再添加', 'warning')
return
}
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' })
} else {
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' });
this.options.push({ subjectChoicesId: '', optionName: '', optionContent: '' })
}
},
removeOption(item) {
removeOption (item) {
let index = this.options.indexOf(item)
if (index !== -1) {
this.options.splice(index, 1)
}
},
// 点击事件回调
hasClick(hasClick) {
hasClick (hasClick) {
this.editType = 1
},
initMultipleAnswers() {
initMultipleAnswers () {
if (isNotEmpty(this.subjectInfo.answer)) {
this.multipleAnswers = this.subjectInfo.answer.answer.split(',')
}
},
getMultipleAnswers() {
getMultipleAnswers () {
if (this.multipleAnswers.length > 0) {
return this.multipleAnswers.join(',')
}
......@@ -297,26 +297,13 @@ export default {
</script>
<style lang="scss" scoped>
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
.editor-content{
margin-top: 20px;
}
.subject-info {
padding-right: 12px;
}
.subject-tinymce {
padding-left: 12px;
}
.analysis-form-item {
margin-top: 20px;
}
</style>
......@@ -35,7 +35,6 @@
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
<el-col :span="14">
<div class="subject-tinymce">
......@@ -62,8 +61,8 @@ export default {
return {
id: '',
serialNumber: 1,
examinationId: '',
categoryId: 0,
examinationId: -1,
categoryId: -1,
subjectName: '',
type: 1,
answer: {
......@@ -159,9 +158,8 @@ export default {
resetTempSubject (serialNumber, score) {
this.subjectInfo = {
id: '',
serialNumber: 1,
examinationId: '',
categoryId: 0,
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 1,
choicesType: 0,
......@@ -180,7 +178,7 @@ export default {
this.subjectInfo.score = score
}
},
initDefaultOptions() {
initDefaultOptions () {
}
}
......
export const nextSubjectType = {
last: 1,
next: 0,
current: 2
}
export const answerType = {
'true': 'right',
'false': 'incorrect'
}
import { formatDate, commonFilter, isNotEmpty } from '../utils/util'
import { statusType, examType, subjectType } from '../utils/constant'
import { statusType, examType, subjectType, subjectTypeTag } from '../utils/constant'
/**
* 日期格式化
......@@ -31,6 +30,9 @@ export function statusTypeFilter (status) {
* @returns {string}
*/
export function simpleStrFilter (str, length) {
if (length === undefined) {
length = 20
}
return commonFilter(str, length)
}
......@@ -48,8 +50,8 @@ export function examTypeFilter (type) {
* @param status
* @returns {string}
*/
export function examStatusFilter (status) {
return status === 0 ? '已发布' : '未发布'
export function publicStatusFilter (status) {
return parseInt(status) === 0 ? '已发布' : '草稿'
}
/**
......@@ -61,3 +63,36 @@ export function subjectTypeFilter (type) {
return subjectType[type]
}
/**
* 题目标签
* @param type
* @returns {*}
*/
export function subjectTypeTagFilter (type) {
return subjectTypeTag[type]
}
/**
* 考试提交状态
* @param type
* @returns {*}
*/
export function submitStatusFilter (type) {
const typeMap = {
0: '待批改',
1: '已批改',
2: '待批改',
3: '统计完成'
}
return typeMap[type]
}
/**
* success状态
* @param status, 自动传入
* @param expectStatus
* @returns {string}
*/
export function simpleTagStatusFilter (status, expectStatus) {
return status === expectStatus ? 'success' : 'warning'
}
......@@ -139,12 +139,14 @@ export default {
actions: '操作',
edit: '修改',
view: '查看',
preview: '预览',
publish: '发布',
draft: '草稿',
delete: '删除',
cancel: '取 消',
confirm: '确 定',
save: '保存',
select: '选择',
saveAndAdd: '保存并添加',
username: '账号',
name: '姓名',
......@@ -179,10 +181,17 @@ export default {
attention: '注意事项',
startTime: '开始时间',
endTime: '结束时间',
totalScore: '考试总分',
examTime: '考试时间',
totalScore: '总分',
totalSubject: '题目数',
subjectManagement: '题目管理',
subjectName: '题目名称',
userAnswer: '考生答案',
correct: '正确',
inCorrect: '错误',
time: '耗时',
answerCorrectType: '答题结果',
score: '得分',
subject: {
serialNumber: '序号',
type: '类型',
......@@ -202,7 +211,7 @@ export default {
modifier: '修改人'
},
public: '发布',
retrieve: '回收',
withdraw: '撤回',
categoryName: '分类名称',
categoryDesc: '分类描述',
sort: '排序号',
......@@ -214,7 +223,8 @@ export default {
examTime: '考试时间',
submitStatus: '状态',
details: '详情',
marking: '批改'
marking: '批改',
markStatus: '批改状态'
},
knowledge: {
knowledgeName: '名称',
......@@ -241,8 +251,8 @@ export default {
clientSecretPlainText: '密钥明文',
scope: '授权范围',
authorizedGrantTypes: '授权类型',
accessTokenValidity: 'token有效期',
refreshTokenValidity: 'refresh_token有效期'
accessTokenValidity: 'token有效期/秒',
refreshTokenValidity: 'refresh_token有效期/秒'
},
route: {
routeId: '路由ID',
......
......@@ -18,6 +18,7 @@ import VueParticles from 'vue-particles'
import { loadStyle } from './utils/util'
import * as urls from '@/config/env'
import Spinner from 'vue-spinkit'
import moment from 'moment'
const iconfontVersion = ['567566_r22zi6t8noas8aor', '599693_0b5sleso3f1j1yvi', '667895_xte3dcfrvbo6r']
const iconfontUrl = `//at.alicdn.com/t/font_$key.css`
......@@ -30,6 +31,8 @@ Vue.use(Element, {
// 使用登录页粒子效果插件
Vue.use(VueParticles)
Vue.prototype.moment = moment
// loading效果
Vue.component('Spinner', Spinner)
......
......@@ -114,7 +114,7 @@ export const constantExamRouterMap = [
component: Layout,
children: [
{
path: '/exam/exam/subjects/:id',
path: '/exam/subjects/:id',
component: () => import('@/views/exam/examSubjects'),
name: '题目管理',
title: '题目管理',
......@@ -127,13 +127,39 @@ export const constantExamRouterMap = [
component: Layout,
children: [
{
path: '/exam/:examinationId/subjects/:id/:type',
path: '/exam/subjects/detail/:id',
component: () => import('@/views/exam/subjectDetails'),
name: '题目详情',
title: '题目详情',
noCache: true
}
]
},
{
path: '',
component: Layout,
children: [
{
path: '/exam/score/detail/:id',
component: () => import('@/views/exam/scoreDetails'),
name: '成绩详情',
title: '成绩详情',
noCache: true
}
]
},
{
path: '',
component: Layout,
children: [
{
path: '/exam/mark/:id',
component: () => import('@/views/exam/markExam'),
name: '成绩批改',
title: '成绩批改',
noCache: true
}
]
}
]
......
......@@ -7,10 +7,12 @@
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
font-family: -apple-system,system-ui,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #292b2c;
background-color: #fff;
}
label {
......@@ -33,7 +35,7 @@ html {
}
.no-padding {
padding: 0px !important;
padding: 0 !important;
}
.padding-content {
......@@ -117,7 +119,6 @@ code {
background: rgba(66,185,131,.1);
border-radius: 2px;
padding: 16px;
padding: 1rem;
line-height: 1.6rem;
word-spacing: .05rem;
a{
......@@ -128,7 +129,7 @@ code {
//main-container全局样式
.app-container {
padding: 20px;
padding: 12px;
}
.components-container {
......@@ -201,3 +202,7 @@ code {
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.dialog-footer {
text-align: center;
}
......@@ -21,5 +21,4 @@
padding-left: 12px;
}
.collapse-top {
margin-top: 20px;
}
......@@ -8,6 +8,6 @@ $yellow:#FEC171;
$panGreen: #30B08F;
//sidebar
$menuBg:#304156;
$subMenuBg:#1f2d3d;
$menuHover:#001528;
$menuBg:#282828;
$subMenuBg:#282828;
$menuHover:#282828;
......@@ -19,6 +19,14 @@ export const examType = {
// 题目类型
export const subjectType = {
0: '单选题',
2: '判断题',
1: '简答题',
3: '多选题'
}
export const subjectTypeTag = {
0: 'success',
1: 'info',
2: 'warning',
3: 'default'
}
......@@ -237,7 +237,7 @@ export const checkMultipleSelect = (multipleSelection, obj) => {
* 设置浏览器头部标题
*/
export const setTitle = function (title) {
title = title ? `${title}——系统演示` : '系统演示'
title = title ? `${title}——硕果云` : '硕果云'
window.document.title = title
}
......@@ -319,6 +319,15 @@ export const messageSuccess = (obj, message) => {
}
/**
* 警告消息提示
* @param obj
* @param message
*/
export const messageWarn = (obj, message) => {
obj.$message({ message: message, type: 'warning' })
}
/**
* 失败消息提示
* @param obj
* @param message
......@@ -389,3 +398,7 @@ export const commonFilter = (str, length) => {
export const isCreate = (status) => {
return status === 'create'
}
export const trimComma = (str) => {
return str.replace(new RegExp('^,*|,*$', 'gm'), '')
}
......@@ -73,7 +73,6 @@ import { fetchList, addObj, putObj, delAttachment, getDownloadUrl } from '@/api/
import waves from '@/directive/waves'
import { getToken } from '@/utils/auth' // getToken from cookie
import { notifySuccess, messageSuccess, isNotEmpty, formatDate } from '@/utils/util'
import { mapState } from 'vuex'
import SpinnerLoading from '@/components/SpinnerLoading'
export default {
......
<template>
<div :class="className" :style="{height:height,width:width}"/>
</template>
<script>
import echarts from 'echarts' // echarts theme
import { debounce } from '@/utils'
require('echarts/theme/macarons')
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '350px'
}
},
data () {
return {
chart: null
}
},
mounted () {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy () {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart () {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
bottom: '10',
left: 'center',
data: ['4级', '5级', '6级']
},
series: [
{
name: '难度等级分布',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner'
},
labelLine: {
show: false
},
data: [
]
},
{
name: '难度等级分布',
type: 'pie',
radius: ['40%', '55%'],
label: {
normal: {
formatter: function (param) {
return param.name + '\n' + Math.round(param.percent) + '%'
}
}
},
data: [
{ value: 12, name: '4级' },
{ value: 11, name: '5级' },
{ value: 7, name: '6级' }
],
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
})
}
}
}
</script>
......@@ -21,7 +21,7 @@ export default {
},
height: {
type: String,
default: '300px'
default: '350px'
}
},
data () {
......@@ -66,7 +66,7 @@ export default {
},
xAxis: [{
type: 'category',
data: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
data: ['初级', '中级', '高级'],
axisTick: {
alignWithLabel: true
}
......@@ -78,25 +78,15 @@ export default {
}
}],
series: [{
name: 'pageA',
name: '考生水平分布',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [79, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageB',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [80, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageC',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [30, 52, 200, 334, 390, 330, 220],
barWidth: '30%',
data: [14, 4, 3],
label: {
show: true,
position: 'top'
},
animationDuration
}]
})
......
......@@ -19,7 +19,7 @@ export default {
},
height: {
type: String,
default: '350px'
default: '400px'
},
autoResize: {
type: Boolean,
......@@ -78,10 +78,10 @@ export default {
this.__resizeHandler()
}
},
setOptions ({ expectedData, actualData } = {}) {
setOptions ({ examRecordDate, examRecordData } = {}) {
this.chart.setOption({
xAxis: {
data: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
data: examRecordDate,
boundaryGap: false,
axisTick: {
show: false
......@@ -107,27 +107,10 @@ export default {
}
},
legend: {
data: ['预期', '实际']
data: ['考试记录数']
},
series: [{
name: '预期',
itemStyle: {
normal: {
color: '#FF005A',
lineStyle: {
color: '#FF005A',
width: 2
}
}
},
smooth: true,
type: 'line',
data: expectedData,
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: '实际',
name: '考试记录数',
smooth: true,
type: 'line',
itemStyle: {
......@@ -142,7 +125,7 @@ export default {
}
}
},
data: actualData,
data: examRecordData,
animationDuration: 2800,
animationEasing: 'quadraticOut'
}]
......@@ -155,4 +138,3 @@ export default {
}
}
</script>
......@@ -39,8 +39,8 @@
<svg-icon icon-class="chart" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">课程</div>
<count-to :start-val="0" :end-val="13600" :duration="2600" class="card-panel-num"/>
<div class="card-panel-text">考试次</div>
<count-to :start-val="0" :end-val="examinationRecordNumber" :duration="2600" class="card-panel-num"/>
</div>
</div>
</el-col>
......@@ -50,7 +50,7 @@
<script>
import CountTo from 'vue-count-to'
import { getDashboard } from '@/api/admin/sys'
import { isNotEmpty, isSuccess, messageFail } from '@/utils/util'
import { isNotEmpty, isSuccess } from '@/utils/util'
export default {
components: {
......@@ -61,7 +61,8 @@ export default {
onlineUserNumber: 0,
examinationNumber: 0,
examUserNumber: 0,
tenantNumber: 0
tenantNumber: 0,
examinationRecordNumber: 0
}
},
created () {
......@@ -88,6 +89,9 @@ export default {
if (isNotEmpty(data.tenantCount)) {
this.tenantNumber = parseInt(data.tenantCount)
}
if (isNotEmpty(data.examinationRecordNumber)) {
this.examinationRecordNumber = parseInt(data.examinationRecordNumber)
}
}
}).catch(error => {
console.error(error)
......@@ -113,7 +117,6 @@ export default {
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
border-color: rgba(0, 0, 0, .05);
border-radius: 6px;
border-top: 3px solid #40c9c6;
&:hover {
.card-panel-icon-wrapper {
color: #fff;
......
......@@ -19,7 +19,7 @@ export default {
},
height: {
type: String,
default: '300px'
default: '350px'
}
},
data () {
......@@ -47,7 +47,6 @@ export default {
methods: {
initChart () {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
......@@ -56,25 +55,37 @@ export default {
legend: {
left: 'center',
bottom: '10',
data: ['语文', '数学', '英语', '化学', '物理']
data: ['越南', '泰国', '苏丹', '蒙古', '老挝', '韩国', '哈萨克斯坦', '俄罗斯', '阿塞拜疆', '阿富汗']
},
calculable: true,
series: [
{
name: 'WEEKLY WRITE ARTICLES',
name: '考生分布图',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: [
{ value: 320, name: '语文' },
{ value: 240, name: '数学' },
{ value: 149, name: '英语' },
{ value: 100, name: '化学' },
{ value: 59, name: '物理' }
{ value: 1, name: '越南' },
{ value: 3, name: '泰国' },
{ value: 1, name: '苏丹' },
{ value: 1, name: '蒙古' },
{ value: 3, name: '老挝' },
{ value: 1, name: '韩国' },
{ value: 3, name: '哈萨克斯坦' },
{ value: 5, name: '俄罗斯' },
{ value: 1, name: '阿塞拜疆' },
{ value: 1, name: '阿富汗' }
],
animationEasing: 'cubicInOut',
animationDuration: 2600
animationDuration: 2600,
label: {
normal: {
formatter: function (param) {
return param.name + '\n' + Math.round(param.percent) + '%'
}
}
}
}
]
})
......
......@@ -21,7 +21,7 @@ export default {
},
height: {
type: String,
default: '300px'
default: '350px'
}
},
data () {
......
......@@ -5,28 +5,37 @@
<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
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</div>
<div class="chart-wrapper-body">
<line-chart :chart-data="lineChartData"/>
</div>
</el-row>
<el-row :gutter="32">
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<div class="chart-wrapper-header">
<div>趋势</div>
<div>难度等级分布</div>
</div>
<div class="chart-wrapper-body">
<raddar-chart/>
<another-pie-chart/>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<div class="chart-wrapper-header">
<div>趋势</div>
<div>考生分布图</div>
</div>
<div class="chart-wrapper-body">
<pie-chart/>
......@@ -36,7 +45,7 @@
<el-col :xs="24" :sm="24" :lg="8">
<div class="chart-wrapper">
<div class="chart-wrapper-header">
<div>趋势</div>
<div>考生水平分布</div>
</div>
<div class="chart-wrapper-body">
<bar-chart/>
......@@ -44,7 +53,6 @@
</div>
</el-col>
</el-row>
</div>
</template>
......@@ -53,24 +61,23 @@ import PanelGroup from './components/PanelGroup'
import LineChart from './components/LineChart'
import RaddarChart from './components/RaddarChart'
import PieChart from './components/PieChart'
import AnotherPieChart from './components/AnotherPieChart'
import BarChart from './components/BarChart'
import { getExamRecordTendency } from '@/api/admin/sys'
const lineChartData = {
newVisitis: {
expectedData: [100, 120, 161, 134, 105, 160, 165],
actualData: [120, 82, 91, 154, 162, 140, 145]
const dashboardData = {
examRecordData: {
data: [120, 82, 91, 154, 162, 140, 145],
date: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
},
messages: {
expectedData: [200, 192, 120, 144, 160, 130, 140],
actualData: [180, 160, 151, 106, 145, 150, 130]
examRecordData: [180, 160, 151, 106, 145, 150, 130]
},
purchases: {
expectedData: [80, 100, 121, 104, 105, 90, 100],
actualData: [120, 90, 100, 138, 142, 130, 130]
examRecordData: [120, 90, 100, 138, 142, 130, 130]
},
shoppings: {
expectedData: [130, 140, 141, 142, 145, 150, 160],
actualData: [120, 82, 91, 154, 162, 140, 130]
examRecordData: [120, 82, 91, 154, 162, 140, 130]
}
}
......@@ -81,16 +88,41 @@ export default {
LineChart,
RaddarChart,
PieChart,
AnotherPieChart,
BarChart
},
created () {
this.getExamRecordData()
},
data () {
return {
lineChartData: lineChartData.newVisitis
query: {
pastDays: 7
},
lineChartData: {
examRecordData: [],
examRecordDate: []
},
options: [{
value: 7,
label: '过去7天'
}, {
value: 15,
label: '过去15天'
}, {
value: 30,
label: '过去30天'
}]
}
},
methods: {
handleSetLineChartData (type) {
this.lineChartData = lineChartData[type]
this.lineChartData = dashboardData[type]
},
getExamRecordData () {
getExamRecordTendency(this.query).then(response => {
this.lineChartData = response.data.data
})
}
}
}
......@@ -98,7 +130,7 @@ export default {
<style rel="stylesheet/scss" lang="scss" scoped>
.dashboard-editor-container {
padding: 20px;
padding: 12px;
.chart-wrapper {
background: #fff;
padding: 16px 16px 0;
......@@ -110,6 +142,12 @@ export default {
border-bottom: 1px solid #EBEEF5;
-webkit-box-sizing: border-box;
box-sizing: border-box;
.chart-wrapper-header-select {
position: absolute;
right: 0;
top: 6px;
padding: 8px 12px;
}
}
.chart-wrapper-body {
padding-top: 20px;
......
......@@ -3,8 +3,8 @@
<div class="filter-container">
<el-input v-model="listQuery.courseName" placeholder="课程名称" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button v-if="course_btn_add" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" plain @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="course_btn_del" class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="course_btn_add" class="filter-item" type="primary" style="margin-left: 10px;" icon="el-icon-check" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="course_btn_del" class="filter-item" type="danger" icon="el-icon-delete" @click="handleDeletes">{{ $t('table.del') }}</el-button>
</div>
<spinner-loading v-if="listLoading"/>
<el-table
......@@ -40,8 +40,8 @@
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300">
<template slot-scope="scope">
<el-button v-if="course_btn_edit" type="text" @click="handleUpdate(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="course_btn_del" type="text" @click="handleDelete(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</el-button>
<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>
</template>
</el-table-column>
</el-table>
......@@ -275,8 +275,6 @@ export default {
this.getList()
notifySuccess(this, '删除成功')
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
}).catch(() => {})
},
// 批量删除
......
......@@ -3,8 +3,8 @@
<div class="filter-container">
<el-input :placeholder="$t('table.examinationName')" v-model="listQuery.examinationName" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button v-if="exam_btn_add" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" plain @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="exam_btn_del" class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="exam_btn_add" class="filter-item" type="primary" style="margin-left: 10px;" icon="el-icon-check" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="exam_btn_del" class="filter-item" type="danger" icon="el-icon-delete" @click="handleDeletes">{{ $t('table.del') }}</el-button>
</div>
<spinner-loading v-if="listLoading"/>
<!--考试列表-->
......@@ -23,7 +23,7 @@
<span>{{ scope.row.examinationName | simpleStrFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.type')">
<el-table-column :label="$t('table.examinationType')">
<template slot-scope="scope">
<span>{{ scope.row.type | examTypeFilter }}</span>
</template>
......@@ -33,14 +33,9 @@
<span>{{ scope.row | courseFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.startTime')">
<el-table-column :label="$t('table.examTime')" width="300">
<template slot-scope="scope">
<span>{{ scope.row.startTime | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.endTime')">
<template slot-scope="scope">
<span>{{ scope.row.endTime | fmtDate('yyyy-MM-dd hh:mm') }}</span>
<span>{{ scope.row.startTime | fmtDate('yyyy-MM-dd hh:mm') }}~{{ scope.row.endTime | fmtDate('yyyy-MM-dd hh:mm') }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.totalScore')">
......@@ -50,16 +45,16 @@
</el-table-column>
<el-table-column :label="$t('table.status')">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusTypeFilter ">{{ scope.row.status | examStatusFilter }}</el-tag>
<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="300">
<el-table-column :label="$t('table.actions')" class-name="status-col" width="400">
<template slot-scope="scope">
<el-button v-if="exam_btn_edit" type="text" @click="handleUpdate(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="exam_btn_edit && scope.row.status == 1" type="text" @click="handlePublic(scope.row, 0)" icon="el-icon-check">{{ $t('table.public') }}</el-button>
<el-button v-if="exam_btn_edit && scope.row.status == 0" type="text" @click="handlePublic(scope.row, 1)" icon="el-icon-remove-outline">{{ $t('table.retrieve') }}</el-button>
<el-button v-if="exam_btn_subject" type="text" @click="handleSubjectManagement(scope.row)" icon="el-icon-document">{{ $t('table.subjectManagement') }}</el-button>
<el-button v-if="exam_btn_del" type="text" @click="handleDelete(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</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>
<el-button v-if="exam_btn_del" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}</el-button>
</template>
</el-table-column>
</el-table>
......@@ -377,7 +372,7 @@ export default {
endTime: '',
duration: '',
totalScore: '',
status: 0,
status: 1,
avatar: '',
collegeId: '',
majorId: '',
......@@ -460,8 +455,6 @@ export default {
this.getList()
notifySuccess(this, '删除成功')
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
}).catch(() => {})
},
// 批量删除
......@@ -502,7 +495,7 @@ export default {
// 加载题目
handleSubjectManagement (row) {
this.$router.push({
path: `/exam/exam/subjects/${row.id}`,
path: `/exam/subjects/${row.id}`
})
},
// 发布考试
......
......@@ -3,8 +3,8 @@
<div class="filter-container">
<el-input v-model="listQuery.knowledgeName" placeholder="知识名称" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button class="filter-item" style="margin-left: 10px;" icon="el-icon-check" plain @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button class="filter-item" type="primary" style="margin-left: 10px;" icon="el-icon-check" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button class="filter-item" type="danger" icon="el-icon-delete" @click="handleDeletes">{{ $t('table.del') }}</el-button>
</div>
<spinner-loading v-if="listLoading"/>
<el-table
......@@ -30,15 +30,15 @@
</el-table-column>
<el-table-column :label="$t('table.knowledge.status')" min-width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusTypeFilter">{{ scope.row.status | statusFilter }}</el-tag>
<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="300px">
<template slot-scope="scope">
<el-button v-if="scope.row.status == 1" type="text" @click="handlePublic(scope.row, 0)" icon="el-icon-check">{{ $t('table.public') }}</el-button>
<el-button v-if="scope.row.status == 0" type="text" @click="handlePublic(scope.row, 1)" icon="el-icon-remove-outline">{{ $t('table.retrieve') }}</el-button>
<el-button type="text" @click="handleUpdate(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button type="text" @click="handleDelete(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</el-button>
<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>
</template>
</el-table-column>
</el-table>
......@@ -66,6 +66,7 @@
<el-row>
<el-col :span="24">
<el-upload
v-show="temp.id !== undefined"
:show-file-list="showFileList"
:on-success="handleUploadSuccess"
:on-exceed="handleExceed"
......@@ -134,7 +135,7 @@ export default {
order: 'descending'
},
temp: {
id: '',
id: undefined,
knowledgeName: '',
knowledgeDesc: '',
attachmentId: '',
......@@ -223,7 +224,7 @@ export default {
},
resetTemp () {
this.temp = {
id: '',
id: undefined,
knowledgeName: '',
knowledgeDesc: '',
attachmentId: '',
......@@ -285,8 +286,6 @@ export default {
this.getList()
notifySuccess(this, '删除成功')
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
}).catch(() => {})
},
// 批量删除
......
<template>
<div class="details-container">
<el-row :gutter="40">
<el-col :span="24">
<el-card class="score-info">
<div slot="header">
<span>成绩详情</span>
</div>
<el-row>
<el-col :span="6">
<div class="user-info">
<span class="user-info-item" :title="examRecord.examinationName">考试名称:{{ examRecord.examinationName | simpleStrFilter }}</span>
<br>
<span class="user-info-item" v-show="examRecord.userName !== undefined && examRecord.userName !== ''">考生姓名:{{ examRecord.userName }}</span>
<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>
<br>
</div>
</el-col>
<el-col :span="3">
<div class="description">
<div>成绩</div>
<div class="description-score">{{ examRecord.score }}</div>
</div>
</el-col>
<el-col :span="3">
<div class="description">
<div>耗时</div>
<div class="description-score">{{ examRecord.duration }}</div>
</div>
</el-col>
<el-col :span="6">
<div class="score-chart">
<div class="chart" ref="chart" style="height: 150px; width: 100%;"></div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-card class="subject-list">
<el-table
ref="multipleTable"
:key="tableKey"
:data="list"
:default-sort="{ prop: 'id', order: 'descending' }"
highlight-current-row
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>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.type')" min-width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.subject.type | subjectTypeTagFilter" effect="dark" size="small">{{ scope.row.subject.type | subjectTypeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.userAnswer')" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.answer | simpleStrFilter(15) }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.subject.answer')" min-width="120">
<template slot-scope="scope">
<span>{{ scope.row.subject.answer.answer | simpleStrFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.examRecord.markStatus')" min-width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.markStatus | simpleTagStatusFilter(1)" effect="dark" size="small">{{ scope.row.markStatus | submitStatusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.answerCorrectType')" min-width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.answerType | simpleTagStatusFilter(0)" effect="dark" size="small">{{ scope.row.answerType | correctTypeFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.time')" min-width="90">
<template slot-scope="scope">
<span>{{ scope.row.duration }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.score')" min-width="90">
<template slot-scope="scope">
<span>{{ scope.row.score }}</span>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import CountTo from 'vue-count-to'
import waves from '@/directive/waves'
import { mapGetters } from 'vuex'
import SpinnerLoading from '@/components/SpinnerLoading'
import echarts from 'echarts'
import { examRecordDetails } from '@/api/exam/examRecord'
import { messageFail } from '@/utils/util'
require('echarts/theme/macarons')
export default {
name: 'ScoreDetails',
components: { SpinnerLoading, CountTo },
directives: {
waves
},
filters: {
correctTypeFilter (type) {
const correctTypeMap = {
0: '正确',
1: '错误'
}
return correctTypeMap[type]
}
},
data () {
return {
score: 90,
time: '50分钟',
chart: null,
chartData: [],
tableKey: 0,
list: [],
examRecordId: undefined,
examRecord: {
examinationName: '',
userName: '',
deptName: '',
score: 0,
startTime: '',
endTime: '',
duration: ''
}
}
},
created () {
this.examRecordId = this.$route.params.id
this.getExamRecord()
},
computed: {
...mapGetters([
'elements',
'permissions'
])
},
methods: {
getExamRecord () {
examRecordDetails(this.examRecordId).then(response => {
if (response.data.data === null) {
messageFail(this, '加载成绩失败')
return
}
this.examRecord = response.data.data
const { inCorrectNumber, correctNumber, answers } = this.examRecord
this.chartData.push({ name: '错误数', value: inCorrectNumber })
this.chartData.push({ name: '正确数', value: correctNumber })
this.list = answers
this.initChart()
}).catch(() => {
messageFail(this, '加载成绩失败')
})
},
initChart () {
this.chart = echarts.init(this.$refs.chart, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [
{
name: '答题统计',
type: 'pie',
radius: ['50%', '70%'],
avoidLabelOverlap: false,
animationEasing: 'cubicInOut',
animationDuration: 2600,
label: {
normal: {
formatter: function (param) {
return param.name + ': ' + Math.round(param.value)
}
}
},
data: this.chartData
}
]
})
}
}
}
</script>
<style lang="scss" scoped>
.dialog-footer {
margin-top: 20px;
}
.details-container {
padding: 12px;
.score-info {
height: 250px;
}
}
.user-info {
font-size: 13px;
.user-info-item {
display:inline-block;
padding:5px 10px;
cursor:pointer;
}
}
.description {
display: inline-block;
padding: 20px 0 0 20px;
font-size: 18px;
opacity: .9;
.description-score {
padding-top: 8px;
font-size: 24px;
}
}
.subject-list {
padding: 12px;
margin-top: 12px;
}
</style>
......@@ -3,18 +3,38 @@
<el-tabs v-model="activeName" @tab-click="handleTabChange">
<!-- 单选题 -->
<el-tab-pane label="单选题" name="0" :disabled="tempSubject.type !== 0 && dialogStatus !== dialogStatusType.create">
<choices ref="choices" subjectInfo="tempSubject"></choices>
<transition name="el-fade-in">
<choices ref="choices" subjectInfo="tempSubject"/>
</transition>
</el-tab-pane>
<!-- 多选题 -->
<el-tab-pane label="多选题" name="3" :disabled="tempSubject.type !== 3 && dialogStatus !== dialogStatusType.create">
<multiple-choices ref="multipleChoices" subjectInfo="tempSubject"></multiple-choices>
<transition name="el-fade-in">
<multiple-choices ref="multipleChoices" subjectInfo="tempSubject"/>
</transition>
</el-tab-pane>
<!-- 判断题 -->
<el-tab-pane label="判断题" name="2" :disabled="tempSubject.type !== 2 && dialogStatus !== dialogStatusType.create">
<transition name="el-fade-in">
<judgement ref="judgement" subjectInfo="tempSubject"/>
</transition>
</el-tab-pane>
<!-- 简答题 -->
<el-tab-pane label="简答题" name="1" :disabled="tempSubject.type !== 1 && dialogStatus !== dialogStatusType.create">
<short-answer ref="shortAnswer" subjectInfo="tempSubject"></short-answer>
<transition name="el-fade-in">
<short-answer ref="shortAnswer" subjectInfo="tempSubject"/>
</transition>
</el-tab-pane>
<!-- 从题库新增 -->
<el-tab-pane name="4" v-if="subjectsType === '0'" :disabled="dialogStatus !== dialogStatusType.create">
<span slot="label"><i class="el-icon-document"/> 从题库新增</span>
<transition name="el-fade-in">
<category-subjects ref="addFromSubjects" @selected="handleSelectSubject"/>
</transition>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer collapse-top">
<div slot="footer" class="dialog-footer collapse-top" v-show="activeName !== '4'">
<el-button @click="dialogSubjectFormVisible = false">{{ $t('table.cancel') }}</el-button>
<el-button v-if="dialogStatus === dialogStatusType.create" type="primary" @click="createSubjectData">{{ $t('table.save') }}</el-button>
<el-button v-else type="primary" @click="updateSubjectData">{{ $t('table.save') }}</el-button>
......@@ -26,7 +46,7 @@
<script>
import waves from '@/directive/waves'
import { mapGetters } from 'vuex'
import { getSubject, addSubject, putSubject, delSubject, exportSubject } from '@/api/exam/subject'
import { getSubject, addSubject, putSubject } from '@/api/exam/subject'
import { notifySuccess, isNotEmpty, isCreate } from '@/utils/util'
import { dialogStatusConstant } from '@/utils/constant'
import SpinnerLoading from '@/components/SpinnerLoading'
......@@ -34,10 +54,12 @@ import Tinymce from '@/components/Tinymce'
import Choices from '@/components/Subjects/Choices'
import MultipleChoices from '@/components/Subjects/MultipleChoices'
import ShortAnswer from '@/components/Subjects/ShortAnswer'
import Judgement from '@/components/Subjects/Judgement'
import CategorySubjects from '@/components/Subjects/CategorySubjects'
export default {
name: 'CourseManagement',
components: { Tinymce, SpinnerLoading, Choices, MultipleChoices, ShortAnswer },
name: 'SubjectDetails',
components: { Tinymce, SpinnerLoading, Choices, MultipleChoices, ShortAnswer, Judgement, CategorySubjects },
directives: {
waves
},
......@@ -47,12 +69,13 @@ export default {
activeName: '0',
dialogStatus: '',
dialogStatusType: { ...dialogStatusConstant },
examinationId: '',
examinationId: undefined,
categoryId: undefined,
// 题目临时信息
tempSubject: {
id: '',
examinationId: '',
categoryId: 0,
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 0,
choicesType: 0,
......@@ -72,10 +95,23 @@ export default {
analysis: '',
level: 2
},
// 0:试卷的题目,1:题库的题目
subjectsType: '0'
}
},
created () {
this.getSubject()
let subjectInfo = this.$route.params.id
if (isNotEmpty(subjectInfo)) {
let subjectInfoArr = subjectInfo.split('-')
// 0:试卷的题目,1:题库的题目
this.subjectsType = subjectInfoArr[3]
if (subjectInfoArr[3] === '0') {
this.examinationId = subjectInfoArr[0]
} else {
this.categoryId = subjectInfoArr[0]
}
this.getSubject(subjectInfoArr[1], subjectInfoArr[2])
}
},
computed: {
...mapGetters([
......@@ -84,12 +120,10 @@ export default {
])
},
methods: {
getSubject() {
let subjectId = this.$route.params.id
this.examinationId = this.$route.params.examinationId
if (isNotEmpty(subjectId)) {
getSubject (id, type) {
if (isNotEmpty(id)) {
// 加载选项信息
getSubject(subjectId, { type: this.$route.params.type }).then(response => {
getSubject(id, { type: type }).then(response => {
const subjectInfo = response.data.data
this.tempSubject = subjectInfo
this.dialogStatus = dialogStatusConstant.update
......@@ -133,6 +167,7 @@ export default {
if (ref.validate()) {
const subjectInfo = ref.getSubjectInfo()
subjectInfo.examinationId = this.examinationId
subjectInfo.categoryId = this.categoryId
putSubject(subjectInfo).then(() => {
this.dialogSubjectFormVisible = false
notifySuccess(this, '更新成功')
......@@ -146,13 +181,13 @@ export default {
const subjectInfo = ref.getSubjectInfo()
// 绑定考试ID
subjectInfo.examinationId = this.examinationId
subjectInfo.categoryId = this.categoryId
// 创建
if (isCreate(this.dialogStatus)) {
addSubject(subjectInfo).then(() => {
this.resetTempSubject(subjectInfo.score)
this.dialogStatus = dialogStatusConstant.create
ref.clearValidate()
this.getSubject()
notifySuccess(this, '创建成功')
})
} else {
......@@ -186,6 +221,9 @@ export default {
case '1':
ref = this.$refs['shortAnswer']
break
case '2':
ref = this.$refs['judgement']
break
case '3':
ref = this.$refs['multipleChoices']
break
......@@ -199,25 +237,87 @@ export default {
let subjectInfo = ref.getSubjectInfo()
// 绑定考试ID
subjectInfo.examinationId = this.examinationId
addSubject(subjectInfo).then(() => {
subjectInfo.categoryId = this.categoryId
addSubject(subjectInfo).then((response) => {
this.dialogSubjectFormVisible = false
this.getSubject()
// 获取题目
const { id, type } = response.data.data
this.updateRouteSubjectId(id, type)
notifySuccess(this, '创建成功')
})
}
},
// 切换题目类型
changeSubjectType (value) {
console.log(value)
},
resetActiveName () {
// 重置选项卡至单选题
this.activeName = '0'
},
resetTempSubject (score) {
this.tempSubject = {
id: '',
examinationId: undefined,
categoryId: undefined,
subjectName: '',
type: 0,
choicesType: 0,
options: [
{ subjectChoicesId: '', optionName: 'A', optionContent: '' },
{ subjectChoicesId: '', optionName: 'B', optionContent: '' },
{ subjectChoicesId: '', optionName: 'C', optionContent: '' },
{ subjectChoicesId: '', optionName: 'D', optionContent: '' }
],
answer: {
subjectId: '',
answer: '',
answerType: '',
score: ''
},
score: score,
analysis: '',
level: 2
}
this.$refs['choices'].resetTempSubject(score)
this.$refs['shortAnswer'].resetTempSubject(score)
this.$refs['multipleChoices'].resetTempSubject(score)
this.$refs['judgement'].resetTempSubject(score)
},
updateRouteSubjectId (subjectId, type) {
let subjectInfoArr = this.$route.params.id.split('-')
// 当subjectId为undefined时才刷新页面
if (subjectInfoArr[1] !== subjectId) {
this.$router.push({
path: `/exam/subjects/detail/${subjectInfoArr[0]}-${subjectId}-${type}-${subjectInfoArr[3]}`
})
}
},
// 选择题目回调
handleSelectSubject (selected) {
if (isNotEmpty(selected)) {
this.resetSubject(selected)
this.tempSubject = selected
this.categoryId = selected.categoryId
// 切换到对应的题型选项卡
this.updateCurrentTag(selected.type)
setTimeout(() => {
const ref = this.getSubjectRef()
if (isNotEmpty(ref)) {
ref.resetTempSubject(score)
this.$nextTick(() => {
ref.clearValidate()
ref.setSubjectInfo(selected)
})
}
}, 200)
}
},
resetSubject (subjected) {
subjected.id = undefined
subjected.examinationId = undefined
if (isNotEmpty(subjected.options)) {
subjected.options.forEach(option => {
option.id = ''
})
}
}
}
......@@ -225,7 +325,7 @@ export default {
</script>
<style lang="scss" scoped>
.collapse-top {
.dialog-footer {
margin-top: 20px;
}
</style>
......@@ -6,7 +6,7 @@
<div class="right-menu">
<template v-if="device!=='mobile'">
<a class="animated fadeIn hi">{{ $t('navbar.hi') }}{{ userInfo.name }}</a>
<a class="animated fadeIn hi">{{ tip }},{{ userInfo.name }}</a>
<el-tooltip :content="$t('navbar.lock')" effect="dark" placement="bottom">
<lock class="lock right-menu-item"/>
......@@ -72,11 +72,13 @@ export default {
},
data () {
return {
avatarUrl: ''
avatarUrl: '',
tip: ''
}
},
created () {
this.userInfo.sex = parseInt(this.userInfo.sex)
this.getTip()
},
computed: {
...mapGetters([
......@@ -97,6 +99,17 @@ export default {
this.$store.dispatch('LogOut').then(() => {
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
})
},
getTip () {
let self = this
let date = new Date()
if (date.getHours() >= 0 && date.getHours() < 12) {
self.tip = '上午好'
} else if (date.getHours() >= 12 && date.getHours() < 18) {
self.tip = '下午好'
} else {
self.tip = '晚上好'
}
}
}
}
......@@ -152,7 +165,7 @@ export default {
.user-avatar {
width: 40px;
height: 40px;
border-radius: 10px;
border-radius: 50% !important;
}
.el-icon-caret-bottom {
position: absolute;
......
<template>
<div class="menu-wrapper">
<div style="background-color: rgb(48, 65, 86);">
<div class="ams-block-component ams-block" style="color: rgb(255, 255, 255); font-size: 30px; text-align: center; margin-bottom: 8px; font-family: Roboto; padding-top: 10px;">
硕果云<div class="ams-operations el-form--inline"></div>
<div style="background-color: #282828;">
<div class="logo">
<span>sg-</span>admin<div class="el-form--inline"></div>
</div>
</div>
<template v-for="(item, index) in menu">
......@@ -58,3 +58,16 @@ export default {
}
}
</script>
<style lang="scss" scoped>
.logo {
color: #fff;
font-size: 24px;
text-align: center;
margin-bottom: 8px;
font-family: Roboto,sans-serif;
padding-top: 10px;
span {
color: #00abff;
}
}
</style>
......@@ -5,9 +5,9 @@
:default-active="$route.path"
:collapse="isCollapse"
mode="vertical"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
background-color="#282828"
text-color="#FFF"
active-text-color="#00abff"
>
<sidebar-item :menu="menu" :is-collapse="isCollapse"/>
</el-menu>
......
......@@ -163,10 +163,10 @@ export default {
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
margin-left: 12px;
}
&:last-of-type {
margin-right: 15px;
margin-right: 12px;
}
&.active {
background-color: #42b983;
......
......@@ -3,7 +3,7 @@
<div class="filter-container">
<el-input :placeholder="$t('table.title')" v-model="listQuery.title" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button v-if="log_btn_del" class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="log_btn_del" class="filter-item" type="danger" icon="el-icon-delete" @click="handleDeletes">{{ $t('table.del') }}</el-button>
</div>
<spinner-loading v-if="listLoading"/>
<el-table
......@@ -18,7 +18,7 @@
<el-table-column type="selection" width="55"/>
<el-table-column :label="$t('table.log.type')" width="120px" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.type | statusTypeFilter">{{ scope.row.type | statusFilter }}</el-tag>
<el-tag :type="scope.row.type | statusTypeFilter" effect="dark" size="small">{{ scope.row.type | statusFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('table.log.title')" prop="title">
......@@ -51,14 +51,14 @@
<span>{{ scope.row.creator }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.log.createDate')" sortable prop="createDate">
<el-table-column :label="$t('table.log.createDate')" prop="createDate" min-width="100">
<template slot-scope="scope">
<span>{{ scope.row.createDate | timeFilter }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col">
<template slot-scope="scope">
<el-button v-if="log_btn_del" type="text" @click="handleDelete(scope.row)">{{ $t('table.delete') }}
<el-button v-if="log_btn_del" type="danger" size="mini" @click="handleDelete(scope.row)">{{ $t('table.delete') }}
</el-button>
</template>
</el-table-column>
......
......@@ -3,8 +3,8 @@
<div class="filter-container">
<el-input :placeholder="$t('table.client.clientId')" v-model="listQuery.clientId" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button v-if="client_btn_add" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" plain @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="client_btn_del" class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="client_btn_add" type="primary" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="client_btn_del" type="danger" class="filter-item" icon="el-icon-delete" @click="handleDeletes">{{ $t('table.del') }}</el-button>
</div>
<spinner-loading v-if="listLoading"/>
<el-table
......@@ -39,8 +39,8 @@
</el-table-column>
<el-table-column :label="$t('table.actions')" class-name="status-col" width="300px">
<template slot-scope="scope">
<el-button v-if="client_btn_edit" type="text" @click="handleUpdate(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="client_btn_del" type="text" @click="handleDelete(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</el-button>
<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>
</template>
</el-table-column>
</el-table>
......@@ -324,8 +324,6 @@ export default {
this.getList()
notifySuccess(this, '删除成功')
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
}).catch(() => {})
},
// 批量删除
......
......@@ -2,7 +2,7 @@
<div class="tab-container">
<div class="filter-container">
<el-button v-if="dept_btn_add" icon="el-icon-check" type="primary" @click="handlerAdd">添加</el-button>
<el-button v-if="dept_btn_del" icon="el-icon-delete" plain @click="handleDelete">删除</el-button>
<el-button v-if="dept_btn_del" icon="el-icon-delete" type="danger" @click="handleDelete">删除</el-button>
<el-row>
<el-col :span="5" style ="margin-top:10px;">
......
......@@ -3,9 +3,9 @@
<div class="filter-container">
<el-button v-if="menu_btn_add" icon="el-icon-check" type="primary" @click="handlerAddSuper">添加顶级菜单</el-button>
<el-button v-if="menu_btn_add" icon="el-icon-check" type="primary" @click="handlerAdd">添加子菜单</el-button>
<el-button v-if="menu_btn_del" icon="el-icon-delete" plain @click="handleDelete">{{ $t('table.del') }}</el-button>
<el-button v-if="menu_btn_import" icon="el-icon-upload2" plain @click="handleImport">{{ $t('table.import') }}</el-button>
<el-button v-if="menu_btn_export" icon="el-icon-download" plain @click="handleExport">{{ $t('table.export') }}</el-button>
<el-button v-if="menu_btn_del" icon="el-icon-delete" type="danger" @click="handleDelete">{{ $t('table.del') }}</el-button>
<el-button v-if="menu_btn_import" icon="el-icon-upload2" type="success" @click="handleImport">{{ $t('table.import') }}</el-button>
<el-button v-if="menu_btn_export" icon="el-icon-download" type="success" @click="handleExport">{{ $t('table.export') }}</el-button>
<el-row>
<el-col :span="4" style ="margin-top:10px;">
......
......@@ -3,8 +3,8 @@
<div class="filter-container">
<el-input :placeholder="$t('table.roleName')" v-model="listQuery.roleName" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button v-if="role_btn_add" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" plain @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="role_btn_del" class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="role_btn_add" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" type="primary" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="role_btn_del" class="filter-item" icon="el-icon-delete" type="danger" @click="handleDeletes">{{ $t('table.del') }}</el-button>
</div>
<el-table
......@@ -40,14 +40,14 @@
</el-table-column>
<el-table-column :label="$t('table.status')" width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusTypeFilter">{{ scope.row.status | statusFilter }}</el-tag>
<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">
<template slot-scope="scope">
<el-button v-if="role_btn_edit" type="text" @click="handleUpdate(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="role_btn_del" type="text" @click="handleDelete(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</el-button>
<el-button v-if="role_btn_auth" type="text" @click="handlePermission(scope.row)" icon="el-icon-circle-plus-outline">{{ $t('table.permission') }}</el-button>
<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-table-column>
</el-table>
......@@ -301,8 +301,6 @@ export default {
this.getList()
notifySuccess(this, '删除成功')
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
}).catch(() => {})
},
// 批量删除
......
......@@ -3,10 +3,10 @@
<div class="filter-container">
<el-input placeholder="输入姓名查询" v-model="listQuery.name" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.query') }}</el-button>
<el-button v-if="user_btn_add" class="filter-item" icon="el-icon-check" plain @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="user_btn_del" class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="user_btn_import" class="filter-item" icon="el-icon-upload2" plain @click="handleImport">{{ $t('table.import') }}</el-button>
<el-button v-if="user_btn_export" class="filter-item" icon="el-icon-download" plain @click="handleExport">{{ $t('table.export') }}</el-button>
<el-button v-if="user_btn_add" class="filter-item" icon="el-icon-check" type="primary" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="user_btn_del" class="filter-item" icon="el-icon-delete" type="danger" @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="user_btn_import" class="filter-item" icon="el-icon-upload2" type="success" @click="handleImport">{{ $t('table.import') }}</el-button>
<el-button v-if="user_btn_export" class="filter-item" icon="el-icon-download" type="success" @click="handleExport">{{ $t('table.export') }}</el-button>
</div>
<spinner-loading v-if="listLoading"/>
<el-table
......@@ -46,15 +46,15 @@
</el-table-column>
<el-table-column :label="$t('table.status')">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusTypeFilter">{{ scope.row.status | statusFilter }}</el-tag>
<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">
<template slot-scope="scope">
<el-button v-if="user_btn_edit" type="text" @click="handleUpdate(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="user_btn_edit && scope.row.status === 0" type="text" @click="handleEnableOrDisable(scope.row, 1)" icon="el-icon-remove-outline">{{ $t('table.disable') }}</el-button>
<el-button v-else type="text" @click="handleEnableOrDisable(scope.row, 0)" icon="el-icon-check">{{ $t('table.enable') }}</el-button>
<el-button v-if="user_btn_edit" type="text" @click="handleResetPassword(scope.row)" icon="el-icon-refresh">{{ $t('table.resetPassword') }}</el-button>
<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-else type="success" size="mini" @click="handleEnableOrDisable(scope.row, 0)">{{ $t('table.enable') }}</el-button>
</template>
</el-table-column>
</el-table>
......@@ -264,14 +264,6 @@ export default {
}
},
data () {
// 校验手机号码
const checkPhone = (rule, value, callback) => {
if (!(/^1[34578]\d{9}$/.test(value))) {
callback(new Error('请输入正确的手机号码!'))
} else {
callback()
}
}
return {
tableKey: 0,
list: null,
......@@ -311,9 +303,7 @@ export default {
create: '新建'
},
rules: {
name: [{ required: true, message: '请输入账号', trigger: 'change' }],
username: [{ required: true, message: '请输入姓名', trigger: 'change' }],
phone: [{ required: true, message: '请输入手机号码', trigger: 'change' }, { validator: checkPhone, trigger: 'change' }]
identifier: [{ required: true, message: '请输入账号', trigger: 'change' }]
},
downloadLoading: false,
treeDeptData: [],
......
......@@ -3,8 +3,8 @@
<div class="filter-container">
<el-input :placeholder="$t('table.tenant.tenantCode')" v-model="listQuery.tenantCode" style="width: 200px;" class="filter-item" @keyup.enter.native="handleFilter"/>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">{{ $t('table.search') }}</el-button>
<el-button v-if="tenant_btn_add" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" plain @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="tenant_btn_del" class="filter-item" icon="el-icon-delete" plain @click="handleDeletes">{{ $t('table.del') }}</el-button>
<el-button v-if="tenant_btn_add" type="primary" class="filter-item" style="margin-left: 10px;" icon="el-icon-check" @click="handleCreate">{{ $t('table.add') }}</el-button>
<el-button v-if="tenant_btn_del" type="danger" class="filter-item" icon="el-icon-delete" @click="handleDeletes">{{ $t('table.del') }}</el-button>
</div>
<spinner-loading v-if="listLoading"/>
<el-table
......@@ -34,13 +34,13 @@
</el-table-column>
<el-table-column :label="$t('table.tenant.status')">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusTypeFilter">{{ scope.row.status | statusFilter }}</el-tag>
<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">
<template slot-scope="scope">
<el-button v-if="tenant_btn_edit" type="text" @click="handleUpdate(scope.row)" icon="el-icon-edit">{{ $t('table.edit') }}</el-button>
<el-button v-if="tenant_btn_del" type="text" @click="handleDelete(scope.row)" icon="el-icon-delete">{{ $t('table.delete') }}</el-button>
<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>
</template>
</el-table-column>
</el-table>
......@@ -276,8 +276,6 @@ export default {
this.getList()
notifySuccess(this, '删除成功')
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
}).catch(() => {})
},
// 批量删除
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -17,10 +17,11 @@ export function fetchSubjectList (query) {
})
}
export function getObj (id) {
export function getObj (id, query) {
return request({
url: baseSubjectUrl + id,
method: 'get'
method: 'get',
params: query
})
}
......
<template>
<div>
<div class="subject-exam-title">{{exam.examinationName}}(共{{subjectCount}}题,合计{{exam.totalScore}}分)</div>
<div class="subject-content">
<div class="subject-title">
{{ index }}
<span class="subject-title-content" v-html="subjectInfo.subjectName"/>
<span class="subject-title-content">&nbsp;({{subjectInfo.score}})分</span>
</div>
<ul class="subject-options" v-for="option in options" :key="option.id">
<li class="subject-option">
<input class="toggle" type="checkbox" :checked="userAnswer === option.optionName" :id="'option' + option.id" @change="toggleOption(option)">
<label :for="'option' + option.id">
<span class="subject-option-prefix">{{ option.optionName }}&nbsp;</span>
<span v-html="option.optionContent" class="subject-option-prefix" />
</label>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'Choices',
data () {
return {
exam: {
examinationName: '',
totalScore: ''
},
subjectCount: 0,
subjectInfo: {
subjectName: '',
score: 0
},
options: [],
userAnswer: '',
index: ''
}
},
methods: {
getAnswer () {
return this.userAnswer
},
setAnswer (answer) {
this.userAnswer = answer
},
setSubjectInfo (exam, subject, subjectCount, index) {
this.exam = exam
this.subjectCount = subjectCount
this.subjectInfo = subject
if (subject.hasOwnProperty('options')) {
this.options = subject.options
}
if (subject.hasOwnProperty('answer')) {
this.setAnswer(subject.answer.answer)
}
this.index = index + '.'
},
getSubjectInfo () {
this.subjectInfo.options = this.options
return this.subjectInfo
},
// 选中选项
toggleOption (option) {
this.userAnswer = option.optionName
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../styles/subject.scss";
</style>
<template>
<div>
<div class="subject-exam-title">{{exam.examinationName}}(共{{subjectCount}}题,合计{{exam.totalScore}}分)</div>
<div class="subject-content">
<div class="subject-title">
{{ index }}
<span class="subject-title-content" v-html="subjectInfo.subjectName"/>
<span class="subject-title-content">&nbsp;({{subjectInfo.score}})分</span>
</div>
<ul class="subject-options" v-for="option in options" :key="option.id">
<li class="subject-option">
<input class="toggle" type="checkbox" :checked="userAnswer === option.optionName" :id="'option' + option.id" @change="toggleOption(option)">
<label :for="'option' + option.id">
<span class="subject-option-prefix">{{ option.optionName }}&nbsp;</span>
</label>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'Judgement',
data () {
return {
exam: {
examinationName: '',
totalScore: ''
},
subjectCount: 0,
subjectInfo: {
subjectName: '',
score: 0
},
options: [
{ id: 1, optionName: '正确' },
{ id: 2, optionName: '错误' }
],
userAnswer: '',
index: ''
}
},
methods: {
getAnswer () {
return this.userAnswer
},
setAnswer (answer) {
this.userAnswer = answer
},
setSubjectInfo (exam, subject, subjectCount, index) {
this.exam = exam
this.subjectCount = subjectCount
this.subjectInfo = subject
if (subject.hasOwnProperty('answer')) {
this.setAnswer(subject.answer.answer)
}
this.index = index + '.'
},
getSubjectInfo () {
this.subjectInfo.options = this.options
return this.subjectInfo
},
// 选中选项
toggleOption (option) {
this.userAnswer = option.optionName
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../styles/subject.scss";
</style>
<template>
<div>
<div class="subject-exam-title">{{exam.examinationName}}(共{{subjectCount}}题,合计{{exam.totalScore}}分)</div>
<div class="subject-content">
<div class="subject-title">
{{ index }}
<span class="subject-title-content" v-html="subjectInfo.subjectName"/>
<span class="subject-title-content">&nbsp;({{subjectInfo.score}})分</span>
</div>
<ul class="subject-options" v-for="option in options" :key="option.id">
<li class="subject-option">
<input class="toggle" type="checkbox" :checked="isChecked(option.optionName)" :id="'option' + option.id" @change="toggleOption($event, option)">
<label :for="'option' + option.id">
<span class="subject-option-prefix">{{ option.optionName }}&nbsp;</span>
<span v-html="option.optionContent" class="subject-option-prefix" />
</label>
</li>
</ul>
</div>
</div>
</template>
<script>
import { isNotEmpty } from '@/utils/util'
export default {
name: 'MultipleChoices',
data () {
return {
exam: {
examinationName: '',
totalScore: ''
},
subjectCount: 0,
subjectInfo: {
subjectName: '',
score: 0
},
options: [],
userAnswer: [],
index: ''
}
},
watch: {
},
methods: {
getAnswer () {
return this.userAnswer.join(',')
},
setAnswer (answer) {
if (isNotEmpty(answer)) {
this.userAnswer = answer.split(',')
}
},
setSubjectInfo (exam, subject, subjectCount, index) {
this.exam = exam
this.subjectCount = subjectCount
this.subjectInfo = subject
if (subject.hasOwnProperty('options')) {
this.options = subject.options
}
if (subject.hasOwnProperty('answer')) {
this.setAnswer(subject.answer.answer)
}
this.index = index + '.'
},
getSubjectInfo () {
this.subjectInfo.options = this.options
return this.subjectInfo
},
// 选中选项
toggleOption ($event, option) {
if ($event.target.checked) {
if (!this.userAnswer.includes(option.optionName)) {
this.userAnswer.push(option.optionName)
}
} else {
this.userAnswer.splice(this.userAnswer.findIndex(item => item === option.optionName), 1)
}
},
isChecked (optionName) {
return this.userAnswer.includes(optionName)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../styles/subject.scss";
</style>
<template>
<div>
<div class="subject-exam-title">{{exam.examinationName}}(共{{subjectCount}}题,合计{{exam.totalScore}}分)</div>
<div class="subject-content">
<div class="subject-title">
{{ index }}
<span class="subject-title-content" v-html="subjectInfo.subjectName"/>
<span class="subject-title-content">&nbsp;({{subjectInfo.score}})分</span>
<div class="subject-tinymce">
<tinymce ref="editor" :height="300" v-model="userAnswer"/>
</div>
</div>
</div>
</div>
</template>
<script>
import Tinymce from '@/components/Tinymce'
export default {
name: 'ShortAnswer',
components: {
Tinymce
},
data () {
return {
exam: {
examinationName: '',
totalScore: ''
},
subjectCount: 0,
subjectInfo: {
subjectName: '',
score: 0
},
userAnswer: '',
index: ''
}
},
methods: {
getAnswer () {
return this.userAnswer
},
setAnswer (answer) {
this.userAnswer = answer
},
setSubjectInfo (exam, subject, subjectCount, index) {
this.exam = exam
this.subjectCount = subjectCount
this.subjectInfo = subject
if (subject.hasOwnProperty('answer')) {
this.setAnswer(subject.answer.answer)
}
this.index = index + '.'
},
getSubjectInfo () {
return this.subjectInfo
}
}
}
</script>
<style lang="scss" scoped>
@import "../../../styles/subject.scss";
.subject-tinymce {
margin: 12px;
}
</style>
export const nextSubjectType = {
last: 1,
next: 0,
current: 2
}
export const answerType = {
'true': 'right',
'false': 'incorrect'
}
......@@ -46,7 +46,7 @@ export const constantRouterMap = [
component: () => import('@/views/exam/exams')
},
{
path: '/start',
path: '/start/:id',
name: 'start',
component: () => import('@/views/exam/startExam')
},
......
.subject-exam-title{
font-size: 18px;
line-height: 25px;
padding: 18px 20px;
border-bottom: 1px solid #DEDEDE;
margin-bottom: 12px;
}
.subject {
padding-left: 30px;
padding-right: 75px;
}
.subject-content{
margin-top: 18px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
background: #fff;
z-index: 1;
position: relative;
}
/* 题目 */
.subject-title {
font-size: 18px;
line-height: 22px;
.subject-title-number {
display: inline-block;
line-height: 22px;
}
.subject-title-content {
display: inline-block;
}
}
.subject-options {
margin: 0;
padding: 0;
list-style: none;
> li {
position: relative;
font-size: 24px;
.toggle {
opacity: 0;
text-align: center;
width: 35px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none;
/* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.toggle+label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
background-size: 30px;
}
.toggle:checked+label {
background-size: 30px;
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
label {
word-break: break-all;
padding: 10px 10px 10px 45px;
display: block;
line-height: 1.0;
transition: color 0.4s;
}
/* 选项名称 */
.subject-option-prefix {
font-size: 16px;
display: inline-block
}
}
}
.subject-answer {
padding: 16px;
}
......@@ -149,7 +149,7 @@ export const isNotEmpty = (obj) => {
* @param duration
*/
export const notify = (obj, title, msg, type, duration) => {
obj.$notify({ title: title, message: msg, type: type, duration: duration, offset: 70})
obj.$notify({ title: title, message: msg, type: type, duration: duration, offset: 70 })
}
/**
......@@ -180,6 +180,43 @@ export const notifyFail = (obj, msg) => {
}
/**
* 消息提示
* @param obj
* @param message
* @param type
*/
export const message = (obj, message, type) => {
obj.$message({ message: message, type: type, offset: 70 })
}
/**
* 成功消息提示
* @param obj
* @param message
*/
export const messageSuccess = (obj, message) => {
obj.$message({ message: message, type: 'success', offset: 70 })
}
/**
* 警告消息提示
* @param obj
* @param message
*/
export const messageWarn = (obj, message) => {
obj.$message({ message: message, type: 'warning', offset: 70 })
}
/**
* 失败消息提示
* @param obj
* @param message
*/
export const messageFail = (obj, message) => {
obj.$message({ message: message, type: 'error', offset: 70 })
}
/**
* 手机号验证
* @param str
* @returns {boolean}
......
......@@ -18,7 +18,7 @@ export default {
},
methods: {
// 提交考试
handleSubmitExam() {
handleSubmitExam () {
debugger
this.$refs.mainRef.handleSubmitExam()
}
......
......@@ -3,28 +3,29 @@
<div class="container">
<div class="site-info">
<el-row>
<el-col :span="4" :offset="2" class="footer-col">
<el-col :span="3" :offset="2" class="footer-col">
<h4>链接</h4>
<a target="_blank" href="https://gitee.com/wells2333/spring-microservice-exam">码云</a>
<a target="_blank" href="https://github.com/">GITHUB</a>
<a target="_blank" href="http://118.25.138.130:81">管理后台</a>
<a target="_blank" href="https://gitee.com/wells2333/spring-microservice-exam/blob/master/CHANGELOG.md">更新日志</a>
</el-col>
<el-col :span="4" class="footer-col">
<el-col :span="3" class="footer-col">
<h4>工具</h4>
<a target="_blank" href="http://element-cn.eleme.io">Element Ui</a>
<a target="_blank" href="https://cn.vuejs.org/">Vue</a>
</el-col>
<el-col :span="4" class="footer-col">
<el-col :span="3" class="footer-col">
<h4>社区</h4>
<a target="_blank" href="https://www.kancloud.cn/tangyi/spring-microservice-exam/1322864">看云</a>
<a target="_blank" href="https://gitee.com/wells2333/spring-microservice-exam/issues">反馈建议</a>
</el-col>
<el-col :span="4" class="footer-col">
<el-col :span="7" class="footer-col">
<div class="we-chat">
</div>
<div class="we-chat-new">
加入技术交流群,请扫二维码
<br>
(Spring Cloud技术交流群)
</div>
</el-col>
</el-row>
......@@ -173,11 +174,25 @@ export default {
.we-chat {
position: absolute;
right: 0;
top: 80px;
top: 60px;
width: 216px;
z-index: 2;
padding-top: 260px;
background-image: url("../../../static/images/home/qq.png");
background-repeat: no-repeat;
background-position: top;
text-align: center;
color: #bdb8ce;
line-height: 22px;
}
.we-chat-new {
position: absolute;
top: 60px;
width: 216px;
z-index: 2;
padding-top: 232px;
background-image: url("../../../static/images/home/WechatIMG4.png");
padding-top: 260px;
background-image: url("../../../static/images/home/qq_new.png");
background-repeat: no-repeat;
background-position: top;
text-align: center;
......
......@@ -28,6 +28,7 @@
<template slot="title">功能</template>
<el-menu-item index="exams" @click="open('/exams')">在线考试</el-menu-item>
<el-menu-item index="practices" @click="open('/practices')">在线学习</el-menu-item>
<el-menu-item index="exam-record" @click="open('/exam-record')">考试记录</el-menu-item>
<el-menu-item index="incorrect" @click="open('/incorrect')">错题本</el-menu-item>
</el-submenu>
<el-submenu index="/u">
......@@ -74,17 +75,16 @@
<script>
import { mapState } from 'vuex'
import { fetchList } from '@/api/exam/exam'
import { isNotEmpty } from '@/utils/util'
export default {
mounted () {
// handleScroll为页面滚动的监听回调
window.addEventListener("scroll", this.handleScroll);
window.addEventListener('scroll', this.handleScroll)
},
destroyed() {
//同时在destroyed回调中移除监听:
window.removeEventListener("scroll", this.handleScroll);
destroyed () {
// 同时在destroyed回调中移除监听:
window.removeEventListener('scroll', this.handleScroll)
},
computed: {
// 获取用户信息
......@@ -113,7 +113,7 @@ export default {
// 导航栏切换
open (path) {
if (path !== this.$route.fullPath) {
if ('/start' === this.$route.fullPath) {
if (this.$route.fullPath === '/start') {
this.$confirm('是否要结束当前考试?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
......@@ -163,7 +163,7 @@ export default {
this.$store.dispatch('LogOut').then(() => {
this.login = false
this.$router.push('/home')
}).catch(error => {
}).catch(() => {
this.login = false
this.$router.push('/home')
})
......@@ -174,7 +174,7 @@ export default {
this.login = true
}
},
search() {
search () {
if (isNotEmpty(this.query)) {
this.$router.push({name: 'exams', query: {query: this.query}})
}
......@@ -208,7 +208,7 @@ export default {
vertical-align: top;
cursor: pointer;
.home-link {
margin-right: -30px;
margin-right: -100px;
}
.site-name {
display: inline-block;
......@@ -217,7 +217,6 @@ export default {
height: 32px;
background-size: 100%;
margin: 17px 0 0 33px;
background-image: url("../../../static/images/home/scloud-logo.png");
}
}
.nav-bar {
......
......@@ -20,7 +20,7 @@ export default {
},
methods: {
// TODO 提交考试
handleSubmitExam() {
handleSubmitExam () {
console.log('handleSubmitExam')
}
}
......
......@@ -12,7 +12,7 @@
style="width: 100%;">
<el-table-column label="考试名称" align="center">
<template slot-scope="scope">
<span :title="scope.row.examinationName">{{ scope.row.examinationName | examinationNameFilter }}</span>
<span :title="scope.row.examinationName">{{ scope.row.examinationName }}</span>
</template>
</el-table-column>
<el-table-column label="考试类型" min-width="90" align="center">
......@@ -109,7 +109,7 @@ export default {
timeFilter (time) {
return formatDate(new Date(time), 'yyyy-MM-dd hh:mm')
},
examinationNameFilter(name) {
examinationNameFilter (name) {
return cropStr(name, 8)
}
},
......
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