草稿计划支持选择场次后“批量移除已选场次”,一次最多 100 个。
批量操作会完整校验:包含不存在、其他计划或已发布计划的场次时整批拒绝,不会部分删除。[ExamsController.cs (line 384)](E:/jiaowu/src/Jiaowu.Api/Controllers/ExamsController.cs:384) 草稿计划新增“删除草稿”按钮,删除前显示计划名称及场次数量确认。[ExamsView.vue (line 292)](E:/jiaowu/web/src/views/ExamsView.vue:292) 删除草稿计划会级联清理场次和监考关联;已发布计划不能删除。[ExamsController.cs (line 81)](E:/jiaowu/src/Jiaowu.Api/Controllers/ExamsController.cs:81) 删除后会自动切换到其他计划;没有剩余计划时正确显示空状态。 新增草稿、已发布、跨计划混选及级联删除测试。[ExamDeletionControllerTests.cs (line 17)](E:/jiaowu/tests/Jiaowu.Api.Tests/ExamDeletionControllerTests.cs:17)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { Download, Plus, Promotion, Refresh, UserFilled, Setting } from '@element-plus/icons-vue'
|
||||
import { Delete, Download, Plus, Promotion, Refresh, UserFilled, Setting } from '@element-plus/icons-vue'
|
||||
import http, { apiErrorMessage } from '../api/http'
|
||||
import { downloadApiFile } from '../api/excel'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
@@ -22,6 +22,8 @@ const timeSlots = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const arrangeLoading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const removeLoading = ref(false)
|
||||
const deletePlanLoading = ref(false)
|
||||
const planDialog = ref(false)
|
||||
const sessionDialog = ref(false)
|
||||
const editingSession = ref<any | null>(null)
|
||||
@@ -104,6 +106,12 @@ async function load() {
|
||||
?? plans.value.find((x) => x.termIsCurrent)
|
||||
?? plans.value[0]
|
||||
if (plan) await selectPlan(plan.id)
|
||||
else {
|
||||
selected.value = null
|
||||
selectedSessionIds.value = []
|
||||
tasks.value = []
|
||||
timeSlots.value = []
|
||||
}
|
||||
} catch (error) { ElMessage.error(apiErrorMessage(error)) }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
@@ -189,6 +197,35 @@ async function removeSession(row: any) {
|
||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||
}
|
||||
}
|
||||
async function removeSelectedSessions() {
|
||||
if (!selected.value || selectedSessionIds.value.length === 0) return
|
||||
const count = selectedSessionIds.value.length
|
||||
if (count > 100) {
|
||||
ElMessage.warning('一次最多移除100个考试场次,请分批选择')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定从当前考试计划移除已选的 ${count} 个考试场次吗?`,
|
||||
'批量移除考试场次',
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认移除',
|
||||
},
|
||||
)
|
||||
removeLoading.value = true
|
||||
const res = await http.post(
|
||||
`/exams/plans/${selected.value.id}/sessions/batch-remove`,
|
||||
{ sessionIds: selectedSessionIds.value },
|
||||
)
|
||||
ElMessage.success(`已移除 ${res.data.removedCount} 个考试场次`)
|
||||
await selectPlan(selected.value.id)
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||
} finally {
|
||||
removeLoading.value = false
|
||||
}
|
||||
}
|
||||
function selectFilteredTasks() {
|
||||
selectedTaskIds.value = Array.from(new Set([
|
||||
...selectedTaskIds.value,
|
||||
@@ -252,6 +289,29 @@ async function publishPlan() {
|
||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||
}
|
||||
}
|
||||
async function deleteDraftPlan() {
|
||||
if (!selected.value || selected.value.status !== 'Draft') return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除草稿考试计划“${selected.value.name}”吗?计划内 ${selected.value.sessions.length} 个考试场次也会一并删除,此操作无法撤销。`,
|
||||
'删除考试计划',
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonText: '删除草稿',
|
||||
},
|
||||
)
|
||||
deletePlanLoading.value = true
|
||||
await http.delete(`/exams/plans/${selected.value.id}`)
|
||||
ElMessage.success('草稿考试计划已删除')
|
||||
selected.value = null
|
||||
selectedSessionIds.value = []
|
||||
await load()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
||||
} finally {
|
||||
deletePlanLoading.value = false
|
||||
}
|
||||
}
|
||||
async function exportSignInSheets() {
|
||||
if (!selected.value) return
|
||||
exportLoading.value = true
|
||||
@@ -345,6 +405,14 @@ onMounted(async () => {
|
||||
<el-button v-if="selected.status === 'Draft'" :icon="Setting" @click="autoArrange('all')" :loading="arrangeLoading">一键完成</el-button>
|
||||
<el-button v-if="selected.status === 'Draft'" :icon="Plus" @click="openSession()">批量安排场次</el-button>
|
||||
<el-button v-if="selected.status === 'Draft'" type="primary" :icon="Promotion" @click="publishPlan">发布计划</el-button>
|
||||
<el-button
|
||||
v-if="selected.status === 'Draft'"
|
||||
type="danger"
|
||||
plain
|
||||
:icon="Delete"
|
||||
:loading="deletePlanLoading"
|
||||
@click="deleteDraftPlan"
|
||||
>删除草稿</el-button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="exam-filter-bar">
|
||||
@@ -359,6 +427,14 @@ onMounted(async () => {
|
||||
<el-button link type="primary" @click="selectFilteredSessions">选择筛选结果</el-button>
|
||||
<el-button link @click="clearSessionSelection">清空选择</el-button>
|
||||
<el-tag v-if="selectedSessionIds.length" type="info">已选 {{ selectedSessionIds.length }} 个</el-tag>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
:icon="Delete"
|
||||
:loading="removeLoading"
|
||||
:disabled="selectedSessionIds.length === 0"
|
||||
@click="removeSelectedSessions"
|
||||
>批量移除已选场次</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="exam-timeline">
|
||||
|
||||
Reference in New Issue
Block a user