Compare commits

..

1 Commits

Author SHA1 Message Date
wf 82b826a338 王菲的提交
1 year ago

@ -1,32 +1,17 @@
module.exports = {
// 表示这是一个根级配置文件
root: true,
// 设置环境,使得 ESLint 可以识别 Node.js 环境
env: {
node: true // 设置为 Node 环境,启用 Node 全局变量和特性
node: true
},
// 继承的规则集
'extends': [
// 使用 Vue.js 插件的基本规则,确保 Vue.js 文件符合基本的风格要求
'plugin:vue/essential',
// 使用 @vue/standard 规则集Vue 官方推荐的 JavaScript 风格规范,包含常见的最佳实践
'@vue/standard'
],
// 定义自定义的规则
rules: {
// 在生产环境中禁用 console防止 console.log 代码进入生产环境
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// 在生产环境中禁用 debugger避免生产环境中出现调试信息
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
// 设置解析器,确保 Babel 可以正确解析现代 JavaScript 代码
parserOptions: {
parser: 'babel-eslint' // 使用 babel-eslint 解析器来支持最新的 JavaScript 特性
parser: 'babel-eslint'
}
}

@ -1,6 +1,6 @@
import { post } from '@/utils/request' // 从工具文件 'request' 中导入 post 请求方法
import { post } from '@/utils/request'
export default {
index: () => post('/api/student/dashboard/index'),// 'index' 方法发送 POST 请求到 '/api/student/dashboard/index'
task: () => post('/api/student/dashboard/task') // 'task' 方法发送 POST 请求到 '/api/student/dashboard/task'
index: () => post('/api/student/dashboard/index'),
task: () => post('/api/student/dashboard/task')
}

@ -1,6 +1,6 @@
import { post } from '@/utils/request'// 从工具文件 'request' 中导入 post 请求方法
import { post } from '@/utils/request'
export default {
select: id => post('/api/student/exam/paper/select/' + id),// 通过传递 'id' 参数来请求某个试卷的详细信息
pageList: query => post('/api/student/exam/paper/pageList', query)// 发送带有查询参数的请求,获取试卷分页数据
select: id => post('/api/student/exam/paper/select/' + id),
pageList: query => post('/api/student/exam/paper/pageList', query)
}

@ -1,12 +1,8 @@
import { post } from '@/utils/request'// 从工具文件 'request' 中导入 post 请求方法
import { post } from '@/utils/request'
export default {
// 发送带有查询参数的请求,获取考试试卷的分页数据
pageList: query => post('/api/student/exampaper/answer/pageList', query),
// 提交学生的考试答案
answerSubmit: form => post('/api/student/exampaper/answer/answerSubmit', form),
// 标记某个试卷为已读
read: id => post('/api/student/exampaper/answer/read/' + id),
// 编辑试卷或答案
edit: form => post('/api/student/exampaper/answer/edit', form)
}

@ -1,9 +1,6 @@
import { post, postWithLoadTip } from '@/utils/request' // 导入自定义的 post 请求方法
import { post, postWithLoadTip } from '@/utils/request'
export default {
// 用户登录
login: query => postWithLoadTip(`/api/user/login`, query),
// 用户登出
logout: query => post(`/api/user/logout`, query)
}

@ -1,5 +1,4 @@
import { post } from '@/utils/request' // 导入自定义的 post 请求方法
import { post } from '@/utils/request'
export default {
// 当前模块没有任何 API 方法
}

@ -1,5 +1,5 @@
import { postWithLoadTip } from '@/utils/request' // 导入带有加载提示的 post 请求方法
import { postWithLoadTip } from '@/utils/request'
export default {
register: query => postWithLoadTip(`/api/student/user/register`, query) // 用户注册接口
register: query => postWithLoadTip(`/api/student/user/register`, query)
}

@ -1,6 +1,6 @@
import { post } from '@/utils/request' // 导入 post 请求方法
import { post } from '@/utils/request'
export default {
list: query => post('/api/student/education/subject/list'), // 获取学科列表
select: id => post('/api/student/education/subject/select/' + id) // 选择特定学科
list: query => post('/api/student/education/subject/list'),
select: id => post('/api/student/education/subject/select/' + id)
}

@ -1,11 +1,11 @@
import { post } from '@/utils/request' // 导入 post 请求方法
import { post } from '@/utils/request'
export default {
createUser: query => post('/api/student/user/edit', query), // 创建或编辑用户信息
getCurrentUser: () => post('/api/student/user/current'), // 获取当前用户信息
getUserEvent: () => post('/api/student/user/log'), // 获取用户事件日志
update: query => post('/api/student/user/update', query), // 更新用户信息
messagePageList: query => post('/api/student/user/message/page', query), // 获取消息分页列表
read: id => post('/api/student/user/message/read/' + id), // 标记消息为已读
getMessageCount: () => post('/api/student/user/message/unreadCount') // 获取未读消息数量
createUser: query => post('/api/student/user/edit', query),
getCurrentUser: () => post('/api/student/user/current'),
getUserEvent: () => post('/api/student/user/log'),
update: query => post('/api/student/user/update', query),
messagePageList: query => post('/api/student/user/message/page', query),
read: id => post('/api/student/user/message/read/' + id),
getMessageCount: () => post('/api/student/user/message/unreadCount')
}

@ -1,9 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon' // 引入 SvgIcon 组件
import SvgIcon from '@/components/SvgIcon'// svg component
// 在全局注册 SvgIcon 组件
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/) // 动态引入 './svg' 目录下的所有 .svg 文件
const requireAll = requireContext => requireContext.keys().map(requireContext) // 获取所有的 SVG 文件并引入
requireAll(req) // 执行文件引入
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

@ -1,68 +1,55 @@
import Vue from 'vue'
import App from './App.vue'
import { router } from './router' // 导入路由配置
import store from './store' // 导入 Vuex 状态管理
import 'normalize.css/normalize.css' // 导入 Normalize.css 以确保浏览器间一致的样式
import Element from 'element-ui' // 导入 Element UI 库
import 'element-ui/lib/theme-chalk/index.css' // 导入 Element UI 样式
import { router } from './router'
import store from './store'
import 'normalize.css/normalize.css'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import '@/styles/index.scss' // 导入全局样式
import './icons' // 导入图标库
import NProgress from 'nprogress' // 导入进度条库
import 'nprogress/nprogress.css' // 导入进度条的样式
import '@/styles/index.scss' // global css
import './icons' // icon
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
// 全局注册 Element UI设置默认组件大小为 medium
Vue.use(Element, {
size: 'medium' // 设置 Element UI 默认尺寸
size: 'medium' // set element-ui default size
})
// 关闭生产环境提示
Vue.config.productionTip = false
// 配置 NProgress去掉 spinner 进度条的旋转动画
NProgress.configure({ showSpinner: false })
NProgress.configure({ showSpinner: false }) // NProgress Configuration
// 在路由切换之前触发的钩子函数
router.beforeEach(async (to, from, next) => {
// 启动进度条
// start progress bar
NProgress.start()
// 设置页面标题,如果路由元数据中有 title 字段则使用它
if (to.meta.title !== undefined) {
document.title = to.meta.title
} else {
document.title = '\u200E' // 如果没有设置 title则使用一个空的标题
document.title = '\u200E'
}
// 设置页面背景色,如果路由元数据中有 bodyBackground 字段则使用它
if (to.meta.bodyBackground !== undefined) {
document.querySelector('body').setAttribute('style', 'background: ' + to.meta.bodyBackground)
} else {
document.querySelector('body').removeAttribute('style') // 如果没有设置背景色,则移除背景样式
document.querySelector('body').removeAttribute('style')
}
// 页面路径发生变化时,发送百度统计的页面浏览事件(如果 _hmt 对象已定义)
if (to.path) {
// eslint-disable-next-line no-undef
_hmt.push(['_trackPageview', '/#' + to.fullPath])
}
// 继续路由导航
next()
})
// 路由切换后触发的钩子函数
router.afterEach((to, from, next) => {
// 完成进度条的动画
// finish progress bar
NProgress.done()
})
// 将路由实例挂载到 Vue 原型上,方便在全局任何地方访问
Vue.prototype.$$router = router
// 创建 Vue 实例,并挂载到 #app 元素
new Vue({
router: router, // 将路由实例传递给 Vue 实例
store: store, // 将 Vuex 实例传递给 Vue 实例
render: h => h(App) // 渲染根组件
}).$mount('#app') // 挂载到页面上的 #app 元素
router: router,
store: store,
render: h => h(App)
}).$mount('#app')

@ -1,135 +1,91 @@
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/layout' // 导入布局组件
import Layout from '@/layout'
// 使用 Vue Router 插件
Vue.use(Router)
// 创建 Vue Router 实例
const router = new Router({
routes: [
// 登录路由
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/index'), // 异步加载登录页面
meta: { title: '登录', bodyBackground: '#fbfbfb' } // 页面标题和背景颜色
},
// 注册路由
{
path: '/register',
name: 'Register',
component: () => import('@/views/register/index'), // 异步加载注册页面
meta: { title: '注册', bodyBackground: '#fbfbfb' }
},
// 根路径重定向到首页
{ path: '/login', name: 'Login', component: () => import('@/views/login/index'), meta: { title: '登录', bodyBackground: '#fbfbfb' } },
{ path: '/register', name: 'Register', component: () => import('@/views/register/index'), meta: { title: '注册', bodyBackground: '#fbfbfb' } },
{
path: '/',
component: Layout, // 使用布局组件
redirect: '/index', // 默认重定向到 /index
component: Layout,
redirect: '/index',
children: [
{
path: 'index', // 首页路由
component: () => import('@/views/dashboard/index'), // 异步加载首页组件
path: 'index',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: '首页' } // 页面标题
meta: { title: '首页' }
}
]
},
// 试卷中心路由
{
path: '/paper',
component: Layout, // 使用布局组件
component: Layout,
children: [
{
path: 'index', // 试卷中心首页
component: () => import('@/views/paper/index'), // 异步加载试卷中心组件
path: 'index',
component: () => import('@/views/paper/index'),
name: 'PaperIndex',
meta: { title: '试卷中心' } // 页面标题
meta: { title: '试卷中心' }
}
]
},
// 考试记录路由
{
path: '/record',
component: Layout,
children: [
{
path: 'index', // 考试记录首页
component: () => import('@/views/record/index'), // 异步加载考试记录组件
path: 'index',
component: () => import('@/views/record/index'),
name: 'RecordIndex',
meta: { title: '考试记录' }
}
]
},
// 错题本路由
{
path: '/question',
component: Layout,
children: [
{
path: 'index', // 错题本首页
component: () => import('@/views/question-error/index'), // 异步加载错题本组件
path: 'index',
component: () => import('@/views/question-error/index'),
name: 'QuestionErrorIndex',
meta: { title: '错题本' }
}
]
},
// 个人中心路由
{
path: '/user',
component: Layout,
children: [
{
path: 'index', // 个人信息页面
component: () => import('@/views/user-info/index'), // 异步加载个人信息组件
path: 'index',
component: () => import('@/views/user-info/index'),
name: 'UserInfo',
meta: { title: '个人中心' }
}
]
},
// 消息中心路由
{
path: '/user',
component: Layout,
children: [
{
path: 'message', // 消息页面
component: () => import('@/views/user-info/message'), // 异步加载消息中心组件
path: 'message',
component: () => import('@/views/user-info/message'),
name: 'UserMessage',
meta: { title: '消息中心' }
}
]
},
// 试卷答题页面
{
path: '/do',
name: 'ExamPaperDo',
component: () => import('@/views/exam/paper/do'), // 异步加载试卷答题组件
meta: { title: '试卷答题' } // 页面标题
},
// 试卷批改页面
{
path: '/edit',
name: 'ExamPaperEdit',
component: () => import('@/views/exam/paper/edit'), // 异步加载试卷批改组件
meta: { title: '试卷批改' }
},
// 试卷查看页面
{
path: '/read',
name: 'ExamPaperRead',
component: () => import('@/views/exam/paper/read'), // 异步加载试卷查看组件
meta: { title: '试卷查看' }
},
// 404 页面,当路径没有匹配到任何路由时显示
{
path: '*',
component: () => import('@/views/error-page/404'), // 异步加载404错误页面
meta: { title: '404' }
{ path: '/do', name: 'ExamPaperDo', component: () => import('@/views/exam/paper/do'), meta: { title: '试卷答题' } },
{ path: '/edit', name: 'ExamPaperEdit', component: () => import('@/views/exam/paper/edit'), meta: { title: '试卷批改' } },
{ path: '/read', name: 'ExamPaperRead', component: () => import('@/views/exam/paper/read'), meta: { title: '试卷查看' } },
{ path: '*', component: () => import('@/views/error-page/404'), meta: { title: '404' }
}
]
})
// 导出 router 实例
export { router }

@ -1,34 +1,23 @@
// 引入 Vue 和 Vuex 库
import Vue from 'vue'
import Vuex from 'vuex'
// 使用 Vuex 插件
Vue.use(Vuex)
// https://webpack.js.org/guides/dependency-management/#requirecontext
// 使用 Webpack 的 require.context 来动态加载模块
// 该方法可以在项目中找到所有位于 './modules' 目录下的 `.js` 文件
const modulesFiles = require.context('./modules', true, /\.js$/)
// 通过遍历 `modulesFiles` 中的每个模块路径,动态导入所有 Vuex 模块
// 无需手动 import每个模块会自动引入
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// 通过正则表达式提取模块的名称
// './app.js' -> 'app'
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
// 导入模块的内容
const value = modulesFiles(modulePath)
// 将模块添加到 `modules` 对象中
modules[moduleName] = value.default
return modules
}, {})
// 创建 Vuex store并将所有动态导入的模块添加进去
const store = new Vuex.Store({
modules
})
// 导出创建的 store 实例
export default store

