feat(admin): 选手管理新增「小组视图」—— 激活状态与所属学校清单
- api/admin.js:新增 getTeams({ group, activated, includeTest })
- Admin.vue:列表/小组视图切换、组别 + 激活状态 + 含测试三筛选、汇总条、小组表格(所属学校 / 成员数 / 已激活 / 状态)
线上验证:R86 已部署(dist 157 文件 / 70 个 .gz),线上入口 index-DQf5Bj8S.js
This commit is contained in:
@@ -19,6 +19,15 @@ export function getAccounts(params) {
|
||||
return request.get('/admin/accounts', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取小组(团队)激活状态清单(R84 续六)
|
||||
* @param {object} params - { group, activated, includeTest }
|
||||
* @returns {Promise} { list, summary, filteredTotal, filters }
|
||||
*/
|
||||
export function getTeams(params) {
|
||||
return request.get('/admin/teams', { params })
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入 Excel 批量创建账号
|
||||
* @param {FormData} formData - 包含 Excel 文件的 FormData
|
||||
|
||||
@@ -123,6 +123,16 @@
|
||||
<!-- ========== 选手管理 ========== -->
|
||||
<template v-if="activeMenu === 'accounts'">
|
||||
<h2 class="section-heading">选手管理</h2>
|
||||
<!-- 二级视图切换(R84 续六):选手列表(原有)/ 小组视图(新增) -->
|
||||
<div class="view-switch">
|
||||
<el-radio-group v-model="accountsView" size="small" @change="onAccountsViewChange">
|
||||
<el-radio-button value="list">选手列表</el-radio-button>
|
||||
<el-radio-button value="teams">小组视图</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- ===== 视图一:选手列表(原有,逻辑与筛选未改动) ===== -->
|
||||
<template v-if="accountsView === 'list'">
|
||||
<div class="toolbar">
|
||||
<el-input v-model="accountSearch" placeholder="搜索姓名/身份证号/手机号" clearable class="search-input" @change="onAccountFilterChange" />
|
||||
<el-select v-model="accountGroupFilter" placeholder="组别筛选" clearable @change="onAccountFilterChange" class="filter-select">
|
||||
@@ -195,6 +205,67 @@
|
||||
@size-change="onAccountSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ===== 视图二:小组视图(R84 续六新增)===== -->
|
||||
<!-- 需求:查看已激活/未激活小组,且必须携带小组所属学校名称。
|
||||
口径:小组键 = 组别 + 队伍号;激活 = 全组成员均已激活;
|
||||
学校 = aggregation.organization_cn(同一小组多校时并列显示并标「多校」)。 -->
|
||||
<template v-else>
|
||||
<div class="toolbar">
|
||||
<el-select v-model="teamGroupFilter" placeholder="组别筛选" clearable @change="onTeamFilterChange" class="filter-select">
|
||||
<el-option label="中职组" value="zhongzhi" />
|
||||
<el-option label="高职组" value="gaozhi" />
|
||||
<el-option label="本科组" value="benke" />
|
||||
<el-option label="教师组" value="jiaoshi" />
|
||||
</el-select>
|
||||
<el-select v-model="teamActivatedFilter" placeholder="激活状态" clearable @change="onTeamFilterChange" class="filter-select">
|
||||
<el-option label="已激活(全组已激活)" value="true" />
|
||||
<el-option label="未激活(含部分激活)" value="false" />
|
||||
</el-select>
|
||||
<el-checkbox v-model="teamIncludeTest" class="import-test-flag" @change="onTeamFilterChange">
|
||||
含测试数据
|
||||
</el-checkbox>
|
||||
<el-button size="small" type="info" plain :loading="teamsLoading" @click="fetchTeams">刷新</el-button>
|
||||
<span class="team-summary" v-if="teamSummary">
|
||||
共 {{ teamSummary.total }} 个小组 · 已激活 {{ teamSummary.activated }} · 部分激活 {{ teamSummary.partial }} · 未激活 {{ teamSummary.inactive }}
|
||||
|成员 {{ teamSummary.memberActivated }}/{{ teamSummary.memberTotal }} 已激活
|
||||
</span>
|
||||
</div>
|
||||
<el-table :data="teams" stripe v-loading="teamsLoading" class="admin-table" style="width: 100%">
|
||||
<el-table-column label="组别" min-width="90" align="center">
|
||||
<template #default="{ row }">{{ row.groupName }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="小组编号" min-width="210">
|
||||
<template #default="{ row }">{{ row.teamNumber || row.teamId || ('队伍 #' + row.teamUid) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属学校" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.schoolName">{{ row.schoolName }}</span>
|
||||
<span v-else class="team-school-missing">— 未填写</span>
|
||||
<el-tag v-if="row.schoolConflict" type="warning" size="small" style="margin-left: 6px">多校</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成员数" min-width="90" align="center">
|
||||
<template #default="{ row }">{{ row.memberCount }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已激活成员" min-width="110" align="center">
|
||||
<template #default="{ row }">{{ row.activatedCount }} / {{ row.memberCount }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status === 'activated'" type="success" size="small">已激活</el-tag>
|
||||
<el-tag v-else-if="row.status === 'partial'" type="warning" size="small">部分激活</el-tag>
|
||||
<el-tag v-else type="info" size="small">未激活</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-if="!teamsLoading && teams.length === 0"
|
||||
description="没有符合条件的小组(可能被组别 / 激活状态 / 测试数据筛选排除)"
|
||||
:image-size="80"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- ========== 试卷管理 ========== -->
|
||||
@@ -1632,7 +1703,12 @@ function onMenuSelect(index) {
|
||||
sessionStorage.setItem('admin_activeMenu', index)
|
||||
// 切换时加载对应数据
|
||||
if (index === 'dashboard') fetchStats()
|
||||
if (index === 'accounts') fetchAccounts()
|
||||
// 选手管理:默认加载选手列表;若用户当前停在「小组视图」页签则同时刷新小组数据,
|
||||
// 避免切回该菜单时表格空白(R84 续六)
|
||||
if (index === 'accounts') {
|
||||
fetchAccounts()
|
||||
if (accountsView.value === 'teams') fetchTeams()
|
||||
}
|
||||
if (index === 'questions' && questionGroup.value) {
|
||||
// R29:试卷管理只刷题库(含大题库);「考生答题记录 / 大题作答查证」已迁到成绩管理
|
||||
refreshQuestionData(true)
|
||||
@@ -1771,6 +1847,41 @@ const accountGroupFilter = ref('')
|
||||
const accountActivatedFilter = ref('')
|
||||
const accountDateRange = ref(null)
|
||||
|
||||
// ---- 小组视图(R84 续六)----
|
||||
// 与「选手列表」共用二级页签 accountsView;筛选条件独立,互不干扰。
|
||||
const accountsView = ref('list') // 'list' | 'teams'
|
||||
const teams = ref([])
|
||||
const teamsLoading = ref(false)
|
||||
const teamGroupFilter = ref('')
|
||||
const teamActivatedFilter = ref('')
|
||||
const teamIncludeTest = ref(false)
|
||||
const teamSummary = ref(null)
|
||||
|
||||
async function fetchTeams() {
|
||||
teamsLoading.value = true
|
||||
try {
|
||||
const params = {}
|
||||
if (teamGroupFilter.value) params.group = teamGroupFilter.value
|
||||
if (teamActivatedFilter.value !== '') params.activated = teamActivatedFilter.value
|
||||
// 显式传 '0'/'1':后端只有 'true'/'1' 等真值字符串才认「含测试数据」,
|
||||
// 缺省或其它值一律按「不含测试数据」处理(生产口径)。
|
||||
params.includeTest = teamIncludeTest.value ? '1' : '0'
|
||||
const data = await adminApi.getTeams(params)
|
||||
teams.value = (data && data.list) || []
|
||||
teamSummary.value = (data && data.summary) || null
|
||||
} catch (e) {
|
||||
console.warn('获取小组列表失败', e)
|
||||
} finally {
|
||||
teamsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onTeamFilterChange() { fetchTeams() }
|
||||
|
||||
function onAccountsViewChange(view) {
|
||||
if (view === 'teams') fetchTeams()
|
||||
}
|
||||
|
||||
async function fetchAccounts() {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
@@ -3670,6 +3781,17 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* R84 续六:选手管理二级视图切换(选手列表 / 小组视图) */
|
||||
.view-switch { margin-bottom: 16px; }
|
||||
.team-summary {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
}
|
||||
/* 小组视图:未填写学校时的占位样式(区别于有值文本) */
|
||||
.team-school-missing { color: #c0c4cc; }
|
||||
|
||||
/* R30:`.exam-score-form` 相关样式已随「考生答题记录 · 评分」弹窗一并删除 */
|
||||
|
||||
.search-input { width: 220px; }
|
||||
|
||||
Reference in New Issue
Block a user