微服務的編程測評系統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.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;搜了一些文章&…