当前位置: 首页 > news >正文

微服务的编程测评系统9-竞赛新增-竞赛编辑

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. 竞赛新增
    • 1.1 竞赛基本信息增加-后端开发
    • 1.2 竞赛新增题目-后端
    • 1.3 竞赛基本信息-前端
    • 1.4 竞赛新增题目-前端
  • 2. 竞赛编辑
    • 2.1 竞赛详情-后端
    • 2.2 竞赛详情-前端
    • 2.3 竞赛基本信息编辑-后端
    • 2.4 竞赛基本信息编辑
    • 2.5 题目信息编辑
    • 2.6 竞赛题目信息编辑-前端
  • 总结


前言

1. 竞赛新增

1.1 竞赛基本信息增加-后端开发

可以添加没有题目的竞赛,后期来添加题目
但是没有题目的竞赛不能发布,可以保存,保存的题目可以在列表看到
然后是竞赛的开始时间必须在当前时间以后
然后是结束时间必须在开始时间之后,还有就是竞赛名称不要一样

@Data
public class ExamAddDTO {private String title;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime startTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime endTime;
}

DTO字段加上 @JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”)
意思就是前端可以传入字符串类型的事件
VO加上 @JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”)
意思就是返回给前端的时间是字符串类型

    EXAM_TITLE_HAVE_EXITED(3201,"竞赛标题重复"),START_TIME_BEFORE_NOW_TIME(3202,"开始时间在当前时间之前"),START_TIME_BEFORE_END_TIME(3203,"开始时间在结束时间之后");
    @Overridepublic int add(ExamAddDTO examAddDTO) {//校验竞赛名字是否重复List<Exam> exams = examMapper.selectList(new LambdaQueryWrapper<Exam>().eq(Exam::getTitle, examAddDTO.getTitle()));if(CollectionUtil.isNotEmpty(exams)){throw new ServiceException(ResultCode.EXAM_TITLE_HAVE_EXITED);}if(examAddDTO.getStartTime().isBefore(LocalDateTime.now())){throw new ServiceException(ResultCode.START_TIME_BEFORE_NOW_TIME);}if(examAddDTO.getEndTime().isBefore(examAddDTO.getStartTime())){throw new ServiceException(ResultCode.START_TIME_BEFORE_END_TIME);}Exam exam = new Exam();BeanUtil.copyProperties(examAddDTO,exam);examMapper.insert(exam);return 0;}

1.2 竞赛新增题目-后端

新增竞赛可以新增竞赛基本信息,没有题目
也可以新增基本信息,有题目,反正必须要有基本信息
题目可以后续去增加

在这里插入图片描述
添加题目之前,要先保存基本信息add,先生成不包含题目的竞赛
然后再新增题目,插入表exam_question
这样就可以利用上一个生成的接口了
添加题目成功以后,可以点击提交,就添加成功了,然后可以点击暂不发布的按钮,就可以了
在这里插入图片描述

在这里插入图片描述
先选择的题目,题目顺序靠前,或者order较小

@Data
public class ExamQuestionAddDTO {private Long examId;private LinkedHashSet<Long> questionIds;
}

这里我们用LinkedHashSet来存储questionId
因为List不能去重,questionId是不能重复的
因为Set是无序的,不会按照我们前端传入的顺序来操作,所以也不用这个

    @Overridepublic boolean questionAdd(ExamQuestionAddDTO examQuestionAddDTO) {//先看竞赛Id存不存在Exam exam = getExamById(examQuestionAddDTO.getExamId());//然后是看题目Id是否正确LinkedHashSet<Long> questionIds = examQuestionAddDTO.getQuestionIds();int count =1;for(Long questionId : questionIds){//先查询这个ID存不存在Question question = questionMapper.selectById(questionId);if(question == null){throw new ServiceException(ResultCode.QUESTION_ID_NOT_EXIST);}ExamQuestion examQuestion = new ExamQuestion();examQuestion.setExamId(exam.getExamId());examQuestion.setQuestionId(questionId);examQuestion.setQuestionOrder(count++);examQuestionMapper.insert(examQuestion);}return true;}

但是这样写有问题
第一个问题就是会频繁的访问数据库
第二个问题就是有一个questionId不存在的时候,整个流程都应该取消,但是这个无法实现
所以我们可以使用mybatisPlus的整体id查询方法或者整体插入的方法

mybatisPlus的selectByIds方法就是根据一系列id来查询,只用访问一次数据库可就可以了

然后是整体插入,这个方法不在BaseMapper中,
整体插入是saveBatch方法,我们可以双击shift+shift,来搜索这个方法

在这里插入图片描述
然后是往下找实现的方法
在这里插入图片描述
这个是抽象类,还不行
在这里插入图片描述
最后找到这个实现类
方法就在这里,我们只需要继承这个类,然后就可以使用方法了

public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper,ExamQuestion> implements IExamService {

或者这样写也是可以的

public class ExamServiceImpl extends ServiceImpl<BaseMapper<ExamQuestion>,ExamQuestion> implements IExamService {

在这里插入图片描述
saveBatch这个方法就是对一个集合直接进行插入了

    @Overridepublic boolean questionAdd(ExamQuestionAddDTO examQuestionAddDTO) {//先看竞赛Id存不存在Exam exam = getExamById(examQuestionAddDTO.getExamId());//然后是看题目Id是否正确LinkedHashSet<Long> questionIds = examQuestionAddDTO.getQuestionIds();List<Question> questionList = questionMapper.selectByIds(questionIds);if(CollectionUtil.isEmpty(questionList)){return true;}if(questionList.size()<questionIds.size()){throw new ServiceException(ResultCode.QUESTION_ID_NOT_EXIST);}return saveExamQuestionList(questionIds, exam);}private boolean saveExamQuestionList(LinkedHashSet<Long> questionIds, Exam exam) {List<ExamQuestion> examQuestionList  = new ArrayList<>();int count =1;for(Long questionId : questionIds){//先查询这个ID存不存在Question question = questionMapper.selectById(questionId);if(question == null){throw new ServiceException(ResultCode.QUESTION_ID_NOT_EXIST);}ExamQuestion examQuestion = new ExamQuestion();examQuestion.setExamId(exam.getExamId());examQuestion.setQuestionId(questionId);examQuestion.setQuestionOrder(count++);examQuestionList.add(examQuestion);}return saveBatch(examQuestionList);}private Exam getExamById(Long examId) {Exam exam = examMapper.selectById(examId);if(exam == null){throw new ServiceException(ResultCode.EXAM_ID_NOT_EXIST);}return exam;}

这样就OK了

1.3 竞赛基本信息-前端

在exam.vue中

function onAddExam(){router.push("/oj/layout/updateExam")
}
<template><div class="add-exam-component-box"><div class="add-exam-component"><!-- 竞赛信息模块 --><div class="exam-base-info-box"><!-- 标题 --><div class="exam-base-title"><span class="base-title">{{ type === 'edit' ? '编辑竞赛' : '添加竞赛' }}</span><span class="go-back" @click="goBack">返回</span></div><!-- 基本信息 --><div class="exam-base-info"><div class="group-box"><div class="group-item"><div class="item-label required">竞赛名称</div><div><el-input v-model="formExam.title" style="width:420px" placeholder="请填写竞赛名称"></el-input></div></div></div><div class="group-box"><div class="group-item"><div class="item-label required">竞赛周期</div><div><el-date-picker v-model="formExam.examDate" :disabledDate="disabledDate" type="datetimerange"start-placeholder="竞赛开始时间" end-placeholder="竞赛结束时间" value-format="YYYY-MM-DD HH:mm:ss" /></div></div></div><div class="group-box"><div class="group-item"><el-button class="exam-base-info-button" type="primary" plain @click="saveBaseInfo">保存</el-button></div></div></div></div><!-- 添加竞赛题目 --><div class="exam-select-question-box"><el-button class="exam-add-question" :icon="Plus" type="text" @click="addQuestion()">添加题目</el-button><el-table height="136px" :data="formExam.examQuestionList" class="question-select-list"><el-table-column prop="questionId" width="180px" label="题目id" /><el-table-column prop="title" :show-overflow-tooltip="true" label="题目标题" /><el-table-column prop="difficulty" width="80px" label="题目难度"><template #default="{ row }"><div v-if="row.difficulty === 1" style="color:#3EC8FF;">简单</div><div v-if="row.difficulty === 2" style="color:#FE7909;">中等</div><div v-if="row.difficulty === 3" style="color:#FD4C40;">困难</div></template></el-table-column><el-table-column label="操作" width="80px"><template #default="{ row }"><el-button circle type="text" @click="deleteExamQuestion(formExam.examId, row.questionId)">删除</el-button></template></el-table-column></el-table></div><!-- 题目配置模块 题目列表勾选加序号 --><div><el-dialog v-model="dialogVisible"><div class="exam-list-box"><div class="exam-list-title required">选择竞赛题目</div><el-form inline="true"><el-form-item label="题目难度"><selector v-model="params.difficulty" style="width: 120px;"></selector></el-form-item><el-form-item label="题目名称"><el-input v-model="params.title" placeholder="请您输入要搜索的题目标题" /></el-form-item><el-form-item><el-button @click="onSearch" plain>搜索</el-button><el-button @click="onReset" plain type="info">重置</el-button></el-form-item></el-form><!-- 题目列表 --><el-table :data="questionList" @select="handleRowSelect"><el-table-column type="selection"></el-table-column><el-table-column prop="questionId" label="题目id" /><el-table-column prop="title" label="题目标题" /><el-table-column prop="difficulty" label="题目难度"><template #default="{ row }"><div v-if="row.difficulty === 1" style="color:#3EC8FF;">简单</div><div v-if="row.difficulty === 2" style="color:#FE7909;">中等</div><div v-if="row.difficulty === 3" style="color:#FD4C40;">困难</div></template></el-table-column></el-table><!-- 分页区域 --><div class="exam-question-list-button"><el-pagination background size="small" layout="total, sizes, prev, pager, next, jumper" :total="total"v-model:current-page="params.pageNum" v-model:page-size="params.pageSize":page-sizes="[1, 5, 10, 15, 20]" @size-change="handleSizeChange"@current-change="handleCurrentChange" /><el-button class="question-select-submit" type="primary" plain@click="submitSelectQuestion">提交</el-button></div></div></el-dialog></div><!-- 提交任务区域 --><div class="submit-box absolute"><el-button type="info" plain @click="goBack">取消</el-button><el-button type="primary" plain @click="publishExam">发布竞赛</el-button></div></div></div>
</template><script setup>
import Selector from "@/components/QuestionSelector.vue"
import router from '@/router'
import { reactive, ref } from "vue"
import { Plus } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router';const type = useRoute().query.type
const formExam = reactive({examId: '',title: '',examDate: ''
})const params = reactive({pageNum: 1,pageSize: 10,difficulty: '',title: ''
})</script><style lang="scss" scoped>
.add-exam-component-box {height: 100%;overflow: hidden;position: relative;
}.exam-list-box {background: #fff;padding: 20px 24px;.question-select-submit {margin-left: 0;margin-top: 20px;width: 100%;}.exam-list-title {font-size: 14px;color: rgba(0, 0, 0, 0.85);position: relative;padding: 15px 20px;padding-top: 0;&.required::before {position: absolute;content: '*';font-size: 20px;color: red;left: 10px;}}
}.add-exam-component {width: 100%;background: #fff;padding-bottom: 120px;overflow-y: auto;box-sizing: border-box;height: calc(100vh - 50px);margin-top: -10px;.exam-select-question-box {background: #fff;border-bottom: 1px solid #fff;border-radius: 2px;width: 100%;.exam-add-question {font-size: 14px;float: right;margin: 10px 20px 5px 0;}.question-select-list {margin: 0 0 20px 0;height: 200px;}}.exam-base-info-box {background: #fff;border-bottom: 1px solid #fff;border-radius: 2px;margin-bottom: 10px;width: 100%;box-sizing: border-box;.exam-base-title {width: 100%;box-sizing: border-box;height: 52px;border-bottom: 1px solid #e9e9e9;display: flex;justify-content: space-between;align-items: center;.base-title {font-size: 16px;font-weight: 500;color: #333333;}.go-back {color: #999;cursor: pointer;}}.exam-base-info {box-sizing: border-box;border-bottom: 1px solid #e9e9e9;}.mesage-list-content {box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.1);background-color: rgba(255, 255, 255, 1);border-radius: 10px;width: 1200px;margin-top: 20px;}}.group-box {display: flex;align-items: center;justify-content: space-between;width: calc(100% - 64px);margin: 24px 0;.group-item {display: flex;align-items: center;width: 100%;.exam-base-info-button {margin-left: 104px;width: 420px;}.item-label {font-size: 14px;font-weight: 400;width: 94px;text-align: left;color: rgba(0, 0, 0, 0.85);position: relative;padding-left: 10px;&.required::before {position: absolute;content: '*';font-size: 20px;color: red;left: 0px;top: -2px;}}}}.submit-box {display: flex;align-items: center;justify-content: center;background: transparent;&.absolute {position: absolute;width: calc(100% - 48px);bottom: 0;background: #fff;z-index: 999;}}
}
</style><style>
.w-e-text-container {min-height: 142px;
}
</style>
          <span class="base-title">{{ type === 'edit' ? '编辑竞赛' : '添加竞赛' }}</span>

这个是由路由的参数决定的

const type = useRoute().query.type

这个可以获得路由的参数

function goBack(){router.go(-1)
}

这个是返回上一级

async function saveBaseInfo() {const fd = new FormData()for (let key in formExam) {if (key === 'examDate') {fd.append('startTime', formExam.examDate[0]);fd.append('endTime', formExam.examDate[1]);} else if (key !== 'startTime' && key !== 'endTime') {fd.append(key, formExam[key])}}  await examAddService(fd)ElMessage.success('基本信息保存成功')
}

这里的时间没有转为字符串,但是会自动转为字符串的,因为Json中没有Date类型

export function examAddService(params = {}) {return service({url: "/exam/add",method: "post",data: params,});
}

这样就成功了

1.4 竞赛新增题目-前端

在这里插入图片描述

        <el-button class="exam-add-question" :icon="Plus" type="text" @click="addQuestion()">添加题目</el-button>

添加题目,会先把题目数据加载到弹出框中,然后用表格的形式展示数据

        <el-dialog v-model="dialogVisible">
         examMapper.insert(exam) ;return exam.getExamId();

修改后端,保存成功以后,要返回竞赛Id

async function saveBaseInfo() {const fd = new FormData()for (let key in formExam) {if (key === 'examDate') {fd.append('startTime', formExam.examDate[0]);fd.append('endTime', formExam.examDate[1]);} else if (key !== 'startTime' && key !== 'endTime') {fd.append(key, formExam[key])}}  const res = await examAddService(fd)formExam.examId = res.dataElMessage.success('基本信息保存成功')
}
const questionList = ref([])
const total = ref(0)async function getQuestionList() {const result = await getQuestionListService(params)console.log(result)questionList.value = result.rowstotal.value = result.total
}const dialogVisible = ref(false)
function addQuestion() {if (formExam.examId === null || formExam.examId === '') {ElMessage.error('请先保存竞赛基本信息')} else {getQuestionList()dialogVisible.value = true}
}

然后就可以获取题目列表数据了,都是一样的

然后是搜索,分页那些功能

function handleSizeChange() {params.pageNum = 1getQuestionList()
}function handleCurrentChange() {getQuestionList()
}function onSearch() {params.pageNum = 1getQuestionList()
}function onReset() {params.pageNum = 1params.pageSize = 10params.title = ''params.difficulty = ''getQuestionList()
}

在这里插入图片描述
这样就可以了
然后是选择题目提交了

在这里插入图片描述

              <el-table-column type="selection"></el-table-column>

加上了这个就可以多选了

在这里插入图片描述
这个是勾选小框框的时候要触发的事件

            <el-table :data="questionList" @select="handleRowSelect">
const questionIdSet = ref([])function handleRowSelect(selection) {questionIdSet.value = []selection.forEach(element => {questionIdSet.value.push(element.questionId)});
}

selection就是多选的集合,只要多选变了,就会触发这个事件

              <el-button class="question-select-submit" type="primary" plain@click="submitSelectQuestion">提交</el-button>

然后是提交了

async function submitSelectQuestion() {if (questionIdSet.value && questionIdSet.value.length < 1) {ElMessage.error('请先选择要提交的题目')return false}const examQ = reactive({examId: formExam.examId,questionIdSet: questionIdSet.value})console.log(examQ)await addExamQuestionService(examQ);dialogVisible.value = falseElMessage.success('竞赛题目添加成功')
}

其实reactive分装Json和formData分装Json都是一样的格式吗,都是一样的效果
只不过以后formData还可以分装文件
这样就可以了

export function examAddService(params = {}) {return service({url: "/exam/add",method: "post",data: params,});
}export function addExamQuestionService(params = {}) {return service({url: "/exam/question/add",method: "post",data: params,});
}

但是还要注意一下,就是后端返回的Long,会截断的
所以要改一下·

         examMapper.insert(exam) ;return exam.getExamId().toString();

这样就OK了

还有就是这个多选框是自动区分选择顺序的

2. 竞赛编辑

2.1 竞赛详情-后端

@Data
public class ExamDetailVO {private String title;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime startTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime endTime;List<QuestionVO> questionVOList;
}

虽然需要返回的题目列表没有QuestionVO那么多属性,但是后面我们可以设置不返回的

    @GetMapping("/detail")public R<ExamDetailVO> detail(Long examId){log.info("获取竞赛详细信息,examId:{}",examId);return R.ok(iExamService.detail(examId));}
    @Overridepublic ExamDetailVO detail(Long examId) {Exam exam = getExamById(examId);ExamDetailVO examDetailVO = new ExamDetailVO();BeanUtil.copyProperties(exam,examDetailVO);//先根据examId,查出所有的题目,在查出所有的questionIdList<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>().select(ExamQuestion::getQuestionId).orderByAsc(ExamQuestion::getQuestionOrder).eq(ExamQuestion::getExamId, examId));List<Long> questionIdList = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();if(CollectionUtil.isEmpty(questionIdList)){return examDetailVO;}//在查出所有的questionList<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>().select(Question::getQuestionId,Question::getTitle,Question::getDifficulty).in(Question::getQuestionId, questionIdList));List<QuestionVO> questionVOList = BeanUtil.copyToList(questionList, QuestionVO.class);examDetailVO.setQuestionVOList(questionVOList);return examDetailVO;}
BeanUtil.copyProperties

这个是拷贝对象的属性,直接拷贝就是了,不用管谁大谁小,就是拷贝相同名字的字段
BeanUtil.copyToList是拷贝数组,第二个参数是拷贝目的的元素类型,第一个参数是源数组

然后用select就可以指定要查出哪些数据了,像那些前端不用的createName和time就不用select了,就不用返回给前端了

然后就可以测试了
先测试没有题目的竞赛

‘’

这里我们题目列表为空,那么就不要返回list了,直接忽略掉
空的数据就不要返回给前端了

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ExamDetailVO {private String title;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime startTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime endTime;List<QuestionVO> examQuestionList;
}

加上注解@JsonInclude(JsonInclude.Include.NON_NULL),就表示字段为null的就不会返回给前端了
但是数组为[],还是会返回的,数组为[],与数组为null,不是一回事
在这里插入图片描述
这样就OK了

然后是测试有题目的竞赛
在这里插入图片描述
因为select中没有createname和createTime字段,所以是null,但是数据库中可不是null
然后就是因为前端不需要这两个字段,所以没有加入select,所以就不用返回给前端看了,因为是null,而且也不需要

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class QuestionVO {@JsonSerialize(using = ToStringSerializer.class)private Long questionId;private String title;private Integer difficulty;private String createName;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;
}

这样就OK了
在这里插入图片描述

2.2 竞赛详情-前端

点击编辑按钮,进入编辑竞赛界面,然后要先展示详细信息

在exam.vue中

                <el-button v-if="isNotStartExam(row) && row.status == 0" type="text" @click="onEdit(row.examId)">编辑
function onAddExam(){router.push("/oj/layout/updateExam?type=add")
}function onEdit(examId){// router.push("/oj/layout/updateExam?type=edit&examId=${examId}")router.push(`/oj/layout/updateExam?type=edit&examId=${examId}`)
}

如果要使$符号起作用的话,那么就不能用双引号,就只能用esc下面的引号

export function getExamDetailService(examId) {return service({url: "/exam/detail",method: "get",params: { examId },});
}
const formExam = reactive({examId: '',title: '',examDate: ''
})async function getExamDetail(){const examId = useRoute().query.examIdif(examId){const res = await getExamDetailService(examId);Object.assign(formExam,res.data)formExam.examId = examIdformExam.examDate = [res.data.startTime,res.data.endTime]}
}getExamDetail()

前端的数据类型不是固定的,是比较浮动的,是不严格的,所以Object.assign就会把res.data的所有属性都赋值给formExam了
还有就是有一点,就是在获取竞赛列表的时候,后端没有返回竞赛id

@Data
public class ExamVO {@JsonSerialize(using = ToStringSerializer.class)private Long examId;private String title;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime startTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime endTime;private Integer status;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;private String createName;}

记得加 @JsonSerialize(using = ToStringSerializer.class)
不然又会截断
在这里插入图片描述
这样就成功了

2.3 竞赛基本信息编辑-后端

有两个部分的编辑,一个是竞赛基本信息的编辑,一个题目信息的编辑,就是添加或者删除,题目本身信息是无法修改的

@Data
public class ExamEditDTO extends ExamAddDTO{private Long examId;
}
    @PutMapping("/edit")public R<Void> edit(@RequestBody ExamEditDTO examEditDTO){log.info("编辑题目基本信息examEditDTO:{}",examEditDTO);return toR(iExamService.edit(examEditDTO));}
    @Overridepublic int edit(ExamEditDTO examEditDTO) {Exam exam = getExamById(examEditDTO.getExamId());checkExamEditOrAddDTO(examEditDTO,examEditDTO.getExamId());exam.setTitle(examEditDTO.getTitle());exam.setStartTime(examEditDTO.getStartTime());exam.setEndTime(examEditDTO.getEndTime());return examMapper.updateById(exam);}private void checkExamEditOrAddDTO(ExamAddDTO examInfo,Long examId){List<Exam> exams = examMapper.selectList(new LambdaQueryWrapper<Exam>().eq(Exam::getTitle, examInfo.getTitle()).ne(examId!=null,Exam::getExamId,examId));if(CollectionUtil.isNotEmpty(exams)){throw new ServiceException(ResultCode.EXAM_TITLE_HAVE_EXITED);}if(examInfo.getStartTime().isBefore(LocalDateTime.now())){throw new ServiceException(ResultCode.START_TIME_BEFORE_NOW_TIME);}if(examInfo.getEndTime().isBefore(examInfo.getStartTime())){throw new ServiceException(ResultCode.START_TIME_BEFORE_END_TIME);}}

对于add基本信息,要检查标题是否与其他竞赛重复
而对于edit的话,同样也要检查是否重复,但是检查的时候,一定要排除掉自己,如果examId不为null,说明是edit,那么就要ne来排除自己,如果examId为null,说明是add,那么就不用排除自己

在这里插入图片描述
这样就成功了

2.4 竞赛基本信息编辑

export function editExamService(params = {}) {return service({url: "/exam/edit",method: "put",data: params,});
}

修改个人基本信息就是点击保存按钮

async function saveBaseInfo() {const fd = new FormData()for (let key in formExam) {if (key === 'examDate') {fd.append('startTime', formExam.examDate[0]);fd.append('endTime', formExam.examDate[1]);} else if (key !== 'startTime' && key !== 'endTime') {fd.append(key, formExam[key])}}  fd.forEach((value,key)=>{console.log("key:",key,"value:",value)})if(formExam.examId){await editExamService(fd)}else{const res = await examAddService(fd)formExam.examId = res.data}ElMessage.success('基本信息保存成功')
}

这样就成功了

2.5 题目信息编辑

就是添加或者删除题目
添加题目已经开发过了
然后就是开发删除功能了
后端删除题目的时候,第一要看竞赛是否存在,然后是这个竞赛有没有这个题目
前端只需要传题目id和竞赛id就可以了
还有就是在开始竞赛的时候,不能删除题目
就是开始时间在当前时间之前的时候,说明竞赛已经开始了
还有就是在竞赛开始的时候,,也不能添加题目
还有在竞赛开始的时候,也不能编辑竞赛基本信息
或者在竞赛结束的时候,也不能修改的

    @DeleteMapping("/question/delete")public R<Void> questionDelete(Long examId,Long questionId){log.info("在竞赛中删除题目examId:{},questionId:{}",examId,questionId);return toR(iExamService.questionDelete(examId,questionId));}
    @Overridepublic int questionDelete(Long examId, Long questionId) {Exam exam = getExamById(examId);if(LocalDateTime.now().isAfter(exam.getStartTime())){//说明竞赛已经开始或者结束throw new ServiceException(ResultCode.EXAM_HAVE_STARED);}return examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getQuestionId, questionId).eq(ExamQuestion::getExamId, examId));}

这样就成功了

在这里插入图片描述
然后还有一个问题就是
以前选过的题目,但是在添加题目那里还是会显示出来,可能会导致数据库重复添加
所以的修改一下获取题目列表的接口
就是前端可以传入已经添加的题目id,这样后端就不要返回这些id了

@Getter
@Setter
public class QuestionQueryDTO extends PageQueryDTO {private String title;private Integer difficulty;private String excludeIdStr;private Set<Long> excludeIdSet;}

get请求的DTO有集合参数的话,一般会把集合里面的元素拿出来,然后弄成一个字符串,然后传给后端,多个元素的话,拼成一个字符串,那么就要有分隔符,就是一个特殊字符,比如分号
这就是get请求处理集合参数的方法
拼接的字符串,就放入excludeIdStr里面,后端处理之后,把生成的LongId放入excludeIdSet

public class Constants {public static final String SPLIT_SEM = ";";
}
    @Overridepublic List<QuestionVO> list(QuestionQueryDTO questionQueryDTO) {String excludeIdStr = questionQueryDTO.getExcludeIdStr();if(StrUtil.isNotEmpty(excludeIdStr)){//说明是在竞赛中查询题目列表String[] idString = excludeIdStr.split(Constants.SPLIT_SEM);//将字符串的id变为LongSet<Long> collect = Arrays.stream(idString).map(Long::valueOf).collect(Collectors.toSet());questionQueryDTO.setExcludeIdSet(collect);}PageHelper.startPage(questionQueryDTO.getPageNum(), questionQueryDTO.getPageSize());return questionMapper.selectQuestionList(questionQueryDTO);}

StrUtil.isNotEmpty是专门对String类型判空
Arrays.stream(idString).map(Long::valueOf).collect(Collectors.toSet());是用流的方式来转换

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ck.system.mapper.question.QuestionMapper"><select id="selectQuestionList" resultType="com.ck.system.domain.question.vo.QuestionVO">SELECTtq.question_id,tq.title,tq.difficulty,ts.nick_name as create_name,tq.create_timeFROMtb_question tqleft jointb_sys_user tsontq.create_by = ts.user_id<where><if test="difficulty !=null ">AND difficulty = #{difficulty}</if><if test="title !=null and title !='' ">AND title LIKE CONCAT('%',#{title},'%')</if><if test="excludeIdSet !=null and !excludeIdSet.isEmpty()"><foreach collection="excludeIdSet" open=" AND tq.question_id NOT IN( " close=" ) " item="id" separator=",">#{id}</foreach></if></where>ORDER BYcreate_time DESC</select>
</mapper>

在这里插入图片描述

这样就OK了

2.6 竞赛题目信息编辑-前端

export function deleteExamQuestionService(examId,questionId) {return service({url: "/exam/question/delete",method: "delete",params: { examId ,questionId},});
}
async function deleteExamQuestion(examId,questionId){await deleteExamQuestionService(examId,questionId);getExamDetail()ElMessage.success("删除题目成功")
}

但是有一个问题,就是
getExamDetail中
Object.assign(formExam,res.data)中
如果questionList为null,会直接就不返回这个字段了
然后就不会把空的list赋值给questionList了,然后list里面的数据就是上一次只有一个的数据
所以我们在赋值之前把它弄为空

//获取竞赛详细信息
async function getExamDetail(){const examId = useRoute().query.examIdconsole.log("examId",examId)if(examId){const res = await getExamDetailService(examId);formExam.examQuestionList = []Object.assign(formExam,res.data)console.log("formExam",formExam)formExam.examId = examIdformExam.examDate = [res.data.startTime,res.data.endTime]console.log("formExam",formExam)}
}

注意不能直接在deleteExamQuestion或者addExamQuestion中调用getExamDetail方法

因为
useRoute() 是 Vue Router 提供的组合式 API,只能在 Vue 组件的 setup() 函数或

所以useRoute()只能在js里面使用,或者在函数里面使用,但是这个函数必须是在js里面会马上调用的,所以我们弄一个新的方法

就可以了

async function getExamDetailByExamId(examId){const res = await getExamDetailService(examId);formExam.examQuestionList = []Object.assign(formExam,res.data)formExam.examId = examIdformExam.examDate = [res.data.startTime,res.data.endTime]
}
async function deleteExamQuestion(examId,questionId){await deleteExamQuestionService(examId,questionId);getExamDetailByExamId(examId)ElMessage.success("删除题目成功")
}
async function submitSelectQuestion() {if (questionIdSet.value && questionIdSet.value.length < 1) {ElMessage.error('请先选择要提交的题目')return false}const examQ = reactive({examId: formExam.examId,questionIdSet: questionIdSet.value})console.log("questionIdSet",questionIdSet.value)console.log(examQ)await addExamQuestionService(examQ);dialogVisible.value = falsegetExamDetailByExamId(formExam.examId)ElMessage.success('竞赛题目添加成功')
}

这样就OK了

总结

http://www.dtcms.com/a/309220.html

相关文章:

  • 如何保护 Redis 实例的安全?
  • 快速排序算法详解与洛谷例题实战
  • 【PHP 构造函数与析构函数:从基础到高级的完整指南】
  • 直播平台中的美白滤镜实现:美颜SDK的核心架构与性能优化指南
  • Qt结合ffmpeg实现图片参数调节/明亮度对比度饱和度设置/滤镜的使用
  • Windows编译安装ffmpeg和sdl
  • CG--逻辑判断1
  • 实战指南:如何将Git仓库中的特定文件夹及其历史完整迁移到另一个仓库
  • Git 各场景使用方法总结
  • java8学习笔记-Stream流
  • 在uni-app中引入本地日志插件
  • 城市数字孪生之GISBox三维顶层重建白皮书
  • 操作系统:共享内存通信(Shared Memory Systems)
  • WAIC 2025再发AI十大展望
  • WaitForSingleObject 函数参数影响及信号处理分析
  • SpringAI智能客服Function Calling兼容性问题解决方案
  • 中国信通院/华为:智能体技术和应用研究报告(2025)(转载)
  • 充电桩与照明“联动”创新:智慧灯杆破解新能源基建难题
  • AntFlow 1.0.0 正式发布:企业级开源工作流引擎,历经一年打磨,全面上线!
  • Nginx配置优先级问题导致静态资源404
  • 新书速览|Python数据分析师成长之路
  • 实战指南|虚拟电厂管理平台搭建全流程解析(一)
  • 谷歌Firebase动态链接将失效:如何选择深度链接替代方案?
  • ccf接口测试实战
  • 机器学习sklearn:编码、哑变量、二值化和分段
  • Implement recovery based on PITR using dump file and binlog
  • 用离子交换树脂做镍钴分离的工艺优势
  • Solana:解决Anchor Build编译程序报错 no method named `source_file` found for struct
  • 暑期算法训练.12
  • 练习javaweb+mysql+jsp