@ -1,58 +1,29 @@
// initial state
const state = {
user: {
// 用户性别枚举包含男性key: 1, value: '男'和女性key: 2, value: '女'
sexEnum: [{ key: 1, value: '男' }, { key: 2, value: '女' }],
// 用户年级枚举,从一年级到高三
levelEnum: [
{ key: 1, value: '一年级' }, { key: 2, value: '二年级' }, { key: 3, value: '三年级' },
{ key: 4, value: '四年级' }, { key: 5, value: '五年级' }, { key: 6, value: '六年级' },
levelEnum: [{ key: 1, value: '一年级' }, { key: 2, value: '二年级' }, { key: 3, value: '三年级' }, { key: 4, value: '四年级' }, { key: 5, value: '五年级' }, { key: 6, value: '六年级' },
{ key: 7, value: '初一' }, { key: 8, value: '初二' }, { key: 9, value: '初三' },
{ key: 10, value: '高一' }, { key: 11, value: '高二' }, { key: 12, value: '高三' }
],
// 用户角色枚举,包含学生、教师、管理员
{ key: 10, value: '高一' }, { key: 11, value: '高二' }, { key: 12, value: '高三' }],
roleEnum: [{ key: 1, value: '学生' }, { key: 2, value: '教师' }, { key: 3, value: '管理员' }],
// 用户消息相关枚举,读取状态和标记
message: {
// 消息已读状态标记success: 已读, warning: 未读)
readTag: [{ key: true, value: 'success' }, { key: false, value: 'warning' }],
// 消息已读文本(已读、未读)
readText: [{ key: true, value: '已读' }, { key: false, value: '未读' }]
}
},
exam: {
examPaper: {
// 试卷类型枚举(固定试卷、时段试卷)
paperTypeEnum: [{ key: 1, value: '固定试卷' }, { key: 4, value: '时段试卷' }]
},
examPaperAnswer: {
// 试卷答题状态枚举(待批改、完成)
statusEnum: [{ key: 1, value: '待批改' }, { key: 2, value: '完成' }],
// 试卷答题状态标签样式(警告、成功)
statusTag: [{ key: 1, value: 'warning' }, { key: 2, value: 'success' }]
},
question: {
// 题目类型枚举(单选题、多选题、判断题、填空题、简答题)
typeEnum: [
{ key: 1, value: '单选题' }, { key: 2, value: '多选题' }, { key: 3, value: '判断题' },
{ key: 4, value: '填空题' }, { key: 5, value: '简答题' }
],
typeEnum: [{ key: 1, value: '单选题' }, { key: 2, value: '多选题' }, { key: 3, value: '判断题' }, { key: 4, value: '填空题' }, { key: 5, value: '简答题' }],
answer: {
// 答案正确与否的标签success: 正确, danger: 错误, warning: 待批改)
doRightTag: [{ key: true, value: 'success' }, { key: false, value: 'danger' }, { key: null, value: 'warning' }],
// 答案正确与否的文本(正确、错误、待批改)
doRightEnum: [{ key: true, value: '正确' }, { key: false, value: '错误' }, { key: null, value: '待批改' }],
// 答案完成与否的标签info: 未完成, success: 完成)
doCompletedTag: [{ key: false, value: 'info' }, { key: true, value: 'success' }]
}
}
@ -61,38 +32,30 @@ const state = {
// getters
const getters = {
// 获取枚举值的方法传入枚举数组和要查找的key返回对应的value
enumFormat: (state) => (arrary, key) => {
return format(arrary, key)
}
}
// actions
const actions = {
// actions 为空,意味着该模块没有异步操作
}
const actions = {}
// mutations
const mutations = {
// mutations 为空,意味着该模块没有同步操作
}
const mutations = {}
// 用于格式化枚举数组传入数组和key查找匹配的key并返回对应的value
const format = function (array, key) {
for (let item of array) {
// 如果枚举项的key和传入的key匹配返回对应的value
if (item.key === key) {
return item.value
}
}
return null // 如果没有找到匹配项则返回null
return null
}
// 导出 Vuex 模块配置,开启命名空间
export default {
namespaced: true, // 使用命名空间
state, // state 是模块的状态数据
getters, // getters 用于计算派生状态
actions, // actions 用于异步操作
mutations // mutations 用于同步修改状态
namespaced: true,
state,
getters,
actions,
mutations
}

@ -1,33 +1,24 @@
// 引入学科相关的 API
import subjectApi from '@/api/subject'
const state = {
// 存储学科数据
subjects: []
}
const getters = {
// 获取学科名称和等级的格式化显示文本
subjectEnumFormat: (state) => (key) => {
// 遍历所有学科找到与传入key匹配的学科
for (let item of state.subjects) {
// 如果学科ID与传入的key匹配返回学科名称和等级名称的拼接文本
if (item.id === key) {
return item.name + ' ( ' + item.levelName + ' )'
}
}
// 如果没有找到匹配的学科返回null
return null
}
}
// actions
const actions = {
// 初始化学科数据,调用 API 获取学科列表
initSubject ({ commit }) {
// 调用学科API的list方法获取学科数据
subjectApi.list().then(re => {
// 提交mutation将获取到的学科数据保存到state
commit('setSubjects', re.response)
})
}
@ -35,18 +26,15 @@ const actions = {
// mutations
const mutations = {
// 设置学科数据到state中
setSubjects: (state, subjects) => {
state.subjects = subjects
}
}
// 导出 Vuex 模块配置
export default {
// 使用命名空间确保该模块的状态、getters、actions 和 mutations 不会与其他模块冲突
namespaced: true,
state, // 模块的状态数据
getters, // 模块的getter方法用于派生数据
actions, // 模块的异步操作
mutations // 模块的同步操作
state,
getters,
actions,
mutations
}

@ -1,36 +1,22 @@
// 引入js-cookie库用于操作浏览器的cookie
import Cookies from 'js-cookie'
// 引入用户相关的API
import userApi from '@/api/user'
// initial state
const state = {
// 获取存储在cookie中的用户名
userName: Cookies.get('studentUserName'),
// 获取存储在cookie中的用户信息
userInfo: Cookies.get('studentUserInfo'),
// 获取存储在cookie中的用户头像路径
imagePath: Cookies.get('studentImagePath'),
// 存储用户的消息数量初始化为0
messageCount: 0
}
// actions
const actions = {
// 初始化用户信息调用API获取当前用户信息
initUserInfo ({ commit }) {
// 调用用户API获取当前用户信息
userApi.getCurrentUser().then(re => {
// 提交mutation将获取到的用户信息保存到state中
commit('setUserInfo', re.response)
})
},
// 获取用户的消息信息调用API获取未读消息数量
getUserMessageInfo ({ commit }) {
// 调用用户API获取未读消息数量
userApi.getMessageCount().then(re => {
// 提交mutation将消息数量保存到state中
commit('setMessageCount', re.response)
})
}
@ -38,35 +24,24 @@ const actions = {
// mutations
const mutations = {
// 设置用户名并保存到cookie中过期时间为30天
setUserName (state, userName) {
state.userName = userName
Cookies.set('studentUserName', userName, { expires: 30 })
},
// 设置用户信息并保存到cookie中过期时间为30天
setUserInfo: (state, userInfo) => {
state.userInfo = userInfo
Cookies.set('studentUserInfo', userInfo, { expires: 30 })
},
// 设置用户头像路径并保存到cookie中过期时间为30天
setImagePath: (state, imagePath) => {
state.imagePath = imagePath
Cookies.set('studentImagePath', imagePath, { expires: 30 })
},
// 设置消息数量
setMessageCount: (state, messageCount) => {
state.messageCount = messageCount
},
// 减少消息数量
messageCountSubtract: (state, num) => {
state.messageCount = state.messageCount - num
},
// 清除用户登录信息从state和cookie中删除相关信息
clearLogin (state) {
Cookies.remove('studentUserName')
Cookies.remove('studentUserInfo')
@ -74,11 +49,9 @@ const mutations = {
}
}
// 导出Vuex模块配置
export default {
// 使用命名空间确保该模块的状态、getters、actions 和 mutations 不会与其他模块冲突
namespaced: true,
state, // 模块的状态数据
mutations, // 模块的同步操作
actions // 模块的异步操作
state,
mutations,
actions
}

@ -1,87 +1,102 @@
/**
* 将时间转换为指定格式的字符串
* @param {(Object|string|number)} time - 时间对象时间戳或时间字符串
* @param {string} cFormat - 输出格式 '{y}-{m}-{d} {h}:{i}:{s}'
* @returns {string} 格式化后的时间字符串
* Created by PanJiaChen on 16/11/18.
*/
export function parseTime(time, cFormat) {
/**
* Parse the time to string
* @param {(Object|string|number)} time
* @param {string} cFormat
* @returns {string}
*/
export function parseTime (time, cFormat) {
if (arguments.length === 0) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' // 默认格式
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time) // 字符串时间转为数字
time = parseInt(time)
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000 // 将10位数的时间戳转为13位毫秒级
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1, // 月份从0开始因此加1
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay() // 获取星期几0表示星期天
a: date.getDay()
}
// 将格式化字符串中的占位符替换为对应的时间值
// eslint-disable-next-line camelcase
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } // 星期显示中文
// Note: getDay() returns 0 on Sunday
// eslint-disable-next-line standard/computed-property-even-spacing
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
if (result.length > 0 && value < 10) {
value = '0' + value // 如果是单数字补0
value = '0' + value
}
return value || 0
})
// eslint-disable-next-line camelcase
return time_str
}
/**
* 格式化时间为友好的相对时间字符串
* @param {number} time - 时间戳
* @param {string} option - 可选的自定义格式
* @returns {string} 格式化后的时间
* @param {number} time
* @param {string} option
* @returns {string}
*/
export function formatTime(time, option) {
export function formatTime (time, option) {
if (('' + time).length === 10) {
time = parseInt(time) * 1000 // 将10位时间戳转为13位毫秒级
time = parseInt(time) * 1000
} else {
time = +time // 转换为数字
time = +time
}
const d = new Date(time)
const now = Date.now() // 当前时间
const diff = (now - d) / 1000 // 计算差值(秒)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚' // 小于30秒
return '刚刚'
} else if (diff < 3600) {
return Math.ceil(diff / 60) + '分钟前' // 小于1小时
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前' // 小于1天
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前' // 小于2天
return '1天前'
}
if (option) {
return parseTime(time, option) // 如果传入了自定义格式,则使用自定义格式
return parseTime(time, option)
} else {
return (
d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
)
}
}
/**
* 从URL中解析查询参数为对象
* @param {string} url - URL字符串
* @returns {Object} 解析出的查询参数对象
* @param {string} url
* @returns {Object}
*/
export function getQueryObject(url) {
export function getQueryObject (url) {
url = url == null ? window.location.href : url
const search = url.substring(url.lastIndexOf('?') + 1)
const obj = {}
@ -97,42 +112,40 @@ export function getQueryObject(url) {
}
/**
* 计算字符串的字节长度UTF-8编码
* @param {string} str - 字符串
* @returns {number} 字节长度
* @param {string} input value
* @returns {number} output value
*/
export function byteLength(str) {
export function byteLength (str) {
// returns the byte length of an utf8 string
let s = str.length
for (let i = str.length - 1; i >= 0; i--) {
for (var i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) s++
else if (code > 0x7ff && code <= 0xffff) s += 2
if (code >= 0xDC00 && code <= 0xDFFF) i-- // UTF-16编码的高低位
if (code >= 0xDC00 && code <= 0xDFFF) i--
}
return s
}
/**
* 清理数组中的空值
* @param {Array} actual - 原始数组
* @returns {Array} 清理后的数组
* @param {Array} actual
* @returns {Array}
*/
export function cleanArray(actual) {
export function cleanArray (actual) {
const newArray = []
for (let i = 0; i < actual.length; i++) {
if (actual[i]) {
newArray.push(actual[i]) // 只保留非空值
newArray.push(actual[i])
}
}
return newArray
}
/**
* 将对象转换为URL查询参数字符串
* @param {Object} json - 查询参数对象
* @returns {string} 编码后的查询参数字符串
* @param {Object} json
* @returns {Array}
*/
export function param(json) {
export function param (json) {
if (!json) return ''
return cleanArray(
Object.keys(json).map(key => {
@ -143,11 +156,10 @@ export function param(json) {
}
/**
* 将查询字符串转换为对象
* @param {string} url - URL字符串
* @returns {Object} 查询参数对象
* @param {string} url
* @returns {Object}
*/
export function param2Obj(url) {
export function param2Obj (url) {
const search = url.split('?')[1]
if (!search) {
return {}
@ -164,57 +176,199 @@ export function param2Obj(url) {
}
/**
* 将HTML字符串转为纯文本
* @param {string} val - HTML字符串
* @returns {string} 纯文本
* @param {string} val
* @returns {string}
*/
export function html2Text(val) {
export function html2Text (val) {
const div = document.createElement('div')
div.innerHTML = val
return div.textContent || div.innerText
}
/**
* 合并两个对象后者属性覆盖前者
* @param {Object} target - 目标对象
* @param {(Object|Array)} source - 源对象或数组
* @returns {Object} 合并后的对象
* Merges two objects, giving the last one precedence
* @param {Object} target
* @param {(Object|Array)} source
* @returns {Object}
*/
export function objectMerge(target, source) {
export function objectMerge (target, source) {
if (typeof target !== 'object') {
target = {}
}
if (Array.isArray(source)) {
return source.slice() // 数组直接返回副本
return source.slice()
}
Object.keys(source).forEach(property => {
const sourceProperty = source[property]
if (typeof sourceProperty === 'object') {
target[property] = objectMerge(target[property], sourceProperty) // 递归合并对象
target[property] = objectMerge(target[property], sourceProperty)
} else {
target[property] = sourceProperty // 直接赋值
target[property] = sourceProperty
}
})
return target
}
/**
* 切换元素的类名
* @param {HTMLElement} element - 元素
* @param {string} className - 类名
* @param {HTMLElement} element
* @param {string} className
*/
export function toggleClass(element, className) {
export function toggleClass (element, className) {
if (!element || !className) {
return
}
let classString = element.className
const nameIndex = classString.indexOf(className)
if (nameIndex === -1) {
classString += '' + className // 如果类名不存在,则添加
classString += '' + className
} else {
classString =
classString.substr(0, nameIndex) +
classString.substr(nameIndex + className.length) // 否则移除
classString.substr(nameIndex + className.length)
}
element.className = classString
}
/**
* @param {string} type
* @returns {Date}
*/
export function getTime (type) {
if (type === 'start') {
return new Date().getTime() - 3600 * 1000 * 24 * 90
} else {
return new Date(new Date().toDateString())
}
}
/**
* @param {Function} func
* @param {number} wait
* @param {boolean} immediate
* @return {*}
*/
export function debounce (func, wait, immediate) {
let timeout, args, context, timestamp, result
const later = function () {
// 据上一次触发时间间隔
const last = +new Date() - timestamp
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
// 如果设定为immediate===true因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function (...args) {
context = this
timestamp = +new Date()
const callNow = immediate && !timeout
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
/**
* This is just a simple version of deep copy
* Has a lot of edge cases bug
* If you want to use a perfect deep copy, use lodash's _.cloneDeep
* @param {Object} source
* @returns {Object}
*/
export function deepClone (source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {}
Object.keys(source).forEach(keys => {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
/**
* @param {Array} arr
* @returns {Array}
*/
export function uniqueArr (arr) {
return Array.from(new Set(arr))
}
/**
* @returns {string}
*/
export function createUniqueString () {
const timestamp = +new Date() + ''
const randomNum = parseInt((1 + Math.random()) * 65536) + ''
return (+(randomNum + timestamp)).toString(32)
}
/**
* Check if an element has a class
* @param {HTMLElement} elm
* @param {string} cls
* @returns {boolean}
*/
export function hasClass (ele, cls) {
return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))
}
/**
* Add class to element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function addClass (ele, cls) {
if (!hasClass(ele, cls)) ele.className += ' ' + cls
}
/**
* Remove class from element
* @param {HTMLElement} elm
* @param {string} cls
*/
export function removeClass (ele, cls) {
if (hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)')
ele.className = ele.className.replace(reg, ' ')
}
}
export function formatSeconds (theTime) {
let theTime1 = 0
let theTime2 = 0
if (theTime > 60) {
theTime1 = parseInt(theTime / 60)
theTime = parseInt(theTime % 60)
if (theTime1 > 60) {
theTime2 = parseInt(theTime1 / 60)
theTime1 = parseInt(theTime1 % 60)
}
}
let result = '' + parseInt(theTime) + '秒'
if (theTime1 > 0) {
result = '' + parseInt(theTime1) + '分' + result
}
if (theTime2 > 0) {
result = '' + parseInt(theTime2) + '小时' + result
}
return result
}

@ -1,69 +1,56 @@
import axios from 'axios'
import vue from 'vue'
// 请求处理函数
const request = function (loadtip, query) {
let loading
// 如果需要显示加载提示
if (loadtip) {
loading = vue.prototype.$loading({
lock: false, // 不锁定页面
text: '正在加载中…', // 加载提示文本
spinner: 'el-icon-loading', // 使用的加载图标
background: 'rgba(0, 0, 0, 0.5)' // 加载背景
lock: false,
text: '正在加载中…',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.5)'
})
}
// 发送请求并处理响应
return axios.request(query)
.then(res => {
// 如果有加载提示,关闭加载
if (loadtip) {
loading.close()
}
// 根据响应码进行处理
if (res.data.code === 401) {
// 如果未授权,跳转到登录页面
vue.prototype.$$router.push({ path: '/login' })
return Promise.reject(res.data)
} else if (res.data.code === 500 || res.data.code === 501) {
// 如果发生服务器错误,返回拒绝的 Promise
} else if (res.data.code === 500) {
return Promise.reject(res.data)
} else if (res.data.code === 501) {
return Promise.reject(res.data)
} else if (res.data.code === 502) {
// 如果会话过期,跳转到登录页面
vue.prototype.$$router.push({ path: '/login' })
return Promise.reject(res.data)
} else {
// 如果响应正常,返回解析后的数据
return Promise.resolve(res.data)
}
})
.catch(e => {
// 如果发生错误,关闭加载提示
if (loadtip) {
loading.close()
}
// 显示错误消息
vue.prototype.$message.error(e.message)
return Promise.reject(e.message)
})
}
// 发送 POST 请求(无加载提示)
const post = function (url, params) {
const query = {
url: url, // 请求 URL
method: 'post', // 请求方法
withCredentials: true, // 支持跨域请求
timeout: 30000, // 请求超时时间
data: params, // 请求数据
headers: { 'Content-Type': 'application/json', 'request-ajax': true } // 请求头,指定为 JSON 格式
url: url,
method: 'post',
withCredentials: true,
timeout: 30000,
data: params,
headers: { 'Content-Type': 'application/json', 'request-ajax': true }
}
return request(false, query) // 调用请求处理函数,不显示加载提示
return request(false, query)
}
// 发送带加载提示的 POST 请求
const postWithLoadTip = function (url, params) {
const query = {
url: url,
@ -73,10 +60,9 @@ const postWithLoadTip = function (url, params) {
data: params,
headers: { 'Content-Type': 'application/json', 'request-ajax': true }
}
return request(true, query) // 调用请求处理函数,显示加载提示
return request(true, query)
}
// 发送不带加载提示的 POST 请求
const postWithOutLoadTip = function (url, params) {
const query = {
url: url,
@ -86,36 +72,33 @@ const postWithOutLoadTip = function (url, params) {
data: params,
headers: { 'Content-Type': 'application/json', 'request-ajax': true }
}
return request(false, query) // 调用请求处理函数,不显示加载提示
return request(false, query)
}
// 发送 GET 请求
const get = function (url, params) {
const query = {
url: url, // 请求 URL
method: 'get', // 请求方法
withCredentials: true, // 支持跨域请求
timeout: 30000, // 请求超时时间
params: params, // 请求参数
headers: { 'request-ajax': true } // 请求头,标识这是一个 Ajax 请求
url: url,
method: 'get',
withCredentials: true,
timeout: 30000,
params: params,
headers: { 'request-ajax': true }
}
return request(false, query) // 调用请求处理函数,不显示加载提示
return request(false, query)
}
// 发送 POST 请求,适用于文件上传(表单数据)
const form = function (url, params) {
const query = {
url: url,
method: 'post',
withCredentials: true,
timeout: 30000,
data: params, // 上传的文件数据
headers: { 'Content-Type': 'multipart/form-data', 'request-ajax': true } // 设置请求头为表单数据类型
data: params,
headers: { 'Content-Type': 'multipart/form-data', 'request-ajax': true }
}
return request(false, query) // 调用请求处理函数,不显示加载提示
return request(false, query)
}
// 导出各个请求方法
export {
post,
postWithLoadTip,

@ -1,65 +1,58 @@
// easeInOutQuad 缓动函数,用于实现平滑的加速和减速效果
Math.easeInOutQuad = function (t, b, c, d) {
t /= d / 2 // 将时间轴的进度按 2 分段进行计算
t /= d / 2
if (t < 1) {
// 如果在前半段,应用加速效果
return c / 2 * t * t + b
}
t-- // 后半段调整 t使动画反向
return -c / 2 * (t * (t - 2) - 1) + b // 应用减速效果
t--
return -c / 2 * (t * (t - 2) - 1) + b
}
// requestAnimationFrame 用于创建流畅的动画,优先使用原生的 requestAnimationFrame若不支持则回退到 setTimeout
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
var requestAnimFrame = (function () {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60) }
})()
/**
* scroll 移动页面的 scrollTop
* 因为很难准确检测到滚动元素所以这里通过直接修改所有可能的滚动元素
* @param {number} amount 滚动的目标位置
* Because it's so fucking difficult to detect the scrolling element, just move them all
* @param {number} amount
*/
function move(amount) {
document.documentElement.scrollTop = amount // 修改 html 元素的 scrollTop
document.body.parentNode.scrollTop = amount // 修改父级元素的 scrollTop
document.body.scrollTop = amount // 修改 body 的 scrollTop
function move (amount) {
document.documentElement.scrollTop = amount
document.body.parentNode.scrollTop = amount
document.body.scrollTop = amount
}
/**
* 获取当前页面的滚动位置
*/
function position() {
function position () {
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
}
/**
* 实现平滑滚动到目标位置
* @param {number} to 目标滚动位置
* @param {number} duration 滚动持续时间
* @param {Function} callback 滚动结束后的回调函数
* @param {number} to
* @param {number} duration
* @param {Function} callback
*/
export function scrollTo(to, duration, callback) {
const start = position() // 获取当前的滚动位置
const change = to - start // 计算需要滚动的距离
const increment = 20 // 每次滚动的步进时间
let currentTime = 0 // 当前动画时间
duration = (typeof (duration) === 'undefined') ? 500 : duration // 默认滚动持续时间为 500ms
export function scrollTo (to, duration, callback) {
const start = position()
const change = to - start
const increment = 20
let currentTime = 0
duration = (typeof (duration) === 'undefined') ? 500 : duration
var animateScroll = function () {
currentTime += increment // 增加动画时间
var val = Math.easeInOutQuad(currentTime, start, change, duration) // 使用缓动函数计算当前的滚动位置
move(val) // 执行滚动
// 如果动画还没有结束,继续执行
// increment the time
currentTime += increment
// find the value with the quadratic in-out easing function
var val = Math.easeInOutQuad(currentTime, start, change, duration)
// move the document.body
move(val)
// do the animation unless its over
if (currentTime < duration) {
requestAnimFrame(animateScroll) // 请求下一帧
requestAnimFrame(animateScroll)
} else {
// 动画完成后,执行回调函数(如果存在)
if (callback && typeof (callback) === 'function') {
// the animation is done so lets callback
callback()
}
}
}
animateScroll() // 启动动画
animateScroll()
}

@ -3,91 +3,74 @@
*/
/**
* 检查给定的路径是否为外部链接
* @param {string} path - 路径字符串
* @returns {Boolean} - 如果路径是外部链接则返回 true否则返回 false
* @param {string} path
* @returns {Boolean}
*/
export function isExternal (path) {
// 正则匹配 http://, https://, mailto: 和 tel: 开头的链接
return /^(https?:|mailto:|tel:)/.test(path)
}
/**
* 检查给定的用户名是否为有效的系统用户名
* @param {string} str - 用户名字符串
* @returns {Boolean} - 如果用户名有效'admin' 'editor'返回 true否则返回 false
* @param {string} str
* @returns {Boolean}
*/
export function validUsername (str) {
// 定义一个有效的用户名数组
// eslint-disable-next-line camelcase
const valid_map = ['admin', 'editor']
// 判断输入的用户名是否在有效用户名数组中
return valid_map.indexOf(str.trim()) >= 0
}
/**
* 验证给定的 URL 是否是有效的 URL
* @param {string} url - URL 字符串
* @returns {Boolean} - 如果 URL 是有效的返回 true否则返回 false
* @param {string} url
* @returns {Boolean}
*/
export function validURL (url) {
// 正则表达式用于匹配有效的 URL
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
// 使用正则表达式测试 URL 是否有效
return reg.test(url)
}
/**
* 验证字符串是否仅包含小写字母
* @param {string} str - 字符串
* @returns {Boolean} - 如果字符串只包含小写字母返回 true否则返回 false
* @param {string} str
* @returns {Boolean}
*/
export function validLowerCase (str) {
// 正则表达式匹配小写字母
const reg = /^[a-z]+$/
return reg.test(str)
}
/**
* 验证字符串是否仅包含大写字母
* @param {string} str - 字符串
* @returns {Boolean} - 如果字符串只包含大写字母返回 true否则返回 false
* @param {string} str
* @returns {Boolean}
*/
export function validUpperCase (str) {
// 正则表达式匹配大写字母
const reg = /^[A-Z]+$/
return reg.test(str)
}
/**
* 验证字符串是否仅包含字母大小写字母都可以
* @param {string} str - 字符串
* @returns {Boolean} - 如果字符串只包含字母返回 true否则返回 false
* @param {string} str
* @returns {Boolean}
*/
export function validAlphabets (str) {
// 正则表达式匹配所有字母
const reg = /^[A-Za-z]+$/
return reg.test(str)
}
/**
* 验证给定的字符串是否为有效的电子邮件地址
* @param {string} email - 电子邮件地址
* @returns {Boolean} - 如果电子邮件格式有效返回 true否则返回 false
* @param {string} email
* @returns {Boolean}
*/
export function validEmail (email) {
// 正则表达式匹配有效的电子邮件地址格式
// eslint-disable-next-line no-useless-escape
const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return reg.test(email)
}
/**
* 检查一个值是否为字符串类型
* @param {string} str -
* @returns {Boolean} - 如果值是字符串返回 true否则返回 false
* @param {string} str
* @returns {Boolean}
*/
export function isString (str) {
// 判断值是否为字符串类型
if (typeof str === 'string' || str instanceof String) {
return true
}
@ -95,14 +78,11 @@ export function isString (str) {
}
/**
* 检查一个值是否为数组类型
* @param {Array} arg -
* @returns {Boolean} - 如果值是数组返回 true否则返回 false
* @param {Array} arg
* @returns {Boolean}
*/
export function isArray (arg) {
// 如果支持 Array.isArray(),直接使用它
if (typeof Array.isArray === 'undefined') {
// 如果不支持,使用 Object.prototype.toString 来判断类型
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)

@ -1,9 +1,7 @@
<template>
<div style="margin-top: 10px">
<!-- 轮播图 -->
<el-row>
<el-carousel :interval="5000" arrow="always" type="card">
<!-- 每个轮播项 -->
<el-carousel-item >
<img src="@/assets/carousel/1.png" class="carousel-img">
</el-carousel-item>
@ -18,15 +16,11 @@
</el-carousel-item>
</el-carousel>
</el-row>
<!-- 任务中心 -->
<el-row class="app-item-contain">
<h3 class="index-title-h3" style="border-left: solid 10px #3651d4;">任务中心</h3>
<div style="padding-left: 15px">
<!-- 任务列表折叠面板 -->
<el-collapse v-loading="taskLoading" accordion v-if="taskList.length !== 0">
<el-collapse v-loading="taskLoading" accordion v-if="taskList.length!==0">
<el-collapse-item :title="taskItem.title" :name="taskItem.id" :key="taskItem.id" v-for="taskItem in taskList">
<!-- 任务内容展示 -->
<table class="index-task-table">
<tr v-for="paperItem in taskItem.paperItems" :key="paperItem.examPaperId">
<td class="index-task-table-paper">
@ -38,15 +32,14 @@
</el-tag>
</td>
<td width="80px">
<!-- 按钮根据试卷状态不同显示 -->
<router-link target="_blank" :to="{path:'/do', query:{id:paperItem.examPaperId}}" v-if="paperItem.status === null">
<el-button type="text" size="small">开始答题</el-button>
<router-link target="_blank" :to="{path:'/do',query:{id:paperItem.examPaperId}}" v-if="paperItem.status === null">
<el-button type="text" size="small">开始答题</el-button>
</router-link>
<router-link target="_blank" :to="{path:'/edit', query:{id:paperItem.examPaperAnswerId}}" v-else-if="paperItem.status === 1">
<el-button type="text" size="small">批改试卷</el-button>
<router-link target="_blank" :to="{path:'/edit',query:{id:paperItem.examPaperAnswerId}}" v-else-if="paperItem.status === 1">
<el-button type="text" size="small">批改试卷</el-button>
</router-link>
<router-link target="_blank" :to="{path:'/read', query:{id:paperItem.examPaperAnswerId}}" v-else-if="paperItem.status === 2">
<el-button type="text" size="small">查看试卷</el-button>
<router-link target="_blank" :to="{path:'/read',query:{id:paperItem.examPaperAnswerId}}" v-else-if="paperItem.status === 2">
<el-button type="text" size="small">查看试卷</el-button>
</router-link>
</td>
</tr>
@ -55,8 +48,6 @@
</el-collapse>
</div>
</el-row>
<!-- 固定试卷 -->
<el-row class="app-item-contain">
<h3 class="index-title-h3">固定试卷</h3>
<div style="padding-left: 15px">
@ -66,7 +57,7 @@
<div style="padding: 14px;">
<span>{{item.name}}</span>
<div class="bottom clearfix">
<router-link target="_blank" :to="{path:'/do', query:{id:item.id}}">
<router-link target="_blank" :to="{path:'/do',query:{id:item.id}}">
<el-button type="text" class="button">开始做题</el-button>
</router-link>
</div>
@ -75,8 +66,6 @@
</el-col>
</div>
</el-row>
<!-- 时段试卷 -->
<el-row class="app-item-contain">
<h3 class="index-title-h3" style="border-left: solid 10px rgb(220, 208, 65);">时段试卷</h3>
<div style="padding-left: 15px">
@ -91,7 +80,7 @@
<span>{{item.endTime}}</span>
</p>
<div class="bottom clearfix">
<router-link target="_blank" :to="{path:'/do', query:{id:item.id}}">
<router-link target="_blank" :to="{path:'/do',query:{id:item.id}}">
<el-button type="text" class="button">开始做题</el-button>
</router-link>
</div>
@ -106,26 +95,21 @@
<script>
import { mapState, mapGetters } from 'vuex'
import indexApi from '@/api/dashboard'
export default {
data () {
return {
//
fixedPaper: [],
timeLimitPaper: [],
pushPaper: [],
loading: false, //
taskLoading: false, //
taskList: [] //
loading: false,
taskLoading: false,
taskList: []
}
},
// API
created () {
let _this = this
this.loading = true
indexApi.index().then(re => {
//
_this.fixedPaper = re.response.fixedPaper
_this.timeLimitPaper = re.response.timeLimitPaper
_this.pushPaper = re.response.pushPaper
@ -134,30 +118,22 @@ export default {
this.taskLoading = true
indexApi.task().then(re => {
//
_this.taskList = re.response
_this.taskLoading = false
})
},
methods: {
//
statusTagFormatter (status) {
return this.enumFormat(this.statusTag, status)
},
//
statusTextFormatter (status) {
return this.enumFormat(this.statusEnum, status)
}
},
computed: {
// Vuex
...mapGetters('enumItem', [
'enumFormat'
]),
//
...mapState('enumItem', {
statusEnum: state => state.exam.examPaperAnswer.statusEnum,
statusTag: state => state.exam.examPaperAnswer.statusTag
@ -166,13 +142,12 @@ export default {
}
</script>
<style lang="scss" scoped>
.index-title-h3 {
font-size: 22px;
font-weight: 400;
color: #1f2f3d;
border-left: solid 10px #2ce8b4; /* 左侧边框 */
border-left: solid 10px #2ce8b4;
padding-left: 10px;
}
@ -184,7 +159,6 @@ export default {
margin: 0;
}
/* 设置轮播图项的背景色 */
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
@ -217,6 +191,6 @@ export default {
}
.clearfix:after {
clear: both;
clear: both
}
</style>

@ -1,22 +1,16 @@
<template>
<div class="errPage-container">
<!-- 返回按钮 -->
<el-button icon="arrow-left" class="pan-back-btn" @click="back">
返回
</el-button>
<!-- 错误信息和内容展示 -->
<el-row>
<el-col :span="12">
<!-- 错误标题和描述 -->
<h1 class="text-jumbo text-ginormous">
Oops!
</h1>
gif来源<a href="https://zh.airbnb.com/" target="_blank">airbnb</a> 页面
<h2>你没有权限去该页面</h2>
<h6>如有不满请联系你领导</h6>
<!-- 提供的链接列表 -->
<ul class="list-unstyled">
<li>或者你可以去:</li>
<li class="link-type">
@ -30,43 +24,30 @@
<li><a href="#" @click.prevent="dialogVisible=true">点我看图</a></li>
</ul>
</el-col>
<!-- 错误页面的图片展示 -->
<el-col :span="12">
<img :src="errGif" width="313" height="428" alt="Girl has dropped her ice cream.">
</el-col>
</el-row>
<!-- 弹窗展示图像 -->
<el-dialog :visible.sync="dialogVisible" title="随便看">
<img :src="ewizardClap" class="pan-img">
</el-dialog>
</div>
</template>
<script>
// GIF
import errGif from '@/assets/401_images/401.gif'
export default {
name: 'Page401', //
name: 'Page401',
data () {
return {
// GIF
errGif: errGif + '?' + +new Date(),
//
ewizardClap: 'https://wpimg.wallstcn.com/007ef517-bafd-4066-aae4-6883632d9646',
//
dialogVisible: false
}
},
methods: {
//
back () {
// URL 'noGoBack'
if (this.$route.query.noGoBack) {
this.$router.push({ path: '/dashboard' })
} else {
@ -77,54 +58,38 @@ export default {
}
</script>
<style lang="scss" scoped>
/* 错误页面的容器样式 */
.errPage-container {
width: 800px;
max-width: 100%;
margin: 100px auto; /* 页面居中 */
/* 返回按钮样式 */
margin: 100px auto;
.pan-back-btn {
background: #008489;
color: #fff;
border: none!important;
}
/* GIF 图片的样式 */
.pan-gif {
margin: 0 auto;
display: block;
}
/* 弹窗图片的样式 */
.pan-img {
display: block;
margin: 0 auto;
width: 100%;
}
/* 大标题样式 */
.text-jumbo {
font-size: 60px;
font-weight: 700;
color: #484848;
}
/* 列表样式 */
.list-unstyled {
font-size: 14px;
li {
padding-bottom: 5px;
}
a {
color: #008489;
text-decoration: none;
/* 鼠标悬停时添加下划线 */
&:hover {
text-decoration: underline;
}

@ -1,19 +1,13 @@
<template>
<div class="wscn-http404-container">
<!-- 404 错误页面 -->
<div class="wscn-http404">
<div class="pic-404">
<!-- 父图片显示404背景 -->
<img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
<!-- 子图片三个云朵图片 -->
<img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<!-- 错误提示内容 -->
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">请检查你的访问地址是否正确, 或点击下面按钮返回主页.</div>
@ -23,12 +17,11 @@
</div>
</template>
<script>
export default {
name: 'Page404',
computed: {
//
message () {
return '页面未找到...'
}
@ -38,31 +31,26 @@ export default {
<style lang="scss" scoped>
.wscn-http404-container{
transform: translate(-50%,-50%); /* 页面居中 */
transform: translate(-50%,-50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px; /* 设置宽度 */
padding: 0 50px; /* 设置左右内边距 */
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left; /* 左浮动 */
width: 600px; /* 设置宽度 */
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%; /* 父图片填满父容器 */
width: 100%;
}
&__child {
position: absolute; /* 设置绝对定位 */
/* 各个子图片的动画 */
position: absolute;
&.left {
width: 80px;
top: 17px;
@ -96,36 +84,80 @@ export default {
animation-fill-mode: forwards;
animation-delay: 1s;
}
/* 各种云朵的动画 */
@keyframes cloudLeft {
0% { top: 17px; left: 220px; opacity: 0; }
20% { top: 33px; left: 188px; opacity: 1; }
80% { top: 81px; left: 92px; opacity: 1; }
100% { top: 97px; left: 60px; opacity: 0; }
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% { top: 10px; left: 420px; opacity: 0; }
20% { top: 40px; left: 360px; opacity: 1; }
70% { top: 130px; left: 180px; opacity: 1; }
100% { top: 160px; left: 120px; opacity: 0; }
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% { top: 100px; left: 500px; opacity: 0; }
20% { top: 120px; left: 460px; opacity: 1; }
80% { top: 180px; left: 340px; opacity: 1; }
100% { top: 200px; left: 300px; opacity: 0; }
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
@ -179,8 +211,14 @@ export default {
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% { transform: translateY(60px); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}

@ -1,110 +1,82 @@
<template>
<!-- 根容器控制整体加载状态 -->
<div v-loading="qLoading" style="line-height:1.8">
<!-- 判断题目类型是否为1-5有效类型 -->
<div v-if="qType==1||qType==2||qType==3||qType==4||qType==5">
<!-- 单选题类型 -->
<div v-if="qType==1">
<!-- 题目标题 -->
<div v-if="qType==1" >
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<!-- 单选题选项 -->
<el-radio-group v-model="answer.content">
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix">
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
</div>
</div>
<!-- 多选题类型 -->
<div v-else-if="qType==2">
<div v-else-if="qType==2" >
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<!-- 多选题选项 -->
<el-checkbox-group v-model="answer.contentArray">
<el-checkbox v-for="item in question.items" :label="item.prefix" :key="item.prefix">
<el-checkbox-group v-model="answer.contentArray" >
<el-checkbox v-for="item in question.items" :label="item.prefix" :key="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<!-- 判断题类型 -->
<div v-else-if="qType==3">
<div v-else-if="qType==3" >
<div class="q-title" v-html="question.title" style="display: inline;margin-right: 10px"/>
<span style="padding-right: 10px;">(</span>
<el-radio-group v-model="answer.content">
<!-- 判断题选项 -->
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix">
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix">
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
<span style="padding-left: 10px;">)</span>
</div>
<!-- 填空题类型 -->
<div v-else-if="qType==4">
<div v-else-if="qType==4" >
<div class="q-title" v-html="question.title"/>
<!-- 渲染多个输入框每个对应一个空 -->
<div v-if="answer.contentArray!==null">
<el-form-item v-for="item in question.items" :label="item.prefix" :key="item.prefix" label-width="50px" style="margin-top: 10px;margin-bottom: 10px;">
<el-input v-model="answer.contentArray[item.prefix-1]" />
<el-form-item :label="item.prefix" :key="item.prefix" v-for="item in question.items" label-width="50px" style="margin-top: 10px;margin-bottom: 10px;">
<el-input v-model="answer.contentArray[item.prefix-1]" />
</el-form-item>
</div>
</div>
<!-- 简答题类型 -->
<div v-else-if="qType==5">
<div class="q-title" v-html="question.title"/>
<div>
<!-- 多行文本输入框 -->
<el-input v-model="answer.content" type="textarea" rows="5"></el-input>
<el-input v-model="answer.content" type="textarea" rows="5" ></el-input>
</div>
</div>
<!-- 显示答题结果 -->
<div class="question-answer-show-item" style="margin-top: 15px">
<span class="question-show-item">结果</span>
<el-tag :type="doRightTagFormatter(answer.doRight)">
{{ doRightTextFormatter(answer.doRight) }}
</el-tag>
</div>
<!-- 显示分数 -->
<div class="question-answer-show-item">
<span class="question-show-item">分数</span>
<span>{{question.score}}</span>
</div>
<!-- 显示难度 -->
<div class="question-answer-show-item">
<span class="question-show-item">难度</span>
<el-rate disabled v-model="question.difficult" class="question-show-item"></el-rate>
</div>
<!-- 显示解析 -->
<br/>
<div class="question-answer-show-item" style="line-height: 1.8">
<span class="question-show-item">解析</span>
<span v-html="question.analyze" class="q-item-span-content" />
</div>
<!-- 显示正确答案 -->
<div class="question-answer-show-item">
<span class="question-show-item">正确答案</span>
<span v-if="qType==1||qType==2||qType==5" v-html="question.correct" class="q-item-span-content"/>
<span v-if="qType==1||qType==2 ||qType==5" v-html="question.correct" class="q-item-span-content"/>
<span v-if="qType==3" v-html="trueFalseFormatter(question)" class="q-item-span-content"/>
<span v-if="qType==4">{{question.correctArray}}</span>
</div>
</div>
<!-- 无效题目类型 -->
<div v-else>
</div>
</div>
</template>
<script>
@ -112,47 +84,39 @@ import { mapState, mapGetters } from 'vuex'
export default {
name: 'QuestionShow',
props: {
//
question: {
type: Object,
default: function () {
return {}
}
},
//
answer: {
type: Object,
default: function () {
return { id: null, content: '', contentArray: [], doRight: false }
}
},
//
qLoading: {
type: Boolean,
default: false
},
// 1-5
qType: {
type: Number,
default: 0
}
},
methods: {
//
trueFalseFormatter (question) {
return question.items.filter(d => d.prefix === question.correct)[0].content
},
//
doRightTagFormatter (status) {
return this.enumFormat(this.doRightTag, status)
},
//
doRightTextFormatter (status) {
return this.enumFormat(this.doRightEnum, status)
}
},
computed: {
// Vuex
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
doRightEnum: state => state.exam.question.answer.doRightEnum,

@ -1,95 +1,77 @@
<template>
<div style="line-height:1.8">
<!-- 判断题目类型是否为单选 -->
<div v-if="qType==1" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<!-- 单选题选项组 -->
<el-radio-group v-model="answer.content" @change="answer.completed = true" >
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
</div>
</div>
<!-- 判断题目类型是否为多选 -->
<div v-else-if="qType==2" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div class="q-content">
<!-- 多选题选项组 -->
<el-checkbox-group v-model="answer.contentArray" @change="answer.completed = true" >
<el-checkbox v-for="item in question.items" :label="item.prefix" :key="item.prefix" >
<el-checkbox v-for="item in question.items" :label="item.prefix" :key="item.prefix" >
<span class="question-prefix">{{item.prefix}}.</span>
<span v-html="item.content" class="q-item-span-content"></span>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<!-- 判断题目类型是否为判断题 -->
<div v-else-if="qType==3" v-loading="qLoading">
<div class="q-title" v-html="question.title" style="display: inline;margin-right: 10px"/>
<span style="padding-right: 10px;">(</span>
<el-radio-group v-model="answer.content" @change="answer.completed = true" >
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<el-radio v-for="item in question.items" :key="item.prefix" :label="item.prefix" >
<span v-html="item.content" class="q-item-span-content"></span>
</el-radio>
</el-radio-group>
<span style="padding-left: 10px;">)</span>
</div>
<!-- 判断题目类型是否为填空题 -->
<div v-else-if="qType==4" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div>
<!-- 渲染多个填空输入框 -->
<el-form-item :label="item.prefix" :key="item.prefix" v-for="item in question.items" label-width="50px" style="margin-top: 10px;margin-bottom: 10px;">
<el-input v-model="answer.contentArray[item.prefix-1]" @change="answer.completed = true" />
<el-form-item :label="item.prefix" :key="item.prefix" v-for="item in question.items" label-width="50px" style="margin-top: 10px;margin-bottom: 10px;">
<el-input v-model="answer.contentArray[item.prefix-1]" @change="answer.completed = true" />
</el-form-item>
</div>
</div>
<!-- 判断题目类型是否为简答题 -->
<div v-else-if="qType==5" v-loading="qLoading">
<div class="q-title" v-html="question.title"/>
<div>
<!-- 多行文本框输入 -->
<el-input v-model="answer.content" type="textarea" rows="5" @change="answer.completed = true"/>
<el-input v-model="answer.content" type="textarea" rows="5" @change="answer.completed = true"/>
</div>
</div>
<!-- 非法题目类型处理 -->
<div v-else>
</div>
</div>
</template>
<script>
export default {
name: 'QuestionShow',
props: {
//
question: {
type: Object,
default: function () {
return {}
}
},
//
answer: {
type: Object,
default: function () {
return { id: null, content: '', contentArray: [] }
}
},
//
qLoading: {
type: Boolean,
default: false
},
//
qType: {
type: Number,
default: 0

@ -1,75 +1,51 @@
<template>
<div>
<!-- 显示考试标题及剩余时间 -->
<el-row class="do-exam-title">
<el-row class="do-exam-title">
<el-col :span="24">
<!-- 遍历题目列表生成题目标签 -->
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<!-- 根据完成状态设置标签样式点击跳转到对应题目 -->
<el-tag :type="questionCompleted(item.completed)" class="do-exam-title-tag"
@click="goAnchor('#question-' + item.itemOrder)">
{{ item.itemOrder }}
</el-tag>
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag :type="questionCompleted(item.completed)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">{{item.itemOrder}}</el-tag>
</span>
<!-- 显示剩余考试时间 -->
<span class="do-exam-time">
<label>剩余时间</label>
<label>{{ formatSeconds(remainTime) }}</label>
<label>{{formatSeconds(remainTime)}}</label>
</span>
</el-col>
</el-row>
<!-- 隐藏的标题部分可用于其他用途 -->
<el-row class="do-exam-title-hidden">
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag class="do-exam-title-tag">{{ item.itemOrder }}</el-tag>
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag class="do-exam-title-tag" >{{item.itemOrder}}</el-tag>
</span>
<span class="do-exam-time">
<label>剩余时间</label>
</span>
</el-col>
</el-row>
<!-- 主容器 -->
<el-container class="app-item-contain">
<!-- 页面头部显示试卷信息 -->
<el-container class="app-item-contain">
<el-header class="align-center">
<h1>{{ form.name }}</h1>
<h1>{{form.name}}</h1>
<div>
<!-- 显示试卷总分及考试时间 -->
<span class="question-title-padding">试卷总分{{ form.score }}</span>
<span class="question-title-padding">考试时间{{ form.suggestTime }}分钟</span>
<span class="question-title-padding">试卷总分{{form.score}}</span>
<span class="question-title-padding">考试时间{{form.suggestTime}}分钟</span>
</div>
</el-header>
<!-- 主体内容 -->
<el-main>
<!-- 表单绑定数据显示题目内容 -->
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<!-- 遍历题目类型 -->
<el-row :key="index" v-for="(titleItem, index) in form.titleItems">
<!-- 显示题目分类标题 -->
<h3>{{ titleItem.name }}</h3>
<!-- 遍历题目列表 -->
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length !== 0">
<el-form-item :key="questionItem.itemOrder"
:label="questionItem.itemOrder + '.'"
<el-row :key="index" v-for="(titleItem,index) in form.titleItems">
<h3>{{titleItem.name}}</h3>
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length!==0">
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px"
:id="'question-' + questionItem.itemOrder">
<!-- 引用题目编辑组件 -->
<QuestionEdit :qType="questionItem.questionType"
:question="questionItem"
:answer="answer.answerItems[questionItem.itemOrder - 1]" />
class="exam-question-item" label-width="50px" :id="'question-'+ questionItem.itemOrder">
<QuestionEdit :qType="questionItem.questionType" :question="questionItem"
:answer="answer.answerItems[questionItem.itemOrder-1]"/>
</el-form-item>
</el-card>
</el-row>
<!-- 提交与取消按钮 -->
<el-row class="do-align-center">
<el-button type="primary" @click="submitForm"></el-button>
<el-button>取消</el-button>
</el-row>
<el-row class="do-align-center">
<el-button type="primary" @click="submitForm"></el-button>
<el-button>取消</el-button>
</el-row>
</el-form>
</el-main>
</el-container>
@ -87,93 +63,87 @@ export default {
components: { QuestionEdit },
data () {
return {
form: {}, //
formLoading: false, //
answer: { //
form: {},
formLoading: false,
answer: {
questionId: null,
doTime: 0, //
answerItems: [] //
doTime: 0,
answerItems: []
},
timer: null, //
remainTime: 0 //
timer: null,
remainTime: 0
}
},
created () {
//
let id = this.$route.query.id
let _this = this
if (id && parseInt(id) !== 0) {
this.formLoading = true
_this.formLoading = true
examPaperApi.select(id).then(re => {
this.form = re.response
this.remainTime = re.response.suggestTime * 60 //
this.initAnswer()
this.timeReduce() //
this.formLoading = false
_this.form = re.response
_this.remainTime = re.response.suggestTime * 60
_this.initAnswer()
_this.timeReduce()
_this.formLoading = false
})
}
},
mounted () {
},
beforeDestroy () {
//
window.clearInterval(this.timer)
},
methods: {
formatSeconds (theTime) {
return formatSeconds(theTime) //
return formatSeconds(theTime)
},
timeReduce () {
//
this.timer = setInterval(() => {
if (this.remainTime <= 0) {
this.submitForm() //
let _this = this
this.timer = setInterval(function () {
if (_this.remainTime <= 0) {
_this.submitForm()
} else {
this.answer.doTime++
this.remainTime--
++_this.answer.doTime
--_this.remainTime
}
}, 1000)
},
questionCompleted (completed) {
//
return this.enumFormat(this.doCompletedTag, completed)
},
goAnchor (selector) {
//
this.$el.querySelector(selector).scrollIntoView({ behavior: 'instant', block: 'center' })
this.$el.querySelector(selector).scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' })
},
initAnswer () {
//
this.answer.id = this.form.id
this.form.titleItems.forEach(titleItem => {
titleItem.questionItems.forEach(question => {
this.answer.answerItems.push({
questionId: question.id,
content: null,
contentArray: [],
completed: false,
itemOrder: question.itemOrder
})
})
})
let titleItemArray = this.form.titleItems
for (let tIndex in titleItemArray) {
let questionArray = titleItemArray[tIndex].questionItems
for (let qIndex in questionArray) {
let question = questionArray[qIndex]
this.answer.answerItems.push({ questionId: question.id, content: null, contentArray: [], completed: false, itemOrder: question.itemOrder })
}
}
},
submitForm () {
//
window.clearInterval(this.timer)
this.formLoading = true
let _this = this
window.clearInterval(_this.timer)
_this.formLoading = true
examPaperAnswerApi.answerSubmit(this.answer).then(re => {
if (re.code === 1) {
//
this.$alert(`试卷得分:${re.response}`, '考试结果', {
_this.$alert('试卷得分:' + re.response + '分', '考试结果', {
confirmButtonText: '返回考试记录',
callback: () => {
this.$router.push('/record/index')
callback: action => {
_this.$router.push('/record/index')
}
})
} else {
//
this.$message.error(re.message)
_this.$message.error(re.message)
}
this.formLoading = false
}).catch(() => {
this.formLoading = false
_this.formLoading = false
}).catch(e => {
_this.formLoading = false
})
}
},
@ -188,14 +158,17 @@ export default {
<style lang="scss" scoped>
.align-center {
text-align: center;
text-align: center
}
.exam-question-item {
padding: 10px;
.el-form-item__label {
font-size: 15px !important;
}
}
.question-title-padding {
padding-left: 25px;
padding-right: 25px;

@ -1,203 +1,152 @@
<template>
<!-- 显示答题的标题区域 -->
<div>
<!-- 题目顺序的显示区域 -->
<el-row class="do-exam-title" style="background-color: #F5F5DC">
<el-col :span="24">
<!-- 遍历所有的答题项并显示 -->
<div>
<el-row class="do-exam-title" style="background-color: #F5F5DC">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<!-- 显示题目编号并为每个题目添加点击事件跳转到对应的题目 -->
<el-tag :type="questionDoRightTag(item.doRight)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">
{{item.itemOrder}}
</el-tag>
<el-tag :type="questionDoRightTag(item.doRight)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<!-- 隐藏的题目顺序区域可以用于后台显示等用途 -->
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<!-- 遍历所有的答题项并显示 -->
</el-col>
</el-row>
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<!-- 显示题目编号 -->
<el-tag class="do-exam-title-tag" >{{item.itemOrder}}</el-tag>
<el-tag class="do-exam-title-tag" >{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<!-- 主体内容区域 -->
<el-container class="app-item-contain">
<el-header class="align-center">
<!-- 显示考试的名称 -->
<h1>{{form.name}}</h1>
<div>
<!-- 显示考试得分和耗时 -->
<span class="question-title-padding">试卷得分{{answer.score}}</span>
<span class="question-title-padding">试卷耗时{{formatSeconds(answer.doTime)}}</span>
</div>
</el-header>
<!-- 主内容部分 -->
<el-main>
<!-- 表单展示部分 -->
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<!-- 遍历每个题目的标题 -->
<el-row :key="index" v-for="(titleItem,index) in form.titleItems">
<!-- 显示题目分类名称 -->
<h3>{{titleItem.name}}</h3>
<!-- 题目卡片只有当题目数量不为 0 时才显示 -->
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length!==0">
<!-- 遍历每个题目显示具体内容 -->
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px" :id="'question-'+ questionItem.itemOrder">
<el-row>
<!-- 显示每个题目的答案 -->
<QuestionAnswerShow :qType="questionItem.questionType" :question="questionItem" :answer="answer.answerItems[questionItem.itemOrder-1]"/>
</el-row>
<!-- 如果该题没有批改显示评分选项 -->
<el-row v-if="answer.answerItems[questionItem.itemOrder-1].doRight === null">
<label style="color: #e6a23c">批改</label>
<!-- 评分单选按钮 -->
<el-radio-group v-model="answer.answerItems[questionItem.itemOrder-1].score">
<el-radio v-for="item in scoreSelect(questionItem.score)" :key="item" :label="item" >
{{item}}
</el-radio>
</el-radio-group>
</el-row>
</el-form-item>
</el-card>
</el-row>
<!-- 底部操作按钮 -->
<el-row class="do-align-center">
<!-- 提交按钮 -->
<el-button type="primary" @click="submitForm"></el-button>
<!-- 取消按钮 -->
<el-button>取消</el-button>
</el-row>
</el-form>
</el-main>
</el-container>
</div>
</el-col>
</el-row>
<el-container class="app-item-contain">
<el-header class="align-center">
<h1>{{form.name}}</h1>
<div>
<span class="question-title-padding">试卷得分{{answer.score}}</span>
<span class="question-title-padding">试卷耗时{{formatSeconds(answer.doTime)}}</span>
</div>
</el-header>
<el-main>
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<el-row :key="index" v-for="(titleItem,index) in form.titleItems">
<h3>{{titleItem.name}}</h3>
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length!==0">
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px" :id="'question-'+ questionItem.itemOrder">
<el-row>
<QuestionAnswerShow :qType="questionItem.questionType" :question="questionItem" :answer="answer.answerItems[questionItem.itemOrder-1]"/>
</el-row>
<el-row v-if="answer.answerItems[questionItem.itemOrder-1].doRight === null">
<label style="color: #e6a23c">批改</label>
<el-radio-group v-model="answer.answerItems[questionItem.itemOrder-1].score">
<el-radio v-for="item in scoreSelect(questionItem.score)" :key="item" :label="item" >
{{item}}
</el-radio>
</el-radio-group>
</el-row>
</el-form-item>
</el-card>
</el-row>
<el-row class="do-align-center">
<el-button type="primary" @click="submitForm"></el-button>
<el-button>取消</el-button>
</el-row>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex' // Vuex
import { formatSeconds } from '@/utils' //
import QuestionAnswerShow from '../components/QuestionAnswerShow' //
import examPaperAnswerApi from '@/api/examPaperAnswer' // API
import { mapState, mapGetters } from 'vuex'
import { formatSeconds } from '@/utils'
import QuestionAnswerShow from '../components/QuestionAnswerShow'
import examPaperAnswerApi from '@/api/examPaperAnswer'
export default {
components: { QuestionAnswerShow }, //
components: { QuestionAnswerShow },
data () {
return {
form: {}, //
formLoading: false, //
form: {},
formLoading: false,
answer: {
id: null, // ID
score: 0, //
doTime: 0, //
answerItems: [], //
doRight: false //
id: null,
score: 0,
doTime: 0,
answerItems: [],
doRight: false
}
}
},
//
created () {
let id = this.$route.query.id // ID
let id = this.$route.query.id
let _this = this
if (id && parseInt(id) !== 0) {
_this.formLoading = true //
// API
_this.formLoading = true
examPaperAnswerApi.read(id).then(re => {
_this.form = re.response.paper //
_this.answer = re.response.answer //
_this.formLoading = false //
_this.form = re.response.paper
_this.answer = re.response.answer
_this.formLoading = false
})
}
},
methods: {
//
submitForm () {
let _this = this
_this.formLoading = true //
// API
_this.formLoading = true
examPaperAnswerApi.edit(this.answer).then(re => {
if (re.code === 1) {
//
_this.$alert('试卷得分:' + re.response + '分', '考试结果', {
confirmButtonText: '返回考试记录',
callback: action => {
_this.$router.push('/record/index') //
_this.$router.push('/record/index')
}
})
} else {
_this.$message.error(re.message) //
_this.$message.error(re.message)
}
_this.formLoading = false //
_this.formLoading = false
}).catch(e => {
_this.formLoading = false //
_this.formLoading = false
})
},
//
scoreSelect (score) {
let array = []
for (let i = 0; i <= parseInt(score); i++) {
array.push(i.toString()) //
array.push(i.toString())
}
if (score.indexOf('.') !== -1) {
array.push(score) //
array.push(score)
}
return array
},
//
formatSeconds (theTime) {
return formatSeconds(theTime)
},
//
questionDoRightTag (status) {
return this.enumFormat(this.doRightTag, status)
},
//
goAnchor (selector) {
this.$el.querySelector(selector).scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' })
}
},
// Vuex
computed: {
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
doRightTag: state => state.exam.question.answer.doRightTag //
doRightTag: state => state.exam.question.answer.doRightTag
})
}
}
</script>
<style lang="scss" scoped>
/* 居中对齐样式 */
.align-center {
text-align: center
}
/* 题目项的样式 */
.exam-question-item{
padding: 10px;
.el-form-item__label{
font-size: 15px !important; /* 调整标签字体大小 */
font-size: 15px !important;
}
}
/* 标题部分的内边距 */
.question-title-padding{
padding-left: 25px;
padding-right: 25px;

@ -1,145 +1,110 @@
<template>
<div>
<!-- 题目顺序显示区域 -->
<el-row class="do-exam-title" style="background-color: #F5F5DC">
<el-col :span="24">
<!-- 遍历答题项并显示题号 -->
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<!-- 使用标签显示题号点击后跳转到对应的题目位置 -->
<el-tag :type="questionDoRightTag(item.doRight)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">
{{item.itemOrder}}
</el-tag>
<div>
<el-row class="do-exam-title" style="background-color: #F5F5DC">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag :type="questionDoRightTag(item.doRight)" class="do-exam-title-tag" @click="goAnchor('#question-'+item.itemOrder)">{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<!-- 隐藏的题目顺序显示区域 -->
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<!-- 遍历答题项并显示题号 -->
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<!-- 显示题号 -->
<el-tag class="do-exam-title-tag">{{item.itemOrder}}</el-tag>
</el-col>
</el-row>
<el-row class="do-exam-title-hidden">
<el-col :span="24">
<span :key="item.itemOrder" v-for="item in answer.answerItems">
<el-tag class="do-exam-title-tag" >{{item.itemOrder}}</el-tag>
</span>
</el-col>
</el-row>
<!-- 主要内容容器 -->
<el-container class="app-item-contain">
<el-header class="align-center">
<!-- 显示试卷名称 -->
<h1>{{form.name}}</h1>
<div>
<!-- 显示试卷得分和考试耗时 -->
<span class="question-title-padding">试卷得分{{answer.score}}</span>
<span class="question-title-padding">试卷耗时{{formatSeconds(answer.doTime)}}</span>
</div>
</el-header>
<!-- 主体部分 -->
<el-main>
<!-- 表单部分显示题目和答案 -->
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<!-- 遍历试卷中的每个题目组 -->
<el-row :key="index" v-for="(titleItem, index) in form.titleItems">
<!-- 显示题目组名称 -->
<h3>{{titleItem.name}}</h3>
<!-- 如果题目组中有题目则显示 -->
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length !== 0">
<!-- 遍历每个题目 -->
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px" :id="'question-' + questionItem.itemOrder">
<QuestionAnswerShow :qType="questionItem.questionType" :question="questionItem" :answer="answer.answerItems[questionItem.itemOrder - 1]"/>
</el-form-item>
</el-card>
</el-row>
</el-form>
</el-main>
</el-container>
</div>
</el-col>
</el-row>
<el-container class="app-item-contain">
<el-header class="align-center">
<h1>{{form.name}}</h1>
<div>
<span class="question-title-padding">试卷得分{{answer.score}}</span>
<span class="question-title-padding">试卷耗时{{formatSeconds(answer.doTime)}}</span>
</div>
</el-header>
<el-main>
<el-form :model="form" ref="form" v-loading="formLoading" label-width="100px">
<el-row :key="index" v-for="(titleItem,index) in form.titleItems">
<h3>{{titleItem.name}}</h3>
<el-card class="exampaper-item-box" v-if="titleItem.questionItems.length!==0">
<el-form-item :key="questionItem.itemOrder" :label="questionItem.itemOrder+'.'"
v-for="questionItem in titleItem.questionItems"
class="exam-question-item" label-width="50px" :id="'question-'+ questionItem.itemOrder">
<QuestionAnswerShow :qType="questionItem.questionType" :question="questionItem" :answer="answer.answerItems[questionItem.itemOrder-1]"/>
</el-form-item>
</el-card>
</el-row>
</el-form>
</el-main>
</el-container>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex' // Vuex
import { formatSeconds } from '@/utils' //
import QuestionAnswerShow from '../components/QuestionAnswerShow' //
import examPaperAnswerApi from '@/api/examPaperAnswer' // API
import { mapState, mapGetters } from 'vuex'
import { formatSeconds } from '@/utils'
import QuestionAnswerShow from '../components/QuestionAnswerShow'
import examPaperAnswerApi from '@/api/examPaperAnswer'
export default {
components: { QuestionAnswerShow }, //
components: { QuestionAnswerShow },
data () {
return {
form: {}, //
formLoading: false, //
form: {},
formLoading: false,
answer: {
id: null, // ID
score: 0, //
doTime: 0, //
answerItems: [], //
doRight: false //
id: null,
score: 0,
doTime: 0,
answerItems: [],
doRight: false
}
}
},
//
created () {
let id = this.$route.query.id // ID
let id = this.$route.query.id
let _this = this
if (id && parseInt(id) !== 0) {
_this.formLoading = true //
// API
_this.formLoading = true
examPaperAnswerApi.read(id).then(re => {
_this.form = re.response.paper //
_this.answer = re.response.answer //
_this.formLoading = false //
_this.form = re.response.paper
_this.answer = re.response.answer
_this.formLoading = false
})
}
},
methods: {
//
formatSeconds (theTime) {
return formatSeconds(theTime)
},
//
questionDoRightTag (status) {
return this.enumFormat(this.doRightTag, status)
},
//
goAnchor (selector) {
this.$el.querySelector(selector).scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' })
}
},
computed: {
...mapGetters('enumItem', ['enumFormat']), // Vuex
...mapGetters('enumItem', ['enumFormat']),
...mapState('enumItem', {
doRightTag: state => state.exam.question.answer.doRightTag //
doRightTag: state => state.exam.question.answer.doRightTag
})
}
}
</script>
<style lang="scss" scoped>
/* 居中对齐样式 */
.align-center {
text-align: center
}
/* 题目项的样式 */
.exam-question-item {
.exam-question-item{
padding: 10px;
.el-form-item__label {
font-size: 15px !important; /* 调整标签字体大小 */
.el-form-item__label{
font-size: 15px !important;
}
}
/* 标题部分的内边距 */
.question-title-padding {
.question-title-padding{
padding-left: 25px;
padding-right: 25px;
}

@ -1,83 +1,51 @@
'use strict'
const path = require('path')
// 定义一个函数,用于处理路径的拼接
function resolve (dir) {
return path.join(__dirname, dir) // 将传入的路径与当前工作目录拼接,形成绝对路径
return path.join(__dirname, dir)
}
module.exports = {
// 项目的基础路径,设置为相对路径 './'
publicPath: './',
// 设置输出的目录名,构建结果将输出到 'student' 文件夹
outputDir: 'student',
// 设置静态资源的目录名,相对 'outputDir',静态资源将输出到 'student/static'
assetsDir: 'static',
// 是否在开发环境下启用 lint 规则
lintOnSave: true,
// 是否生成生产环境的 source map通常生产环境下为了安全性关闭
productionSourceMap: false,
// 配置开发服务器
devServer: {
// 启动开发服务器时自动打开浏览器
open: true,
// 设置开发服务器的主机名
host: 'localhost',
// 设置开发服务器的端口号
port: 8001,
// 是否启用 https
https: false,
// 是否启用热更新HMR
hotOnly: false,
// 配置代理,解决跨域问题
proxy: {
'/api': {
target: 'http://localhost:8000', // 目标 API 服务器地址
changeOrigin: true // 是否改变请求头中的 origin
target: 'http://localhost:8000',
changeOrigin: true
}
}
},
// 配置多页面构建(如果项目需要多个入口)
pages: {
index: {
// 入口文件
entry: 'src/main.js',
// 模板文件
template: 'public/index.html',
// 构建后的文件名
filename: 'index.html'
}
},
// 配置 webpack
chainWebpack (config) {
// 处理 SVG 文件的 loader 配置,设置 svg-sprite-loader
// set svg-sprite-loader
config.module
.rule('svg') // 定义默认的 svg 规则
.exclude.add(resolve('src/icons')) // 排除 'src/icons' 文件夹
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
// 创建新的规则来处理 src/icons 目录下的 SVG 文件
config.module
.rule('icons')
.test(/\.svg$/) // 匹配 .svg 文件
.include.add(resolve('src/icons')) // 只处理 src/icons 目录下的 SVG 文件
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader') // 使用 svg-sprite-loader 加载器
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]' // 设置 symbolId 格式为 'icon-[name]',生成 svg 图标时使用
symbolId: 'icon-[name]'
})
.end()
}

@ -1,199 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 定义 Maven 构建的基本信息 -->
<modelVersion>4.0.0</modelVersion>
<!-- 项目基础信息 -->
<groupId>com.mindskip</groupId> <!-- 项目所属的组织ID -->
<artifactId>xzs</artifactId> <!-- 项目的唯一标识 -->
<version>3.8.0</version> <!-- 项目的版本号 -->
<packaging>jar</packaging> <!-- 打包类型JAR 文件 -->
<name>xzs</name> <!-- 项目的名称 -->
<url>https://www.mindskip.net/xzs.html</url> <!-- 项目的主页链接 -->
<description>学之思开源考试系统 - mysql版支持多种题型选择题、多选题、判断题、填空题、解答题以及数学公式包含PC端、小程序端扩展性强部署方便(集成部署、前后端分离部署、docker部署)、界面设计友好、代码结构清晰</description> <!-- 项目的描述 -->
<groupId>com.mindskip</groupId>
<artifactId>xzs</artifactId>
<version>3.8.0</version>
<packaging>jar</packaging>
<name>xzs</name>
<url>https://www.mindskip.net/xzs.html</url>
<description>学之思开源考试系统 - mysql版支持多种题型选择题、多选题、判断题、填空题、解答题以及数学公式包含PC端、小程序端扩展性强部署方便(集成部署、前后端分离部署、docker部署)、界面设计友好、代码结构清晰</description>
<!-- 设置项目的属性 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 设置源代码编码为 UTF-8 -->
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- 设置报告输出编码为 UTF-8 -->
<maven.compiler.source>1.8</maven.compiler.source> <!-- 设置 Java 编译版本为 1.8 -->
<maven.compiler.target>1.8</maven.compiler.target> <!-- 设置 Java 编译目标版本为 1.8 -->
<java.version>1.8</java.version> <!-- 设置 Java 版本为 1.8 -->
<mysql.version>8.0.17</mysql.version> <!-- 设置 MySQL 版本 -->
<spring.boot.version>2.1.6.RELEASE</spring.boot.version> <!-- 设置 Spring Boot 版本 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<mysql.version>8.0.17</mysql.version>
<spring.boot.version>2.1.6.RELEASE</spring.boot.version>
</properties>
<!-- 设置父级 POM 配置,继承自 Spring Boot 官方 POM -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- 父级 POM 不需要相对路径 -->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 问题管理 -->
<issueManagement>
<system>Gitee Issue</system> <!-- 使用 Gitee Issue 来管理问题 -->
<url>https://gitee.com/mindskip/xzs-mysql/issues</url> <!-- 项目问题管理链接 -->
<system>Gitee Issue</system>
<url>https://gitee.com/mindskip/xzs-mysql/issues</url>
</issueManagement>
<!-- 许可证信息 -->
<licenses>
<license>
<name>GNU Affero General Public License v3.0</name> <!-- 项目使用的许可证类型 -->
<url>https://www.gnu.org/licenses/agpl-3.0.txt</url> <!-- 许可证链接 -->
<name>GNU Affero General Public License v3.0</name>
<url>https://www.gnu.org/licenses/agpl-3.0.txt</url>
</license>
</licenses>
<!-- 开发者信息 -->
<developers>
<developer>
<organization>武汉思维跳跃科技有限公司</organization> <!-- 开发组织 -->
<organizationUrl>https://www.mindskip.net</organizationUrl> <!-- 开发组织网站 -->
<email>mindskip@qq.com</email> <!-- 开发者邮箱 -->
<organization>武汉思维跳跃科技有限公司</organization>
<organizationUrl>https://www.mindskip.net</organizationUrl>
<email>mindskip@qq.com</email>
</developer>
</developers>
<!-- SCM 配置,定义项目的版本控制信息 -->
<scm>
<connection>scm:git@gitee.com:mindskip/xzs-mysql.git</connection> <!-- Git 连接 -->
<developerConnection>scm:git@gitee.com:mindskip/xzs-mysql.git</developerConnection> <!-- 开发者 Git 连接 -->
<url>git@gitee.com:mindskip/xzs-mysql.git</url> <!-- 项目的 Git 网址 -->
<connection>scm:git@gitee.com:mindskip/xzs-mysql.git</connection>
<developerConnection>scm:git@gitee.com:mindskip/xzs-mysql.git</developerConnection>
<url>git@gitee.com:mindskip/xzs-mysql.git</url>
</scm>
<!-- 配置 Maven 仓库 -->
<repositories>
<repository>
<id>aliyun-releases</id> <!-- 仓库 ID -->
<url>https://maven.aliyun.com/repository/public</url> <!-- 阿里云公共 Maven 仓库地址 -->
<id>aliyun-releases</id>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
<!-- 项目所需的依赖项 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <!-- Spring Boot 安全框架 -->
<version>${spring.boot.version}</version> <!-- 使用配置的 Spring Boot 版本 -->
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId> <!-- Spring Boot 验证框架 -->
<version>${spring.boot.version}</version> <!-- 使用配置的 Spring Boot 版本 -->
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <!-- Spring Boot Web 框架 -->
<version>${spring.boot.version}</version> <!-- 使用配置的 Spring Boot 版本 -->
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId> <!-- 排除默认 Tomcat使用 Undertow -->
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId> <!-- Spring Boot Undertow 服务器支持 -->
<version>${spring.boot.version}</version> <!-- 使用配置的 Spring Boot 版本 -->
<artifactId>spring-boot-starter-undertow</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId> <!-- MyBatis Spring Boot 启动器 -->
<version>2.1.0</version> <!-- MyBatis 版本 -->
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId> <!-- PageHelper 分页插件 -->
<version>1.2.12</version> <!-- PageHelper 版本 -->
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!-- MySQL 连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version> <!-- 使用配置的 MySQL 版本 -->
<version>${mysql.version}</version>
</dependency>
<!-- Apache Commons 库 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.1</version> <!-- Apache Commons Pool 版本 -->
<version>2.6.1</version>
</dependency>
<!-- Apache HttpClient 库 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version> <!-- Apache HttpClient 版本 -->
<version>4.5.9</version>
</dependency>
<!-- ModelMapper用于对象映射 -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.3</version> <!-- ModelMapper 版本 -->
<version>2.3.3</version>
</dependency>
<!-- Apache Commons Lang 库 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version> <!-- Apache Commons Lang 版本 -->
<version>3.9</version>
</dependency>
<!-- 七牛云 SDK -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.2.25</version> <!-- 七牛云 SDK 版本 -->
<version>7.2.25</version>
</dependency>
<!-- Spring Boot 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version> <!-- 使用配置的 Spring Boot 版本 -->
<scope>test</scope> <!-- 仅用于测试 -->
<version>${spring.boot.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring Security 测试依赖 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope> <!-- 仅用于测试 -->
<scope>test</scope>
</dependency>
<!-- 热加载调试工具 -->
<!-- hot debug -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>${spring.boot.version}</version> <!-- 使用配置的 Spring Boot 版本 -->
<scope>provided</scope> <!-- 仅在开发环境提供 -->
<optional>true</optional> <!-- 可选依赖 -->
<version>${spring.boot.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 项目构建配置 -->
<build>
<plugins>
<!-- Spring Boot Maven 插件,用于构建 Spring Boot 项目 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version> <!-- 使用配置的 Spring Boot 版本 -->
<version>${spring.boot.version}</version>
<configuration>
<fork>true</fork> <!-- 启用进程分叉 -->
<fork>true</fork>
</configuration>
</plugin>
<!-- Maven Surefire 插件,用于跳过测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests> <!-- 跳过测试 -->
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>

@ -7,50 +7,39 @@ import org.springframework.stereotype.Component;
/**
* @version 3.5.0
* @description: 访 Spring Bean
* ApplicationContextAware ApplicationContext
* Spring Bean
*
* @description: The type Application context provider.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
@Component // 将该类声明为 Spring 容器中的一个组件,使其被自动扫描和注册。
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
// 静态变量,保存 Spring 容器的上下文对象。
private static ApplicationContext context;
// 私有构造方法,防止外部直接实例化该类。
private ApplicationContextProvider() {
}
/**
* Spring ApplicationContext
*
* @param applicationContext Spring
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
/**
* Spring Bean
* Gets bean.
*
* @param <T>
* @param aClass Bean
* @return Bean
* @param <T> the type parameter
* @param aClass the a class
* @return the bean
*/
public static <T> T getBean(Class<T> aClass) {
return context.getBean(aClass);
}
/**
* Bean Spring Bean
* Gets bean.
*
* @param <T>
* @param name Bean
* @return Bean
* @param <T> the type parameter
* @param name the name
* @return the bean
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {

@ -2,32 +2,27 @@ package com.mindskip.xzs.configuration.property;
/**
* @version 3.5.0
* @description: Cookie Cookie
* Cookie
* Cookie "xzs" Cookie
* @description: The type Cookie config.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
public class CookieConfig {
/**
* Cookie
* "xzs" Cookie 使
* Gets name.
*
* @return Cookie
* @return the name
*/
public static String getName() {
return "xzs";
}
/**
* Cookie
* 30 30 * 24 * 60 * 60
* Cookie
* Gets interval.
*
* @return Cookie
* @return the interval
*/
public static Integer getInterval() {
return 30 * 24 * 60 * 60;
}// 30天的秒数
}
}

@ -2,50 +2,46 @@ package com.mindskip.xzs.configuration.property;
/**
* @version 3.5.0
* @description:
* `publicKey` `privateKey`
*
* @description: The type Password key config.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
public class PasswordKeyConfig {
// 用于存储公钥
private String publicKey;
// 用于存储私钥
private String privateKey;
/**
*
* Gets public key.
*
* @return
* @return the public key
*/
public String getPublicKey() {
return publicKey;
}
/**
*
* Sets public key.
*
* @param publicKey
* @param publicKey the public key
*/
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
/**
*
* Gets private key.
*
* @return
* @return the private key
*/
public String getPrivateKey() {
return privateKey;
}
/**
*
* Sets private key.
*
* @param privateKey
* @param privateKey the private key
*/
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;

@ -1,5 +1,6 @@
package com.mindskip.xzs.configuration.property;
import java.time.Duration;
import java.util.List;
@ -8,83 +9,81 @@ import java.util.List;
* @description: The type Qn config.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Qn ()
*/
public class QnConfig {
private String url; // 七牛云服务的URL
private String bucket; // 七牛云的存储桶名称
private String accessKey; // 七牛云的AccessKey用于身份验证
private String secretKey; // 七牛云的SecretKey用于身份验证
private String url;
private String bucket;
private String accessKey;
private String secretKey;
/**
* Qn URL
* Gets url.
*
* @return URL
* @return the url
*/
public String getUrl() {
return url;
}
/**
* Qn URL
* Sets url.
*
* @param url URL
* @param url the url
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Qn
* Gets bucket.
*
* @return
* @return the bucket
*/
public String getBucket() {
return bucket;
}
/**
* Qn
* Sets bucket.
*
* @param bucket
* @param bucket the bucket
*/
public void setBucket(String bucket) {
this.bucket = bucket;
}
/**
* Qn AccessKey
* Gets access key.
*
* @return AccessKey
* @return the access key
*/
public String getAccessKey() {
return accessKey;
}
/**
* Qn AccessKey
* Sets access key.
*
* @param accessKey AccessKey
* @param accessKey the access key
*/
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
/**
* Qn SecretKey
* Gets secret key.
*
* @return SecretKey
* @return the secret key
*/
public String getSecretKey() {
return secretKey;
}
/**
* Qn SecretKey
* Sets secret key.
*
* @param secretKey SecretKey
* @param secretKey the secret key
*/
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;

@ -4,90 +4,88 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* @version 3.5.0
* @description: The type System config.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* URL
* @ConfigurationProperties application.properties application.yml
*/
@ConfigurationProperties(prefix = "system")
public class SystemConfig {
private PasswordKeyConfig pwdKey; // 密码加密的相关配置
private List<String> securityIgnoreUrls; // 安全检查时需要忽略的 URL 列表
private WxConfig wx; // 微信相关的配置
private QnConfig qn; // 七牛云存储配置
private PasswordKeyConfig pwdKey;
private List<String> securityIgnoreUrls;
private WxConfig wx;
private QnConfig qn;
/**
*
* Gets pwd key.
*
* @return
* @return the pwd key
*/
public PasswordKeyConfig getPwdKey() {
return pwdKey;
}
/**
*
* Sets pwd key.
*
* @param pwdKey
* @param pwdKey the pwd key
*/
public void setPwdKey(PasswordKeyConfig pwdKey) {
this.pwdKey = pwdKey;
}
/**
* URL
* Gets security ignore urls.
*
* @return URL
* @return the security ignore urls
*/
public List<String> getSecurityIgnoreUrls() {
return securityIgnoreUrls;
}
/**
* URL
* Sets security ignore urls.
*
* @param securityIgnoreUrls URL
* @param securityIgnoreUrls the security ignore urls
*/
public void setSecurityIgnoreUrls(List<String> securityIgnoreUrls) {
this.securityIgnoreUrls = securityIgnoreUrls;
}
/**
*
* Gets wx.
*
* @return
* @return the wx
*/
public WxConfig getWx() {
return wx;
}
/**
*
* Sets wx.
*
* @param wx
* @param wx the wx
*/
public void setWx(WxConfig wx) {
this.wx = wx;
}
/**
*
* Gets qn.
*
* @return
* @return the qn
*/
public QnConfig getQn() {
return qn;
}
/**
*
* Sets qn.
*
* @param qn
* @param qn the qn
*/
public void setQn(QnConfig qn) {
this.qn = qn;

@ -19,66 +19,55 @@ import java.util.stream.Collectors;
* @description: The type Exception handle.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
*
* @ControllerAdvice
*/
@ControllerAdvice
public class ExceptionHandle {
// 日志记录器,用于记录异常信息
private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
/**
*
* Handler rest response.
*
* @param e
* @return RestResponse
* @param e the e
* @return the rest response
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public RestResponse handler(Exception e) {
// 记录异常信息到日志
logger.error(e.getMessage(), e);
// 返回内部错误的统一响应
return new RestResponse<>(SystemCode.InnerError.getCode(), SystemCode.InnerError.getMessage());
}
/**
* (MethodArgumentNotValidException)
*
* Handler rest response.
*
* @param e MethodArgumentNotValidException
* @return RestResponse
* @param e the e
* @return the rest response
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public RestResponse handler(MethodArgumentNotValidException e) {
// 提取并格式化所有字段的验证错误信息
String errorMsg = e.getBindingResult().getAllErrors().stream().map(file -> {
FieldError fieldError = (FieldError) file;
return ErrorUtil.parameterErrorFormat(fieldError.getField(), fieldError.getDefaultMessage());
}).collect(Collectors.joining());
// 返回参数验证错误的统一响应
return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg);
}
/**
* (BindException)
*
* Handler rest response.
*
* @param e BindException
* @return RestResponse
* @param e the e
* @return the rest response
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public RestResponse handler(BindException e) {
// 提取并格式化所有字段的绑定错误信息
String errorMsg = e.getBindingResult().getAllErrors().stream().map(file -> {
FieldError fieldError = (FieldError) file;
return ErrorUtil.parameterErrorFormat(fieldError.getField(), fieldError.getDefaultMessage());
}).collect(Collectors.joining());
// 返回参数绑定错误的统一响应
return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg);
}
}

@ -8,26 +8,24 @@ import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
/**
* @version 3.5.0
* @description: The type Web mvc configuration.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring MVC
* @Configuration Web
*/
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
private final TokenHandlerInterceptor tokenHandlerInterceptor; // 用于处理 Token 验证的拦截器
private final SystemConfig systemConfig; // 系统配置,包含微信配置等
private final TokenHandlerInterceptor tokenHandlerInterceptor;
private final SystemConfig systemConfig;
/**
*
* Instantiates a new Web mvc configuration.
*
* @param tokenHandlerInterceptor Token
* @param systemConfig
* @param tokenHandlerInterceptor the token handler interceptor
* @param systemConfig the system config
*/
@Autowired
public WebMvcConfiguration(TokenHandlerInterceptor tokenHandlerInterceptor, SystemConfig systemConfig) {
@ -35,63 +33,38 @@ public class WebMvcConfiguration extends WebMvcConfigurationSupport {
this.systemConfig = systemConfig;
}
/**
*
*
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/", "/student/index.html"); // 将根路径重定向到 /student/index.html
registry.addRedirectViewController("/student", "/student/index.html"); // 将 /student 重定向到 /student/index.html
registry.addRedirectViewController("/admin", "/admin/index.html"); // 将 /admin 重定向到 /admin/index.html
registry.addRedirectViewController("/", "/student/index.html");
registry.addRedirectViewController("/student", "/student/index.html");
registry.addRedirectViewController("/admin", "/admin/index.html");
}
/**
* 访 classpath:/static/
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**") // 匹配所有路径
.addResourceLocations("classpath:/static/") // 指定静态资源的存储路径
.setCachePeriod(31556926); // 设置缓存过期时间
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(31556926);
}
/**
*
* 使 TokenHandlerInterceptor `/api/wx/**`
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 获取系统配置中的微信相关配置,提取需要忽略的 URL 列表
List<String> securityIgnoreUrls = systemConfig.getWx().getSecurityIgnoreUrls();
String[] ignores = new String[securityIgnoreUrls.size()];
// 配置 TokenHandlerInterceptor 处理 `/api/wx/**` 路径,并排除配置中的忽略 URL
registry.addInterceptor(tokenHandlerInterceptor)
.addPathPatterns("/api/wx/**") // 拦截 `/api/wx/**` 路径
.excludePathPatterns(securityIgnoreUrls.toArray(ignores)); // 排除忽略的路径
super.addInterceptors(registry); // 保留父类的拦截器配置
.addPathPatterns("/api/wx/**")
.excludePathPatterns(securityIgnoreUrls.toArray(ignores));
super.addInterceptors(registry);
}
/**
*
*
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 匹配所有路径
.allowCredentials(true) // 允许发送凭证(如 Cookies
.allowedMethods("*") // 允许所有 HTTP 方法
.allowedOrigins("*") // 允许所有源的跨域请求
.allowedHeaders("*"); // 允许所有请求头
super.addCorsMappings(registry); // 保留父类的跨域配置
registry.addMapping("/**")
.allowCredentials(true)
.allowedMethods("*")
.allowedOrigins("*")
.allowedHeaders("*");
super.addCorsMappings(registry);
}
}

@ -5,67 +5,64 @@ package com.mindskip.xzs.configuration.spring.security;
* @description: The type Authentication bean.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
*
* DTO
*/
public class AuthenticationBean {
private String userName; // 用户名
private String password; // 密码
private boolean remember; // 是否选择记住我
private String userName;
private String password;
private boolean remember;
/**
*
* Gets user name.
*
* @return
* @return the user name
*/
public String getUserName() {
return userName;
}
/**
*
* Sets user name.
*
* @param userName
* @param userName the user name
*/
public void setUserName(String userName) {
this.userName = userName;
}
/**
*
* Gets password.
*
* @return
* @return the password
*/
public String getPassword() {
return password;
}
/**
*
* Sets password.
*
* @param password
* @param password the password
*/
public void setPassword(String password) {
this.password = password;
}
/**
*
* Is remember boolean.
*
* @return true false
* @return the boolean
*/
public boolean isRemember() {
return remember;
}
/**
*
* Sets remember.
*
* @param remember
* @param remember the remember
*/
public void setRemember(boolean remember) {
this.remember = remember;
}
}

@ -15,34 +15,21 @@ import java.io.IOException;
* @description:
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring Security
* 访
*/
@Component
public final class LoginAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
/**
* URL
* Instantiates a new Login authentication entry point.
*/
public LoginAuthenticationEntryPoint() {
super("/api/user/login"); // 设置未认证时跳转的登录页面路径
super("/api/user/login");
}
/**
*
* 访
*
* @param request
* @param response
* @param authException
* @throws IOException I/O
* @throws ServletException Servlet
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
RestUtil.response(response, SystemCode.UNAUTHORIZED); // 返回未授权错误码
RestUtil.response(response, SystemCode.UNAUTHORIZED);
}
}

@ -16,25 +16,11 @@ import java.io.IOException;
* @description:
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring Security `AccessDeniedHandler` 访
* 访
*/
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
/**
* 访
* 访
*
* @param httpServletRequest
* @param httpServletResponse
* @param e
* @throws IOException I/O
* @throws ServletException Servlet
*/
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
RestUtil.response(httpServletResponse, SystemCode.AccessDenied); // 返回权限拒绝错误码
RestUtil.response(httpServletResponse, SystemCode.AccessDenied);
}
}

@ -16,25 +16,12 @@ import java.io.IOException;
* @description:
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
*
*/
@Component
public class RestAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
/**
*
*
*
*
* @param request
* @param response
* @param exception
* @throws IOException I/O
* @throws ServletException Servlet
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
RestUtil.response(response, SystemCode.AuthError.getCode(), exception.getMessage()); // 返回认证错误码和错误消息
RestUtil.response(response, SystemCode.AuthError.getCode(), exception.getMessage());
}
}

@ -1,5 +1,6 @@
package com.mindskip.xzs.configuration.spring.security;
import com.mindskip.xzs.context.WebContext;
import com.mindskip.xzs.domain.enums.RoleEnum;
import com.mindskip.xzs.domain.enums.UserStatusEnum;
@ -26,10 +27,6 @@ import java.util.ArrayList;
* @description:
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring Security `AuthenticationProvider`
*
*
*/
@Component
public class RestAuthenticationProvider implements AuthenticationProvider {
@ -39,11 +36,11 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
private final WebContext webContext;
/**
* `RestAuthenticationProvider`
* Instantiates a new Rest authentication provider.
*
* @param authenticationService
* @param userService
* @param webContext web
* @param authenticationService the authentication service
* @param userService the user service
* @param webContext the web context
*/
@Autowired
public RestAuthenticationProvider(AuthenticationService authenticationService, UserService userService, WebContext webContext) {
@ -52,54 +49,35 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
this.webContext = webContext;
}
/**
*
*
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName(); // 获取用户名
String password = (String) authentication.getCredentials(); // 获取密码
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// 根据用户名查询用户
com.mindskip.xzs.domain.User user = userService.getUserByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("用户名或密码错误"); // 用户未找到,抛出异常
throw new UsernameNotFoundException("用户名或密码错误");
}
// 验证用户名和密码是否正确
boolean result = authenticationService.authUser(user, username, password);
if (!result) {
throw new BadCredentialsException("用户名或密码错误"); // 密码错误,抛出异常
throw new BadCredentialsException("用户名或密码错误");
}
// 检查用户是否被禁用
UserStatusEnum userStatusEnum = UserStatusEnum.fromCode(user.getStatus());
if (UserStatusEnum.Disable == userStatusEnum) {
throw new LockedException("用户被禁用"); // 用户被禁用,抛出异常
throw new LockedException("用户被禁用");
}
// 为认证通过的用户分配角色
ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(RoleEnum.fromCode(user.getRole()).getRoleName()));
// 创建一个经过认证的用户对象
User authUser = new User(user.getUserName(), user.getPassword(), grantedAuthorities);
return new UsernamePasswordAuthenticationToken(authUser, authUser.getPassword(), authUser.getAuthorities());
}
/**
*
*
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return true; // 支持所有类型的认证
return true;
}
}

@ -20,12 +20,9 @@ import java.util.Date;
/**
* @version 3.5.0
* @description:
* @description:
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring Security
*
*/
@Component
public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
@ -34,10 +31,10 @@ public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuc
private final UserService userService;
/**
*
* Instantiates a new Rest authentication success handler.
*
* @param eventPublisher
* @param userService
* @param eventPublisher the event publisher
* @param userService the user service
*/
@Autowired
public RestAuthenticationSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService) {
@ -45,37 +42,22 @@ public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuc
this.userService = userService;
}
/**
*
*
* @param request HTTP
* @param response HTTP
* @param authentication
* @throws IOException I/O
* @throws ServletException Servlet
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Object object = authentication.getPrincipal(); // 获取认证用户信息
Object object = authentication.getPrincipal();
if (null != object) {
User springUser = (User) object; // 转换为 Spring Security 的 User 对象
com.mindskip.xzs.domain.User user = userService.getUserByUserName(springUser.getUsername()); // 获取数据库中的用户信息
User springUser = (User) object;
com.mindskip.xzs.domain.User user = userService.getUserByUserName(springUser.getUsername());
if (null != user) {
// 记录用户登录事件
UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
userEventLog.setContent(user.getUserName() + " 登录了学之思开源考试系统");
eventPublisher.publishEvent(new UserEvent(userEventLog)); // 发布用户登录事件
// 创建一个新的用户对象,包含用户名和头像路径
eventPublisher.publishEvent(new UserEvent(userEventLog));
com.mindskip.xzs.domain.User newUser = new com.mindskip.xzs.domain.User();
newUser.setUserName(user.getUserName());
newUser.setImagePath(user.getImagePath());
// 返回登录成功响应,包含用户基本信息
RestUtil.response(response, SystemCode.OK.getCode(), SystemCode.OK.getMessage(), newUser);
}
} else {
// 如果没有认证信息,返回未授权的响应
RestUtil.response(response, SystemCode.UNAUTHORIZED.getCode(), SystemCode.UNAUTHORIZED.getMessage());
}
}

@ -16,13 +16,9 @@ import java.util.ArrayList;
/**
* @version 3.5.0
* @description: , ...
* @description: ,...
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring Security UserDetailsService
*
* Spring Security
*/
@Component
public class RestDetailsServiceImpl implements UserDetailsService {
@ -30,38 +26,27 @@ public class RestDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
/**
*
* Instantiates a new Rest details service.
*
* @param userService
* @param userService the user service
*/
@Autowired
public RestDetailsServiceImpl(UserService userService) {
this.userService = userService;
}
/**
*
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名从数据库获取用户信息
com.mindskip.xzs.domain.User user = userService.getUserByUserName(username);
if (user == null) {
// 如果用户不存在,抛出异常
throw new UsernameNotFoundException("Username not found.");
throw new UsernameNotFoundException("Username not found.");
}
// 获取用户角色并构建授予的权限SimpleGrantedAuthority
ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(RoleEnum.fromCode(user.getRole()).getRoleName()));
// 返回包含用户名、密码和角色权限的 User 对象
return new User(user.getUserName(), user.getPassword(), grantedAuthorities);
}
}

@ -1,6 +1,7 @@
package com.mindskip.xzs.configuration.spring.security;
import com.mindskip.xzs.configuration.property.CookieConfig;
import com.mindskip.xzs.utility.JsonUtil;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -22,71 +23,44 @@ import java.io.InputStream;
* @description:
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring Security
* JSON UsernamePasswordAuthenticationToken
*
*/
public class RestLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final org.slf4j.Logger logger = LoggerFactory.getLogger(RestLoginAuthenticationFilter.class);
/**
*
*
* @param `/api/user/login` POST
* Instantiates a new Rest login authentication filter.
*/
public RestLoginAuthenticationFilter() {
super(new AntPathRequestMatcher("/api/user/login", "POST"));
}
/**
* JSON
*
* @param request HTTP
* @param response HTTP
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest;
try (InputStream is = request.getInputStream()) {
// 从请求输入流中读取 JSON 数据并转换为 AuthenticationBean 对象
AuthenticationBean authenticationBean = JsonUtil.toJsonObject(is, AuthenticationBean.class);
// 将“记住我”标志存储到请求属性中
request.setAttribute(TokenBasedRememberMeServices.DEFAULT_PARAMETER, authenticationBean.isRemember());
// 创建认证对象
authRequest = new UsernamePasswordAuthenticationToken(authenticationBean.getUserName(), authenticationBean.getPassword());
} catch (IOException e) {
// 读取请求体时出错,记录错误日志并创建空的认证对象
logger.error(e.getMessage(), e);
authRequest = new UsernamePasswordAuthenticationToken("", "");
}
// 设置认证对象的详细信息
setDetails(request, authRequest);
// 使用认证管理器进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
*
* Sets user details service.
*
* @param userDetailsService
* @param userDetailsService the user details service
*/
void setUserDetailsService(UserDetailsService userDetailsService) {
// 创建自定义的记住我服务
RestTokenBasedRememberMeServices tokenBasedRememberMeServices = new RestTokenBasedRememberMeServices(CookieConfig.getName(), userDetailsService);
tokenBasedRememberMeServices.setTokenValiditySeconds(CookieConfig.getInterval());
// 设置记住我服务
setRememberMeServices(tokenBasedRememberMeServices);
}
/**
* IP
*
* @param request HTTP
* @param authRequest
*/
private void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}

@ -21,8 +21,6 @@ import java.util.Date;
* @description:
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
*
*/
@Component
public class RestLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
@ -31,10 +29,10 @@ public class RestLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
private final UserService userService;
/**
*
* Instantiates a new Rest logout success handler.
*
* @param eventPublisher
* @param userService
* @param eventPublisher the event publisher
* @param userService the user service
*/
@Autowired
public RestLogoutSuccessHandler(ApplicationEventPublisher eventPublisher, UserService userService) {
@ -42,31 +40,15 @@ public class RestLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
this.userService = userService;
}
/**
*
*
*
* @param request HTTP
* @param response HTTP
* @param authentication
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// 获取认证对象中的用户信息
org.springframework.security.core.userdetails.User springUser = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
if (null != springUser) {
// 查找数据库中的用户信息
User user = userService.getUserByUserName(springUser.getUsername());
// 创建用户登出事件日志
UserEventLog userEventLog = new UserEventLog(user.getId(), user.getUserName(), user.getRealName(), new Date());
userEventLog.setContent(user.getUserName() + " 登出了学之思开源考试系统");
// 发布用户登出事件
eventPublisher.publishEvent(new UserEvent(userEventLog));
}
// 返回登出成功的响应
RestUtil.response(response, SystemCode.OK);
}
}

@ -11,33 +11,21 @@ import javax.servlet.http.HttpServletRequest;
* @description: Cookie
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* Spring Security TokenBasedRememberMeServices "记住我"
* 使 cookie 访
*/
public class RestTokenBasedRememberMeServices extends TokenBasedRememberMeServices {
/**
*
* Instantiates a new Rest token based remember me services.
*
* @param key
* @param userDetailsService
* @param key the key
* @param userDetailsService the user details service
*/
public RestTokenBasedRememberMeServices(String key, UserDetailsService userDetailsService) {
super(key, userDetailsService);
}
/**
*
*
*
* @param request HTTP
* @param parameter "remember-me"
* @return "记住我"
*/
@Override
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
// 从请求的属性中获取 rememberMe 参数,判断是否需要记住我功能
return (boolean) request.getAttribute(DEFAULT_PARAMETER);
}
}

@ -15,59 +15,48 @@ import java.io.IOException;
* @description: The type Rest util.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*
* JSON HttpServletResponse
*/
public class RestUtil {
// 用于日志记录的日志器
private static final Logger logger = LoggerFactory.getLogger(RestUtil.class);
/**
* 使
* Response.
*
* @param response HTTP
* @param systemCode
* @param response the response
* @param systemCode the system code
*/
public static void response(HttpServletResponse response, SystemCode systemCode) {
// 调用第二个方法,传递系统码和系统消息
response(response, systemCode.getCode(), systemCode.getMessage());
}
/**
*
* Response.
*
* @param response HTTP
* @param systemCode
* @param msg
* @param response the response
* @param systemCode the system code
* @param msg the msg
*/
public static void response(HttpServletResponse response, int systemCode, String msg) {
// 调用第三个方法,传递系统码、消息,且不传内容
response(response, systemCode, msg, null);
}
/**
*
* Response.
*
* @param response HTTP
* @param systemCode
* @param msg
* @param content
* @param response the response
* @param systemCode the system code
* @param msg the msg
* @param content the content
*/
public static void response(HttpServletResponse response, int systemCode, String msg, Object content) {
try {
// 创建一个 RestResponse 对象,用于封装响应数据
RestResponse res = new RestResponse<>(systemCode, msg, content);
// 将 RestResponse 对象转换为 JSON 字符串
String resStr = JsonUtil.toJsonStr(res);
// 设置响应的内容类型为 JSON
response.setContentType("application/json;charset=utf-8");
// 将 JSON 响应写入到响应流
response.getWriter().write(resStr);
} catch (IOException e) {
// 捕获并记录异常
logger.error(e.getMessage(), e);
}
}

@ -17,27 +17,43 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Collections;
import java.util.List;
/**
* @version 3.5.0
* @description: The type Security configurer.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
@Configuration
@EnableWebSecurity
public class SecurityConfigurer {
/**
* Web
* The type Form login web security configurer adapter.
*/
@Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
private final SystemConfig systemConfig; // 系统配置
private final LoginAuthenticationEntryPoint restAuthenticationEntryPoint; // 认证失败入口
private final RestAuthenticationProvider restAuthenticationProvider; // 自定义认证提供者
private final RestDetailsServiceImpl formDetailsService; // 自定义用户详情服务
private final RestAuthenticationSuccessHandler restAuthenticationSuccessHandler; // 登录成功处理器
private final RestAuthenticationFailureHandler restAuthenticationFailureHandler; // 登录失败处理器
private final RestLogoutSuccessHandler restLogoutSuccessHandler; // 登出成功处理器
private final RestAccessDeniedHandler restAccessDeniedHandler; // 权限不足处理器
private final SystemConfig systemConfig;
private final LoginAuthenticationEntryPoint restAuthenticationEntryPoint;
private final RestAuthenticationProvider restAuthenticationProvider;
private final RestDetailsServiceImpl formDetailsService;
private final RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;
private final RestAuthenticationFailureHandler restAuthenticationFailureHandler;
private final RestLogoutSuccessHandler restLogoutSuccessHandler;
private final RestAccessDeniedHandler restAccessDeniedHandler;
/**
*
* Instantiates a new Form login web security configurer adapter.
*
* @param systemConfig the system config
* @param restAuthenticationEntryPoint the rest authentication entry point
* @param restAuthenticationProvider the rest authentication provider
* @param formDetailsService the form details service
* @param restAuthenticationSuccessHandler the rest authentication success handler
* @param restAuthenticationFailureHandler the rest authentication failure handler
* @param restLogoutSuccessHandler the rest logout success handler
* @param restAccessDeniedHandler the rest access denied handler
*/
@Autowired
public FormLoginWebSecurityConfigurerAdapter(SystemConfig systemConfig, LoginAuthenticationEntryPoint restAuthenticationEntryPoint, RestAuthenticationProvider restAuthenticationProvider, RestDetailsServiceImpl formDetailsService, RestAuthenticationSuccessHandler restAuthenticationSuccessHandler, RestAuthenticationFailureHandler restAuthenticationFailureHandler, RestLogoutSuccessHandler restLogoutSuccessHandler, RestAccessDeniedHandler restAccessDeniedHandler) {
@ -52,59 +68,69 @@ public class SecurityConfigurer {
}
/**
* HTTP
* @param http http
* @throws Exception exception
* csrf is the from submit get method
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable(); // 禁用框架选项防止出现X-Frame-Options错误
http.headers().frameOptions().disable();
List<String> securityIgnoreUrls = systemConfig.getSecurityIgnoreUrls(); // 获取需要忽略安全检查的URL列表
List<String> securityIgnoreUrls = systemConfig.getSecurityIgnoreUrls();
String[] ignores = new String[securityIgnoreUrls.size()];
http
.addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 添加自定义认证过滤器
.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint) // 设置认证失败时的处理方式
.and().authenticationProvider(restAuthenticationProvider) // 配置自定义认证提供者
.addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint)
.and().authenticationProvider(restAuthenticationProvider)
.authorizeRequests()
.antMatchers(securityIgnoreUrls.toArray(ignores)).permitAll() // 配置忽略认证的URL
.antMatchers("/api/admin/**").hasRole(RoleEnum.ADMIN.getName()) // 配置只有ADMIN角色的用户才能访问
.antMatchers("/api/student/**").hasRole(RoleEnum.STUDENT.getName()) // 配置只有STUDENT角色的用户才能访问
.anyRequest().permitAll() // 其他请求允许访问
.and().exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) // 配置权限不足时的处理方式
.and().formLogin().successHandler(restAuthenticationSuccessHandler).failureHandler(restAuthenticationFailureHandler) // 配置表单登录成功和失败的处理器
.and().logout().logoutUrl("/api/user/logout").logoutSuccessHandler(restLogoutSuccessHandler).invalidateHttpSession(true) // 配置登出后的处理
.and().rememberMe().key(CookieConfig.getName()).tokenValiditySeconds(CookieConfig.getInterval()).userDetailsService(formDetailsService) // 配置记住我功能
.and().csrf().disable() // 禁用CSRF防护
.cors(); // 启用CORS支持
.antMatchers(securityIgnoreUrls.toArray(ignores)).permitAll()
.antMatchers("/api/admin/**").hasRole(RoleEnum.ADMIN.getName())
.antMatchers("/api/student/**").hasRole(RoleEnum.STUDENT.getName())
.anyRequest().permitAll()
.and().exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
.and().formLogin().successHandler(restAuthenticationSuccessHandler).failureHandler(restAuthenticationFailureHandler)
.and().logout().logoutUrl("/api/user/logout").logoutSuccessHandler(restLogoutSuccessHandler).invalidateHttpSession(true)
.and().rememberMe().key(CookieConfig.getName()).tokenValiditySeconds(CookieConfig.getInterval()).userDetailsService(formDetailsService)
.and().csrf().disable()
.cors();
}
/**
* CORS
* Cors configuration source cors configuration source.
*
* @return the cors configuration source
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setMaxAge(3600L); // 设置跨域请求的最大有效时间
configuration.setAllowedOrigins(Collections.singletonList("*")); // 允许所有域名进行跨域请求
configuration.setAllowedMethods(Collections.singletonList("*")); // 允许所有HTTP方法如GET、POST
configuration.setAllowCredentials(true); // 允许携带凭证
configuration.setAllowedHeaders(Collections.singletonList("*")); // 允许所有请求头
configuration.setMaxAge(3600L);
configuration.setAllowedOrigins(Collections.singletonList("*"));
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList("*"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration); // 对/api/**路径启用CORS配置
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
/**
*
* Authentication filter rest login authentication filter.
*
* @return the rest login authentication filter
* @throws Exception the exception
*/
@Bean
public RestLoginAuthenticationFilter authenticationFilter() throws Exception {
RestLoginAuthenticationFilter authenticationFilter = new RestLoginAuthenticationFilter();
authenticationFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler); // 设置成功登录的处理器
authenticationFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler); // 设置失败登录的处理器
authenticationFilter.setAuthenticationManager(authenticationManagerBean()); // 设置认证管理器
authenticationFilter.setUserDetailsService(formDetailsService); // 设置用户详情服务
authenticationFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
authenticationFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
authenticationFilter.setAuthenticationManager(authenticationManagerBean());
authenticationFilter.setUserDetailsService(formDetailsService);
return authenticationFilter;
}
}
}

@ -8,53 +8,56 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* @version 3.3.0
* @description: The enum System code.
* Copyright (C), 2020-2021,
* @date 2021/5/25 10:45
*/
@Component
public class WebContext {
private static final String USER_ATTRIBUTES = "USER_ATTRIBUTES"; // 请求上下文中存储当前用户的键
private final UserService userService; // 用户服务,用于根据用户名查询用户信息
private static final String USER_ATTRIBUTES = "USER_ATTRIBUTES";
private final UserService userService;
/**
* Instantiates a new Web context.
*
* @param userService the user service
*/
@Autowired
public WebContext(UserService userService) {
this.userService = userService; // 构造器注入 UserService
this.userService = userService;
}
/**
*
* Sets current user.
*
* @param user
* @param user the user
*/
public void setCurrentUser(User user) {
RequestContextHolder.currentRequestAttributes().setAttribute(USER_ATTRIBUTES, user, RequestAttributes.SCOPE_REQUEST);
}
/**
*
* Gets current user.
*
* @return
* @return the current user
*/
public User getCurrentUser() {
// 尝试从请求上下文中获取当前用户
User user = (User) RequestContextHolder.currentRequestAttributes().getAttribute(USER_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);
// 如果当前请求上下文中已经有用户,则直接返回
if (null != user) {
return user;
} else {
// 如果请求上下文中没有用户,尝试从 Spring Security 中获取认证用户
org.springframework.security.core.userdetails.User springUser = (org.springframework.security.core.userdetails.User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (null == springUser) {
return null; // 如果没有认证的用户,返回 null
return null;
}
// 根据 Spring Security 的用户信息获取应用系统中的 User 对象
user = userService.getUserByUserName(springUser.getUsername());
// 如果成功获取到用户信息,将其存储到请求上下文中
if (null != user) {
setCurrentUser(user);
}
return user;
}
}
}
}

@ -6,42 +6,45 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* @version 3.3.0
* @description: The enum System code.
* Copyright (C), 2020-2021,
* @date 2021/5/25 10:45
*/
@Component
public class WxContext {
private static final String USER_ATTRIBUTES = "USER_ATTRIBUTES"; // 当前用户信息存储的键
private static final String USER_TOKEN_ATTRIBUTES = "USER_TOKEN_ATTRIBUTES"; // 当前用户令牌存储的键
private static final String USER_ATTRIBUTES = "USER_ATTRIBUTES";
private static final String USER_TOKEN_ATTRIBUTES = "USER_TOKEN_ATTRIBUTES";
/**
*
* Sets context.
*
* @param user
* @param userToken
* @param user the user
* @param userToken the user token
*/
public void setContext(User user, UserToken userToken) {
// 将用户信息存储在请求的作用域中
RequestContextHolder.currentRequestAttributes().setAttribute(USER_ATTRIBUTES, user, RequestAttributes.SCOPE_REQUEST);
// 将用户令牌存储在请求的作用域中
RequestContextHolder.currentRequestAttributes().setAttribute(USER_TOKEN_ATTRIBUTES, userToken, RequestAttributes.SCOPE_REQUEST);
}
/**
*
* Gets current user.
*
* @return
* @return the current user
*/
public User getCurrentUser() {
// 从请求上下文中获取存储的用户信息
return (User) RequestContextHolder.currentRequestAttributes().getAttribute(USER_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);
}
/**
*
* Gets current user token.
*
* @return
* @return the current user token
*/
public UserToken getCurrentUserToken() {
// 从请求上下文中获取存储的用户令牌
return (UserToken) RequestContextHolder.currentRequestAttributes().getAttribute(USER_TOKEN_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST);
}
}
}

@ -1,21 +1,3 @@
package com.mindskip.xzs.controller;
import com.mindskip.xzs.base.SystemCode;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ErrorController extends BasicErrorController {
@ -24,7 +6,7 @@ public class ErrorController extends BasicErrorController {
// 构造函数,调用父类构造器初始化错误属性和配置
public ErrorController() {
super((ErrorAttributes) new DefaultErrorAttributes(), new ErrorProperties());
super(new DefaultErrorAttributes(), new ErrorProperties());
}
// 错误处理方法返回JSON格式的错误信息

@ -13,8 +13,6 @@ import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
*
*/

@ -1,12 +1,3 @@
package com.mindskip.xzs.controller.wx;
import com.mindskip.xzs.context.WxContext;
import com.mindskip.xzs.domain.User;
import com.mindskip.xzs.domain.UserToken;
import com.mindskip.xzs.utility.ModelMapperSingle;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseWXApiController {
// 定义一个静态的ModelMapper实例用于对象之间的转换
protected final static ModelMapper modelMapper = ModelMapperSingle.Instance();

@ -1,22 +1,3 @@
package com.mindskip.xzs.controller.wx.student;
import com.mindskip.xzs.base.RestResponse;
import com.mindskip.xzs.configuration.property.SystemConfig;
import com.mindskip.xzs.controller.wx.BaseWXApiController;
import com.mindskip.xzs.domain.UserToken;
import com.mindskip.xzs.domain.enums.UserStatusEnum;
import com.mindskip.xzs.service.AuthenticationService;
import com.mindskip.xzs.service.UserService;
import com.mindskip.xzs.service.UserTokenService;
import com.mindskip.xzs.utility.WxUtil;
import com.mindskip.xzs.viewmodel.wx.student.user.BindInfo;
import com.mindskip.xzs.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@Controller("WXStudentAuthController")
@RequestMapping(value = "/api/wx/student/auth")
@ResponseBody

@ -1,32 +1,3 @@
package com.mindskip.xzs.controller.wx.student;
import com.mindskip.xzs.base.RestResponse;
import com.mindskip.xzs.controller.wx.BaseWXApiController;
import com.mindskip.xzs.domain.TaskExam;
import com.mindskip.xzs.domain.TaskExamCustomerAnswer;
import com.mindskip.xzs.domain.TextContent;
import com.mindskip.xzs.domain.User;
import com.mindskip.xzs.domain.enums.ExamPaperTypeEnum;
import com.mindskip.xzs.domain.task.TaskItemAnswerObject;
import com.mindskip.xzs.domain.task.TaskItemObject;
import com.mindskip.xzs.service.ExamPaperService;
import com.mindskip.xzs.service.TaskExamCustomerAnswerService;
import com.mindskip.xzs.service.TaskExamService;
import com.mindskip.xzs.service.TextContentService;
import com.mindskip.xzs.utility.DateTimeUtil;
import com.mindskip.xzs.utility.JsonUtil;
import com.mindskip.xzs.viewmodel.student.dashboard.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Controller("WXStudentDashboardController")
@RequestMapping(value = "/api/wx/student/dashboard")
@ResponseBody

@ -1,34 +1,3 @@
package com.mindskip.xzs.controller.wx.student;
import com.mindskip.xzs.base.RestResponse;
import com.mindskip.xzs.controller.wx.BaseWXApiController;
import com.mindskip.xzs.domain.*;
import com.mindskip.xzs.domain.enums.QuestionTypeEnum;
import com.mindskip.xzs.event.CalculateExamPaperAnswerCompleteEvent;
import com.mindskip.xzs.event.UserEvent;
import com.mindskip.xzs.service.ExamPaperAnswerService;
import com.mindskip.xzs.service.ExamPaperService;
import com.mindskip.xzs.service.SubjectService;
import com.mindskip.xzs.utility.DateTimeUtil;
import com.mindskip.xzs.utility.ExamUtil;
import com.mindskip.xzs.utility.PageInfoHelper;
import com.mindskip.xzs.viewmodel.admin.exam.ExamPaperEditRequestVM;
import com.mindskip.xzs.viewmodel.student.exampaper.ExamPaperAnswerPageResponseVM;
import com.mindskip.xzs.viewmodel.student.exampaper.ExamPaperAnswerPageVM;
import com.github.pagehelper.PageInfo;
import com.mindskip.xzs.viewmodel.student.exam.ExamPaperReadVM;
import com.mindskip.xzs.viewmodel.student.exam.ExamPaperSubmitItemVM;
import com.mindskip.xzs.viewmodel.student.exam.ExamPaperSubmitVM;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
@Controller("WXStudentExamPaperAnswerController") // 控制器类,处理学生的考试答题相关请求
@RequestMapping(value = "/api/wx/student/exampaper/answer") // 定义路径指定该控制器处理的请求URL
@ResponseBody // 表示返回的对象会自动转换为JSON格式

@ -1,23 +1,3 @@
package com.mindskip.xzs.controller.wx.student;
import com.mindskip.xzs.base.RestResponse;
import com.mindskip.xzs.controller.wx.BaseWXApiController;
import com.mindskip.xzs.domain.ExamPaper;
import com.mindskip.xzs.domain.Subject;
import com.mindskip.xzs.service.ExamPaperService;
import com.mindskip.xzs.service.SubjectService;
import com.mindskip.xzs.utility.DateTimeUtil;
import com.mindskip.xzs.utility.PageInfoHelper;
import com.mindskip.xzs.viewmodel.admin.exam.ExamPaperEditRequestVM;
import com.mindskip.xzs.viewmodel.student.exam.ExamPaperPageResponseVM;
import com.mindskip.xzs.viewmodel.student.exam.ExamPaperPageVM;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Controller("WXStudentExamController") // 控制器类,处理学生考试相关请求
@RequestMapping(value = "/api/wx/student/exampaper") // 定义路径指定该控制器处理的请求URL
@ResponseBody // 表示返回的对象会自动转换为JSON格式

@ -1,34 +1,3 @@
package com.mindskip.xzs.controller.wx.student;
import com.mindskip.xzs.base.RestResponse;
import com.mindskip.xzs.controller.wx.BaseWXApiController;
import com.mindskip.xzs.domain.Message;
import com.mindskip.xzs.domain.MessageUser;
import com.mindskip.xzs.domain.User;
import com.mindskip.xzs.domain.UserEventLog;
import com.mindskip.xzs.domain.enums.RoleEnum;
import com.mindskip.xzs.domain.enums.UserStatusEnum;
import com.mindskip.xzs.event.UserEvent;
import com.mindskip.xzs.service.AuthenticationService;
import com.mindskip.xzs.service.MessageService;
import com.mindskip.xzs.service.UserEventLogService;
import com.mindskip.xzs.service.UserService;
import com.mindskip.xzs.utility.DateTimeUtil;
import com.mindskip.xzs.utility.PageInfoHelper;
import com.mindskip.xzs.viewmodel.student.user.*;
import com.github.pagehelper.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Controller("WXStudentUserController") // 控制器类,处理学生用户相关请求
@RequestMapping(value = "/api/wx/student/user") // 定义路径指定该控制器处理的请求URL
@ResponseBody // 返回结果会自动转换为JSON格式

@ -7,7 +7,7 @@ public class ExamPaper implements Serializable {
private static final long serialVersionUID = 8509645224550501395L;
private Integer id; // 试卷ID
private Integer id;
/**
*
@ -59,280 +59,130 @@ public class ExamPaper implements Serializable {
*/
private Integer frameTextContentId;
private Integer createUser; // 创建用户ID
private Integer createUser;
private Date createTime; // 创建时间
private Date createTime;
private Boolean deleted; // 是否删除(逻辑删除)
private Boolean deleted;
private Integer taskExamId; // 任务试卷ID
private Integer taskExamId;
/**
* ID
*
* @return ID
*/
public Integer getId() {
return id;
}
/**
* ID
*
* @param id ID
*/
public void setId(Integer id) {
this.id = id;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
/**
* ID
*
* @return ID
*/
public Integer getSubjectId() {
return subjectId;
}
/**
* ID
*
* @param subjectId ID
*/
public void setSubjectId(Integer subjectId) {
this.subjectId = subjectId;
}
/**
*
*
* @return
*/
public Integer getPaperType() {
return paperType;
}
/**
*
*
* @param paperType
*/
public void setPaperType(Integer paperType) {
this.paperType = paperType;
}
/**
*
*
* @return
*/
public Integer getGradeLevel() {
return gradeLevel;
}
/**
*
*
* @param gradeLevel
*/
public void setGradeLevel(Integer gradeLevel) {
this.gradeLevel = gradeLevel;
}
/**
*
*
* @return
*/
public Integer getScore() {
return score;
}
/**
*
*
* @param score
*/
public void setScore(Integer score) {
this.score = score;
}
/**
*
*
* @return
*/
public Integer getQuestionCount() {
return questionCount;
}
/**
*
*
* @param questionCount
*/
public void setQuestionCount(Integer questionCount) {
this.questionCount = questionCount;
}
/**
*
*
* @return
*/
public Integer getSuggestTime() {
return suggestTime;
}
/**
*
*
* @param suggestTime
*/
public void setSuggestTime(Integer suggestTime) {
this.suggestTime = suggestTime;
}
/**
*
*
* @return
*/
public Date getLimitStartTime() {
return limitStartTime;
}
/**
*
*
* @param limitStartTime
*/
public void setLimitStartTime(Date limitStartTime) {
this.limitStartTime = limitStartTime;
}
/**
*
*
* @return
*/
public Date getLimitEndTime() {
return limitEndTime;
}
/**
*
*
* @param limitEndTime
*/
public void setLimitEndTime(Date limitEndTime) {
this.limitEndTime = limitEndTime;
}
/**
* ID
*
* @return ID
*/
public Integer getFrameTextContentId() {
return frameTextContentId;
}
/**
* ID
*
* @param frameTextContentId ID
*/
public void setFrameTextContentId(Integer frameTextContentId) {
this.frameTextContentId = frameTextContentId;
}
/**
* ID
*
* @return ID
*/
public Integer getCreateUser() {
return createUser;
}
/**
* ID
*
* @param createUser ID
*/
public void setCreateUser(Integer createUser) {
this.createUser = createUser;
}
/**
*
*
* @return
*/
public Date getCreateTime() {
return createTime;
}
/**
*
*
* @param createTime
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
/**
*
*
* @return
*/
public Boolean getDeleted() {
return deleted;
}
/**
*
*
* @param deleted
*/
public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
/**
* ID
*
* @return ID
*/
public Integer getTaskExamId() {
return taskExamId;
}
/**
* ID
*
* @param taskExamId ID
*/
public void setTaskExamId(Integer taskExamId) {
this.taskExamId = taskExamId;
}

@ -7,9 +7,9 @@ public class ExamPaperAnswer implements Serializable {
private static final long serialVersionUID = -2143539181805283910L;
private Integer id; // 答案ID
private Integer id;
private Integer examPaperId; // 试卷ID
private Integer examPaperId;
/**
*
@ -22,7 +22,7 @@ public class ExamPaperAnswer implements Serializable {
private Integer paperType;
/**
* ID
*
*/
private Integer subjectId;
@ -32,7 +32,7 @@ public class ExamPaperAnswer implements Serializable {
private Integer systemScore;
/**
*
* ()
*/
private Integer userScore;
@ -52,17 +52,17 @@ public class ExamPaperAnswer implements Serializable {
private Integer questionCount;
/**
*
* ()
*/
private Integer doTime;
/**
* 1 2
* (1 2)
*/
private Integer status;
/**
* ID
*
*/
private Integer createUser;
@ -71,274 +71,124 @@ public class ExamPaperAnswer implements Serializable {
*/
private Date createTime;
private Integer taskExamId; // 任务试卷ID
private Integer taskExamId;
/**
* ID
*
* @return ID
*/
public Integer getId() {
return id;
}
/**
* ID
*
* @param id ID
*/
public void setId(Integer id) {
this.id = id;
}
/**
* ID
*
* @return ID
*/
public Integer getExamPaperId() {
return examPaperId;
}
/**
* ID
*
* @param examPaperId ID
*/
public void setExamPaperId(Integer examPaperId) {
this.examPaperId = examPaperId;
}
/**
*
*
* @return
*/
public String getPaperName() {
return paperName;
}
/**
*
*
* @param paperName
*/
public void setPaperName(String paperName) {
this.paperName = paperName == null ? null : paperName.trim();
}
/**
*
*
* @return
*/
public Integer getPaperType() {
return paperType;
}
/**
*
*
* @param paperType
*/
public void setPaperType(Integer paperType) {
this.paperType = paperType;
}
/**
* ID
*
* @return ID
*/
public Integer getSubjectId() {
return subjectId;
}
/**
* ID
*
* @param subjectId ID
*/
public void setSubjectId(Integer subjectId) {
this.subjectId = subjectId;
}
/**
*
*
* @return
*/
public Integer getSystemScore() {
return systemScore;
}
/**
*
*
* @param systemScore
*/
public void setSystemScore(Integer systemScore) {
this.systemScore = systemScore;
}
/**
*
*
* @return
*/
public Integer getUserScore() {
return userScore;
}
/**
*
*
* @param userScore
*/
public void setUserScore(Integer userScore) {
this.userScore = userScore;
}
/**
*
*
* @return
*/
public Integer getPaperScore() {
return paperScore;
}
/**
*
*
* @param paperScore
*/
public void setPaperScore(Integer paperScore) {
this.paperScore = paperScore;
}
/**
*
*
* @return
*/
public Integer getQuestionCorrect() {
return questionCorrect;
}
/**
*
*
* @param questionCorrect
*/
public void setQuestionCorrect(Integer questionCorrect) {
this.questionCorrect = questionCorrect;
}
/**
*
*
* @return
*/
public Integer getQuestionCount() {
return questionCount;
}
/**
*
*
* @param questionCount
*/
public void setQuestionCount(Integer questionCount) {
this.questionCount = questionCount;
}
/**
*
*
* @return
*/
public Integer getDoTime() {
return doTime;
}
/**
*
*
* @param doTime
*/
public void setDoTime(Integer doTime) {
this.doTime = doTime;
}
/**
*
*
* @return 1 2
*/
public Integer getStatus() {
return status;
}
/**
*
*
* @param status
*/
public void setStatus(Integer status) {
this.status = status;
}
/**
* ID
*
* @return ID
*/
public Integer getCreateUser() {
return createUser;
}
/**
* ID
*
* @param createUser ID
*/
public void setCreateUser(Integer createUser) {
this.createUser = createUser;
}
/**
*
*
* @return
*/
public Date getCreateTime() {
return createTime;
}
/**
*
*
* @param createTime
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
/**
* ID
*
* @return ID
*/
public Integer getTaskExamId() {
return taskExamId;
}
/**
* ID
*
* @param taskExamId ID
*/
public void setTaskExamId(Integer taskExamId) {
this.taskExamId = taskExamId;
}

@ -4,9 +4,9 @@ package com.mindskip.xzs.domain;
import java.util.List;
public class ExamPaperAnswerInfo {
private ExamPaper examPaper;
private ExamPaperAnswer examPaperAnswer;
private List<ExamPaperQuestionCustomerAnswer> examPaperQuestionCustomerAnswers;
public ExamPaper examPaper;
public ExamPaperAnswer examPaperAnswer;
public List<ExamPaperQuestionCustomerAnswer> examPaperQuestionCustomerAnswers;
public ExamPaper getExamPaper() {
return examPaper;

@ -7,85 +7,72 @@ public class ExamPaperQuestionCustomerAnswer implements Serializable {
private static final long serialVersionUID = 3389482731220342366L;
private Integer id; // 记录的唯一标识符
private Integer id;
/**
* Id
*
*/
private Integer questionId;
/**
* Id
*
*/
private Integer examPaperId;
/**
* Id
*
*/
private Integer examPaperAnswerId;
/**
*
*
*/
private Integer questionType;
/**
*
*
*/
private Integer subjectId;
/**
*
*
*/
private Integer customerScore;
/**
*
*
*/
private Integer questionScore;
/**
* ID
*
*
*/
private Integer questionTextContentId;
/**
*
*
*/
private String answer;
/**
* ID
*
*
*/
private Integer textContentId;
/**
*
*
*/
private Boolean doRight;
/**
*
* ID
*/
private Integer createUser;
private Date createTime; // 做题时间,即学生提交答案的时间
private Date createTime;
private Integer itemOrder; // 题目在试卷中的顺序
private Integer itemOrder;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}

@ -9,54 +9,45 @@ public class Message implements Serializable {
private static final long serialVersionUID = -3510265139403747341L;
private Integer id; // 消息的唯一标识符
private Integer id;
/**
*
*
*/
private String title;
/**
*
*
*/
private String content;
private Date createTime; // 消息的创建时间
private Date createTime;
/**
* ID
* ID
*/
private Integer sendUserId;
/**
*
*
*/
private String sendUserName;
/**
*
*
*/
private String sendRealName;
/**
*
*
*/
private Integer receiveUserCount;
/**
*
*
*/
private Integer readCount;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}
@ -81,7 +72,7 @@ public class Message implements Serializable {
this.content = content == null ? null : content.trim();
}
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") // 格式化时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
public Date getCreateTime() {
return createTime;
}

@ -7,48 +7,40 @@ public class MessageUser implements Serializable {
private static final long serialVersionUID = -4042932811802896498L;
private Integer id; // 消息用户关联记录的唯一标识符
private Integer id;
/**
* ID
* ID
*/
private Integer messageId;
/**
* ID
* ID
*/
private Integer receiveUserId;
/**
*
*
*/
private String receiveUserName;
/**
*
*
*/
private String receiveRealName;
/**
*
*
*/
private Boolean readed;
private Date createTime; // 记录创建时间
private Date createTime;
/**
*
*
*/
private Date readTime;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}

@ -11,75 +11,59 @@ public class Question implements Serializable {
private static final long serialVersionUID = 8826266720383164363L;
private Integer id; // 题目唯一标识符
private Integer id;
/**
*
* 1.
* 2.
* 3.
* 4.
* 5.
* 1. 2. 3. 4. 5.
*/
private Integer questionType;
/**
* ID
*
*
*/
private Integer subjectId;
/**
* ()
*
*/
private Integer score;
/**
*
*
*/
private Integer gradeLevel;
/**
*
*
*/
private Integer difficult;
/**
*
*
*/
private String correct;
/**
*
* ID
*
*/
private Integer infoTextContentId;
/**
* ID
* ID
*
*/
private Integer createUser;
/**
*
* 1.
* 1.
*/
private Integer status;
/**
*
*
*/
private Date createTime;
private Boolean deleted; // 标记是否删除,删除的题目不会被展示
// Getter 和 Setter 方法
private Boolean deleted;
public Integer getId() {
return id;
@ -177,13 +161,7 @@ public class Question implements Serializable {
this.deleted = deleted;
}
/**
*
*
*
* @param correct
* @param correctArray
*/
public void setCorrectFromVM(String correct, List<String> correctArray) {
int qType = this.getQuestionType();
if (qType == QuestionTypeEnum.MultipleChoice.getCode()) {

@ -6,35 +6,29 @@ public class Subject implements Serializable {
private static final long serialVersionUID = 8058095034457106501L;
private Integer id; // 学科唯一标识符
private Integer id;
/**
*
*
*
*/
private String name;
/**
*
* 1-12
* (1-12)
*/
private Integer level;
/**
*
*
*
*/
private String levelName;
/**
*
*
*/
private Integer itemOrder;
private Boolean deleted; // 是否删除,标记学科是否被删除
// Getter 和 Setter 方法
private Boolean deleted;
public Integer getId() {
return id;
@ -83,4 +77,4 @@ public class Subject implements Serializable {
public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
}
}

@ -7,48 +7,40 @@ public class TaskExam implements Serializable {
private static final long serialVersionUID = -7014704644631536195L;
private Integer id; // 任务的唯一标识符
private Integer id;
/**
*
*
*/
private String title;
/**
*
*
*/
private Integer gradeLevel;
/**
* JSON
*
* JSON
*/
private Integer frameTextContentId;
/**
* ID
*
*
*/
private Integer createUser;
/**
*
*
*/
private Date createTime;
private Boolean deleted; // 是否已删除,标记任务是否被删除
private Boolean deleted;
/**
*
*
*
*/
private String createUserName;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}

@ -7,34 +7,28 @@ public class TaskExamCustomerAnswer implements Serializable {
private static final long serialVersionUID = -556842372977600137L;
private Integer id; // 唯一标识符
private Integer id;
/**
* Id
*
* Id
*/
private Integer taskExamId;
/**
* ID
* ID
*
*/
private Integer createUser;
/**
*
*
*/
private Date createTime;
/**
*
* JSON
* (Json)
*/
private Integer textContentId;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}

@ -7,33 +7,27 @@ public class TextContent implements Serializable {
private static final long serialVersionUID = -1279530310964668131L;
// 默认构造函数
public TextContent() {
public TextContent(){
}
// 构造函数,初始化内容和创建时间
public TextContent(String content, Date createTime) {
this.content = content;
this.createTime = createTime;
}
private Integer id; // 唯一标识符
private Integer id;
/**
*
* JSON
* (Json)
*/
private String content;
/**
*
*
*/
private Date createTime;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}

@ -7,51 +7,45 @@ public class User implements Serializable {
private static final long serialVersionUID = -7797183521247423117L;
private Integer id; // 用户唯一标识
private Integer id;
private String userUuid; // 用户UUID唯一标识符
private String userUuid;
/**
*
*/
private String userName;
private String password; // 用户密码
private String password;
/**
*
*/
private String realName;
private Integer age; // 用户年龄
private Integer age;
/**
*
* 1.
* 2.
* 1. 2
*/
private Integer sex;
private Date birthDay; // 出生日期
private Date birthDay;
/**
* 1-12
* (1-12)
*/
private Integer userLevel;
private String phone; // 用户电话号码
private String phone;
/**
*
* 1.
* 3.
* 1. 3.
*/
private Integer role;
/**
*
* 1.
* 2.
* 1. 2
*/
private Integer status;
@ -60,11 +54,11 @@ public class User implements Serializable {
*/
private String imagePath;
private Date createTime; // 创建时间
private Date createTime;
private Date modifyTime; // 修改时间
private Date modifyTime;
private Date lastActiveTime; // 最后活跃时间
private Date lastActiveTime;
/**
*
@ -72,11 +66,9 @@ public class User implements Serializable {
private Boolean deleted;
/**
* OpenId
* openId
*/
private String wxOpenId; // 微信 OpenId
// Getter 和 Setter 方法
private String wxOpenId;
public Integer getId() {
return id;

@ -7,7 +7,9 @@ public class UserEventLog implements Serializable {
private static final long serialVersionUID = -3951198127152024633L;
public UserEventLog() {
}
public UserEventLog(Integer userId, String userName, String realName, Date createTime) {
@ -17,10 +19,10 @@ public class UserEventLog implements Serializable {
this.createTime = createTime;
}
private Integer id; // 日志唯一标识
private Integer id;
/**
* ID
* id
*/
private Integer userId;
@ -35,17 +37,15 @@ public class UserEventLog implements Serializable {
private String realName;
/**
*
*
*/
private String content;
/**
*
*
*/
private Date createTime;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}

@ -7,30 +7,30 @@ public class UserToken implements Serializable {
private static final long serialVersionUID = -2414443061696200360L;
private Integer id; // 用户Token的唯一标识
private Integer id;
/**
* Token
* token
*/
private String token;
/**
* ID
* Id
*/
private Integer userId;
/**
* openId
* openId
*/
private String wxOpenId;
/**
* Token
*
*/
private Date createTime;
/**
* Token
*
*/
private Date endTime;
@ -39,8 +39,6 @@ public class UserToken implements Serializable {
*/
private String userName;
// Getter 和 Setter 方法
public Integer getId() {
return id;
}

@ -5,77 +5,45 @@ import java.util.Map;
public enum ExamPaperAnswerStatusEnum {
WaitJudge(1, "待批改"), // 状态1: 待批改
Complete(2, "完成"); // 状态2: 完成
WaitJudge(1, "待批改"),
Complete(2, "完成");
private int code; // 状态码
private String name; // 状态名称
int code;
String name;
/**
*
*
* @param code
* @param name
*/
ExamPaperAnswerStatusEnum(int code, String name) {
this.code = code;
this.name = name;
}
/**
*
*
* @return
*/
public int getCode() {
return code;
}
/**
*
*
* @param code
*/
public void setCode(int code) {
this.code = code;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
// 用于根据状态码查找对应的枚举
private static final Map<Integer, ExamPaperAnswerStatusEnum> keyMap = new HashMap<>();
// 静态块,初始化枚举与状态码的映射
static {
for (ExamPaperAnswerStatusEnum item : ExamPaperAnswerStatusEnum.values()) {
keyMap.put(item.getCode(), item);
}
}
/**
*
*
* @param code
* @return
*/
public static ExamPaperAnswerStatusEnum fromCode(Integer code) {
return keyMap.get(code); // 返回状态码对应的枚举
return keyMap.get(code);
}
}

@ -5,78 +5,47 @@ import java.util.Map;
public enum ExamPaperTypeEnum {
Fixed(1, "固定试卷"), // 类型1: 固定试卷
TimeLimit(4, "时段试卷"), // 类型2: 时段试卷
Task(6, "任务试卷"); // 类型3: 任务试卷
private int code; // 试卷类型的编码
private String name; // 试卷类型的名称
/**
*
*
* @param code
* @param name
*/
Fixed(1, "固定试卷"),
TimeLimit(4, "时段试卷"),
Task(6, "任务试卷");
int code;
String name;
ExamPaperTypeEnum(int code, String name) {
this.code = code;
this.name = name;
}
// 用于存储编码与试卷类型之间的映射关系
private static final Map<Integer, ExamPaperTypeEnum> keyMap = new HashMap<>();
// 静态代码块,初始化编码与试卷类型的映射
static {
for (ExamPaperTypeEnum item : ExamPaperTypeEnum.values()) {
keyMap.put(item.getCode(), item);
}
}
/**
*
*
* @param code
* @return
*/
public static ExamPaperTypeEnum fromCode(Integer code) {
return keyMap.get(code); // 根据编码返回对应的枚举
return keyMap.get(code);
}
/**
*
*
* @return
*/
public int getCode() {
return code;
}
/**
*
*
* @param code
*/
public void setCode(int code) {
this.code = code;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
}

@ -2,57 +2,33 @@ package com.mindskip.xzs.domain.enums;
public enum QuestionStatusEnum {
OK(1, "正常"), // 状态1: 正常
Publish(2, "发布"); // 状态2: 发布
private int code; // 问题状态的编码
private String name; // 问题状态的名称
/**
*
*
* @param code
* @param name
*/
OK(1, "正常"),
Publish(2, "发布");
int code;
String name;
QuestionStatusEnum(int code, String name) {
this.code = code;
this.name = name;
}
/**
*
*
* @return
*/
public int getCode() {
return code;
}
/**
*
*
* @param code
*/
public void setCode(int code) {
this.code = code;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
}

@ -5,95 +5,59 @@ import java.util.Map;
public enum QuestionTypeEnum {
SingleChoice(1, "单选题"), // 单选题
MultipleChoice(2, "多选题"), // 多选题
TrueFalse(3, "判断题"), // 判断题
GapFilling(4, "填空题"), // 填空题
ShortAnswer(5, "简答题"); // 简答题
private int code; // 题型的编码
private String name; // 题型的名称
/**
*
*
* @param code
* @param name
*/
SingleChoice(1, "单选题"),
MultipleChoice(2, "多选题"),
TrueFalse(3, "判断题"),
GapFilling(4, "填空题"),
ShortAnswer(5, "简答题");
int code;
String name;
QuestionTypeEnum(int code, String name) {
this.code = code;
this.name = name;
}
// 定义一个Map保存题型编码与对应枚举的映射
private static final Map<Integer, QuestionTypeEnum> keyMap = new HashMap<>();
static {
for (QuestionTypeEnum item : QuestionTypeEnum.values()) {
keyMap.put(item.getCode(), item); // 将枚举值的code与枚举项映射
keyMap.put(item.getCode(), item);
}
}
/**
*
*
* @param code
* @return
*/
public static QuestionTypeEnum fromCode(Integer code) {
return keyMap.get(code);
}
/**
*
*
* @param code
* @return truefalse
*/
public static boolean needSaveTextContent(Integer code) {
QuestionTypeEnum questionTypeEnum = QuestionTypeEnum.fromCode(code);
switch (questionTypeEnum) {
case GapFilling: // 填空题
case ShortAnswer: // 简答题
return true; // 需要保存文本内容
case GapFilling:
case ShortAnswer:
return true;
default:
return false; // 其他题型不需要
return false;
}
}
/**
*
*
* @return
*/
public int getCode() {
return code;
}
/**
*
*
* @param code
*/
public void setCode(int code) {
this.code = code;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
}

@ -1,88 +1,52 @@
package com.mindskip.xzs.domain.enums;
import java.util.HashMap;
import java.util.Map;
public enum RoleEnum {
STUDENT(1, "STUDENT"), // 学生角色
ADMIN(3, "ADMIN"); // 管理员角色
STUDENT(1, "STUDENT"),
ADMIN(3, "ADMIN");
private int code; // 角色编码
private String name; // 角色名称
int code;
String name;
/**
*
*
* @param code
* @param name
*/
RoleEnum(int code, String name) {
this.code = code;
this.name = name;
}
// 定义一个Map保存角色编码与对应枚举的映射
private static final Map<Integer, RoleEnum> keyMap = new HashMap<>();
static {
for (RoleEnum item : RoleEnum.values()) {
keyMap.put(item.getCode(), item); // 将枚举值的code与枚举项映射
keyMap.put(item.getCode(), item);
}
}
/**
*
*
* @param code
* @return
*/
public static RoleEnum fromCode(Integer code) {
return keyMap.get(code);
}
/**
*
*
* @return
*/
public int getCode() {
return code;
}
/**
*
*
* @param code
*/
public void setCode(int code) {
this.code = code;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* "ROLE_"
*
* @return "ROLE_"
*/
public String getRoleName() {
return "ROLE_" + name;
}
}

@ -1,78 +1,46 @@
package com.mindskip.xzs.domain.enums;
import java.util.HashMap;
import java.util.Map;
public enum UserStatusEnum {
Enable(1, "启用"), // 用户启用状态
Disable(2, "禁用"); // 用户禁用状态
Enable(1, "启用"),
Disable(2, "禁用");
private int code; // 用户状态的编码
private String name; // 用户状态的名称
int code;
String name;
/**
*
*
* @param code
* @param name
*/
UserStatusEnum(int code, String name) {
this.code = code;
this.name = name;
}
// 定义一个Map保存用户状态编码与对应枚举项的映射
private static final Map<Integer, UserStatusEnum> keyMap = new HashMap<>();
static {
for (UserStatusEnum item : UserStatusEnum.values()) {
keyMap.put(item.getCode(), item); // 将枚举值的code与枚举项映射
keyMap.put(item.getCode(), item);
}
}
/**
*
*
* @param code
* @return
*/
public static UserStatusEnum fromCode(Integer code) {
return keyMap.get(code);
}
/**
*
*
* @return
*/
public int getCode() {
return code;
}
/**
*
*
* @param code
*/
public void setCode(int code) {
this.code = code;
}
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}

@ -1,42 +1,21 @@
package com.mindskip.xzs.domain.exam;
public class ExamPaperQuestionItemObject {
private Integer id;
private Integer itemOrder;
private Integer id; // 题目项的唯一标识符
private Integer itemOrder; // 题目项在试卷中的顺序
/**
* ID
*
* @return ID
*/
public Integer getId() {
return id;
}
/**
* ID
*
* @param id ID
*/
public void setId(Integer id) {
this.id = id;
}
/**
*
*
* @return
*/
public Integer getItemOrder() {
return itemOrder;
}
/**
*
*
* @param itemOrder
*/
public void setItemOrder(Integer itemOrder) {
this.itemOrder = itemOrder;
}

@ -1,44 +1,26 @@
package com.mindskip.xzs.domain.exam;
import java.util.List;
public class ExamPaperTitleItemObject {
private String name; // 题目项的名称
private List<ExamPaperQuestionItemObject> questionItems; // 题目项列表
private String name;
private List<ExamPaperQuestionItemObject> questionItems;
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
*
*
* @return
*/
public List<ExamPaperQuestionItemObject> getQuestionItems() {
return questionItems;
}
/**
*
*
* @param questionItems
*/
public void setQuestionItems(List<ExamPaperQuestionItemObject> questionItems) {
this.questionItems = questionItems;
}

@ -1,60 +1,31 @@
package com.mindskip.xzs.domain.other;
public class ExamPaperAnswerUpdate {
private Integer id; // 答卷ID
private Integer customerScore; // 客户端评分
private Boolean doRight; // 是否答对
/**
* ID
*
* @return ID
*/
private Integer id;
private Integer customerScore;
private Boolean doRight;
public Integer getId() {
return id;
}
/**
* ID
*
* @param id ID
*/
public void setId(Integer id) {
this.id = id;
}
/**
*
*
* @return
*/
public Integer getCustomerScore() {
return customerScore;
}
/**
*
*
* @param customerScore
*/
public void setCustomerScore(Integer customerScore) {
this.customerScore = customerScore;
}
/**
*
*
* @return truefalse
*/
public Boolean getDoRight() {
return doRight;
}
/**
*
*
* @param doRight
*/
public void setDoRight(Boolean doRight) {
this.doRight = doRight;
}

@ -1,42 +1,23 @@
package com.mindskip.xzs.domain.other;
public class KeyValue {
private String name; // 键(名称)
private Integer value; // 值
private String name;
private Integer value;
/**
*
*
* @return
*/
public String getName() {
return name;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
*
*
* @return
*/
public Integer getValue() {
return value;
}
/**
*
*
* @param value
*/
public void setValue(Integer value) {
this.value = value;
}

@ -1,80 +1,44 @@
package com.mindskip.xzs.domain.question;
public class QuestionItemObject {
private String prefix; // 题目前缀,例如 "选择题" 或 "填空题"
private String content; // 题目内容
private Integer score; // 题目的分值
private String itemUuid; // 题目的唯一标识符
private String prefix;
private String content;
private Integer score;
private String itemUuid;
/**
*
*
* @return
*/
public String getPrefix() {
return prefix;
}
/**
*
*
* @param prefix
*/
public void setPrefix(String prefix) {
this.prefix = prefix;
}
/**
*
*
* @return
*/
public String getContent() {
return content;
}
/**
*
*
* @param content
*/
public void setContent(String content) {
this.content = content;
}
/**
*
*
* @return
*/
public Integer getScore() {
return score;
}
/**
*
*
* @param score
*/
public void setScore(Integer score) {
this.score = score;
}
/**
*
*
* @return
*/
public String getItemUuid() {
return itemUuid;
}
/**
*
*
* @param itemUuid
*/
public void setItemUuid(String itemUuid) {
this.itemUuid = itemUuid;
}

@ -1,82 +1,47 @@
package com.mindskip.xzs.domain.question;
import java.util.List;
public class QuestionObject {
private String titleContent; // 题目标题内容
private String analyze; // 题目解析
private List<QuestionItemObject> questionItemObjects; // 题目项的列表(例如单选题选项)
private String correct; // 正确答案
private String titleContent;
private String analyze;
private List<QuestionItemObject> questionItemObjects;
private String correct;
/**
*
*
* @return
*/
public String getTitleContent() {
return titleContent;
}
/**
*
*
* @param titleContent
*/
public void setTitleContent(String titleContent) {
this.titleContent = titleContent;
}
/**
*
*
* @return
*/
public String getAnalyze() {
return analyze;
}
/**
*
*
* @param analyze
*/
public void setAnalyze(String analyze) {
this.analyze = analyze;
}
/**
*
*
* @return
*/
public List<QuestionItemObject> getQuestionItemObjects() {
return questionItemObjects;
}
/**
*
*
* @param questionItemObjects
*/
public void setQuestionItemObjects(List<QuestionItemObject> questionItemObjects) {
this.questionItemObjects = questionItemObjects;
}
/**
*
*
* @return
*/
public String getCorrect() {
return correct;
}
/**
*
*
* @param correct
*/
public void setCorrect(String correct) {
this.correct = correct;
}

@ -1,71 +1,41 @@
package com.mindskip.xzs.domain.task;
public class TaskItemAnswerObject {
private Integer examPaperId; // 试卷ID
private Integer examPaperAnswerId; // 试卷答案ID
private Integer status; // 状态(例如未批改、已批改等)
private Integer examPaperId;
private Integer examPaperAnswerId;
private Integer status;
public TaskItemAnswerObject(){
// 无参构造方法
public TaskItemAnswerObject() {
}
// 带参构造方法,初始化字段
public TaskItemAnswerObject(Integer examPaperId, Integer examPaperAnswerId, Integer status) {
this.examPaperId = examPaperId;
this.examPaperAnswerId = examPaperAnswerId;
this.status = status;
}
/**
* ID
*
* @return ID
*/
public Integer getExamPaperId() {
return examPaperId;
}
/**
* ID
*
* @param examPaperId ID
*/
public void setExamPaperId(Integer examPaperId) {
this.examPaperId = examPaperId;
}
/**
* ID
*
* @return ID
*/
public Integer getExamPaperAnswerId() {
return examPaperAnswerId;
}
/**
* ID
*
* @param examPaperAnswerId ID
*/
public void setExamPaperAnswerId(Integer examPaperAnswerId) {
this.examPaperAnswerId = examPaperAnswerId;
}
/**
*
*
* @return
*/
public Integer getStatus() {
return status;
}
/**
*
*
* @param status
*/
public void setStatus(Integer status) {
this.status = status;
}

@ -1,60 +1,31 @@
package com.mindskip.xzs.domain.task;
public class TaskItemObject {
private Integer examPaperId; // 试卷ID
private String examPaperName; // 试卷名称
private Integer itemOrder; // 任务项顺序
/**
* ID
*
* @return ID
*/
private Integer examPaperId;
private String examPaperName;
private Integer itemOrder;
public Integer getExamPaperId() {
return examPaperId;
}
/**
* ID
*
* @param examPaperId ID
*/
public void setExamPaperId(Integer examPaperId) {
this.examPaperId = examPaperId;
}
/**
*
*
* @return
*/
public String getExamPaperName() {
return examPaperName;
}
/**
*
*
* @param examPaperName
*/
public void setExamPaperName(String examPaperName) {
this.examPaperName = examPaperName;
}
/**
*
*
* @return
*/
public Integer getItemOrder() {
return itemOrder;
}
/**
*
*
* @param itemOrder
*/
public void setItemOrder(Integer itemOrder) {
this.itemOrder = itemOrder;
}

@ -4,28 +4,34 @@ import com.mindskip.xzs.domain.ExamPaperAnswerInfo;
import org.springframework.context.ApplicationEvent;
/**
*
* @version 3.3.0
* @description: The type Calculate exam paper answer complete event.
* Copyright (C), 2020-2021,
* @date 2021/5/25 10:45
*/
public class CalculateExamPaperAnswerCompleteEvent extends ApplicationEvent {
private final ExamPaperAnswerInfo examPaperAnswerInfo; // 存储考试答题信息
private final ExamPaperAnswerInfo examPaperAnswerInfo;
/**
*
* Instantiates a new Calculate exam paper answer complete event.
*
* @param examPaperAnswerInfo
* @param examPaperAnswerInfo the exam paper answer info
*/
public CalculateExamPaperAnswerCompleteEvent(final ExamPaperAnswerInfo examPaperAnswerInfo) {
super(examPaperAnswerInfo); // 事件源为考试答题信息
this.examPaperAnswerInfo = examPaperAnswerInfo; // 设置实例属性
super(examPaperAnswerInfo);
this.examPaperAnswerInfo = examPaperAnswerInfo;
}
/**
*
* Gets exam paper answer info.
*
* @return
* @return the exam paper answer info
*/
public ExamPaperAnswerInfo getExamPaperAnswerInfo() {
return examPaperAnswerInfo;
}
}
}

@ -4,28 +4,34 @@ import com.mindskip.xzs.domain.User;
import org.springframework.context.ApplicationEvent;
/**
*
* @version 3.5.0
* @description: The type On registration complete event.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
public class OnRegistrationCompleteEvent extends ApplicationEvent {
private final User user; // 存储用户信息
private final User user;
/**
*
* Instantiates a new On registration complete event.
*
* @param user
* @param user the user
*/
public OnRegistrationCompleteEvent(final User user) {
super(user); // 设置事件源为用户对象
this.user = user; // 设置实例属性
super(user);
this.user = user;
}
/**
*
* Gets user.
*
* @return
* @return the user
*/
public User getUser() {
return user;
}
}

@ -4,28 +4,31 @@ import com.mindskip.xzs.domain.UserEventLog;
import org.springframework.context.ApplicationEvent;
/**
*
* @version 3.5.0
* @description: The type User event.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
public class UserEvent extends ApplicationEvent {
private final UserEventLog userEventLog; // 存储用户操作日志
private final UserEventLog userEventLog;
/**
*
* Instantiates a new User event.
*
* @param userEventLog
* @param userEventLog the user event log
*/
public UserEvent(final UserEventLog userEventLog) {
super(userEventLog); // 设置事件源为用户操作日志
this.userEventLog = userEventLog; // 设置实例属性
super(userEventLog);
this.userEventLog = userEventLog;
}
/**
*
* Gets user event log.
*
* @return
* @return the user event log
*/
public UserEventLog getUserEventLog() {
return userEventLog;
}
}
}

@ -19,7 +19,7 @@ import java.util.List;
/**
* @version 3.5.0
* @description:
* @description: The type Calculate exam paper answer listener.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
@ -32,18 +32,15 @@ public class CalculateExamPaperAnswerListener implements ApplicationListener<Cal
private final TaskExamCustomerAnswerService examCustomerAnswerService;
/**
*
* Instantiates a new Calculate exam paper answer listener.
*
* @param examPaperAnswerService
* @param examPaperQuestionCustomerAnswerService
* @param textContentService
* @param examCustomerAnswerService
* @param examPaperAnswerService the exam paper answer service
* @param examPaperQuestionCustomerAnswerService the exam paper question customer answer service
* @param textContentService the text content service
* @param examCustomerAnswerService the exam customer answer service
*/
@Autowired
public CalculateExamPaperAnswerListener(ExamPaperAnswerService examPaperAnswerService,
ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService,
TextContentService textContentService,
TaskExamCustomerAnswerService examCustomerAnswerService) {
public CalculateExamPaperAnswerListener(ExamPaperAnswerService examPaperAnswerService, ExamPaperQuestionCustomerAnswerService examPaperQuestionCustomerAnswerService, TextContentService textContentService, TaskExamCustomerAnswerService examCustomerAnswerService) {
this.examPaperAnswerService = examPaperAnswerService;
this.examPaperQuestionCustomerAnswerService = examPaperQuestionCustomerAnswerService;
this.textContentService = textContentService;
@ -55,35 +52,23 @@ public class CalculateExamPaperAnswerListener implements ApplicationListener<Cal
public void onApplicationEvent(CalculateExamPaperAnswerCompleteEvent calculateExamPaperAnswerCompleteEvent) {
Date now = new Date();
// 获取考试试卷、试卷答案和试卷问题答案
ExamPaperAnswerInfo examPaperAnswerInfo = (ExamPaperAnswerInfo) calculateExamPaperAnswerCompleteEvent.getSource();
ExamPaper examPaper = examPaperAnswerInfo.getExamPaper();
ExamPaperAnswer examPaperAnswer = examPaperAnswerInfo.getExamPaperAnswer();
List<ExamPaperQuestionCustomerAnswer> examPaperQuestionCustomerAnswers = examPaperAnswerInfo.getExamPaperQuestionCustomerAnswers();
// 插入试卷答案
examPaperAnswerService.insertByFilter(examPaperAnswer);
// 处理需要保存文本内容的答案
examPaperQuestionCustomerAnswers.stream()
.filter(a -> QuestionTypeEnum.needSaveTextContent(a.getQuestionType()))
.forEach(d -> {
// 创建文本内容对象并保存
TextContent textContent = new TextContent(d.getAnswer(), now);
textContentService.insertByFilter(textContent);
// 更新试卷问题答案的文本内容ID
d.setTextContentId(textContent.getId());
d.setAnswer(null); // 清除答案内容
});
// 更新每个试卷问题答案的试卷答案ID
examPaperQuestionCustomerAnswers.stream().filter(a -> QuestionTypeEnum.needSaveTextContent(a.getQuestionType())).forEach(d -> {
TextContent textContent = new TextContent(d.getAnswer(), now);
textContentService.insertByFilter(textContent);
d.setTextContentId(textContent.getId());
d.setAnswer(null);
});
examPaperQuestionCustomerAnswers.forEach(d -> {
d.setExamPaperAnswerId(examPaperAnswer.getId());
});
// 批量插入试卷问题答案
examPaperQuestionCustomerAnswerService.insertList(examPaperQuestionCustomerAnswers);
// 如果是任务型试卷,更新任务答案
switch (ExamPaperTypeEnum.fromCode(examPaper.getPaperType())) {
case Task: {
examCustomerAnswerService.insertOrUpdate(examPaper, examPaperAnswer, now);

@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
/**
* @version 3.5.0
* @description:
* @description: The type Email send listener.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
@ -19,9 +19,7 @@ public class EmailSendListener implements ApplicationListener<OnRegistrationComp
@Override
@NonNull
public void onApplicationEvent(OnRegistrationCompleteEvent event) {
// 获取注册的用户对象
User user = event.getUser();
// 输出日志,表示发送邮件的动作
System.out.println("用户注册,发送邮件给用户: " + user.getUserName());
System.out.println("User register Email sender :" + user.getUserName());
}
}

@ -8,7 +8,7 @@ import org.springframework.stereotype.Component;
/**
* @version 3.5.0
* @description:
* @description: The type User log listener.
* Copyright (C), 2020-2021,
* @date 2021/12/25 9:45
*/
@ -18,9 +18,9 @@ public class UserLogListener implements ApplicationListener<UserEvent> {
private final UserEventLogService userEventLogService;
/**
* UserEventLogService
* Instantiates a new User log listener.
*
* @param userEventLogService
* @param userEventLogService the user event log service
*/
@Autowired
public UserLogListener(UserEventLogService userEventLogService) {
@ -29,7 +29,6 @@ public class UserLogListener implements ApplicationListener<UserEvent> {
@Override
public void onApplicationEvent(UserEvent userEvent) {
// 处理 UserEvent 事件,记录用户事件日志
userEventLogService.insertByFilter(userEvent.getUserEventLog());
}

@ -1,56 +1,16 @@
package com.mindskip.xzs.repository;
/**
* BaseMapper CRUD
* @param <T>
*/
public interface BaseMapper<T> {
/**
*
*
* @param id
* @return
*/
int deleteByPrimaryKey(Integer id);
/**
*
*
* @param record
* @return
*/
int insert(T record);
/**
*
*
* @param record
* @return
*/
int insertSelective(T record);
/**
*
*
* @param id
* @return
*/
T selectByPrimaryKey(Integer id);
/**
*
*
* @param record
* @return
*/
int updateByPrimaryKeySelective(T record);
/**
*
*
* @param record
* @return
*/
int updateByPrimaryKey(T record);
}
}

@ -9,50 +9,16 @@ import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* ExamPaperAnswerMapper
*/
@Mapper
public interface ExamPaperAnswerMapper extends BaseMapper<ExamPaperAnswer> {
/**
*
*
* @param requestVM
* @return
*/
List<ExamPaperAnswer> studentPage(ExamPaperAnswerPageVM requestVM);
/**
*
*
* @return
*/
Integer selectAllCount();
/**
*
*
* @param startTime
* @param endTime
* @return
*/
List<KeyValue> selectCountByDate(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
/**
* IDID
*
* @param paperId ID
* @param userId ID
* @return
*/
ExamPaperAnswer getByPidUid(@Param("pid") Integer paperId, @Param("uid") Integer userId);
/**
*
*
* @param requestVM
* @return
*/
ExamPaperAnswer getByPidUid(@Param("pid") Integer paperId, @Param("uid") Integer uid);
List<ExamPaperAnswer> adminPage(com.mindskip.xzs.viewmodel.admin.paper.ExamPaperAnswerPageRequestVM requestVM);
}
}

@ -12,74 +12,22 @@ import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* ExamPaperMapper
*/
@Mapper
public interface ExamPaperMapper extends BaseMapper<ExamPaper> {
/**
*
*
* @param requestVM
* @return
*/
List<ExamPaper> page(ExamPaperPageRequestVM requestVM);
/**
*
*
* @param requestVM
* @return
*/
List<ExamPaper> taskExamPage(ExamPaperPageRequestVM requestVM);
/**
*
*
* @param requestVM
* @return
*/
List<ExamPaper> studentPage(ExamPaperPageVM requestVM);
/**
*
*
* @param paperFilter
* @return
*/
List<PaperInfo> indexPaper(PaperFilter paperFilter);
/**
*
*
* @return
*/
Integer selectAllCount();
/**
*
*
* @param startTime
* @param endTime
* @return
*/
List<KeyValue> selectCountByDate(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
/**
*
*
* @param taskId ID
* @param paperIds ID
* @return
*/
int updateTaskPaper(@Param("taskId") Integer taskId, @Param("paperIds") List<Integer> paperIds);
int updateTaskPaper(@Param("taskId") Integer taskId,@Param("paperIds") List<Integer> paperIds);
/**
*
*
* @param paperIds ID
* @return
*/
int clearTaskPaper(@Param("paperIds") List<Integer> paperIds);
}
}

@ -11,18 +11,17 @@ import java.util.Date;
import java.util.List;
@Mapper
public interface ExamPaperQuestionCustomerAnswerMapper extends BaseMapper<ExamPaperQuestionCustomerAnswer> {// 接口,负责与数据库交互,操作 ExamPaperQuestionCustomerAnswer 实体类的数据。
public interface ExamPaperQuestionCustomerAnswerMapper extends BaseMapper<ExamPaperQuestionCustomerAnswer> {
List<ExamPaperQuestionCustomerAnswer> selectListByPaperAnswerId(Integer id);
//根据试卷答案的 ID 获取该答案相关的所有试题答题记录。
List<ExamPaperQuestionCustomerAnswer> studentPage(QuestionPageStudentRequestVM requestVM);
//根据学生的查询条件获取分页的试题答题记录。
int insertList(List<ExamPaperQuestionCustomerAnswer> list);
//批量插入试题答题记录。
Integer selectAllCount();
//统计所有的试题答题记录总数。
List<KeyValue> selectCountByDate(@Param("startTime") Date startTime, @Param("endTime") Date endTime);
//根据时间范围统计试题答题记录的数量。
int updateScore(List<ExamPaperAnswerUpdate> examPaperAnswerUpdates);
//批量更新试题答题记录的分数。
}

@ -7,12 +7,11 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MessageMapper extends BaseMapper<Message> {//操作 Message 实体类的数据。
public interface MessageMapper extends BaseMapper<Message> {
List<Message> page(MessagePageRequestVM requestVM);
//根据分页请求参数,查询消息数据的分页列表
List<Message> selectByIds(List<Integer> ids);
//根据消息的 ID 列表批量查询消息
int readAdd(Integer id);
//根据消息 ID 增加该消息的阅读次数
}

@ -7,14 +7,13 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MessageUserMapper extends BaseMapper<MessageUser> {//处理与 MessageUser 实体相关的数据库操作。
public interface MessageUserMapper extends BaseMapper<MessageUser> {
List<MessageUser> selectByMessageIds(List<Integer> ids);
//根据消息 ID 列表,查询与这些消息关联的 MessageUser 记录。
int inserts(List<MessageUser> list);
//批量插入 MessageUser 记录。
List<MessageUser> studentPage(MessageRequestVM requestVM);
//根据学生的查询条件获取与学生相关的消息分页数据。
Integer unReadCount(Integer userId);
//查询指定用户的未读消息数量。
}

@ -10,14 +10,13 @@ import java.util.Date;
import java.util.List;
@Mapper
public interface QuestionMapper extends BaseMapper<Question> {//处理与 Question 实体相关的数据库操作
public interface QuestionMapper extends BaseMapper<Question> {
List<Question> page(QuestionPageRequestVM requestVM);
//根据分页请求参数获取试题的分页列表。
List<Question> selectByIds(@Param("ids") List<Integer> ids);
//根据试题 ID 列表查询对应的试题数据。
Integer selectAllCount();
//统计系统中所有试题的总数。
List<KeyValue> selectCountByDate(@Param("startTime") Date startTime,@Param("endTime") Date endTime);
//按日期范围统计试题的新增数量。
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save