diff --git a/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvActivityController.java b/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvActivityController.java new file mode 100644 index 0000000..af0476a --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvActivityController.java @@ -0,0 +1,146 @@ +package io.v.nutz.zhgh.qsv.controller; + +import cn.wizzer.framework.base.Result; +import io.v.nutz.base.page.Pagination; +import io.v.nutz.base.query.PageForm; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.models.QsvOption; +import io.v.nutz.zhgh.qsv.models.QsvSubject; +import io.v.nutz.zhgh.qsv.service.QsvActivityService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; +import java.util.Arrays; +import java.util.List; + +@IocBean +@At("/platform/qsv/activity") +@Ok("json:full") +public class QsvActivityController { + + @Inject + private Dao dao; + @Inject + private QsvActivityService qsvActivityService; + + @At("") + @Ok("beetl:/platform/qsv/activity/index.html") + @RequiresPermissions("qsv.activity") + public void index() { + + } + + +// 分页查询 + @At + @RequiresPermissions("qsv.activity") + public Result pageData(@Valid PageForm pageForm, Integer year, String title) { + Cnd cnd = Cnd.NEW(); + cnd.andEX("YEAR(startTime)", "=", year); + cnd.and(Cnd.likeEX("title",title)); + Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + return Result.success(pagination); + } + + +// 保存问卷基础信息 + @At + @RequiresPermissions("qsv.activity") + public Result save(QsvActivity qsvActivity) { + if (qsvActivity.getCategory().equals("QUIZ")) { + if (qsvActivity.getMode().equals("SCHEDULED")) { + qsvActivity.setRepeatMode("DAILY"); + } else if (qsvActivity.getMode().equals("REGULAR")) { + qsvActivity.setRepeatMode("TOTAL"); + } + } + dao.insertOrUpdate(qsvActivity); + return Result.success(); + } + + @At + @RequiresPermissions("qsv.activity") + public Result findOne(@Valid String id) { + QsvActivity activity = dao.fetch(QsvActivity.class, id); + return Result.success(activity); + } + +// 删除问卷 + @At + @RequiresPermissions("qsv.activity") + public Result delete(@Valid String id) { + dao.delete(QsvActivity.class, id); + return Result.success(); + } + + +// 保存问卷题目 + @At + @RequiresPermissions("qsv.activity") + @Aop(TransAop.READ_COMMITTED) + public Result saveSubjects(@Param("activityId") @Valid String activityId, @Param("subjects") QsvSubject[] qsvSubjects) { + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + List subjectIds = subjects.stream().map(QsvSubject::getId).toList(); + + //新题目 + List newSubjectIds = Arrays.stream(qsvSubjects).map(QsvSubject::getId).toList(); + + //过滤出需要删除的题目 + List deleteSubjectIds = subjectIds.stream().filter(id -> !newSubjectIds.contains(id)).toList(); + dao.clear(QsvSubject.class, Cnd.where("id", "in", deleteSubjectIds)); + + for (int i = 0; i < qsvSubjects.length; i++) { + QsvSubject qsvSubject = qsvSubjects[i]; + qsvSubject.setSortNum(i + 1); + qsvSubject.setActivityId(activityId); + //更新或添加题目 + dao.insertOrUpdate(qsvSubject); + + + //更新或添加选项 + List newOptions = qsvSubject.getOptions(); + List optionIds = newOptions.stream().map(QsvOption::getId).toList(); + + List oldOptions = dao.query(QsvOption.class, Cnd.where("subjectId", "=", qsvSubject.getId())); + List oldOptionIds = oldOptions.stream().map(QsvOption::getId).toList(); + + + List deleteOptionIds = oldOptionIds.stream().filter(id -> !optionIds.contains(id)).toList(); + dao.clear(QsvOption.class, Cnd.where("id", "in", deleteOptionIds)); + + if (qsvSubject.getType().equals("radio") || qsvSubject.getType().equals("checkbox")) { + for (int i1 = 0; i1 < newOptions.size(); i1++) { + newOptions.get(i1).setSortNum(i1 + 1); + newOptions.get(i1).setSubjectId(qsvSubject.getId()); + } + dao.insertOrUpdate(newOptions); + List correctOptionIds = newOptions.stream().filter(QsvOption::getIsCorrect).map(QsvOption::getId).toList(); + qsvSubject.setCorrectAnswer(correctOptionIds); + } else { + dao.clear(QsvOption.class, Cnd.where("subjectId", "=", qsvSubject.getId())); + } + dao.update(qsvSubject); + } + + return Result.success(); + } + +// 查询问卷题目 + @At + @RequiresPermissions("qsv.activity") + public Result listSubjects(@Valid String activityId) { + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum")); + dao.fetchLinks(subjects, "options",Cnd.NEW().asc("sortNum")); + return Result.success(subjects); + } + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvQuizRankController.java b/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvQuizRankController.java new file mode 100644 index 0000000..d13c12c --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvQuizRankController.java @@ -0,0 +1,63 @@ +package io.v.nutz.zhgh.qsv.controller; + +import cn.wizzer.framework.base.Result; +import io.v.nutz.base.page.Pagination; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.param.QsvQuizRankPageForm; +import io.v.nutz.zhgh.qsv.service.QsvQuizRankService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.util.List; + +@IocBean +@At("/platform/qsv/quizRank") +@Ok("json:full") +public class QsvQuizRankController { + + @Inject + private Dao dao; + @Inject + private QsvQuizRankService qsvQuizRankService; + + @At("") + @Ok("beetl:/platform/qsv/quiz/rank.html") + @RequiresPermissions("qsv.quiz.rank") + public void index() { + + } + +// 根据年度查询问卷 + @At + @RequiresPermissions("qsv.quiz.rank") + public Result listQuiz(Integer year) { + Cnd cnd = Cnd.where("category", "=", "QUIZ"); + cnd.andEX("YEAR(startTime)", "=", year); + cnd.desc("category"); + List list = dao.query(QsvActivity.class, cnd); + return Result.success(list); + } + + @At + @RequiresPermissions("qsv.quiz.rank") + public Result pageData(@Valid QsvQuizRankPageForm pageForm) { + Pagination pagination = qsvQuizRankService.pageData(pageForm); + return Result.success(pagination); + } + + @At + @Ok("void") + @RequiresPermissions("qsv.quiz.rank") + public void exportXlsx(@Valid QsvQuizRankPageForm pageForm, HttpServletResponse response) { + qsvQuizRankService.exportXlsx(pageForm, response); + } + + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvSurveyController.java b/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvSurveyController.java new file mode 100644 index 0000000..e87a328 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/controller/QsvSurveyController.java @@ -0,0 +1,98 @@ +package io.v.nutz.zhgh.qsv.controller; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONObject; +import cn.wizzer.framework.base.Result; +import io.v.nutz.base.page.Pagination; +import io.v.nutz.base.query.PageForm; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.service.QsvActivityService; +import io.v.nutz.zhgh.qsv.service.QsvSurveyService; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.util.List; + +@IocBean +@At("/platform/qsv/survey") +@Ok("json:full") +public class QsvSurveyController { + + @Inject + private Dao dao; + @Inject + private QsvActivityService qsvActivityService; + @Inject + private QsvSurveyService qsvSurveyService; + + @At("") + @Ok("beetl:/platform/qsv/survey/index.html") + @RequiresPermissions("qsv.survey") + public void index() { + + } + + + @At + @RequiresPermissions("qsv.survey") + public Result pageData(@Valid PageForm pageForm, Integer year) { + Cnd cnd = Cnd.where("category", "=", "SURVEY"); + cnd.andEX("YEAR(startTime)", "=", year); + cnd.desc("category"); + Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + return Result.success(pagination); + } + +// 报告 + @At + @RequiresPermissions("qsv.survey") + public Result report(@Valid String activityId) { + List report = qsvSurveyService.report(activityId); + return Result.success(report); + } + +// 选项选择详情 + @At + @RequiresPermissions("qsv.survey") + public Result selectOptionUsers(@Valid String activityId, @Valid String subjectId, @Valid String optionId) { + List answerRecords = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)); + List selectOptionUsers = answerRecords.stream().filter(ext -> ObjectUtil.isNotNull(ext.getExtJson().get(subjectId, JSONObject.class)) && ext.getExtJson().get(subjectId, JSONObject.class).getJSONArray("optionIds").contains(optionId)).toList(); + return Result.success(selectOptionUsers); + } + + +// 用户答题 + @At + @RequiresPermissions("qsv.survey") + public Result userAnswer(@Valid String activityId) { + NutMap map = qsvSurveyService.userAnswer(activityId); + return Result.success(map); + } + + +// 删除用户答题记录 + @At + @RequiresPermissions("qsv.survey") + public Result deleteUserAnswer(@Valid String id) { + dao.delete(QsvUserAnswerRecord.class, id); + return Result.success(); + } + + +// 导出用户答题记录xlsx + @At + @RequiresPermissions("qsv.survey") + public void exportUserAnswerXlsx(@Valid String activityId, HttpServletResponse response) { + qsvSurveyService.exportUserAnswerXlsx(activityId, response); + } + + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/dto/QsvCheckAnswerResult.java b/src/main/java/io/v/nutz/zhgh/qsv/dto/QsvCheckAnswerResult.java new file mode 100644 index 0000000..791f404 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/dto/QsvCheckAnswerResult.java @@ -0,0 +1,17 @@ +package io.v.nutz.zhgh.qsv.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.experimental.Accessors; + +//答题得分计算结果 +@Data +@Accessors(chain = true) +@Builder +public class QsvCheckAnswerResult { + + private boolean isCorrect; + + private float score; + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvController.java b/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvController.java new file mode 100644 index 0000000..a845fca --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvController.java @@ -0,0 +1,45 @@ +package io.v.nutz.zhgh.qsv.h5controller; + +import cn.wizzer.framework.base.Result; +import io.v.nutz.base.page.Pagination; +import io.v.nutz.base.query.PageForm; +import io.v.nutz.zhgh.qsv.service.QsvActivityService; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.nutz.dao.Cnd; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; + +import javax.validation.Valid; + +@IocBean +@At("/platform/h5/qsv") +@Ok("json:full") +@Slf4j +public class H5QsvController { + + @Inject + private QsvActivityService qsvActivityService; + + @At("") + @Ok("beetl:/mobile/qsv/index.html") + @RequiresPermissions("h5.qsv") + public void index() { + + } + +// 分页查询 + @At + @RequiresPermissions("h5.qsv") + public Result pageData(@Valid PageForm pageForm, @Valid String category) { + Cnd cnd = Cnd.NEW(); + cnd.andEX("category", "=", category); + cnd.desc("startTime"); + Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd); + return Result.success(pagination); + } + + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvQuizController.java b/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvQuizController.java new file mode 100644 index 0000000..493220a --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvQuizController.java @@ -0,0 +1,372 @@ +package io.v.nutz.zhgh.qsv.h5controller; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONObject; +import cn.wizzer.framework.base.Result; +import io.v.nutz.web.commons.utils.ShiroUtil; +import io.v.nutz.zhgh.qsv.dto.QsvCheckAnswerResult; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.models.QsvSubject; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.param.QsvAnswerParam; +import io.v.nutz.zhgh.qsv.service.QsvQuizService; +import io.v.nutz.zhgh.qsv.service.QsvUserAnswerRecordService; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authz.annotation.RequiresAuthentication; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.util.cri.Static; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; +import java.util.*; +import java.util.stream.Collectors; + +@IocBean +@At("/platform/h5/qsv/quiz") +@Ok("json:full") +@Slf4j +public class H5QsvQuizController { + + @Inject + private Dao dao; + @Inject + private QsvUserAnswerRecordService qsvUserAnswerRecordService; + @Inject + private QsvQuizService qsvQuizService; + + @At("") + @Ok("beetl:/mobile/qsv/quiz/index.html") + @RequiresAuthentication + public void index() { + + } + + @At + @Ok("beetl:/mobile/qsv/quiz/result.html") + @RequiresAuthentication + public void result() { + + } + +// 题目列表 + @At + @RequiresAuthentication + public Result subjects(@Valid String activityId) { + QsvActivity activity = dao.fetch(QsvActivity.class, activityId); + String mode = activity.getMode(); + //能否重复答题 + Boolean repeatable = activity.getRepeatable(); + //重复答题模式 + String repeatMode = activity.getRepeatMode(); + //答题最大次数 + Integer maxAttempts = activity.getMaxAttempts(); + //题目显示模式 + String displayMode = activity.getDisplayMode(); + + //最终返回的题目数据 + List resultSubjects = new ArrayList<>(); + String answerRecordId = null; + + //查询用户的答题记录 + List answerRecords = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId) + .and("userId", "=", ShiroUtil.getUserId())); + + if (activity.getEndTime().after(new Date())) { + //已结束 查询最新一次的答题记录 + Optional lastRecordOptional = answerRecords.stream().max(Comparator.comparing(QsvUserAnswerRecord::getAnswerTime)); + if (lastRecordOptional.isPresent()) { + List subjectIds = lastRecordOptional.get().getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + answerRecordId = lastRecordOptional.get().getId(); + }else{ + //没生成过 那就看全部的题目 + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + QsvUserAnswerRecord answerRecord = qsvUserAnswerRecordService.insertRecord(activityId, subjects.stream().map(QsvSubject::getId).collect(Collectors.toList())); + answerRecordId = answerRecord.getId(); + + List subjectIds = answerRecord.getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + } + } else { + + if (mode.equals("REGULAR")) { + if (ObjectUtil.isEmpty(answerRecords)) { + //首次进来生成答题记录 + if (displayMode.equals("ALL")) { + resultSubjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + answerRecordId = qsvUserAnswerRecordService.insertRecord(activityId, resultSubjects.stream().map(QsvSubject::getId).collect(Collectors.toList())).getId(); + } else if (displayMode.equals("RANDOM")) { + //单次随机抽取题目数量 + Integer randomCount = activity.getRandomCount(); + + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + if (subjects.size() < randomCount) { + throw new RuntimeException("题目数不够,无法生成题目"); + } + + //从subjects里随机抽取randomCount个题目 + Collections.shuffle(subjects); + List randomSubjects = subjects.subList(0, randomCount); + QsvUserAnswerRecord answerRecord = qsvUserAnswerRecordService.insertRecord(activityId, randomSubjects.stream().map(QsvSubject::getId).collect(Collectors.toList())); + answerRecordId = answerRecord.getId(); + + List subjectIds = answerRecord.getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + } + } else { + if (displayMode.equals("ALL")) { + //判断能否重复答题 如果可重复要根据次数判断是否再次生成记录 不能重复直接返回最新的一次记录 + if (repeatable) { + //已回答次数 + if (answerRecords.size() < maxAttempts) { + //生成答题记录 + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + answerRecordId = qsvUserAnswerRecordService.insertRecord(activityId, subjects.stream().map(QsvSubject::getId).collect(Collectors.toList())).getId(); + QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, answerRecordId); + + List subjectIds = answerRecord.getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + } else { + //已达到最大次数 返回最新一次记录 + Optional maxAnswerRecordOptional = answerRecords.stream().max(Comparator.comparingLong(QsvUserAnswerRecord::getRandomNumber)); + if (maxAnswerRecordOptional.isPresent()) { + List subjectIds = maxAnswerRecordOptional.get().getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + answerRecordId = maxAnswerRecordOptional.get().getId(); + } else { + throw new RuntimeException("业务异常"); + } + } + } else { + Optional maxAnswerRecordOptional = answerRecords.stream().max(Comparator.comparingLong(QsvUserAnswerRecord::getRandomNumber)); + if (maxAnswerRecordOptional.isPresent()) { + List subjectIds = maxAnswerRecordOptional.get().getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + answerRecordId = maxAnswerRecordOptional.get().getId(); + } else { + throw new RuntimeException("业务异常"); + } + } + } else if (displayMode.equals("RANDOM")) { + //判断是否有未答完的记录 + Optional notFinishRecordOptional = answerRecords.stream().filter(r -> !r.getIsFinish()).findFirst(); + if (notFinishRecordOptional.isPresent()) { + List subjectIds = notFinishRecordOptional.get().getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + answerRecordId = notFinishRecordOptional.get().getId(); + } else { + //随机抽取模式 + //单次随机抽取题目数量 + Integer randomCount = activity.getRandomCount(); + //总随机抽取次数 + Integer totalRandom = activity.getTotalRandom(); + + Date startTime = activity.getStartTime(); + Date endTime = activity.getEndTime(); + + //是否同一天 + boolean isSameDay = DateUtil.isSameDay(startTime, endTime); + +// if(!isSameDay){ + if (answerRecords.size() < totalRandom) { + //进行下一次抽取 + List subjectIds = answerRecords.stream().map(QsvUserAnswerRecord::getSubjectIds).flatMap(Collection::stream).toList(); + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId) + .and("id", "not in", subjectIds)); + + if (subjects.size() < randomCount) { + throw new RuntimeException("题目数不够,无法生成题目"); + } + + //从subjects里随机抽取randomCount个题目 + Collections.shuffle(subjects); + resultSubjects = subjects.subList(0, randomCount); + answerRecordId = qsvUserAnswerRecordService.insertRecord(activityId, resultSubjects.stream().map(QsvSubject::getId).collect(Collectors.toList())).getId(); + } else { + //返回最后一次抽取的记录 + Optional maxAnswerRecordOptional = answerRecords.stream().max(Comparator.comparingLong(QsvUserAnswerRecord::getRandomNumber)); + if (maxAnswerRecordOptional.isPresent()) { + List subjectIds = maxAnswerRecordOptional.get().getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + answerRecordId = maxAnswerRecordOptional.get().getId(); + } else { + throw new RuntimeException("业务异常"); + } + } +// }else{ +// +// } + } + } + } + } else if (mode.equals("SCHEDULED")) { + //定时定题 + //查询今天的题目 + + List todayRecords = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId) + .and("userId", "=", ShiroUtil.getUserId()) + .and("attemptDate", "=", DateUtil.today()) + ); + + if (ObjectUtil.isNotEmpty(todayRecords)) { + //判断下每天可以答几次(题目实际上都是一样的) + boolean todayAllFinish = todayRecords.stream().allMatch(QsvUserAnswerRecord::getIsFinish); + //如果今天已生成的题目已答完并且还可以重复答 + if (todayAllFinish && todayRecords.size() < maxAttempts) { + //生成今天的记录 + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId) + .and("displayDate", "=", DateUtil.today())); + QsvUserAnswerRecord answerRecord = qsvUserAnswerRecordService.insertRecord(activityId, subjects.stream().map(QsvSubject::getId).collect(Collectors.toList())); + answerRecordId = answerRecord.getId(); + + List subjectIds = answerRecord.getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + } else { + //今天最大那次的记录 + Optional maxTodayRecord = todayRecords.stream().max(Comparator.comparingLong(QsvUserAnswerRecord::getRandomNumber)); + if (maxTodayRecord.isPresent()) { + answerRecordId = maxTodayRecord.get().getId(); + + List subjectIds = maxTodayRecord.get().getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + } else { + throw new RuntimeException("业务异常"); + } + } + } else { + //生成今天的记录 + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId) + .and("displayDate", "=", DateUtil.today())); + QsvUserAnswerRecord answerRecord = qsvUserAnswerRecordService.insertRecord(activityId, subjects.stream().map(QsvSubject::getId).collect(Collectors.toList())); + answerRecordId = answerRecord.getId(); + + List subjectIds = answerRecord.getSubjectIds(); + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + resultSubjects = dao.query(QsvSubject.class, cnd); + } + } + } + + dao.fetchLinks(resultSubjects, "options", Cnd.NEW().asc("sortNum")); + + resultSubjects.forEach(v -> v.setUserSelectOptionIds(new ArrayList<>())); + + NutMap result = NutMap.NEW().addv("subjects", resultSubjects).addv("answerRecordId", answerRecordId).addv("activity", activity); + return Result.success(result); + } + +// 答题记录 + @At + @RequiresAuthentication + public Result answerRecord(@Valid String answerRecordId) { + QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId); + return Result.success(record); + } + +// 提交答题 + @At + @RequiresAuthentication + public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) { + QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId()); + JSONObject extJson = answerRecord.getExtJson(); + + float totalScore = 0; + + for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) { + JSONObject entries = extJson.get(subject.getId(), JSONObject.class); + if (ObjectUtil.isNotEmpty(entries)) { + entries.set("optionIds", subject.getUserSelectOptionIds()); + entries.set("text", subject.getUserFillContent()); + + QsvCheckAnswerResult answerResult = qsvQuizService.calcScore(subject.getId(), subject.getUserSelectOptionIds()); + entries.set("score", answerResult.getScore()); + entries.set("isCorrect", answerResult.isCorrect()); + + if (answerResult.isCorrect()) { + totalScore += answerResult.getScore(); + } + } + extJson.set(subject.getId(), entries); + } + + answerRecord.setTotalScore(totalScore); + answerRecord.setAttemptDate(new Date()); + answerRecord.setSubmitTime(new Date()); + answerRecord.setAnswerTime(qsvAnswerParam.getAnswerTime()); + answerRecord.setIsFinish(true); + dao.update(answerRecord); + + //重新计算最高分 最新得分 + qsvUserAnswerRecordService.calcByScoreMode(qsvAnswerParam.getActivityId(), ShiroUtil.getUserId()); + + return Result.success("提交成功"); + } + + @At + @RequiresAuthentication + public Result historyScore(@Valid String activityId) { + List answerRecords = dao.query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId).and("userId", "=", ShiroUtil.getUserId()).desc("createdAt")); + return Result.success(answerRecords); + } + +// 答题记录(结果页展示) + @At + @RequiresAuthentication + public Result answerResult(@Valid String answerRecordId) { + QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, answerRecordId); + List subjectIds = answerRecord.getSubjectIds(); + + Cnd cnd = Cnd.where("id", "in", subjectIds); + String dynamic = subjectIds.stream().map(id -> "'" + id + "'").collect(Collectors.joining(",")); + cnd.and(new Static("1=1 ORDER BY FIELD (id," + dynamic + ")")); + + List subjects = dao.query(QsvSubject.class, cnd); + dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum")); + + subjects.forEach(v -> v.setUserSelectOptionIds(new ArrayList<>())); + + QsvActivity activity = dao.fetch(QsvActivity.class, answerRecord.getActivityId()); + NutMap result = NutMap.NEW().addv("subjects", subjects).addv("answerRecordId", answerRecord.getId()).addv("activity", activity); + return Result.success(result); + } + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvSurveyController.java b/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvSurveyController.java new file mode 100644 index 0000000..b0a7bf3 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/h5controller/H5QsvSurveyController.java @@ -0,0 +1,106 @@ +package io.v.nutz.zhgh.qsv.h5controller; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONObject; +import cn.wizzer.framework.base.Result; +import io.v.nutz.web.commons.utils.ShiroUtil; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.models.QsvSubject; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.param.QsvAnswerParam; +import io.v.nutz.zhgh.qsv.service.QsvUserAnswerRecordService; +import org.apache.shiro.authz.annotation.RequiresAuthentication; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.Inject; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; +import org.nutz.mvc.annotation.At; +import org.nutz.mvc.annotation.Ok; +import org.nutz.mvc.annotation.Param; + +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@IocBean +@At("/platform/h5/qsv/survey") +@Ok("json:full") +public class H5QsvSurveyController { + + @Inject + private Dao dao; + @Inject + private QsvUserAnswerRecordService qsvUserAnswerRecordService; + + @At("") + @Ok("beetl:/mobile/qsv/survey/index.html") + @RequiresAuthentication + public void index() { + } + + @At + @RequiresAuthentication + public Result subjects(@Valid String activityId) { + QsvActivity activity = dao.fetch(QsvActivity.class, activityId); + String answerRecordId = null; + + QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId) + .and("userId", "=", ShiroUtil.getUserId())); + if (answerRecord == null) { + List subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + qsvUserAnswerRecordService.insertRecord(activityId, subjects.stream().map(QsvSubject::getId).toList()); + } + + QsvUserAnswerRecord answerRecord2 = dao.fetch(QsvUserAnswerRecord.class, + Cnd.where("activityId", "=", activityId) + .and("userId", "=", ShiroUtil.getUserId())); + + answerRecordId = answerRecord2.getId(); + + List subjects = dao.query(QsvSubject.class, Cnd.where("id", "in", answerRecord2.getSubjectIds())); + dao.fetchLinks(subjects, "options"); + + for (QsvSubject subject : subjects) { + if (subject.getUserSelectOptionIds() == null) { + subject.setUserSelectOptionIds(new ArrayList<>()); + } + } + + NutMap result = NutMap.NEW().addv("subjects", subjects).addv("answerRecordId", answerRecordId).addv("activity", activity); + return Result.success(result); + } + + @At + @RequiresAuthentication + public Result answerRecord(@Valid String answerRecordId) { + QsvUserAnswerRecord record = dao.fetch(QsvUserAnswerRecord.class, answerRecordId); + return Result.success(record); + } + + @At + @RequiresAuthentication + public Result submitAnswer(@Valid @Param("answer") QsvAnswerParam qsvAnswerParam) { + QsvUserAnswerRecord answerRecord = dao.fetch(QsvUserAnswerRecord.class, qsvAnswerParam.getAnswerRecordId()); + JSONObject extJson = answerRecord.getExtJson(); + + for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) { + JSONObject entries = extJson.get(subject.getId(), JSONObject.class); + if (ObjectUtil.isNotEmpty(entries)) { + entries.set("optionIds", subject.getUserSelectOptionIds()); + entries.set("text", subject.getUserFillContent()); + } + extJson.set(subject.getId(), entries); + } + + answerRecord.setAttemptDate(new Date()); + answerRecord.setSubmitTime(new Date()); + answerRecord.setIsFinish(true); + dao.update(answerRecord); + + return Result.success("提交成功"); + } + + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/models/QsvActivity.java b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvActivity.java new file mode 100644 index 0000000..35c2cb8 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvActivity.java @@ -0,0 +1,109 @@ +package io.v.nutz.zhgh.qsv.models; + +import io.v.nutz.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.util.Date; + +@EqualsAndHashCode(callSuper = true) +@Data +@Table("qsv_activity") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("问卷调查投票活动表") +public class QsvActivity extends BaseModel { + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("活动标题") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String title; + + @Column + @Comment("活动描述") + @ColDefine(type = ColType.TEXT) + private String description; + + @Column + @Comment("所属模块(quiz, survey, vote)") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String category; + + @Column + @Comment("活动开始时间") + @ColDefine(type = ColType.DATETIME) + private Date startTime; + + @Column + @Comment("活动结束时间") + @ColDefine(type = ColType.DATETIME) + private Date endTime; + + @Column + @Comment("活动分组ID") + @ColDefine(type = ColType.INT) + private Integer groupId; + + @Column + @Comment("模式:定时定题模式(scheduled)或常规模式(regular)") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String mode; + + @Column + @Comment("是否可重复答题") + @ColDefine(type = ColType.BOOLEAN) + @Default("0") + private Boolean repeatable; + + @Column + @Comment("重复模式:按天(daily)或按活动总次数(total)") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String repeatMode; + + @Column + @Comment("最大答题次数(0表示不限制)") + @ColDefine(type = ColType.INT, width = 1) + private Integer maxAttempts; + + @Column + @Comment("显示模式:全部显示、随机抽取显示(ALL、RANDOM)") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String displayMode; + + @Column + @Comment("单次随机抽取题目数量") + @ColDefine(type = ColType.INT, width = 1) + private Integer randomCount; + + @Column + @Comment("总随机抽取次数") + @ColDefine(type = ColType.INT, width = 1) + private Integer totalRandom; + + @Column + @Comment("随机打乱题目顺序") + @ColDefine(type = ColType.BOOLEAN) + @Default("0") + private Boolean shuffleSubject; + + @Column + @Comment("答题时间限制(以分钟为单位,0表示无限制)") + @ColDefine(type = ColType.INT, width = 1) + private Integer timeLimit; + + @Column + @Comment("答题得分统计(最高、最新)") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String scoreMode; + + @Column + @Comment("封面图片") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String cover; +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/models/QsvOption.java b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvOption.java new file mode 100644 index 0000000..a4fa316 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvOption.java @@ -0,0 +1,49 @@ +package io.v.nutz.zhgh.qsv.models; + +import io.v.nutz.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.io.Serializable; + +@EqualsAndHashCode(callSuper = true) +@Data +@Table("qsv_option") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("问卷调查投票选项表") +public class QsvOption extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("题目表") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String subjectId; + + @Column + @Comment("选项内容") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String text; + + @Column + @Comment("是否为正确答案(仅答题模块使用)") + @ColDefine(type = ColType.BOOLEAN) + private Boolean isCorrect; + + @Column + @Comment("图片地址") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String imgUrl; + + @Column + @Comment("排序") + @ColDefine(type = ColType.INT) + private Integer sortNum; + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/models/QsvSubject.java b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvSubject.java new file mode 100644 index 0000000..49b8733 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvSubject.java @@ -0,0 +1,76 @@ +package io.v.nutz.zhgh.qsv.models; + +import io.v.nutz.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.util.Date; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@Table("qsv_subject") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("问卷调查投票题目表") +public class QsvSubject extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("活动ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String activityId; + + @Column + @Comment("题目标题") + @ColDefine(type = ColType.VARCHAR, width = 255) + private String title; + + @Column + @Comment("题目类型(single, multi, judge, fill)") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String type; + + @Column + @Comment("正确答案") + @ColDefine(type = ColType.MYSQL_JSON) + private List correctAnswer; // 正确答案 + + @Column + @Comment("题目分数(仅答题模块使用)") + @ColDefine(type = ColType.INT, width = 1) + private Integer score; // + + @Column + @Comment("显示时间") + @ColDefine(type = ColType.DATE) + private Date displayDate; + + @Column + @Comment("排序") + @ColDefine(type = ColType.INT, width = 1) + private Integer sortNum; + + /** + * 选项 + */ + @Many(target = QsvOption.class, field = "subjectId") + private List options; + + /** + * 用户选择的选项(选择题) + */ + private List userSelectOptionIds; + + /** + * 用户填写的内容(填空题) + */ + private String userFillContent; + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/models/QsvUserAnswerRecord.java b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvUserAnswerRecord.java new file mode 100644 index 0000000..3ca89ce --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/models/QsvUserAnswerRecord.java @@ -0,0 +1,124 @@ +package io.v.nutz.zhgh.qsv.models; + +import cn.hutool.json.JSONObject; +import io.v.nutz.base.model.BaseModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.nutz.dao.entity.annotation.*; +import org.nutz.dao.interceptor.annotation.PrevInsert; + +import java.util.Date; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@Table("qsv_user_answer_record") +@TableMeta("{'mysql-charset':'utf8mb4'}") +@Comment("问卷调查投票记录表") +public class QsvUserAnswerRecord extends BaseModel { + + @Name + @Comment("ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + @PrevInsert(uu32 = true) + private String id; + + @Column + @Comment("用户ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String userId; + + @Column + @Comment("工号") + @ColDefine(type = ColType.VARCHAR, width = 200) + private String loginName; + + @Column + @Comment("姓名") + @ColDefine(type = ColType.VARCHAR, width = 200) + private String userName; + + @Column + @Comment("分工会") + @ColDefine(type = ColType.VARCHAR, width = 50) + private String unionName; + + @Column + @Comment("分工会ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String unionId; + + @Column + @Comment("单位") + @ColDefine(type = ColType.VARCHAR, width = 200) + private String unitName; + + @Column + @Comment("单位ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String unitId; + + @Column + @Comment("活动ID") + @ColDefine(type = ColType.VARCHAR, width = 32) + private String activityId; + + @Column + @Comment("扩展字段") + @ColDefine(type = ColType.MYSQL_JSON) + private JSONObject extJson; + + @Column + @Comment("题目ID") + @ColDefine(type = ColType.MYSQL_JSON) + private List subjectIds; + + @Column + @Comment("答题得分(仅答题模块使用)") + @ColDefine(type = ColType.FLOAT) + private Float totalScore; + + @Column + @Comment("随机次数(仅答题模块使用随机抽取模式)") + @ColDefine(type = ColType.INT, width = 1) + private Integer randomNumber; + + @Column + @Comment("答题次数(仅答题模块使用)") + @ColDefine(type = ColType.INT, width = 1) + private Integer attemptNumber; + + @Column + @Comment("答题日期(用于统计某一天的答题次数)") + @ColDefine(type = ColType.DATE) + private Date attemptDate; + + @Column + @Comment("是否为最新得分(仅答题模块使用)") + @ColDefine(type = ColType.BOOLEAN) + @Default("0") + private Boolean isLatestScore; + + @Column + @Comment("是否为最高得分(仅答题模块使用)") + @ColDefine(type = ColType.BOOLEAN) + @Default("0") + private Boolean isHighestScore; + + @Column + @Comment("是否完成") + @ColDefine(type = ColType.BOOLEAN) + @Default("0") + private Boolean isFinish; + + @Column + @Comment("提交时间") + @ColDefine(type = ColType.DATETIME) + private Date submitTime; + + @Column + @Comment("答题用时") + @ColDefine(type = ColType.INT) + private Integer answerTime; + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/param/QsvAnswerParam.java b/src/main/java/io/v/nutz/zhgh/qsv/param/QsvAnswerParam.java new file mode 100644 index 0000000..0eb0075 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/param/QsvAnswerParam.java @@ -0,0 +1,26 @@ +package io.v.nutz.zhgh.qsv.param; + +import lombok.Data; + +import java.util.List; + +@Data +//答题参数 +public class QsvAnswerParam { + + private String activityId; + + private String answerRecordId; + + private List subjects; + + private Integer answerTime; + + @Data + public static class Subject{ + private String id; + private List userSelectOptionIds; + private String userFillContent; + } + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/param/QsvQuizRankPageForm.java b/src/main/java/io/v/nutz/zhgh/qsv/param/QsvQuizRankPageForm.java new file mode 100644 index 0000000..cc0405a --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/param/QsvQuizRankPageForm.java @@ -0,0 +1,22 @@ +package io.v.nutz.zhgh.qsv.param; + +import io.v.nutz.base.query.PageForm; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@EqualsAndHashCode(callSuper = true) +@Data +//问卷排名得分分页查询参数 +public class QsvQuizRankPageForm extends PageForm { + + private String activityId; + + private String unionId; + + private String unitId; + + private Date attemptDate; + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/QsvActivityService.java b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvActivityService.java new file mode 100644 index 0000000..404624f --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvActivityService.java @@ -0,0 +1,8 @@ +package io.v.nutz.zhgh.qsv.service; + + +import io.v.nutz.base.service.BaseService; +import io.v.nutz.zhgh.qsv.models.QsvActivity; + +public interface QsvActivityService extends BaseService { +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/QsvQuizRankService.java b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvQuizRankService.java new file mode 100644 index 0000000..b0fb53d --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvQuizRankService.java @@ -0,0 +1,17 @@ +package io.v.nutz.zhgh.qsv.service; + + +import io.v.nutz.base.page.Pagination; +import io.v.nutz.base.service.BaseService; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.param.QsvQuizRankPageForm; + +import javax.servlet.http.HttpServletResponse; + +public interface QsvQuizRankService extends BaseService { + + Pagination pageData(QsvQuizRankPageForm pageForm); + + void exportXlsx(QsvQuizRankPageForm pageForm, HttpServletResponse response); + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/QsvQuizService.java b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvQuizService.java new file mode 100644 index 0000000..5330c48 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvQuizService.java @@ -0,0 +1,20 @@ +package io.v.nutz.zhgh.qsv.service; + + +import io.v.nutz.base.service.BaseService; +import io.v.nutz.zhgh.qsv.dto.QsvCheckAnswerResult; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; + +import java.util.List; + +public interface QsvQuizService extends BaseService { + + /** + * 计算答题分数 + * @param subjectId 题目ID + * @param userSelectOptions 用户选项 + * @return 分数 + */ + QsvCheckAnswerResult calcScore(String subjectId, List userSelectOptions); + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/QsvSurveyService.java b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvSurveyService.java new file mode 100644 index 0000000..649a41f --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvSurveyService.java @@ -0,0 +1,17 @@ +package io.v.nutz.zhgh.qsv.service; + +import io.v.nutz.base.service.BaseService; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import org.nutz.lang.util.NutMap; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +public interface QsvSurveyService extends BaseService { + + List report(String activityId); + + void exportUserAnswerXlsx(String activityId, HttpServletResponse response); + + NutMap userAnswer(String activityId); +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/QsvUserAnswerRecordService.java b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvUserAnswerRecordService.java new file mode 100644 index 0000000..07632dd --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/QsvUserAnswerRecordService.java @@ -0,0 +1,21 @@ +package io.v.nutz.zhgh.qsv.service; + + +import io.v.nutz.base.service.BaseService; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.param.QsvAnswerParam; + +import java.util.List; + +public interface QsvUserAnswerRecordService extends BaseService { + + QsvUserAnswerRecord insertRecord(String activityId, List subjectIds); + + /** + * 计算得分 + */ + float calcScore(QsvAnswerParam qsvAnswerParam); + + void calcByScoreMode(String activityId, String userId); + +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvActivityServiceImpl.java b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvActivityServiceImpl.java new file mode 100644 index 0000000..28a5db0 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvActivityServiceImpl.java @@ -0,0 +1,15 @@ +package io.v.nutz.zhgh.qsv.service.impl; + +import io.v.nutz.base.service.impl.BaseServiceImpl; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.service.QsvActivityService; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.IocBean; + +@IocBean(args = {"refer:dao"}) +public class QsvActivityServiceImpl extends BaseServiceImpl implements QsvActivityService { + + public QsvActivityServiceImpl(Dao dao) { + super(dao); + } +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvQuizRankServiceImpl.java b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvQuizRankServiceImpl.java new file mode 100644 index 0000000..b23fdee --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvQuizRankServiceImpl.java @@ -0,0 +1,145 @@ +package io.v.nutz.zhgh.qsv.service.impl; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; +import cn.hutool.core.util.StrUtil; +import io.v.nutz.base.page.Pagination; +import io.v.nutz.base.service.impl.BaseServiceImpl; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.param.QsvQuizRankPageForm; +import io.v.nutz.zhgh.qsv.service.QsvQuizRankService; +import org.apache.poi.ss.usermodel.Workbook; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.dao.util.cri.SqlExpressionGroup; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.util.NutMap; + +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; + +@IocBean(args = {"refer:dao"}) +public class QsvQuizRankServiceImpl extends BaseServiceImpl implements QsvQuizRankService { + + public QsvQuizRankServiceImpl(Dao dao) { + super(dao); + } + + @Override + public Pagination pageData(QsvQuizRankPageForm pageForm) { + String activityId = pageForm.getActivityId(); + if(StrUtil.isBlank(activityId)){ + return new Pagination(); + } + + QsvActivity activity = dao().fetch(QsvActivity.class, activityId); + if (activity == null) { + return new Pagination(); + } + + String mode = activity.getMode(); + String scoreStatisticsMode = activity.getScoreMode(); + + Sql sql = buildQuerySql(activityId, mode, scoreStatisticsMode, pageForm); + + return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql); + } + + @Override + public void exportXlsx(QsvQuizRankPageForm pageForm, HttpServletResponse response) { + String activityId = pageForm.getActivityId(); + QsvActivity activity = dao().fetch(QsvActivity.class, activityId); + + if (activity == null) { + throw new RuntimeException("活动不存在: " + activityId); + } + + String mode = activity.getMode(); + String scoreStatisticsMode = activity.getScoreMode(); + + Sql sql = buildQuerySql(activityId, mode, scoreStatisticsMode, pageForm); + List list = listMap(sql); + + List exportEntities = new ArrayList<>(); + exportEntities.add(new ExcelExportEntity("姓名", "userName", 20)); + exportEntities.add(new ExcelExportEntity("工号", "loginName", 20)); + exportEntities.add(new ExcelExportEntity("性别", "sex", 20)); + exportEntities.add(new ExcelExportEntity("单位", "unitName", 30)); + exportEntities.add(new ExcelExportEntity("分工会", "unionName", 30)); + exportEntities.add(new ExcelExportEntity("联系方式", "mobile", 30)); + if ("SCHEDULED".equals(mode) && pageForm.getAttemptDate() != null) { + exportEntities.add(new ExcelExportEntity("答题日期", "attemptDate", 20)); + exportEntities.add(new ExcelExportEntity("当天得分", "totalScore", 20)); + } else { + exportEntities.add(new ExcelExportEntity("得分", "sumScore", 20)); + } + + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, list); +// CommonDownloadUtil.download(activity.getTitle() + "得分名单" + ".xlsx", workbook, response); + } + + private Sql buildQuerySql(String activityId, String mode, String scoreStatisticsMode, QsvQuizRankPageForm pageForm) { + StringBuilder sqlBuilder = new StringBuilder(""" + SELECT + t1.userId, + t1.userName, + t1.loginName, + t1.unitName, + t1.unionName, + u.mobile, + t1.totalScore, + t1.attemptDate, + sum(t1.totalScore) as sumScore + FROM + `qsv_user_answer_record` t1 + LEFT JOIN sys_user u ON u.id = t1.userId + $condition + """); + + Cnd cnd = Cnd.NEW(); + cnd.and("t1.activityId","=",activityId); + + if(StrUtil.isNotBlank(pageForm.getSearchKeyword())){ + SqlExpressionGroup seg = new SqlExpressionGroup(); + seg.orLike("t1.loginName", pageForm.getSearchKeyword()); + seg.orLike("t1.userName", pageForm.getSearchKeyword()); + cnd.and(seg); + } + + cnd.andEX("t1.unionId","=",pageForm.getUnionId()); + cnd.andEX("t1.unitId","=",pageForm.getUnitId()); + + if ("SCHEDULED".equals(mode)) { + if ("HIGH".equals(scoreStatisticsMode)) { + cnd.and("t1.isHighestScore","=",1); + } else if ("LAST".equals(scoreStatisticsMode)) { + cnd.and("t1.isLatestScore","=",1); + } + } else if ("REGULAR".equals(mode)) { + if ("HIGH".equals(scoreStatisticsMode)) { + cnd.and("t1.isHighestScore","=",1); + } else if ("LAST".equals(scoreStatisticsMode)) { + cnd.and("t1.isLatestScore","=",1); + } + } + + if (pageForm.getAttemptDate() != null) { + cnd.and("t1.attemptDate","=",pageForm.getAttemptDate()); + } + + cnd.groupBy("t1.userId"); + cnd.desc("t1.totalScore"); + + Sql sql = Sqls.create(sqlBuilder.toString()); + sql.setCondition(cnd); + return sql; + } +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvQuizServiceImpl.java b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvQuizServiceImpl.java new file mode 100644 index 0000000..2ec63b8 --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvQuizServiceImpl.java @@ -0,0 +1,36 @@ +package io.v.nutz.zhgh.qsv.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import io.v.nutz.base.service.impl.BaseServiceImpl; +import io.v.nutz.zhgh.qsv.dto.QsvCheckAnswerResult; +import io.v.nutz.zhgh.qsv.models.QsvSubject; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.service.QsvQuizService; +import org.nutz.dao.Dao; +import org.nutz.ioc.loader.annotation.IocBean; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +@IocBean(args = {"refer:dao"}) +public class QsvQuizServiceImpl extends BaseServiceImpl implements QsvQuizService { + + public QsvQuizServiceImpl(Dao dao) { + super(dao); + } + + @Override + public QsvCheckAnswerResult calcScore(String subjectId, List userSelectOptions) { + QsvSubject subject = dao().fetch(QsvSubject.class, subjectId); + List correctOptionIds = subject.getCorrectAnswer(); + + if(ObjectUtil.isEmpty(userSelectOptions)){ + userSelectOptions = new ArrayList<>(); + } + + //比较选项是否正确 不考虑顺序 + boolean equals = new HashSet<>(userSelectOptions).equals(new HashSet<>(correctOptionIds)); + return QsvCheckAnswerResult.builder().isCorrect(equals).score(equals ? subject.getScore() : 0).build(); + } +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvSurveyServiceImpl.java b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvSurveyServiceImpl.java new file mode 100644 index 0000000..4ee0ebc --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvSurveyServiceImpl.java @@ -0,0 +1,258 @@ +package io.v.nutz.zhgh.qsv.service.impl; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import io.v.nutz.base.service.impl.BaseServiceImpl; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.models.QsvOption; +import io.v.nutz.zhgh.qsv.models.QsvSubject; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.service.QsvSurveyService; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Workbook; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.dao.Sqls; +import org.nutz.dao.sql.Sql; +import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.Lang; +import org.nutz.lang.util.NutMap; + +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@IocBean(args = {"refer:dao"}) +public class QsvSurveyServiceImpl extends BaseServiceImpl implements QsvSurveyService { + public QsvSurveyServiceImpl(Dao dao) { + super(dao); + } + + @Override + public List report(String activityId) { + // 检查活动ID是否为空 + if (activityId == null || activityId.isEmpty()) { + throw new IllegalArgumentException("活动ID不能为空"); + } + + try { + // 查询用户答题记录 + List answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)); + // 将答题记录的扩展JSON转换为JSONObject列表 + List answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList(); + + // 查询活动题目列表 + List subjects = querySubjects(activityId); + // 获取题目ID列表 + List subjectIds = subjects.stream().map(v -> v.getString("id")).toList(); + + // 查询题目选项列表 + List options = dao().query(QsvOption.class, Cnd.where("subjectId", "in", subjectIds).asc("sortNum")); + // 按题目ID分组选项 + Map> optionsGroup = options.stream().collect(Collectors.groupingBy(QsvOption::getSubjectId)); + + // 处理每个题目 + for (NutMap subject : subjects) { + // 获取当前题目的选项列表 + List subjectOptions = Lang.collection2list(optionsGroup.get(subject.getString("id")), NutMap.class); + + // 获取题目类型 + String subjectType = subject.getString("type"); + + // 处理文本类型题目 + if ("text".equals(subjectType)) { + List texts = answerExtList.stream() + .filter(ext -> ObjectUtil.isNull(ext.get(subject.getString("id"), JSONObject.class))) + .map(ext -> ext.get(subject.getString("id"), JSONObject.class).getStr("text")) + .toList(); + subject.put("texts", texts); + } + // 处理单选类型题目 处理多选类型题目 + else if ("radio".equals(subjectType) || "checkbox".equals(subjectType)) { + subjectOptions.forEach(subjectOption -> { + long selectCount = answerExtList.stream() + .filter(ext -> + { + JSONObject jsonObject = ext.get(subject.getString("id"), JSONObject.class); + return jsonObject != null + && jsonObject.getJSONArray("optionIds") != null + && jsonObject.getJSONArray("optionIds").contains(subjectOption.getString("id")); + }) + .count(); + subjectOption.put("selectCount", selectCount); + }); + + long selectTotal = answerExtList.stream() + .filter(ext -> ext.containsKey(subject.getString("id"))) + .count(); + subject.put("selectTotal", selectTotal); + } + + // 添加选项到题目中 + subject.addv("options", subjectOptions); + } + + return subjects; + } catch (Exception e) { + log.error("报告生成失败", e); + throw new RuntimeException("报告生成失败", e); + } + } + + @Override + public void exportUserAnswerXlsx(String activityId, HttpServletResponse response) { + // 检查活动ID是否为空 + if (activityId == null || activityId.isEmpty()) { + throw new IllegalArgumentException("活动ID不能为空"); + } + + try { + // 查询活动信息 + QsvActivity activity = dao().fetch(QsvActivity.class, activityId); + if (activity == null) { + throw new IllegalArgumentException("活动不存在"); + } + + // 查询用户答题记录 + List answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)); + // 将答题记录的扩展JSON转换为JSONObject列表 + List answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList(); + + // 查询活动题目列表 + List subjects = dao().query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + // 将题目列表转换为Map + Map subjectMap = subjects.stream().collect(Collectors.toMap(QsvSubject::getId, v -> v)); + // 获取题目ID列表 + List subjectIds = subjects.stream().map(QsvSubject::getId).toList(); + + // 查询题目选项列表 + List options = dao().query(QsvOption.class, Cnd.where("subjectId", "in", subjectIds).asc("sortNum")); + // 将选项列表转换为Map + Map optionMap = options.stream().collect(Collectors.toMap(QsvOption::getId, v -> v)); + + // 构建Excel导出实体 + List excelExportEntities = buildExcelExportEntities(subjects); + // 构建答题记录列表 + List list = buildAnswerRecords(answerRecords, subjectMap, options); + + // 设置导出参数 + ExportParams exportParams = new ExportParams(); + exportParams.setType(ExcelType.XSSF); + // 导出Excel并下载 + try (Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelExportEntities, list)) { +// CommonDownloadUtil.download(activity.getTitle() + "答题记录" + ".xlsx", workbook, response); + } + } catch (Exception e) { + log.error("导出Excel失败", e); + throw new RuntimeException("导出Excel失败", e); + } + } + + @Override + public NutMap userAnswer(String activityId) { + // 检查活动ID是否为空 + if (activityId == null || activityId.isEmpty()) { + throw new IllegalArgumentException("活动ID不能为空"); + } + + try { + // 查询用户答题记录 + List answerRecords = dao().query(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId)); + // 将答题记录的扩展JSON转换为JSONObject列表 + List answerExtList = answerRecords.stream().map(QsvUserAnswerRecord::getExtJson).toList(); + + // 查询活动题目列表 + List subjects = dao().query(QsvSubject.class, Cnd.where("activityId", "=", activityId)); + // 将题目列表转换为Map + Map subjectMap = subjects.stream().collect(Collectors.toMap(QsvSubject::getId, v -> v)); + // 获取题目ID列表 + List subjectIds = subjects.stream().map(QsvSubject::getId).toList(); + + // 查询题目选项列表 + List options = dao().query(QsvOption.class, Cnd.where("subjectId", "in", subjectIds).asc("sortNum")); + // 将选项列表转换为Map + Map optionMap = options.stream().collect(Collectors.toMap(QsvOption::getId, v -> v)); + + // 构建Excel导出实体 + List excelExportEntities = buildExcelExportEntities(subjects); + // 构建答题记录列表 + List list = buildAnswerRecords(answerRecords, subjectMap, options); + + // 构建表格列信息 + List tableColumns = excelExportEntities.stream() + .map(v -> NutMap.NEW().addv("prop", v.getKey()).addv("label", v.getName())) + .toList(); + + return NutMap.NEW() + .addv("tableColumns", tableColumns) + .addv("tableData", list); + } catch (Exception e) { + throw new RuntimeException("获取用户答题记录失败", e); + } + } + + private List querySubjects(String activityId) { + Sql subjectSql = Sqls.create(""" + SELECT + id, + title, + type, + sortNum + FROM + qsv_subject + WHERE + activityId = @activityId + ORDER BY + sortNum ASC + """); + subjectSql.setParam("activityId", activityId); + return listMap(subjectSql); + } + + private List buildExcelExportEntities(List subjects) { + List excelExportEntities = new ArrayList<>(); + excelExportEntities.add(new ExcelExportEntity("工号", "loginName", 20)); + excelExportEntities.add(new ExcelExportEntity("姓名", "userName", 20)); + excelExportEntities.add(new ExcelExportEntity("单位", "unitName", 20)); + excelExportEntities.add(new ExcelExportEntity("分工会", "unionName", 20)); + excelExportEntities.add(new ExcelExportEntity("联系方式", "mobile", 20)); + for (QsvSubject subject : subjects) { + excelExportEntities.add(new ExcelExportEntity(subject.getTitle(), subject.getId(), 20)); + } + return excelExportEntities; + } + + private List buildAnswerRecords(List answerRecords, Map subjectMap, List options) { + return answerRecords.stream().map(record -> { + NutMap map = NutMap.NEW() + .addv("id", record.getId()) + .addv("loginName", record.getLoginName()) + .addv("userName", record.getUserName()) + .addv("unitName", record.getUnitName()) + .addv("unionName", record.getUnionName()); + JSONObject extJson = record.getExtJson(); + extJson.forEach((k, v) -> { + JSONObject jsonVal = (JSONObject) v; + + String type = subjectMap.get(k).getType(); + if (type.equals("text")) { + map.addv(k, jsonVal.getStr("text")); + } else if (type.equals("radio") || type.equals("checkbox")) { + JSONArray optionIds = jsonVal.getJSONArray("optionIds"); + String selectOptionTexts = options.stream().filter(option -> optionIds.contains(option.getId())) + .map(QsvOption::getText).collect(Collectors.joining(";")); + map.addv(k, selectOptionTexts); + } + }); + return map; + }).collect(Collectors.toList()); + } +} diff --git a/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvUserAnswerRecordServiceImpl.java b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvUserAnswerRecordServiceImpl.java new file mode 100644 index 0000000..9a84f1d --- /dev/null +++ b/src/main/java/io/v/nutz/zhgh/qsv/service/impl/QsvUserAnswerRecordServiceImpl.java @@ -0,0 +1,208 @@ +package io.v.nutz.zhgh.qsv.service.impl; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.json.JSONObject; +import io.v.nutz.base.service.impl.BaseServiceImpl; +import io.v.nutz.base.utils.Vi; +import io.v.nutz.sys.models.User; +import io.v.nutz.web.commons.utils.ShiroUtil; +import io.v.nutz.zhgh.qsv.models.QsvActivity; +import io.v.nutz.zhgh.qsv.models.QsvSubject; +import io.v.nutz.zhgh.qsv.models.QsvUserAnswerRecord; +import io.v.nutz.zhgh.qsv.param.QsvAnswerParam; +import io.v.nutz.zhgh.qsv.service.QsvUserAnswerRecordService; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Chain; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; +import org.nutz.ioc.loader.annotation.IocBean; + +import java.util.*; + +@IocBean(args = {"refer:dao"}) +public class QsvUserAnswerRecordServiceImpl extends BaseServiceImpl implements QsvUserAnswerRecordService { + public QsvUserAnswerRecordServiceImpl(Dao dao) { + super(dao); + } + + @Override + public QsvUserAnswerRecord insertRecord(String activityId, List subjectIds) { + QsvActivity activity = dao().fetch(QsvActivity.class, activityId); + String category = activity.getCategory(); + String mode = activity.getMode(); + String displayMode = activity.getDisplayMode(); + Boolean shuffleSubject = activity.getShuffleSubject(); + + //打乱题目顺序 + if (category.equals("QUIZ") && shuffleSubject) { + Collections.shuffle(subjectIds); + } + + QsvUserAnswerRecord record = new QsvUserAnswerRecord(); + record.setActivityId(activityId); + record.setUserId(ShiroUtil.getUserId()); + record.setSubjectIds(subjectIds); + + JSONObject extJson = new JSONObject(); + for (String subjectId : subjectIds) { + extJson.set(subjectId, Dict.create() + .set("optionIds", new ArrayList<>()) + .set("text", null) + .set("isCorrect", null) + ); + } + record.setExtJson(extJson); + + User user = dao().fetch(User.class, Cnd.where("id", "=", ShiroUtil.getUserId())); + record.setLoginName(user.getLoginname()); + record.setUserName(user.getUsername()); + record.setUnitId(user.getUnitid()); + record.setUnitName(user.getUnitname()); + record.setUnionId(user.getUnionid()); + record.setUnionName(user.getUnionname()); + + //问卷模式 + if (category.equals("QUIZ")) { + if (mode.equals("REGULAR")) { + //常规模式 抽取随机题目 + int count = dao().count(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId) + .and("userId", "=", ShiroUtil.getUserId())); + record.setRandomNumber(count + 1); + } else if (mode.equals("SCHEDULED")) { + //定时定题模式 + int count = dao().count(QsvUserAnswerRecord.class, Cnd.where("activityId", "=", activityId) + .and("userId", "=", ShiroUtil.getUserId()) + .and("attemptDate", "=", DateUtil.today())); + record.setRandomNumber(count + 1); + record.setAttemptDate(new Date()); + } + } + + dao().insert(record); + + return record; + } + + + @Override + public float calcScore(QsvAnswerParam qsvAnswerParam) { + List subjectIds = qsvAnswerParam.getSubjects().stream().map(QsvAnswerParam.Subject::getId).toList(); + List subjects = dao().query(QsvSubject.class, Cnd.where("id", "in", subjectIds)); + + float score = 0; + + for (QsvAnswerParam.Subject subject : qsvAnswerParam.getSubjects()) { + List userSelectOptionIds = subject.getUserSelectOptionIds(); + List correctAnswer = subjects.stream().filter(s -> s.getId().equals(subject.getId())).findFirst().get().getCorrectAnswer(); + + String[] array1 = userSelectOptionIds.toArray(new String[0]); + String[] array2 = correctAnswer.toArray(new String[0]); + Arrays.sort(array1); + Arrays.sort(array2); + boolean isCorrect = Arrays.equals(array1, array2); + + if (isCorrect) { + score += subjects.stream().filter(s -> s.getId().equals(subject.getId())).findFirst().get().getScore(); + } + } + + return score; + } + + @Override + @Aop(TransAop.READ_COMMITTED) + public void calcByScoreMode(String activityId, String userId) { + // 获取活动信息 + QsvActivity activity = dao().fetch(QsvActivity.class, activityId); + if (activity == null) { + throw new RuntimeException("活动找不到" + activityId); + } + + String mode = activity.getMode(); + String scoreMode = activity.getScoreMode(); + + if (mode.equals("SCHEDULED")) { + processScoreRecords(activityId, ShiroUtil.getUserId(), true, scoreMode.equals("HIGH")); + } else if (mode.equals("REGULAR")) { + processScoreRecords(activityId, ShiroUtil.getUserId(), false, scoreMode.equals("HIGH")); + } + } + + +// if (mode.equals("SCHEDULED")) { +// //定时定题模式 +// if (scoreStatisticsMode.equals("HIGHEST")) { +// //也只能取当天最高的分数 +// dao().update(QsvUserAnswerRecord.class, Chain.make("isHighestScore", 0), Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId).and(QsvUserAnswerRecord::getAttemptDate, "=", DateUtil.today())); +// //找出最大的那条 +// QsvUserAnswerRecord maxScoreRecord = dao().fetch(QsvUserAnswerRecord.class, Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId).and(QsvUserAnswerRecord::getAttemptDate, "=", DateUtil.today()).desc(QsvUserAnswerRecord::getTotalScore)); +// if (maxScoreRecord != null) { +// maxScoreRecord.setIsHighestScore(true); +// dao().update(maxScoreRecord); +// } +// } else if (scoreStatisticsMode.equals("LAST")) { +// //也只能取当天最新的记录 +// dao().update(QsvUserAnswerRecord.class, Chain.make("isLatestScore", 0), Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId).and(QsvUserAnswerRecord::getAttemptDate, "=", DateUtil.today())); +// //找出最大的那条 +// QsvUserAnswerRecord latestScoreRecord = dao().fetch(QsvUserAnswerRecord.class, Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId).and(QsvUserAnswerRecord::getAttemptDate, "=", DateUtil.today()).desc(QsvUserAnswerRecord::getUpdatedAt)); +// if (latestScoreRecord != null) { +// latestScoreRecord.setIsLatestScore(true); +// dao().update(latestScoreRecord); +// } +// } +// } else if (mode.equals("REGULAR")) { +// if (scoreStatisticsMode.equals("HIGHEST")) { +// dao().update(QsvUserAnswerRecord.class, Chain.make("isHighestScore", 0), Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId)); +// QsvUserAnswerRecord maxScoreRecord = dao().fetch(QsvUserAnswerRecord.class, Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId).desc(QsvUserAnswerRecord::getTotalScore)); +// if (maxScoreRecord != null) { +// maxScoreRecord.setIsHighestScore(true); +// dao().update(maxScoreRecord); +// } +// } else if (scoreStatisticsMode.equals("LAST")) { +// dao().update(QsvUserAnswerRecord.class, Chain.make("isLatestScore", 0), Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId)); +// QsvUserAnswerRecord latestScoreRecord = dao().fetch(QsvUserAnswerRecord.class, Cnd.where(QsvUserAnswerRecord::getActivityId, "=", activityId).and(QsvUserAnswerRecord::getUserId, "=", userId).desc(QsvUserAnswerRecord::getUpdatedAt)); +// if (latestScoreRecord != null) { +// latestScoreRecord.setIsLatestScore(true); +// dao().update(latestScoreRecord); +// } +// } +// } + + /** + * @param activityId 活动id + * @param userId 用户id + * @param isScheduled 是否是定时定题模式 + * @param isHighest 是否是最高分模式 + */ + private void processScoreRecords(String activityId, String userId, boolean isScheduled, boolean isHighest) { + String flagField = isHighest ? "isHighestScore" : "isLatestScore"; + String orderByField = isHighest ? "totalScore" : "updatedAt"; + + // 更新所有记录的标志位为0 + Cnd cnd = Cnd.where("activityId", "=", activityId) + .and("userId", "=", userId); + if (isScheduled) { + cnd.and("attemptDate", "=", DateUtil.today()); + } + + dao().update(QsvUserAnswerRecord.class, Chain.make(flagField, 0), cnd); + + // 找出符合条件的最大记录 + Cnd cnd2 = Cnd.where("activityId", "=", activityId).and("userId", "=", userId); + if (isScheduled) { + cnd2.and("attemptDate", "=", DateUtil.today()); + } + cnd2.desc(orderByField); + QsvUserAnswerRecord record = dao().fetch(QsvUserAnswerRecord.class, cnd2); + + if (record != null) { + record.setIsHighestScore(isHighest); + record.setIsLatestScore(!isHighest); + dao().update(record); + } + } + + +} diff --git a/src/main/java/io/v/nutz/zhgh/question/controller/QuestionH5Controller.java b/src/main/java/io/v/nutz/zhgh/question/controller/QuestionH5Controller.java index d73cca7..9008da1 100644 --- a/src/main/java/io/v/nutz/zhgh/question/controller/QuestionH5Controller.java +++ b/src/main/java/io/v/nutz/zhgh/question/controller/QuestionH5Controller.java @@ -1,17 +1,15 @@ package io.v.nutz.zhgh.question.controller; -import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.StrUtil; import cn.wizzer.framework.base.Result; import cn.wizzer.framework.page.Pagination; -import io.v.nutz.zhgh.activity.models.ActivityUserScope; import io.v.nutz.base.query.PageForm; +import io.v.nutz.web.commons.utils.ShiroUtil; +import io.v.nutz.zhgh.activity.models.ActivityUserScope; import io.v.nutz.zhgh.question.model.*; import io.v.nutz.zhgh.question.service.QuestionService; -import io.v.nutz.web.commons.utils.ShiroUtil; -import org.nutz.dao.Chain; +import org.apache.shiro.authz.annotation.RequiresAuthentication; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; @@ -26,9 +24,10 @@ import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; import org.springframework.web.bind.annotation.PathVariable; -import java.util.*; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -50,6 +49,7 @@ public class QuestionH5Controller { @At("/list") @Ok("beetl:/mobile/question/list.html") + @RequiresAuthentication public void list() { } @@ -74,6 +74,7 @@ public class QuestionH5Controller { @At("/answer/index") @Ok("beetl:/mobile/question/answer.html") + @RequiresAuthentication public void answerIndex() { } @@ -335,8 +336,25 @@ public class QuestionH5Controller { * @return {@link Object} */ @At("/answerInfo/?") - public Object answerInfo(String id) { - List replies = questionService.dao().query(Question_reply.class, Cnd.where("qid", "=", id).and("userId", "=", ShiroUtil.getPrincipalProperty("id"))); + public Object answerInfo(String id, + @Param(required = false, value = "currentDate") String currentDate, + @Param(required = false, value = "randomCount") Integer randomCount) { + Cnd cnd = Cnd.where("qid", "=", id).and("userId", "=", ShiroUtil.getPrincipalProperty("id")); + + Question question = dao.fetch(Question.class, id); + if (question.getCtMode() == 1) { + cnd.and("DATE(rtime)", "=", currentDate); + } else if (question.getCtMode() == 2) { + if (questionService.isSameDay(question.getStartTime(), question.getEndTime())) { + + } else { + cnd.and("DATE(rtime)", "=", currentDate); + } + } else if (question.getCtMode() == 3) { + + } + + List replies = questionService.dao().query(Question_reply.class, cnd); return Result.success(replies); } @@ -428,6 +446,11 @@ public class QuestionH5Controller { @Param(required = false, value = "randomCount") Integer randomCount) { Question question = dao.fetch(Question.class, questionId); if (question.getCanRepeatedAnswer()) { + //0次则不限制 + if (question.getCanRepeatedAnswerNum() == 0) { + return Result.success(true); + } + if (question.getCtMode() == 3) { int count = dao.count(QuestionUserScore.class, Cnd.where("questionId", "=", questionId).and("userId", "=", ShiroUtil.getUserId())); return Result.success(count < question.getCanRepeatedAnswerNum()); diff --git a/src/main/java/io/v/nutz/zhgh/question/service/QuestionService.java b/src/main/java/io/v/nutz/zhgh/question/service/QuestionService.java index 0abcc79..7ad1dc0 100644 --- a/src/main/java/io/v/nutz/zhgh/question/service/QuestionService.java +++ b/src/main/java/io/v/nutz/zhgh/question/service/QuestionService.java @@ -1,7 +1,8 @@ package io.v.nutz.zhgh.question.service; -import io.v.nutz.zhgh.question.model.Question; +import io.v.nutz.base.service.BaseService; import io.v.nutz.base.service.ViService; +import io.v.nutz.zhgh.question.model.Question; import io.v.nutz.zhgh.question.model.Question_reply; import org.nutz.lang.util.NutMap; diff --git a/src/main/java/io/v/nutz/zhgh/question/service/impl/QuestionServiceImpl.java b/src/main/java/io/v/nutz/zhgh/question/service/impl/QuestionServiceImpl.java index ffba2c1..363873f 100644 --- a/src/main/java/io/v/nutz/zhgh/question/service/impl/QuestionServiceImpl.java +++ b/src/main/java/io/v/nutz/zhgh/question/service/impl/QuestionServiceImpl.java @@ -2,13 +2,13 @@ package io.v.nutz.zhgh.question.service.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import io.v.nutz.base.service.impl.ViServiceImpl; import io.v.nutz.web.commons.utils.ShiroUtil; import io.v.nutz.zhgh.question.model.*; import io.v.nutz.zhgh.question.service.QuestionIssueService; import io.v.nutz.zhgh.question.service.QuestionReplyService; import io.v.nutz.zhgh.question.service.QuestionService; import io.v.nutz.zhgh.question.service.QuestionWorService; -import io.v.nutz.base.service.impl.ViServiceImpl; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Chain; import org.nutz.dao.Cnd; diff --git a/src/main/resources/views/mobile/qsv/index.html b/src/main/resources/views/mobile/qsv/index.html new file mode 100644 index 0000000..002c6fe --- /dev/null +++ b/src/main/resources/views/mobile/qsv/index.html @@ -0,0 +1,131 @@ + + +
+ + + + + + + + + + +
+
+
+ +
+
+
+ {{row.title}} +
+
+ 开始时间:{{row.startTime}} +
+ 结束时间:{{row.endTime}} +
+
+
+
+
+ +
+
+ + + + diff --git a/src/main/resources/views/mobile/qsv/quiz/index.html b/src/main/resources/views/mobile/qsv/quiz/index.html new file mode 100644 index 0000000..72d3345 --- /dev/null +++ b/src/main/resources/views/mobile/qsv/quiz/index.html @@ -0,0 +1,370 @@ + + + + +
+ +
+ + +
⏰{{ remainingTime }}s
+

{{ activity.title }}

+
+ +
+
+

{{index+1}}、{{ subject.title }}

+
+ 多选 + 单选 + {{subject.score}}分 +
+ + + + + + + + +
+
+ {{isEnd ? '已结束' : '提交'}} +
+
+
+
+ + + + diff --git a/src/main/resources/views/mobile/qsv/quiz/result.html b/src/main/resources/views/mobile/qsv/quiz/result.html new file mode 100644 index 0000000..2d7261c --- /dev/null +++ b/src/main/resources/views/mobile/qsv/quiz/result.html @@ -0,0 +1,330 @@ + + + + +
+ +
+ +

{{ activity.title }}

+
+
+
{{totalScore}}
+ +
+
+
+ +
+
+

{{index+1}}、{{ subject.title }}

+
+ 多选 + 单选 + {{subject.score}}分 +
+ + + + + + + + + + +
+
+ + 回答正确 +
+
+ + 回答错误 +
+
+
+ +
+ 查看全部答题记录 +
+
+ + +
+
+
+ {{ item.date }} + 最高分 + 最新得分 +
+
答题分数:{{ item.totalScore }}
+
答题用时:{{ item.answerTime | formatSeconds }}
+
+
+
+
+
+ + + + diff --git a/src/main/resources/views/mobile/qsv/survey/index.html b/src/main/resources/views/mobile/qsv/survey/index.html new file mode 100644 index 0000000..484009f --- /dev/null +++ b/src/main/resources/views/mobile/qsv/survey/index.html @@ -0,0 +1,292 @@ + + + + +
+ +
+

{{ activity.title }}

+
+
+

{{index+1}}、{{ subject.title }}

+
+ 多选 + 单选 + {{subject.score}}分 +
+ + + + + + + + + + + + +
+ +
+
+
+ {{isEnd ? '已结束' : '提交'}} +
+
+
+
+ + + + diff --git a/src/main/resources/views/mobile/question/poll.html b/src/main/resources/views/mobile/question/poll.html index e7fa102..bcc2c84 100644 --- a/src/main/resources/views/mobile/question/poll.html +++ b/src/main/resources/views/mobile/question/poll.html @@ -223,11 +223,6 @@ layout("/mobile/platform.html"){ {{ wor.wsm }} - -
-
{{file.filename}}
-
-
@@ -309,10 +304,6 @@ layout("/mobile/platform.html"){ } }, methods: { - fileClick(file){ - let pdfStreamUrl = encodeURIComponent(FILE_STREAM_PREVIEW_ADDRESS + '?id=' + file.id) //将路径转码 - window.open('/assets/platform/plugins/pdfJs/web/viewer.html?file=' + pdfStreamUrl,file. filename) - }, async getQuestionData() { const resp = await $.get('/mobile/question/h5/questionInfo/' + this.id) this.questionData = resp.data diff --git a/src/main/resources/views/platform/qsv/activity/basicForm.js b/src/main/resources/views/platform/qsv/activity/basicForm.js new file mode 100644 index 0000000..b3f20ad --- /dev/null +++ b/src/main/resources/views/platform/qsv/activity/basicForm.js @@ -0,0 +1,252 @@ +const basicForm = { + template: /*language=HTML*/ ` + +
+ + + + + {{item.label}} + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + 设置 + +
+
+ + +
+
+ + +
+ `, + dicts: ["ACTIVITY_QSV_CATEGORY", "ACTIVITY_QSV_MODE", "ACTIVITY_QSV_REPEAT_MODE", "ACTIVITY_QSV_SCORE_MODE"], + components: { + "drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue") + }, + data() { + return { + dialogVisible: false, + formData: {}, + formRules: { + category: [{ required: true, message: "请选择类型", trigger: "change" }], + title: [{ required: true, message: "请输入标题", trigger: "change" }], + startTime: [{ required: true, message: "请选择开始时间", trigger: "change" }], + endTime: [{ required: true, message: "请选择结束时间", trigger: "change" }], + mode: [{ required: true, message: "请选择出题模式", trigger: "change" }], + scoreMode: [{ required: true, message: "请选择得分统计模式", trigger: "change" }] + }, + activityGroupOptions: [] + } + }, + methods: { + onOpen(id) { + this.dialogVisible = true + this.getActivityGroup() + if (id) { + this.$axios.post("/platform/qsv/activity/findOne", { id }).then((res) => { + if (res.code === 0) { + this.formData = res.data + } + }) + } else { + this.formData = {} + } + }, + onSubmit() { + this.$refs.formRef.validate((valid) => { + if (valid) { + this.$axios.post("/platform/qsv/activity/save", this.formData).then((res) => { + if (res.code === 0) { + this.$message.success(res.msg) + this.dialogVisible = false + this.$emit("refresh", null) + } + }) + } + }) + }, + getActivityGroup() { + this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => { + if (res.code === 0) { + this.activityGroupOptions = res.data + } + }) + } + } +} diff --git a/src/main/resources/views/platform/qsv/activity/index.html b/src/main/resources/views/platform/qsv/activity/index.html new file mode 100644 index 0000000..4e9dfc1 --- /dev/null +++ b/src/main/resources/views/platform/qsv/activity/index.html @@ -0,0 +1,100 @@ + + +
+ + + + + + + + + + + + + + 新增 + + + + + + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/qsv/activity/optionImg.js b/src/main/resources/views/platform/qsv/activity/optionImg.js new file mode 100644 index 0000000..90a44b3 --- /dev/null +++ b/src/main/resources/views/platform/qsv/activity/optionImg.js @@ -0,0 +1,83 @@ +const optionImg = { + template: /*language=HTML*/ ` + +
+ + + + +
+
+ 请上传图片 +
+ +
+ `, + data() { + return { + visible: false, + img: null, + ext: null + } + }, + methods: { + onOpen(img = null, ext) { + this.img = img + this.ext = ext + this.visible = true + }, + handleSuccess(response, file, fileList) { + console.log(response) + console.log(file) + console.log(fileList) + if (response.code === 0) { + this.img = response.data + // this.img = "/platform/sys/file/download?id=t38dmq4beuhrupqb54prl52t4u" + this.$message.success("图片上传成功") + } + }, + beforeUpload(file) {}, + onConfirm() { + this.$emit("confirm", { + img: this.img, + ext: this.ext + }) + this.visible = false + } + }, + style: /*language=CSS*/ ` + .avatar-uploader .el-upload { + border: 1px dashed #d9d9d9; + border-radius: 6px; + cursor: pointer; + position: relative; + overflow: hidden; + } + + .avatar-uploader .el-upload:hover { + border-color: #409EFF; + } + + .avatar-uploader-icon { + font-size: 28px; + color: #8c939d; + width: 178px; + height: 178px; + line-height: 178px; + text-align: center; + } + + .avatar { + width: 178px; + height: 178px; + display: block; + } + ` +} diff --git a/src/main/resources/views/platform/qsv/activity/subjectForm.js b/src/main/resources/views/platform/qsv/activity/subjectForm.js new file mode 100644 index 0000000..36d289c --- /dev/null +++ b/src/main/resources/views/platform/qsv/activity/subjectForm.js @@ -0,0 +1,494 @@ + + +const subjectForm = { + template: /*language=HTML*/ ` +
+ +
+ + +
+
+
+ + 第 {{subjectIndex + 1}} 题 + + +
+ +
+
+ 题目类型: + + + 单选题 + + + 多选题 + + + 填空题 + + + + + +
+ + + + + + + + + +
+ 题目分数: + + + +
+ +
+ 显示日期: + + +
+
+
+ + + +
+ + + + + + + + + +
+
+
+
+ +
+
+ +
+ + + 添加题目 + + + 保存题目 + +
+ + +
+ `, + components: { + 'option-img': optionImg, + }, + data() { + return { + id: null, + visible: false, + formData: {}, + subjects: [], + previewDialogVisible: false, + activity: {}, + formRules: {} + } + }, + methods: { + onOpen(id) { + this.visible = true + if (id) { + this.id = id + this.$axios.post("/platform/qsv/activity/findOne", {id}).then((res) => { + if (res.code === 0) { + this.activity = res.data + } + }) + this.$axios.post("/platform/qsv/activity/listSubjects", {activityId: id}).then((res) => { + if (res.code === 0) { + this.subjects = res.data + } + }) + } + }, + + //生成随机id + generateId() { + return Date.now() + Math.random().toString(36).substr(2, 9) + }, + + //添加题目 + addSubject() { + this.subjects.push({ + id: this.generateId(), + title: "", + hint: "", + type: "radio", + score: 0, + displayDate: "", + options: [ + { + id: this.generateId(), + text: "选项1", + hint: "", + imgUrl: null, + isCorrect: false + }, + { + id: this.generateId(), + text: "选项2", + hint: "", + imgUrl: null, + isCorrect: false + } + ], + }) + }, + + //题目类型切换 + subjectTypeChange(subject, subjectIndex){ + if(subject.type==='text'){ + subject.options = [] + } + }, + + //删除题目 + removeSubject(index) { + this.$confirm("确认删除该题目?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning" + }) + .then(() => { + this.subjects.splice(index, 1) + this.$message.success("删除成功") + }) + .catch(() => { + }) + }, + + //添加选项 + addOption(subject) { + subject.options.push({ + id: this.generateId(), + text: "选项" + subject.options.length + 1, + hint: "", + imgUrl: null, + isCorrect: false + }) + }, + + //删除选项 + removeOption(subject, optionIndex) { + subject.options.splice(optionIndex, 1) + }, + + //打开选项上传图片 + openOptionImg(subjectIndex, optionIndex, option) { + this.$refs.optionImgRef.onOpen(option.imgUrl, { + subjectIndex, + optionIndex + }) + }, + + //选项图片上传成功回调 + onOptionImgConfirm({img, ext}) { + this.$set(this.subjects[ext.subjectIndex].options[ext.optionIndex], "imgUrl", img) + }, + + //删除选项图片 + removeOptionImg(subjectIndex, optionIndex){ + this.$set(this.subjects[subjectIndex].options[optionIndex], "imgUrl", null) + console.log(this.subjects[subjectIndex]) + }, + + //保存 + saveQuestionnaire() { + this.$axios + .post("/platform/qsv/activity/saveSubjects", { + activityId: this.id, + subjects: JSON.stringify(this.subjects) + }) + .then((res) => { + if (res.code === 0) { + this.visible = false + this.$message.success(res.msg) + this.$emit("refresh", null) + } + }) + } + }, + style: /*language=CSS*/ ` + /*.subject-form-dialog,.subject-form-dialog .el-dialog__body {*/ + /* background: rgb(248, 249, 250);*/ + /*}*/ + + .questionnaire-editor { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + } + + .editor-header { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); + margin-bottom: 20px; + } + + .subject-item { + background: white; + border-radius: 8px; + padding: 25px; + margin-bottom: 20px; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); + transition: all 0.3s ease; + border: 1px dashed #d9d9d9; + } + + .subject-item:hover { + box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1); + /*transform: translateY(-1px);*/ + } + + .subject-header { + border-bottom: 1px solid #eee; + padding-bottom: 15px; + margin-bottom: 15px; + } + + .subject-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid #eee; + } + + .toolbar { + background: white; + padding: 15px; + border-radius: 8px; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); + z-index: 1000; + } + + .toolbar-btn { + margin: 5px 0; + width: 100%; + } + + .option-item { + background: #f8f9fa; + border-radius: 4px; + padding: 10px; + margin-bottom: 10px; + display: flex; + align-items: center; + } + + .option-drag-handle { + cursor: move; + margin-right: 10px; + color: #909399; + } + + .option-content { + flex-grow: 1; + margin-right: 10px; + } + + .option-tools { + display: flex; + align-items: center; + gap: 10px; + } + + .preview-dialog { + max-width: 800px; + margin: 0 auto; + } + + .date-visible { + margin-left: 20px; + } + + .subject-meta { + display: flex; + flex-wrap: wrap; + gap: 15px; + margin-top: 15px; + background: #f8f9fa; + padding: 10px; + border-radius: 4px; + } + + .image-preview { + max-width: 200px; + max-height: 200px; + margin-top: 10px; + border-radius: 4px; + border: 1px solid #dcdfe6; + } + + .drag-handler { + cursor: move; + color: #909399; + margin-right: 10px; + } + + .meta-info { + color: #909399; + font-size: 12px; + margin-top: 5px; + } + + .subject-type-icon { + margin-right: 5px; + } + + + .option-img-box{ + position: relative; + width: 40px; + height: 40px; + cursor: pointer; + flex-shrink: 0 + } + + .option-img-box:hover .el-icon-remove { + display: block; + } + + .option-img-box img{ + width: 100%; + height: 100%; + } + + .option-img-box .el-icon-remove{ + position: absolute; + top: -7px; + right: -7px; + color: red; + display: none; + } + + .option-img-box .el-icon-remove:hover{ + transform: scale(1.1); + } + + ` +} diff --git a/src/main/resources/views/platform/qsv/quiz/rank.html b/src/main/resources/views/platform/qsv/quiz/rank.html new file mode 100644 index 0000000..452577a --- /dev/null +++ b/src/main/resources/views/platform/qsv/quiz/rank.html @@ -0,0 +1,111 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 导出xlsx + + + + + + + + + + + + + + +
+ + + + diff --git a/src/main/resources/views/platform/qsv/survey/answer.js b/src/main/resources/views/platform/qsv/survey/answer.js new file mode 100644 index 0000000..e85ec3d --- /dev/null +++ b/src/main/resources/views/platform/qsv/survey/answer.js @@ -0,0 +1,62 @@ +const answer = { + /*language=HTML*/ + template: ` + + + 导出xlsx + + + + + + + + + + `, + data() { + return { + dialogVisible: false, + tableColumns: [], + tableData: [], + activityId: null + } + }, + methods: { + onOpen(activityId) { + this.activityId = activityId + this.dialogVisible = true + this.list() + }, + + list() { + this.$axios.post("/platform/qsv/survey/userAnswer", { activityId: this.activityId }).then((res) => { + if (res.code === 0) { + this.tableColumns = res.data.tableColumns + this.tableData = res.data.tableData + } + }) + }, + + onDelete(id) { + this.$confirm("确定要删除吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning" + }).then(() => { + this.$axios.post("/platform/qsv/survey/deleteUserAnswer", { id }).then((res) => { + if (res.code === 0) { + this.$message.success(res.msg) + this.list() + } + }) + }) + }, + + exportUserAnswerXlsx() { + this.$downLoad("/platform/qsv/survey/exportUserAnswerXlsx", { activityId: this.activityId }) + } + } +} diff --git a/src/main/resources/views/platform/qsv/survey/index.html b/src/main/resources/views/platform/qsv/survey/index.html new file mode 100644 index 0000000..f0f48a3 --- /dev/null +++ b/src/main/resources/views/platform/qsv/survey/index.html @@ -0,0 +1,70 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/src/main/resources/views/platform/qsv/survey/report.js b/src/main/resources/views/platform/qsv/survey/report.js new file mode 100644 index 0000000..a7a8a38 --- /dev/null +++ b/src/main/resources/views/platform/qsv/survey/report.js @@ -0,0 +1,101 @@ +const report = { + /*language=HTML*/ + template: ` + +
+
+
+ 第{{subjectIndex+1}}题: {{subject.title}} + [填空题] + [单选题] + [多选题] +
+ +
+ {{subject.texts.join('、')}} +
+ +
+ + + + + + + + + + +
+ +
+
+ + + + + + + + + + + +
+ `, + dicts: ["ACTIVITY_QSV_CATEGORY"], + data() { + return { + dialogVisible: false, + activityId: null, + report: null, + subjects: [], + optionVisible: false, + optionUsers: [] + } + }, + methods: { + onOpen(activityId) { + this.dialogVisible = true + this.activityId = activityId + this.$axios.post("/platform/qsv/survey/report", { activityId }).then((res) => { + if (res.code === 0) { + this.subjects = res.data + } + }) + }, + openDetail(subjectId, optionId) { + console.log(subjectId) + console.log(optionId) + this.$axios + .post("/platform/qsv/survey/selectOptionUsers", { + activityId: this.activityId, + subjectId, + optionId + }) + .then((res) => { + if (res.code === 0) { + this.optionVisible = true + this.optionUsers = res.data + } + }) + } + }, + style: /*language=CSS*/ ` + .subject{ + margin-bottom: 20px; + } + .subject .title{ + margin-bottom: 5px; + } + .subject .type{ + color: #a6a6a6; + margin-left: 10px; + } + ` +}