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

文章目录

  • 前言
  • 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.pswp.cn/news/917177.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917177.shtml
英文地址,请注明出处:http://en.pswp.cn/news/917177.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《零基础入门AI:线性回归进阶(梯度下降算法详解)》

在上一篇博客中&#xff0c;我们学习了线性回归的基本概念、损失函数&#xff08;如MSE&#xff09;以及最小二乘法。最小二乘法通过求解解析解&#xff08;直接计算出最优参数&#xff09;的方式得到线性回归模型&#xff0c;但它有一个明显的局限&#xff1a;当特征数量很多时…

基于C语言实现的KV存储引擎(一)

基于C语言实现的KV存储引擎项目简介整体架构网络模块的实现recatorproactorNtyco项目简介 本文主要是基于 C 语言来实现一个简单的 KV 存储架构&#xff0c;目的就是将网络模块跟实际开发结合起来。 首先我们知道对于数据的存储可以分为两种方式&#xff0c;一种是在内存中进…

c++和python联合编程示例

安装 C与 Python 绑定工具 pip install pybind11这其实相当于使用 python 安装了一个 c的库 pybind11,这个库只由头文件构成&#xff0c; 支持基础数据类型传递以及 python 的 numpy 和 c的 eigen 库之间的自动转换。 编写 CMakeList.txt cmake_minimum_required(VERSION 3.14)…

【OD机试题解法笔记】贪心歌手

题目描述 一个歌手准备从A城去B城参加演出。 按照合同&#xff0c;他必须在 T 天内赶到歌手途经 N 座城市歌手不能往回走每两座城市之间需要的天数都可以提前获知。歌手在每座城市都可以在路边卖唱赚钱。 经过调研&#xff0c;歌手提前获知了每座城市卖唱的收入预期&#xff1a…

AI: 告别过时信息, 用RAG和一份PDF 为LLM打造一个随需更新的“外脑”

嘿&#xff0c;各位技术同学&#xff01;今天&#xff0c;我们来聊一个大家在使用大语言模型&#xff08;LLM&#xff09;时都会遇到的痛点&#xff1a;知识过时。 无论是像我一样&#xff0c;用 Gemini Pro 学习日新月异的以太坊&#xff0c;还是希望它能精确掌握某个特定工具…

深度学习(鱼书)day08--误差反向传播(后三节)

深度学习&#xff08;鱼书&#xff09;day08–误差反向传播&#xff08;后三节&#xff09;一、激活函数层的实现 这里&#xff0c;我们把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。ReLU层 激活函数ReLU&#xff08;Rectified Linear Unit&#xff…

C# 中生成随机数的常用方法

1. 使用 Random 类&#xff08;简单场景&#xff09; 2. 使用 RandomNumberGenerator 类&#xff08;安全场景&#xff09; 3. 生成指定精度的随机小数 C# 中生成随机数的常用方法&#xff1a; 随机数类型实现方式示例代码特点与适用场景随机整数&#xff08;无范围&#xf…

Flink 算子链设计和源代码实现

1、JobGraph &#xff08;JobManager&#xff09; JobGraph 生成时&#xff0c;通过 ChainingStrategy 连接算子&#xff0c;最终在 Task 中生成 ChainedDriver 链表。StreamingJobGraphGeneratorcreateJobGraph() 构建jobGrapch 包含 JobVertex setChaining() 构建算子链isCha…

对接八大应用渠道

背景最近公司想把游戏包上到各个渠道上&#xff0c;因此需要对接各种渠道&#xff0c;渠道如下&#xff0c;oppo、vivo、华为、小米、应用宝、taptap、荣耀、三星等应用渠道 主要就是对接登录、支付接口&#xff08;后续不知道会不会有其他的&#xff09;&#x…

学习:入门uniapp Vue3组合式API版本(17)

42.打包发行微信小程序的上线全流程 域名 配置 发行 绑定手机号 上传 提交后等待&#xff0c;上传 43.打包H5并发布上线到unicloud的前端页面托管 完善配置 unicloud 手机号实名信息不一致&#xff1a;请确保手机号的实名信息与开发者姓名、身份证号一致&#xff0c;请前往开…

SOLIDWORKS材料明细表设置,属于自己的BOM表模板

