0.前言
HOJ默认是没有同步班级题单和同步班级题库的功能的,有时候用起来觉得不方便,于是就增加一个。
先来看实现的效果:

1.修改方式
一共三个步骤:
第一步:复制公共题单信息,插入到training表。
第二步:插入mapping_training_category表数据。
第三步:在training_problem表中插入数据。
原作者已经有题单插入的代码了,我们参考一下,主要使用它生成的id,修改一下原方法,让它可以返回id。
@Transactional(rollbackFor = Exception.class)
public Long addTraining(TrainingDTO trainingDto) throws StatusForbiddenException, StatusNotFoundException, StatusFailException {
trainingValidator.validateTraining(trainingDto.getTraining());
AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
Long gid = trainingDto.getTraining().getGid();
if (gid == null){
throw new StatusForbiddenException("添加失败,训练所属的团队ID不可为空!");
}
Group group = groupEntityService.getById(gid);
if (group == null || group.getStatus() == 1 && !isRoot) {
throw new StatusNotFoundException("添加训练失败,该团队不存在或已被封禁!");
}
if (!isRoot && !groupValidator.isGroupAdmin(userRolesVo.getUid(), gid)) {
throw new StatusForbiddenException("对不起,您无权限操作!");
}
trainingDto.getTraining().setIsGroup(true);
Training training = trainingDto.getTraining();
trainingEntityService.save(training);
TrainingCategory trainingCategory = trainingDto.getTrainingCategory();
if (trainingCategory.getGid() != null && !Objects.equals(trainingCategory.getGid(), gid)) {
throw new StatusForbiddenException("对不起,您无权限操作!");
}
if (trainingCategory.getId() == null) {
try {
trainingCategory.setGid(gid);
trainingCategoryEntityService.save(trainingCategory);
} catch (Exception ignored) {
QueryWrapper<TrainingCategory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", trainingCategory.getName());
trainingCategory = trainingCategoryEntityService.getOne(queryWrapper, false);
}
}
boolean isOk = mappingTrainingCategoryEntityService.save(new MappingTrainingCategory()
.setTid(training.getId())
.setCid(trainingCategory.getId()));
if (!isOk) {
throw new StatusFailException("添加失败!");
}
return training.getId();
}
@Transactional(rollbackFor = Exception.class)
public Long addTraining(TrainingDTO trainingDto) throws StatusForbiddenException, StatusNotFoundException, StatusFailException {
trainingValidator.validateTraining(trainingDto.getTraining());
AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
boolean isRoot = SecurityUtils.getSubject().hasRole("root");
Long gid = trainingDto.getTraining().getGid();
if (gid == null){
throw new StatusForbiddenException("添加失败,训练所属的团队ID不可为空!");
}
Group group = groupEntityService.getById(gid);
if (group == null || group.getStatus() == 1 && !isRoot) {
throw new StatusNotFoundException("添加训练失败,该团队不存在或已被封禁!");
}
if (!isRoot && !groupValidator.isGroupAdmin(userRolesVo.getUid(), gid)) {
throw new StatusForbiddenException("对不起,您无权限操作!");
}
trainingDto.getTraining().setIsGroup(true);
Training training = trainingDto.getTraining();
trainingEntityService.save(training);
TrainingCategory trainingCategory = trainingDto.getTrainingCategory();
if (trainingCategory.getGid() != null && !Objects.equals(trainingCategory.getGid(), gid)) {
throw new StatusForbiddenException("对不起,您无权限操作!");
}
if (trainingCategory.getId() == null) {
try {
trainingCategory.setGid(gid);
trainingCategoryEntityService.save(trainingCategory);
} catch (Exception ignored) {
QueryWrapper<TrainingCategory> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", trainingCategory.getName());
trainingCategory = trainingCategoryEntityService.getOne(queryWrapper, false);
}
}
boolean isOk = mappingTrainingCategoryEntityService.save(new MappingTrainingCategory()
.setTid(training.getId())
.setCid(trainingCategory.getId()));
if (!isOk) {
throw new StatusFailException("添加失败!");
}
return training.getId();
}
@Transactional(rollbackFor = Exception.class) public Long addTraining(TrainingDTO trainingDto) throws StatusForbiddenException, StatusNotFoundException, StatusFailException { trainingValidator.validateTraining(trainingDto.getTraining()); AccountProfile userRolesVo = (AccountProfile) SecurityUtils.getSubject().getPrincipal(); boolean isRoot = SecurityUtils.getSubject().hasRole("root"); Long gid = trainingDto.getTraining().getGid(); if (gid == null){ throw new StatusForbiddenException("添加失败,训练所属的团队ID不可为空!"); } Group group = groupEntityService.getById(gid); if (group == null || group.getStatus() == 1 && !isRoot) { throw new StatusNotFoundException("添加训练失败,该团队不存在或已被封禁!"); } if (!isRoot && !groupValidator.isGroupAdmin(userRolesVo.getUid(), gid)) { throw new StatusForbiddenException("对不起,您无权限操作!"); } trainingDto.getTraining().setIsGroup(true); Training training = trainingDto.getTraining(); trainingEntityService.save(training); TrainingCategory trainingCategory = trainingDto.getTrainingCategory(); if (trainingCategory.getGid() != null && !Objects.equals(trainingCategory.getGid(), gid)) { throw new StatusForbiddenException("对不起,您无权限操作!"); } if (trainingCategory.getId() == null) { try { trainingCategory.setGid(gid); trainingCategoryEntityService.save(trainingCategory); } catch (Exception ignored) { QueryWrapper<TrainingCategory> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("name", trainingCategory.getName()); trainingCategory = trainingCategoryEntityService.getOne(queryWrapper, false); } } boolean isOk = mappingTrainingCategoryEntityService.save(new MappingTrainingCategory() .setTid(training.getId()) .setCid(trainingCategory.getId())); if (!isOk) { throw new StatusFailException("添加失败!"); } return training.getId(); }
传入的参数有两个,第一个就是班级/团队的id。
前端设计的一个子组件:
// 将公共题单的某一些题单同步到某个班级的题单中
<template>
<div>
<el-card>
<el-button
icon="el-icon-attract"
size="mini"
@click.native="syncProblem()"
type="primary"
>
{{$t('m.Sync_Train')}}
</el-button>
<vxe-table
border="inner"
:data="trainingList"
ref="xTable"
align="center"
@checkbox-change="handleSelectionChange"
@checkbox-all="handlechangeAll"
>
<vxe-table-column type="checkbox" width="60"></vxe-table-column>
<vxe-table-column
field="title"
:title="$t('m.Title')"
align="center"
></vxe-table-column>
<vxe-table-column
field="categoryName"
:title="$t('m.Category')"
align="center"
></vxe-table-column>
<vxe-table-column
field="problemCount"
:title="$t('m.Problem_Number')"
align="center"
></vxe-table-column>
</vxe-table>
<Pagination
:total="total"
:page-size="limit"
@on-change="currentChange"
:current.sync="currentPage"
@on-page-size-change="onPageSizeChange"
:layout="'prev, pager, next, sizes'"
></Pagination>
</el-card>
</div>
</template>
<script>
import api from "@/common/api";
import Pagination from "@/components/oj/common/Pagination";
import mMessage from "@/common/message";
export default {
name: "SyncGroupList",
components: {
Pagination,
mMessage,
},
props: {
// 班级/团队id
groupID: {
type: Number,
default: null,
},
},
data() {
return {
loading: false,
//默认查询,权限只能是公开的
query: {
keyword: "",
categoryId: null,
auth: "Public",
},
total: 0,
currentPage: 1,
limit: 10,
trainingList: [], //训练列表
selectedTrain:[], //选中的
};
},
methods: {
init() {
console.log("sync init");
this.getTrainingList();
},
// 获取所有题单列表
getTrainingList() {
console.log("getTrainingList");
console.log(this.groupID);
this.loading = true;
api.getTrainingList(this.currentPage, this.limit, this.query).then(
(res) => {
console.log(res);
this.trainingList = res.data.data.records;
this.total = res.data.data.total;
this.loading = false;
},
(err) => {
this.loading = false;
}
);
},
currentChange(page) {
this.currentPage = page;
this.getTrainingList();
},
onPageSizeChange(pageSize) {
this.limit = pageSize;
this.getTrainingList();
},
handleSelectionChange({ records }){
this.selectedTrain = [];
for (let num = 0; num < records.length; num++) {
this.selectedTrain.push(records[num].id);
}
},
//全选
handlechangeAll(){
let trainList = this.$refs.xTable.getCheckboxRecords();
this.selectedTrain = [];
for (let num = 0; num < trainList.length; num++) {
this.selectedTrain.push(trainList[num].id);
}
},
//选中同步题目
syncProblem(){
// console.log(this.selectedTrain)
if(this.selectedTrain.length==0){
mMessage.warning("没有选中任何题单");
}else{
let params={
groupID:this.groupID, //要同步到哪个班级
selectedTrain:this.selectedTrain //同步哪些题单
}
console.log(params)
api.syncPublicTrainToGroupTrain(params).then(
(res) => {
console.log(res);
if(res.data.msg=="success"){
mMessage.success(this.$i18n.t('m.Add_Success'));
}
this.syncGroupListClose()
},
(err) => {
mMessage.error(this.$i18n.t('m.Sync_Error'))
}
);
}
},
syncGroupListClose(){
console.log("syncGroupListClose")
this.$emit('syncGroupListClose');
}
},
};
</script>
// 将公共题单的某一些题单同步到某个班级的题单中
<template>
<div>
<el-card>
<el-button
icon="el-icon-attract"
size="mini"
@click.native="syncProblem()"
type="primary"
>
{{t('m.Title')"
align="center"
></vxe-table-column>
<vxe-table-column
field="categoryName"
:title="t('m.Problem_Number')"
align="center"
></vxe-table-column>
</vxe-table>
<Pagination
:total="total"
:page-size="limit"
@on-change="currentChange"
:current.sync="currentPage"
@on-page-size-change="onPageSizeChange"
:layout="'prev, pager, next, sizes'"
></Pagination>
</el-card>
</div>
</template>
<script>
import api from "@/common/api";
import Pagination from "@/components/oj/common/Pagination";
import mMessage from "@/common/message";
export default {
name: "SyncGroupList",
components: {
Pagination,
mMessage,
},
props: {
// 班级/团队id
groupID: {
type: Number,
default: null,
},
},
data() {
return {
loading: false,
//默认查询,权限只能是公开的
query: {
keyword: "",
categoryId: null,
auth: "Public",
},
total: 0,
currentPage: 1,
limit: 10,
trainingList: [], //训练列表
selectedTrain:[], //选中的
};
},
methods: {
init() {
console.log("sync init");
this.getTrainingList();
},
// 获取所有题单列表
getTrainingList() {
console.log("getTrainingList");
console.log(this.groupID);
this.loading = true;
api.getTrainingList(this.currentPage, this.limit, this.query).then(
(res) => {
console.log(res);
this.trainingList = res.data.data.records;
this.total = res.data.data.total;
this.loading = false;
},
(err) => {
this.loading = false;
}
);
},
currentChange(page) {
this.currentPage = page;
this.getTrainingList();
},
onPageSizeChange(pageSize) {
this.limit = pageSize;
this.getTrainingList();
},
handleSelectionChange({ records }){
this.selectedTrain = [];
for (let num = 0; num < records.length; num++) {
this.selectedTrain.push(records[num].id);
}
},
//全选
handlechangeAll(){
let trainList = this.$refs.xTable.getCheckboxRecords();
this.selectedTrain = [];
for (let num = 0; num < trainList.length; num++) {
this.selectedTrain.push(trainList[num].id);
}
},
//选中同步题目
syncProblem(){
// console.log(this.selectedTrain)
if(this.selectedTrain.length==0){
mMessage.warning("没有选中任何题单");
}else{
let params={
groupID:this.groupID, //要同步到哪个班级
selectedTrain:this.selectedTrain //同步哪些题单
}
console.log(params)
api.syncPublicTrainToGroupTrain(params).then(
(res) => {
console.log(res);
if(res.data.msg=="success"){
mMessage.success(this.$i18n.t('m.Add_Success'));
}
this.syncGroupListClose()
},
(err) => {
mMessage.error(this.$i18n.t('m.Sync_Error'))
}
);
}
},
syncGroupListClose(){
console.log("syncGroupListClose")
this.$emit('syncGroupListClose');
}
},
};
</script>
// 将公共题单的某一些题单同步到某个班级的题单中 <template> <div> <el-card> <el-button icon="el-icon-attract" size="mini" @click.native="syncProblem()" type="primary" > {{$t('m.Sync_Train')}} </el-button> <vxe-table border="inner" :data="trainingList" ref="xTable" align="center" @checkbox-change="handleSelectionChange" @checkbox-all="handlechangeAll" > <vxe-table-column type="checkbox" width="60"></vxe-table-column> <vxe-table-column field="title" :title="$t('m.Title')" align="center" ></vxe-table-column> <vxe-table-column field="categoryName" :title="$t('m.Category')" align="center" ></vxe-table-column> <vxe-table-column field="problemCount" :title="$t('m.Problem_Number')" align="center" ></vxe-table-column> </vxe-table> <Pagination :total="total" :page-size="limit" @on-change="currentChange" :current.sync="currentPage" @on-page-size-change="onPageSizeChange" :layout="'prev, pager, next, sizes'" ></Pagination> </el-card> </div> </template> <script> import api from "@/common/api"; import Pagination from "@/components/oj/common/Pagination"; import mMessage from "@/common/message"; export default { name: "SyncGroupList", components: { Pagination, mMessage, }, props: { // 班级/团队id groupID: { type: Number, default: null, }, }, data() { return { loading: false, //默认查询,权限只能是公开的 query: { keyword: "", categoryId: null, auth: "Public", }, total: 0, currentPage: 1, limit: 10, trainingList: [], //训练列表 selectedTrain:[], //选中的 }; }, methods: { init() { console.log("sync init"); this.getTrainingList(); }, // 获取所有题单列表 getTrainingList() { console.log("getTrainingList"); console.log(this.groupID); this.loading = true; api.getTrainingList(this.currentPage, this.limit, this.query).then( (res) => { console.log(res); this.trainingList = res.data.data.records; this.total = res.data.data.total; this.loading = false; }, (err) => { this.loading = false; } ); }, currentChange(page) { this.currentPage = page; this.getTrainingList(); }, onPageSizeChange(pageSize) { this.limit = pageSize; this.getTrainingList(); }, handleSelectionChange({ records }){ this.selectedTrain = []; for (let num = 0; num < records.length; num++) { this.selectedTrain.push(records[num].id); } }, //全选 handlechangeAll(){ let trainList = this.$refs.xTable.getCheckboxRecords(); this.selectedTrain = []; for (let num = 0; num < trainList.length; num++) { this.selectedTrain.push(trainList[num].id); } }, //选中同步题目 syncProblem(){ // console.log(this.selectedTrain) if(this.selectedTrain.length==0){ mMessage.warning("没有选中任何题单"); }else{ let params={ groupID:this.groupID, //要同步到哪个班级 selectedTrain:this.selectedTrain //同步哪些题单 } console.log(params) api.syncPublicTrainToGroupTrain(params).then( (res) => { console.log(res); if(res.data.msg=="success"){ mMessage.success(this.$i18n.t('m.Add_Success')); } this.syncGroupListClose() }, (err) => { mMessage.error(this.$i18n.t('m.Sync_Error')) } ); } }, syncGroupListClose(){ console.log("syncGroupListClose") this.$emit('syncGroupListClose'); } }, }; </script>
然后在父组件中引用:
<template>
<el-card>
<div class="filter-row">
<el-row>
<el-col :md="3" :xs="24">
<span class="title">{{ $t("m.Group_Training") }}</span>
</el-col>
<!-- 创建团队新题单 -->
<el-col
:md="18"
:xs="24"
v-if="
(isSuperAdmin || isGroupAdmin) && !problemPage && !editProblemPage
"
>
<el-button
v-if="!editPage"
:type="createPage ? 'warning' : 'primary'"
size="small"
@click="handleCreatePage"
:icon="createPage ? 'el-icon-back' : 'el-icon-plus'"
>{{
createPage ? $t("m.Back_To_Admin_Training_List") : $t("m.Create")
}}</el-button
>
<!-- 新增主题库同步到班级题库 -->
<el-button
v-if="!editPage && !createPage"
:type="createPage ? 'warning' : 'primary'"
size="small"
@click="handleTrainingToGroup"
:icon="createPage ? 'el-icon-back' : 'el-icon-plus'"
>{{ $t("m.Training_To_Group") }}</el-button
>
<el-button
v-if="editPage && adminPage"
type="warning"
size="small"
@click="handleEditPage"
icon="el-icon-back"
>{{ $t("m.Back_To_Admin_Training_List") }}</el-button
>
<el-button
:type="adminPage ? 'danger' : 'success'"
v-if="!editPage && !createPage"
size="small"
@click="handleAdminPage"
:icon="adminPage ? 'el-icon-back' : 'el-icon-s-opportunity'"
>{{
adminPage ? $t("m.Back_To_Training_List") : $t("m.Training_Admin")
}}</el-button
>
</el-col>
<el-col
:md="18"
:xs="24"
v-else-if="
(isSuperAdmin || isGroupAdmin) && problemPage && !editProblemPage
"
>
<el-button
type="primary"
size="small"
@click="publicPage = true"
icon="el-icon-plus"
>{{ $t("m.Add_From_Public_Problem") }}</el-button
>
<el-button
type="success"
size="small"
@click="handleGroupPage"
icon="el-icon-plus"
>{{ $t("m.Add_From_Group_Problem") }}</el-button
>
<el-button
type="warning"
size="small"
@click="handleProblemPage(null)"
icon="el-icon-back"
>{{ $t("m.Back_To_Admin_Training_List") }}</el-button
>
</el-col>
<el-col
:md="18"
:xs="24"
v-else-if="(isSuperAdmin || isGroupAdmin) && editProblemPage"
>
<el-button
type="primary"
size="small"
@click="handleEditProblemPage"
icon="el-icon-back"
>{{ $t("m.Back_Admin_Training_Problem_List") }}</el-button
>`
</el-col>
</el-row>
</div>
<template v-if="!adminPage && !createPage && !problemPage">
<vxe-table
border="inner"
stripe
auto-resize
highlight-hover-row
:data="trainingList"
:loading="loading"
align="center"
@cell-click="goGroupTraining"
>
<vxe-table-column
field="rank"
:title="$t('m.Number')"
min-width="60"
show-overflow
>
</vxe-table-column>
<vxe-table-column
field="title"
:title="$t('m.Title')"
min-width="200"
align="center"
>
</vxe-table-column>
<vxe-table-column
field="auth"
:title="$t('m.Auth')"
min-width="100"
align="center"
>
<template v-slot="{ row }">
<el-tag :type="TRAINING_TYPE[row.auth]['color']" effect="dark">
{{ $t("m.Training_" + row.auth) }}
</el-tag>
</template>
</vxe-table-column>
<vxe-table-column
field="categoryName"
:title="$t('m.Category')"
min-width="130"
align="center"
>
<template v-slot="{ row }">
<el-tag
size="large"
:style="
'background-color: #fff; color: ' +
row.categoryColor +
'; border-color: ' +
row.categoryColor +
';'
"
>{{ row.categoryName }}</el-tag
>
</template>
</vxe-table-column>
<vxe-table-column
field="acCount"
:title="$t('m.Progress')"
min-width="120"
align="center"
>
<template v-slot="{ row }">
<span>
<el-tooltip
effect="dark"
:content="row.acCount + '/' + row.problemCount"
placement="top"
>
<el-progress
:text-inside="true"
:stroke-width="20"
:percentage="getPassingRate(row.acCount, row.problemCount)"
></el-progress>
</el-tooltip>
</span>
</template>
</vxe-table-column>
<vxe-table-column
field="problemCount"
:title="$t('m.Problem_Number')"
min-width="70"
align="center"
>
</vxe-table-column>
<vxe-table-column
field="author"
:title="$t('m.Author')"
min-width="130"
align="center"
show-overflow
>
</vxe-table-column>
<vxe-table-column
field="gmtModified"
:title="$t('m.Recent_Update')"
min-width="96"
align="center"
show-overflow
>
<template v-slot="{ row }">
<span>
<el-tooltip
:content="row.gmtModified | localtime"
placement="top"
>
<span>{{ row.gmtModified | fromNow }}</span>
</el-tooltip>
</span>
</template>
</vxe-table-column>
</vxe-table>
<Pagination
:total="total"
:page-size="limit"
@on-change="currentChange"
:current.sync="currentPage"
@on-page-size-change="onPageSizeChange"
:layout="'prev, pager, next, sizes'"
></Pagination>
</template>
<TrainingList
ref="trainingList"
v-if="adminPage && !createPage && !problemPage"
@handleEditPage="handleEditPage"
@currentChange="currentChange"
@handleProblemPage="handleProblemPage"
></TrainingList>
<TrainingProblemList
v-if="problemPage"
:trainingId="trainingId"
@currentChangeProblem="currentChangeProblem"
@handleEditProblemPage="handleEditProblemPage"
ref="trainingProblemList"
>
</TrainingProblemList>
<Training
v-if="createPage && !editPage && !problemPage"
mode="add"
:title="$t('m.Create_Training')"
apiMethod="addGroupTraining"
@handleCreatePage="handleCreatePage"
@currentChange="currentChange"
></Training>
<!-- 团队添加公共题目 -->
<el-dialog
:title="$t('m.Add_Training_Problem')"
width="90%"
:visible.sync="publicPage"
:close-on-click-modal="false"
>
<AddPublicProblem
v-if="publicPage"
:trainingId="trainingId"
apiMethod="getGroupTrainingProblemList"
@currentChangeProblem="currentChangeProblem"
ref="addPublicProblem"
></AddPublicProblem>
</el-dialog>
<!-- 团队添加团队题目 -->
<el-dialog
:title="$t('m.Add_Training_Problem')"
width="350px"
:visible.sync="groupPage"
:close-on-click-modal="false"
>
<AddGroupProblem
:trainingId="trainingId"
@currentChangeProblem="currentChangeProblem"
@handleGroupPage="handleGroupPage"
></AddGroupProblem>
</el-dialog>
<!-- 打开公共题单弹窗 -->
<el-dialog
:title="$t('m.Training_To_Group')"
width="850px"
:visible.sync="groupListPage"
:close-on-click-modal="true"
>
<SyncGroupList
:groupID="groupID"
@syncGroupListClose="syncGroupListClose"
ref="SyncGroupListChild"
> </SyncGroupList>
</el-dialog>
</el-card>
</template>
<script>
import { mapGetters } from "vuex";
import { TRAINING_TYPE } from "@/common/constants";
import Pagination from "@/components/oj/common/Pagination";
import TrainingList from "@/components/oj/group/TrainingList";
import Training from "@/components/oj/group/Training";
import TrainingProblemList from "@/components/oj/group/TrainingProblemList";
import AddPublicProblem from "@/components/oj/group/AddPublicProblem.vue";
import AddGroupProblem from "@/components/oj/group/AddGroupProblem.vue";
import SyncGroupList from "@/components/oj/group/SyncGroupList.vue";
import api from "@/common/api";
export default {
name: "GroupTrainingList",
components: {
Pagination,
TrainingList,
Training,
TrainingProblemList,
AddPublicProblem,
AddGroupProblem,
SyncGroupList,
},
data() {
return {
total: 0,
currentPage: 1,
limit: 10,
trainingList: [],
TRAINING_TYPE: {},
loading: false,
adminPage: false,
createPage: false,
editPage: false,
problemPage: false,
publicPage: false,
groupPage: false,
groupListPage: false, //打开同步题单弹窗
editProblemPage: false,
trainingId: null,
groupID:"", //团队/班级id
};
},
mounted() {
this.TRAINING_TYPE = Object.assign({}, TRAINING_TYPE);
this.init();
},
methods: {
init() {
this.groupID=this.$route.params.groupID
this.getGroupTrainingList();
},
onPageSizeChange(pageSize) {
this.limit = pageSize;
this.init();
},
currentChange(page) {
this.currentPage = page;
this.init();
},
currentChangeProblem() {
this.$refs.trainingProblemList.currentChange(1);
},
getGroupTrainingList() {
this.trainingList = [];
this.loading = true;
api
.getGroupTrainingList(
this.currentPage,
this.limit,
this.$route.params.groupID
)
.then(
(res) => {
this.trainingList = res.data.data.records;
console.log("团队this.trainingList ", this.trainingList);
this.total = res.data.data.total;
this.loading = false;
},
(err) => {
this.loading = false;
}
);
},
goGroupTraining(event) {
this.$router.push({
name: "GroupTrainingDetails",
params: {
trainingID: event.row.id,
groupID: this.$route.params.groupID,
},
});
},
handleCreatePage() {
this.createPage = !this.createPage;
},
// 公共训练同步至班级
handleTrainingToGroup() {
console.log("公共训练同步至班级");
this.groupListPage = true;
setTimeout(() => {
this.$refs.SyncGroupListChild.getTrainingList();
});
},
//关闭弹窗
syncGroupListClose(){
this.groupListPage=false
this.getGroupTrainingList()
},
handleEditPage() {
this.editPage = !this.editPage;
this.$refs.trainingList.editPage = this.editPage;
},
handleAdminPage() {
this.adminPage = !this.adminPage;
this.createPage = false;
this.editPage = false;
},
handleProblemPage(trainingId) {
this.trainingId = trainingId;
this.problemPage = !this.problemPage;
},
handleGroupPage() {
this.groupPage = !this.groupPage;
},
handleEditProblemPage() {
this.editProblemPage = !this.editProblemPage;
this.$refs.trainingProblemList.editPage = this.editProblemPage;
},
getPassingRate(ac, total) {
if (!total) {
return 0;
}
return ((ac / total) * 100).toFixed(2);
},
},
computed: {
...mapGetters(["isAuthenticated", "isSuperAdmin", "isGroupAdmin"]),
},
};
</script>
<style scoped>
.title {
font-size: 20px;
vertical-align: middle;
float: left;
}
.filter-row {
margin-bottom: 5px;
text-align: center;
}
@media screen and (max-width: 768px) {
.filter-row span {
margin-left: 5px;
margin-right: 5px;
}
}
@media screen and (min-width: 768px) {
.filter-row span {
margin-left: 10px;
margin-right: 10px;
}
}
</style>
<template>
<el-card>
<div class="filter-row">
<el-row>
<el-col :md="3" :xs="24">
<span class="title">{{ t('m.Number')"
min-width="60"
show-overflow
>
</vxe-table-column>
<vxe-table-column
field="title"
:title="t('m.Auth')"
min-width="100"
align="center"
>
<template v-slot="{ row }">
<el-tag :type="TRAINING_TYPE[row.auth]['color']" effect="dark">
{{ t('m.Category')"
min-width="130"
align="center"
>
<template v-slot="{ row }">
<el-tag
size="large"
:style="
'background-color: #fff; color: ' +
row.categoryColor +
'; border-color: ' +
row.categoryColor +
';'
"
>{{ row.categoryName }}</el-tag
>
</template>
</vxe-table-column>
<vxe-table-column
field="acCount"
:title="t('m.Problem_Number')"
min-width="70"
align="center"
>
</vxe-table-column>
<vxe-table-column
field="author"
:title="t('m.Recent_Update')"
min-width="96"
align="center"
show-overflow
>
<template v-slot="{ row }">
<span>
<el-tooltip
:content="row.gmtModified | localtime"
placement="top"
>
<span>{{ row.gmtModified | fromNow }}</span>
</el-tooltip>
</span>
</template>
</vxe-table-column>
</vxe-table>
<Pagination
:total="total"
:page-size="limit"
@on-change="currentChange"
:current.sync="currentPage"
@on-page-size-change="onPageSizeChange"
:layout="'prev, pager, next, sizes'"
></Pagination>
</template>
<TrainingList
ref="trainingList"
v-if="adminPage && !createPage && !problemPage"
@handleEditPage="handleEditPage"
@currentChange="currentChange"
@handleProblemPage="handleProblemPage"
></TrainingList>
<TrainingProblemList
v-if="problemPage"
:trainingId="trainingId"
@currentChangeProblem="currentChangeProblem"
@handleEditProblemPage="handleEditProblemPage"
ref="trainingProblemList"
>
</TrainingProblemList>
<Training
v-if="createPage && !editPage && !problemPage"
mode="add"
:title="t('m.Add_Training_Problem')"
width="90%"
:visible.sync="publicPage"
:close-on-click-modal="false"
>
<AddPublicProblem
v-if="publicPage"
:trainingId="trainingId"
apiMethod="getGroupTrainingProblemList"
@currentChangeProblem="currentChangeProblem"
ref="addPublicProblem"
></AddPublicProblem>
</el-dialog>
<!-- 团队添加团队题目 -->
<el-dialog
:title="t('m.Training_To_Group')"
width="850px"
:visible.sync="groupListPage"
:close-on-click-modal="true"
>
<SyncGroupList
:groupID="groupID"
@syncGroupListClose="syncGroupListClose"
ref="SyncGroupListChild"
> </SyncGroupList>
</el-dialog>
</el-card>
</template>
<script>
import { mapGetters } from "vuex";
import { TRAINING_TYPE } from "@/common/constants";
import Pagination from "@/components/oj/common/Pagination";
import TrainingList from "@/components/oj/group/TrainingList";
import Training from "@/components/oj/group/Training";
import TrainingProblemList from "@/components/oj/group/TrainingProblemList";
import AddPublicProblem from "@/components/oj/group/AddPublicProblem.vue";
import AddGroupProblem from "@/components/oj/group/AddGroupProblem.vue";
import SyncGroupList from "@/components/oj/group/SyncGroupList.vue";
import api from "@/common/api";
export default {
name: "GroupTrainingList",
components: {
Pagination,
TrainingList,
Training,
TrainingProblemList,
AddPublicProblem,
AddGroupProblem,
SyncGroupList,
},
data() {
return {
total: 0,
currentPage: 1,
limit: 10,
trainingList: [],
TRAINING_TYPE: {},
loading: false,
adminPage: false,
createPage: false,
editPage: false,
problemPage: false,
publicPage: false,
groupPage: false,
groupListPage: false, //打开同步题单弹窗
editProblemPage: false,
trainingId: null,
groupID:"", //团队/班级id
};
},
mounted() {
this.TRAINING_TYPE = Object.assign({}, TRAINING_TYPE);
this.init();
},
methods: {
init() {
this.groupID=this.$route.params.groupID
this.getGroupTrainingList();
},
onPageSizeChange(pageSize) {
this.limit = pageSize;
this.init();
},
currentChange(page) {
this.currentPage = page;
this.init();
},
currentChangeProblem() {
this.$refs.trainingProblemList.currentChange(1);
},
getGroupTrainingList() {
this.trainingList = [];
this.loading = true;
api
.getGroupTrainingList(
this.currentPage,
this.limit,
this.$route.params.groupID
)
.then(
(res) => {
this.trainingList = res.data.data.records;
console.log("团队this.trainingList ", this.trainingList);
this.total = res.data.data.total;
this.loading = false;
},
(err) => {
this.loading = false;
}
);
},
goGroupTraining(event) {
this.$router.push({
name: "GroupTrainingDetails",
params: {
trainingID: event.row.id,
groupID: this.$route.params.groupID,
},
});
},
handleCreatePage() {
this.createPage = !this.createPage;
},
// 公共训练同步至班级
handleTrainingToGroup() {
console.log("公共训练同步至班级");
this.groupListPage = true;
setTimeout(() => {
this.$refs.SyncGroupListChild.getTrainingList();
});
},
//关闭弹窗
syncGroupListClose(){
this.groupListPage=false
this.getGroupTrainingList()
},
handleEditPage() {
this.editPage = !this.editPage;
this.$refs.trainingList.editPage = this.editPage;
},
handleAdminPage() {
this.adminPage = !this.adminPage;
this.createPage = false;
this.editPage = false;
},
handleProblemPage(trainingId) {
this.trainingId = trainingId;
this.problemPage = !this.problemPage;
},
handleGroupPage() {
this.groupPage = !this.groupPage;
},
handleEditProblemPage() {
this.editProblemPage = !this.editProblemPage;
this.$refs.trainingProblemList.editPage = this.editProblemPage;
},
getPassingRate(ac, total) {
if (!total) {
return 0;
}
return ((ac / total) * 100).toFixed(2);
},
},
computed: {
...mapGetters(["isAuthenticated", "isSuperAdmin", "isGroupAdmin"]),
},
};
</script>
<style scoped>
.title {
font-size: 20px;
vertical-align: middle;
float: left;
}
.filter-row {
margin-bottom: 5px;
text-align: center;
}
@media screen and (max-width: 768px) {
.filter-row span {
margin-left: 5px;
margin-right: 5px;
}
}
@media screen and (min-width: 768px) {
.filter-row span {
margin-left: 10px;
margin-right: 10px;
}
}
</style>
<template> <el-card> <div class="filter-row"> <el-row> <el-col :md="3" :xs="24"> <span class="title">{{ $t("m.Group_Training") }}</span> </el-col> <!-- 创建团队新题单 --> <el-col :md="18" :xs="24" v-if=" (isSuperAdmin || isGroupAdmin) && !problemPage && !editProblemPage " > <el-button v-if="!editPage" :type="createPage ? 'warning' : 'primary'" size="small" @click="handleCreatePage" :icon="createPage ? 'el-icon-back' : 'el-icon-plus'" >{{ createPage ? $t("m.Back_To_Admin_Training_List") : $t("m.Create") }}</el-button > <!-- 新增主题库同步到班级题库 --> <el-button v-if="!editPage && !createPage" :type="createPage ? 'warning' : 'primary'" size="small" @click="handleTrainingToGroup" :icon="createPage ? 'el-icon-back' : 'el-icon-plus'" >{{ $t("m.Training_To_Group") }}</el-button > <el-button v-if="editPage && adminPage" type="warning" size="small" @click="handleEditPage" icon="el-icon-back" >{{ $t("m.Back_To_Admin_Training_List") }}</el-button > <el-button :type="adminPage ? 'danger' : 'success'" v-if="!editPage && !createPage" size="small" @click="handleAdminPage" :icon="adminPage ? 'el-icon-back' : 'el-icon-s-opportunity'" >{{ adminPage ? $t("m.Back_To_Training_List") : $t("m.Training_Admin") }}</el-button > </el-col> <el-col :md="18" :xs="24" v-else-if=" (isSuperAdmin || isGroupAdmin) && problemPage && !editProblemPage " > <el-button type="primary" size="small" @click="publicPage = true" icon="el-icon-plus" >{{ $t("m.Add_From_Public_Problem") }}</el-button > <el-button type="success" size="small" @click="handleGroupPage" icon="el-icon-plus" >{{ $t("m.Add_From_Group_Problem") }}</el-button > <el-button type="warning" size="small" @click="handleProblemPage(null)" icon="el-icon-back" >{{ $t("m.Back_To_Admin_Training_List") }}</el-button > </el-col> <el-col :md="18" :xs="24" v-else-if="(isSuperAdmin || isGroupAdmin) && editProblemPage" > <el-button type="primary" size="small" @click="handleEditProblemPage" icon="el-icon-back" >{{ $t("m.Back_Admin_Training_Problem_List") }}</el-button >` </el-col> </el-row> </div> <template v-if="!adminPage && !createPage && !problemPage"> <vxe-table border="inner" stripe auto-resize highlight-hover-row :data="trainingList" :loading="loading" align="center" @cell-click="goGroupTraining" > <vxe-table-column field="rank" :title="$t('m.Number')" min-width="60" show-overflow > </vxe-table-column> <vxe-table-column field="title" :title="$t('m.Title')" min-width="200" align="center" > </vxe-table-column> <vxe-table-column field="auth" :title="$t('m.Auth')" min-width="100" align="center" > <template v-slot="{ row }"> <el-tag :type="TRAINING_TYPE[row.auth]['color']" effect="dark"> {{ $t("m.Training_" + row.auth) }} </el-tag> </template> </vxe-table-column> <vxe-table-column field="categoryName" :title="$t('m.Category')" min-width="130" align="center" > <template v-slot="{ row }"> <el-tag size="large" :style=" 'background-color: #fff; color: ' + row.categoryColor + '; border-color: ' + row.categoryColor + ';' " >{{ row.categoryName }}</el-tag > </template> </vxe-table-column> <vxe-table-column field="acCount" :title="$t('m.Progress')" min-width="120" align="center" > <template v-slot="{ row }"> <span> <el-tooltip effect="dark" :content="row.acCount + '/' + row.problemCount" placement="top" > <el-progress :text-inside="true" :stroke-width="20" :percentage="getPassingRate(row.acCount, row.problemCount)" ></el-progress> </el-tooltip> </span> </template> </vxe-table-column> <vxe-table-column field="problemCount" :title="$t('m.Problem_Number')" min-width="70" align="center" > </vxe-table-column> <vxe-table-column field="author" :title="$t('m.Author')" min-width="130" align="center" show-overflow > </vxe-table-column> <vxe-table-column field="gmtModified" :title="$t('m.Recent_Update')" min-width="96" align="center" show-overflow > <template v-slot="{ row }"> <span> <el-tooltip :content="row.gmtModified | localtime" placement="top" > <span>{{ row.gmtModified | fromNow }}</span> </el-tooltip> </span> </template> </vxe-table-column> </vxe-table> <Pagination :total="total" :page-size="limit" @on-change="currentChange" :current.sync="currentPage" @on-page-size-change="onPageSizeChange" :layout="'prev, pager, next, sizes'" ></Pagination> </template> <TrainingList ref="trainingList" v-if="adminPage && !createPage && !problemPage" @handleEditPage="handleEditPage" @currentChange="currentChange" @handleProblemPage="handleProblemPage" ></TrainingList> <TrainingProblemList v-if="problemPage" :trainingId="trainingId" @currentChangeProblem="currentChangeProblem" @handleEditProblemPage="handleEditProblemPage" ref="trainingProblemList" > </TrainingProblemList> <Training v-if="createPage && !editPage && !problemPage" mode="add" :title="$t('m.Create_Training')" apiMethod="addGroupTraining" @handleCreatePage="handleCreatePage" @currentChange="currentChange" ></Training> <!-- 团队添加公共题目 --> <el-dialog :title="$t('m.Add_Training_Problem')" width="90%" :visible.sync="publicPage" :close-on-click-modal="false" > <AddPublicProblem v-if="publicPage" :trainingId="trainingId" apiMethod="getGroupTrainingProblemList" @currentChangeProblem="currentChangeProblem" ref="addPublicProblem" ></AddPublicProblem> </el-dialog> <!-- 团队添加团队题目 --> <el-dialog :title="$t('m.Add_Training_Problem')" width="350px" :visible.sync="groupPage" :close-on-click-modal="false" > <AddGroupProblem :trainingId="trainingId" @currentChangeProblem="currentChangeProblem" @handleGroupPage="handleGroupPage" ></AddGroupProblem> </el-dialog> <!-- 打开公共题单弹窗 --> <el-dialog :title="$t('m.Training_To_Group')" width="850px" :visible.sync="groupListPage" :close-on-click-modal="true" > <SyncGroupList :groupID="groupID" @syncGroupListClose="syncGroupListClose" ref="SyncGroupListChild" > </SyncGroupList> </el-dialog> </el-card> </template> <script> import { mapGetters } from "vuex"; import { TRAINING_TYPE } from "@/common/constants"; import Pagination from "@/components/oj/common/Pagination"; import TrainingList from "@/components/oj/group/TrainingList"; import Training from "@/components/oj/group/Training"; import TrainingProblemList from "@/components/oj/group/TrainingProblemList"; import AddPublicProblem from "@/components/oj/group/AddPublicProblem.vue"; import AddGroupProblem from "@/components/oj/group/AddGroupProblem.vue"; import SyncGroupList from "@/components/oj/group/SyncGroupList.vue"; import api from "@/common/api"; export default { name: "GroupTrainingList", components: { Pagination, TrainingList, Training, TrainingProblemList, AddPublicProblem, AddGroupProblem, SyncGroupList, }, data() { return { total: 0, currentPage: 1, limit: 10, trainingList: [], TRAINING_TYPE: {}, loading: false, adminPage: false, createPage: false, editPage: false, problemPage: false, publicPage: false, groupPage: false, groupListPage: false, //打开同步题单弹窗 editProblemPage: false, trainingId: null, groupID:"", //团队/班级id }; }, mounted() { this.TRAINING_TYPE = Object.assign({}, TRAINING_TYPE); this.init(); }, methods: { init() { this.groupID=this.$route.params.groupID this.getGroupTrainingList(); }, onPageSizeChange(pageSize) { this.limit = pageSize; this.init(); }, currentChange(page) { this.currentPage = page; this.init(); }, currentChangeProblem() { this.$refs.trainingProblemList.currentChange(1); }, getGroupTrainingList() { this.trainingList = []; this.loading = true; api .getGroupTrainingList( this.currentPage, this.limit, this.$route.params.groupID ) .then( (res) => { this.trainingList = res.data.data.records; console.log("团队this.trainingList ", this.trainingList); this.total = res.data.data.total; this.loading = false; }, (err) => { this.loading = false; } ); }, goGroupTraining(event) { this.$router.push({ name: "GroupTrainingDetails", params: { trainingID: event.row.id, groupID: this.$route.params.groupID, }, }); }, handleCreatePage() { this.createPage = !this.createPage; }, // 公共训练同步至班级 handleTrainingToGroup() { console.log("公共训练同步至班级"); this.groupListPage = true; setTimeout(() => { this.$refs.SyncGroupListChild.getTrainingList(); }); }, //关闭弹窗 syncGroupListClose(){ this.groupListPage=false this.getGroupTrainingList() }, handleEditPage() { this.editPage = !this.editPage; this.$refs.trainingList.editPage = this.editPage; }, handleAdminPage() { this.adminPage = !this.adminPage; this.createPage = false; this.editPage = false; }, handleProblemPage(trainingId) { this.trainingId = trainingId; this.problemPage = !this.problemPage; }, handleGroupPage() { this.groupPage = !this.groupPage; }, handleEditProblemPage() { this.editProblemPage = !this.editProblemPage; this.$refs.trainingProblemList.editPage = this.editProblemPage; }, getPassingRate(ac, total) { if (!total) { return 0; } return ((ac / total) * 100).toFixed(2); }, }, computed: { ...mapGetters(["isAuthenticated", "isSuperAdmin", "isGroupAdmin"]), }, }; </script> <style scoped> .title { font-size: 20px; vertical-align: middle; float: left; } .filter-row { margin-bottom: 5px; text-align: center; } @media screen and (max-width: 768px) { .filter-row span { margin-left: 5px; margin-right: 5px; } } @media screen and (min-width: 768px) { .filter-row span { margin-left: 10px; margin-right: 10px; } } </style>
后台的实现也比较简单,原作者留下了很多现成的接口,直接使用就行。
String groupID=jsonObject.getStr("groupID"); //要同步到哪个班级
JSONArray trainingList= jsonObject.getJSONArray("selectedTrain"); //题单列表(id)
//循环遍历每一个题单数据
for(Object o:trainingList){
Long tid = Long.valueOf(o.toString());
//获取当前训练
Training training = trainingEntityService.getById(tid);
if (training == null) {
throw new StatusNotFoundException("该训练不存在!");
}
training.setGid(Long.valueOf(groupID));
//获取训练的类别
QueryWrapper<MappingTrainingCategory> mappingTrainingCategoryQueryWrapper = new QueryWrapper<>();
mappingTrainingCategoryQueryWrapper.eq("tid", tid);
MappingTrainingCategory mappingTrainingCategory = mappingTrainingCategoryEntityService.getOne(mappingTrainingCategoryQueryWrapper);
TrainingCategory trainingCategory = null;
if (mappingTrainingCategory != null) {
trainingCategory = trainingCategoryEntityService.getById(mappingTrainingCategory.getCid());
}
TrainingDTO trainingDto = new TrainingDTO();
trainingDto.setTraining(training);
trainingDto.setTrainingCategory(trainingCategory);
Long newTrainId = groupTrainingManager.addTraining(trainingDto); //新的训练id
//往新题单里插入题目
trainingProblemMapper.getOldProblemAndInsertNew(tid,newTrainId);
}
String groupID=jsonObject.getStr("groupID"); //要同步到哪个班级
JSONArray trainingList= jsonObject.getJSONArray("selectedTrain"); //题单列表(id)
//循环遍历每一个题单数据
for(Object o:trainingList){
Long tid = Long.valueOf(o.toString());
//获取当前训练
Training training = trainingEntityService.getById(tid);
if (training == null) {
throw new StatusNotFoundException("该训练不存在!");
}
training.setGid(Long.valueOf(groupID));
//获取训练的类别
QueryWrapper<MappingTrainingCategory> mappingTrainingCategoryQueryWrapper = new QueryWrapper<>();
mappingTrainingCategoryQueryWrapper.eq("tid", tid);
MappingTrainingCategory mappingTrainingCategory = mappingTrainingCategoryEntityService.getOne(mappingTrainingCategoryQueryWrapper);
TrainingCategory trainingCategory = null;
if (mappingTrainingCategory != null) {
trainingCategory = trainingCategoryEntityService.getById(mappingTrainingCategory.getCid());
}
TrainingDTO trainingDto = new TrainingDTO();
trainingDto.setTraining(training);
trainingDto.setTrainingCategory(trainingCategory);
Long newTrainId = groupTrainingManager.addTraining(trainingDto); //新的训练id
//往新题单里插入题目
trainingProblemMapper.getOldProblemAndInsertNew(tid,newTrainId);
}
String groupID=jsonObject.getStr("groupID"); //要同步到哪个班级 JSONArray trainingList= jsonObject.getJSONArray("selectedTrain"); //题单列表(id) //循环遍历每一个题单数据 for(Object o:trainingList){ Long tid = Long.valueOf(o.toString()); //获取当前训练 Training training = trainingEntityService.getById(tid); if (training == null) { throw new StatusNotFoundException("该训练不存在!"); } training.setGid(Long.valueOf(groupID)); //获取训练的类别 QueryWrapper<MappingTrainingCategory> mappingTrainingCategoryQueryWrapper = new QueryWrapper<>(); mappingTrainingCategoryQueryWrapper.eq("tid", tid); MappingTrainingCategory mappingTrainingCategory = mappingTrainingCategoryEntityService.getOne(mappingTrainingCategoryQueryWrapper); TrainingCategory trainingCategory = null; if (mappingTrainingCategory != null) { trainingCategory = trainingCategoryEntityService.getById(mappingTrainingCategory.getCid()); } TrainingDTO trainingDto = new TrainingDTO(); trainingDto.setTraining(training); trainingDto.setTrainingCategory(trainingCategory); Long newTrainId = groupTrainingManager.addTraining(trainingDto); //新的训练id //往新题单里插入题目 trainingProblemMapper.getOldProblemAndInsertNew(tid,newTrainId); }
大部分都是现成的,只有getOldProblemAndInsertNew这个是自己写的,也很简单:
<select id="getOldProblemAndInsertNew">
insert into training_problem(tid,pid,display_id)
SELECT #{newTrainId},pid,display_id from training_problem
WHERE tid=#{oldTrainId}
</select>
<select id="getOldProblemAndInsertNew">
insert into training_problem(tid,pid,display_id)
SELECT #{newTrainId},pid,display_id from training_problem
WHERE tid=#{oldTrainId}
</select>
<select id="getOldProblemAndInsertNew"> insert into training_problem(tid,pid,display_id) SELECT #{newTrainId},pid,display_id from training_problem WHERE tid=#{oldTrainId} </select>
这里的oldTrainId对应上面的tid。
3.缺点
这个功能只能将公共训练的题单同步到班级内,每次都是新的,无法将原来一模一样的题单进行更新,好在做题记录都是通用的。
后面的文章再写一篇同步题库的。