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 })
|
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 批量创建账号
|
* 导入 Excel 批量创建账号
|
||||||
* @param {FormData} formData - 包含 Excel 文件的 FormData
|
* @param {FormData} formData - 包含 Excel 文件的 FormData
|
||||||
|
|||||||
@@ -123,6 +123,16 @@
|
|||||||
<!-- ========== 选手管理 ========== -->
|
<!-- ========== 选手管理 ========== -->
|
||||||
<template v-if="activeMenu === 'accounts'">
|
<template v-if="activeMenu === 'accounts'">
|
||||||
<h2 class="section-heading">选手管理</h2>
|
<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">
|
<div class="toolbar">
|
||||||
<el-input v-model="accountSearch" placeholder="搜索姓名/身份证号/手机号" clearable class="search-input" @change="onAccountFilterChange" />
|
<el-input v-model="accountSearch" placeholder="搜索姓名/身份证号/手机号" clearable class="search-input" @change="onAccountFilterChange" />
|
||||||
<el-select v-model="accountGroupFilter" placeholder="组别筛选" clearable @change="onAccountFilterChange" class="filter-select">
|
<el-select v-model="accountGroupFilter" placeholder="组别筛选" clearable @change="onAccountFilterChange" class="filter-select">
|
||||||
@@ -195,6 +205,67 @@
|
|||||||
@size-change="onAccountSizeChange"
|
@size-change="onAccountSizeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|
||||||
<!-- ========== 试卷管理 ========== -->
|
<!-- ========== 试卷管理 ========== -->
|
||||||
@@ -1632,7 +1703,12 @@ function onMenuSelect(index) {
|
|||||||
sessionStorage.setItem('admin_activeMenu', index)
|
sessionStorage.setItem('admin_activeMenu', index)
|
||||||
// 切换时加载对应数据
|
// 切换时加载对应数据
|
||||||
if (index === 'dashboard') fetchStats()
|
if (index === 'dashboard') fetchStats()
|
||||||
if (index === 'accounts') fetchAccounts()
|
// 选手管理:默认加载选手列表;若用户当前停在「小组视图」页签则同时刷新小组数据,
|
||||||
|
// 避免切回该菜单时表格空白(R84 续六)
|
||||||
|
if (index === 'accounts') {
|
||||||
|
fetchAccounts()
|
||||||
|
if (accountsView.value === 'teams') fetchTeams()
|
||||||
|
}
|
||||||
if (index === 'questions' && questionGroup.value) {
|
if (index === 'questions' && questionGroup.value) {
|
||||||
// R29:试卷管理只刷题库(含大题库);「考生答题记录 / 大题作答查证」已迁到成绩管理
|
// R29:试卷管理只刷题库(含大题库);「考生答题记录 / 大题作答查证」已迁到成绩管理
|
||||||
refreshQuestionData(true)
|
refreshQuestionData(true)
|
||||||
@@ -1771,6 +1847,41 @@ const accountGroupFilter = ref('')
|
|||||||
const accountActivatedFilter = ref('')
|
const accountActivatedFilter = ref('')
|
||||||
const accountDateRange = ref(null)
|
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() {
|
async function fetchAccounts() {
|
||||||
accountsLoading.value = true
|
accountsLoading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -3670,6 +3781,17 @@ onMounted(() => {
|
|||||||
align-items: center;
|
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` 相关样式已随「考生答题记录 · 评分」弹窗一并删除 */
|
/* R30:`.exam-score-form` 相关样式已随「考生答题记录 · 评分」弹窗一并删除 */
|
||||||
|
|
||||||
.search-input { width: 220px; }
|
.search-input { width: 220px; }
|
||||||
|
|||||||
Reference in New Issue
Block a user