上一期我们了解了如何在SOLIDWORKS工程图中添加材料明细表?接下来&#xff0c;我们将进行对SOLIDWORKS材料明细表的设置、查看缩略图、模板保存的深度讲解。01 材料明细表设置菜单栏生成表格后左侧菜单栏会显示关于材料明细表的相关设置信息。我们先了解一下菜单栏设置详情&am…

全栈:Maven的作用是什么?本地仓库,私服还有中央仓库的区别?Maven和pom.xml配置文件的关系是什么?

Maven和pom.xml配置文件的关系是什么&#xff1a; Maven是一个构建工具和依赖管理工具&#xff0c;而pom.xml&#xff08;Project Object Model&#xff09;是Maven的核心配置文件。 SSM 框架的项目不一定是 Maven 项目&#xff0c;但推荐使用 Maven进行管理。 SSM 框架的项目可…

超越 ChatGPT:智能体崛起,开启全自主 AI 时代

引言 短短三年,生成式 AI 已从对话助手跨越到能自主规划并完成任务的“智能体(Agentic AI)”时代。这场演进不仅体现在模型规模的提升,更在于系统架构、交互范式与安全治理的全面革新。本文按时间线梳理关键阶段与核心技术,为您呈现 AI 智能体革命的脉络与未来趋势。 1. …

一杯就够:让大脑瞬间在线、让肌肉满电的 “Kick-out Drink” 全解析

一杯就够&#xff1a;让大脑瞬间在线、让肌肉满电的 “Kick-out Drink” 全解析“每天清晨&#xff0c;当闹钟还在哀嚎&#xff0c;你举杯一饮&#xff0c;睡意像被扔出擂台——这&#xff0c;就是 Kick-out Drink 的全部浪漫。”清晨 30 分钟后&#xff0c;250 mL 常温水里溶解…

系统开机时自动执行指令

使用 systemd 创建一个服务单元可以让系统开机时自动执行指令&#xff0c;假设需要执行的指令如下&#xff0c;运行可执行文件&#xff08;/home/demo/可执行文件&#xff09;&#xff0c;并输入参数&#xff08;–input/home/config/demo.yaml&#xff09;&#xff1a; /home/…

Docker 初学者需要了解的几个知识点 (七):php.ini

这段配置是 php.ini 文件中针对 PHP 扩展和 Xdebug 调试工具的设置&#xff0c;主要用于让 PHP 支持数据库连接和代码调试&#xff08;尤其在 Docker 环境中&#xff09;&#xff0c;具体解释如下&#xff1a;[PHP] extensionpdo_mysql extensionmysqli xdebug.modedebug xdebu…

【高阶版】R语言空间分析、模拟预测与可视化高级应用

随着地理信息系统&#xff08;GIS&#xff09;和大尺度研究的发展&#xff0c;空间数据的管理、统计与制图变得越来越重要。R语言在数据分析、挖掘和可视化中发挥着重要的作用&#xff0c;其中在空间分析方面扮演着重要角色&#xff0c;与空间相关的包的数量也达到130多个。在本…

dolphinscheduler中一个脚本用于从列定义中提取列名列表

dolphinscheduler中&#xff0c;我们从一个mysql表导出数据&#xff0c;上传到hdfs, 再创建一个临时表&#xff0c;所以需要用到列名定义和列名列表。 原来定义两个变量&#xff0c;不仅繁锁&#xff0c;还容易出现差错&#xff0c;比如两者列序不对。 所以考虑只定义列定义变量…

JavaWeb(苍穹外卖)--学习笔记16(定时任务工具Spring Task,Cron表达式)

前言 本篇文章是学习B站黑马程序员苍穹外卖的学习笔记&#x1f4d1;。我的学习路线是Java基础语法-JavaWeb-做项目&#xff0c;管理端的功能学习完之后&#xff0c;就进入到了用户端微信小程序的开发&#xff0c;用户端开发的流程大致为用户登录—商品浏览&#xff08;其中涉及…

灵敏度,精度,精确度,精密度,精准度,准确度,分辨率,分辨力——概念

文章目录前提总结前提 我最近在整理一份数据指标要求的时候&#xff0c;总是混淆这几个概念&#xff1a;灵敏度&#xff0c;精度&#xff0c;精确度&#xff0c;精密度&#xff0c;精准度&#xff0c;准确度&#xff0c;分辨率&#xff0c;分辨力&#xff0c;搜了一些文章&…