update:上外贸体测记录

This commit is contained in:
ltx 2026-06-01 17:14:19 +08:00
parent a95d5b1e90
commit 3f88408dcd
4 changed files with 728 additions and 2 deletions

View File

@ -14,10 +14,10 @@ let loadingStatus = false;
console.log(process.env.NODE_ENV, 'process.env.NODE_ENV')
if (process.env.NODE_ENV == 'development') {
// axios.defaults.baseURL = 'http://101.132.164.149:9991/';
axios.defaults.baseURL = 'http://8.153.108.90:9991/'; //测试(直连会触发 CORS,改用 Vite proxy)
// axios.defaults.baseURL = 'http://8.153.108.90:9991/'; //测试(直连会触发 CORS,改用 Vite proxy)
// axios.defaults.baseURL = '/dev-api/'; //测试(通过 Vite 代理转发,避免 CORS)
// axios.defaults.baseURL = 'https://smartsports.yuedongsports.com/'; //正式
// axios.defaults.baseURL = 'http://192.168.0.146:9991/'; //汤龙本地
axios.defaults.baseURL = 'http://172.17.30.204:9991/'; //汤龙本地
// axios.defaults.baseURL = 'http://47.101.60.251:9991/';
// axios.defaults.baseURL = 'http://localhost:9991/';
// axios.defaults.baseURL = 'http://120.48.130.27:14102/';

16
src/api/shangWaiMao.js Normal file
View File

@ -0,0 +1,16 @@
import http from './http.js'
// 获取体测成绩列表页搜索条件
export function GetTempTccjFilterOptions() {
return http.get(`/api/ShangWaiMao/GetTempTccjFilterOptions`)
}
// 获取体测成绩分页列表
export function GetTempTccjPageList(StudentNo, Name, Sex, CollegeName, GradeNo, AdministrativeClass, SportsClass, Conclusion, PageIndex, PageSize) {
return http.get(`/api/ShangWaiMao/GetTempTccjPageList?StudentNo=${StudentNo}&Name=${Name}&Sex=${Sex}&CollegeName=${CollegeName}&GradeNo=${GradeNo}&AdministrativeClass=${AdministrativeClass}&SportsClass=${SportsClass}&Conclusion=${Conclusion}&PageIndex=${PageIndex}&PageSize=${PageSize}`)
}
// 获取体测成绩详情
export function GetTempTccjDetail(StudentNo, SchoolYear, SchoolTerm) {
return http.get(`/api/ShangWaiMao/GetTempTccjDetail?StudentNo=${StudentNo}&SchoolYear=${SchoolYear}&SchoolTerm=${SchoolTerm}`)
}

View File

@ -607,6 +607,14 @@ const routes = [{
path: '/openReservation', //学校管理/开放预约
name: 'openReservation',
component: () => import('@/views/openReservation/index.vue'),
},
{
path: '/TempTcc', //体测成绩查询
name: 'TempTcc',
component: () => import('@/views/TempTcc/index.vue'),
meta: {
keepAlive: false
}
}
]
},

702
src/views/TempTcc/index.vue Normal file
View File

@ -0,0 +1,702 @@
<template>
<div class="tempTcc">
<!-- 搜索区域 -->
<div class="searchArea">
<div class="searchTitle">
<span>体测成绩查询</span>
</div>
<div class="searchBar">
<div class="searchItem">
<span>学号:</span>
<el-input v-model="search.studentNo" placeholder="请输入学号" clearable />
</div>
<div class="searchItem">
<span>姓名:</span>
<el-input v-model="search.name" placeholder="请输入姓名" clearable />
</div>
<div class="searchItem">
<span>性别:</span>
<el-select v-model="search.sex" placeholder="请选择" clearable>
<el-option v-for="item in sexList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
<div class="searchItem">
<span>院系:</span>
<el-select v-model="search.collegeName" placeholder="请选择" clearable filterable>
<el-option v-for="item in collegeList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
<div class="searchItem">
<span>年级:</span>
<el-select v-model="search.gradeNo" placeholder="请选择" clearable>
<el-option v-for="item in gradeList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
<div class="searchItem">
<span>行政班:</span>
<el-select v-model="search.administrativeClass" placeholder="请选择" clearable filterable>
<el-option v-for="item in administrativeClassList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
<div class="searchItem">
<span>体育班:</span>
<el-select v-model="search.sportsClass" placeholder="请选择" clearable filterable>
<el-option v-for="item in sportsClassList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
<div class="searchItem">
<span>等级:</span>
<el-select v-model="search.conclusion" placeholder="全部" clearable>
<el-option v-for="item in conclusionList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
<div class="searchBtns">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</div>
</div>
</div>
<!-- 表格区域 -->
<div class="tableArea">
<el-table :data="tableData"
style="width: 100%; height: calc(100vh - 260px);"
:header-cell-style="{ 'text-align': 'center', 'font-size': '12px', 'color': '#3F404A', padding: '3px', background: '#F8F8FA', height: '50px' }"
:cell-style="{ 'text-align': 'center', padding: '3px' }"
:row-style="{ height: '40px' }">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="studentNo" label="学号" min-width="110" />
<el-table-column prop="name" label="姓名" min-width="90">
<template #default="{ row }">
<span style="color: #1890ff; cursor: pointer;">{{ row.name }}</span>
</template>
</el-table-column>
<el-table-column prop="sex" label="性别" width="70" />
<el-table-column prop="gradeNo" label="年级" width="80" />
<el-table-column prop="administrativeClass" label="行政班" min-width="160" />
<el-table-column prop="sportsClass" label="体育班" min-width="100" />
<el-table-column prop="testState" label="测试状态" width="90">
<template #default="{ row }">
<span :style="{ color: row.testState === '缺项' ? '#f5222d' : row.testState === '已测试' ? '#52c41a' : '' }">
{{ row.testState }}
</span>
</template>
</el-table-column>
<el-table-column prop="exempt" label="免测标志" width="90">
<template #default="{ row }">
<span :style="{ color: row.exempt === '单项免测' ? '#f5222d' : '' }">
{{ row.exempt }}
</span>
</template>
</el-table-column>
<el-table-column prop="addScore" label="附加分" width="80" />
<el-table-column prop="score" label="标准分" width="80" />
<el-table-column prop="healthScore" label="学年总分" width="90" />
<el-table-column prop="conclusion" label="学年结论" width="90" />
<el-table-column prop="remark" label="备注" min-width="80" />
<el-table-column label="操作" width="70" fixed="right">
<template #default="{ row }">
<el-button type="text" :icon="Search" @click="handleViewDetail(row)" />
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
:current-page="pagination.pageIndex"
:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
<!-- 详情弹窗 -->
<el-dialog v-model="detailDialog.visible" title="体测数据详情" width="900px" :close-on-click-modal="false"
destroy-on-close>
<div class="detailContent" v-loading="detailDialog.loading">
<!-- 基本信息 -->
<div class="sectionTitle">基本信息</div>
<el-form label-width="80px" class="basicInfo">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="院系:">
<el-input :value="detailData.collegeName" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年级:">
<el-select :model-value="detailData.gradeNo" disabled style="width:100%">
<el-option :label="detailData.gradeNo" :value="detailData.gradeNo" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="班级:">
<el-input :value="detailData.className" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="学号:">
<el-input :value="detailData.studentNo" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="姓名:">
<el-input :value="detailData.name" readonly />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别:">
<el-select :model-value="detailData.sex" disabled style="width:100%">
<el-option :label="detailData.sex" :value="detailData.sex" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="标准分:">
<el-input :value="detailData.score" readonly />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="附加分:">
<el-input :value="detailData.addScore" readonly />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="总分:">
<el-input :value="detailData.healthScore" readonly />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="结论:">
<el-input :value="detailData.conclusion" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注:">
<el-input :value="detailData.remark" type="textarea" :rows="2" readonly />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 体测数据 -->
<div class="sectionTitle" style="margin-top: 16px;">体测数据</div>
<div class="detailBottom">
<div class="detailTable">
<el-table :data="detailData.itemList"
:header-cell-style="{ 'text-align': 'center', 'font-size': '12px', 'color': '#3F404A', padding: '3px', background: '#F8F8FA' }"
:cell-style="{ 'text-align': 'center', padding: '4px' }"
style="width: 100%;">
<el-table-column type="index" label="序号" width="55" />
<el-table-column prop="itemName" label="项目" min-width="110" />
<el-table-column prop="score" label="成绩" width="80" />
<el-table-column prop="unit" label="单位" width="80" />
<el-table-column prop="additionalScore" label="附加分" width="80">
<template #default="{ row }">
<span :style="{ color: row.additionalScore > 0 ? '#1890ff' : '' }">
{{ row.additionalScore !== undefined ? row.additionalScore : '' }}
</span>
</template>
</el-table-column>
<el-table-column prop="itemScore" label="得分" width="80">
<template #default="{ row }">
<span style="color: #1890ff;">{{ row.itemScore }}</span>
</template>
</el-table-column>
<el-table-column prop="level" label="等级" width="80" />
</el-table>
</div>
<div class="radarChart">
<div class="radarLegend">
<span class="legendItem"><i class="legendDot" style="background:#4472C4;"></i>个人成绩</span>
<span class="legendItem"><i class="legendDot" style="background:#70AD47;"></i>全校平均</span>
</div>
<div id="radarChartDom" style="width: 320px; height: 300px;"></div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import { Search } from '@element-plus/icons-vue';
import * as echarts from 'echarts';
import * as _api from '@/api/shangWaiMao';
// 搜索条件选项 — 用独立 ref 避免整体替换 reactive 数组导致 el-select 内部引用失效
const sexList = ref<any[]>([]);
const collegeList = ref<any[]>([]);
const gradeList = ref<any[]>([]);
const administrativeClassList = ref<any[]>([]);
const sportsClassList = ref<any[]>([]);
const conclusionList = ref<any[]>([]);
// 搜索表单
const search = reactive({
studentNo: '',
name: '',
sex: '',
collegeName: '',
gradeNo: '',
administrativeClass: '',
sportsClass: '',
conclusion: ''
});
// 分页
const pagination = reactive({
pageIndex: 1,
pageSize: 20,
total: 0
});
// 表格数据
const tableData = ref([]);
// 详情弹窗
const detailDialog = reactive({
visible: false,
loading: false
});
const detailData = reactive({
collegeName: '',
gradeNo: '',
className: '',
studentNo: '',
name: '',
sex: '',
score: '',
addScore: '',
healthScore: '',
conclusion: '',
remark: '',
itemList: []
});
// 当前查看详情的行数据
const currentRow = ref<any>(null);
// 获取搜索条件选项
async function getFilterOptions() {
try {
const res = await _api.GetTempTccjFilterOptions();
if (res.code == 200 && res.data) {
sexList.value = res.data.sexList || [];
collegeList.value = res.data.collegeList || [];
gradeList.value = res.data.gradeList || [];
administrativeClassList.value = res.data.administrativeClassList || [];
sportsClassList.value = res.data.sportsClassList || [];
conclusionList.value = res.data.conclusionList || [];
}
} catch (error) {
console.error('获取筛选条件失败:', error);
}
}
// 获取列表数据
async function getPageList() {
try {
const res = await _api.GetTempTccjPageList(
search.studentNo,
search.name,
search.sex,
search.collegeName,
search.gradeNo,
search.administrativeClass,
search.sportsClass,
search.conclusion,
pagination.pageIndex,
pagination.pageSize
);
if (res.code == 200 && res.data) {
const rawList = res.data.datas ?? res.data.list ?? res.data.records ?? (Array.isArray(res.data) ? res.data : []);
tableData.value = Array.isArray(rawList) ? rawList : [];
pagination.total = res.data.total ?? res.data.totalCount ?? 0;
} else {
ElMessage.error(res.message || '获取数据失败');
}
} catch (error) {
console.error('获取列表失败:', error);
}
}
// 查询
function handleSearch() {
pagination.pageIndex = 1;
getPageList();
}
// 重置
function handleReset() {
search.studentNo = '';
search.name = '';
search.sex = '';
search.collegeName = '';
search.gradeNo = '';
search.administrativeClass = '';
search.sportsClass = '';
search.conclusion = '';
pagination.pageIndex = 1;
getPageList();
}
// 切换每页条数
function handleSizeChange(val: number) {
pagination.pageSize = val;
pagination.pageIndex = 1;
getPageList();
}
// 翻页
function handlePageChange(val: number) {
pagination.pageIndex = val;
getPageList();
}
// 查看详情
async function handleViewDetail(row: any) {
currentRow.value = row;
detailDialog.visible = true;
detailDialog.loading = true;
// 重置数据
Object.assign(detailData, {
collegeName: '',
gradeNo: '',
className: '',
studentNo: '',
name: '',
sex: '',
score: '',
addScore: '',
healthScore: '',
conclusion: '',
remark: '',
itemList: []
});
try {
const res = await _api.GetTempTccjDetail(
row.studentNo,
row.schoolYear,
row.schoolTerm
);
if (res.code == 200 && res.data) {
const d = res.data;
detailData.collegeName = d.collegeName || '';
detailData.gradeNo = d.gradeNo || d.grade || '';
detailData.className = d.className || '';
detailData.studentNo = d.studentNo || '';
detailData.name = d.name || '';
detailData.sex = d.sex || '';
detailData.score = d.score ?? '';
detailData.addScore = d.addScore ?? '';
detailData.healthScore = d.healthScore ?? '';
detailData.conclusion = d.conclusion || '';
detailData.remark = d.remark || '';
detailData.itemList = d.itemList || d.tccjList || [];
// 渲染雷达图
await nextTick();
renderRadarChart(d);
} else {
ElMessage.error(res.message || '获取详情失败');
}
} catch (error) {
console.error('获取详情失败:', error);
} finally {
detailDialog.loading = false;
}
}
// 渲染雷达图
function renderRadarChart(d: any) {
const dom = document.getElementById('radarChartDom');
if (!dom) return;
const radarData = d.radarData || {};
const indicators = radarData.indicators || (detailData.itemList
.filter((item: any) => item.itemScore != null && item.itemScore !== '')
.map((item: any) => ({ name: item.itemName, max: 100 })));
const personalData = radarData.personalData || detailData.itemList
.filter((item: any) => item.itemScore != null && item.itemScore !== '')
.map((item: any) => item.itemScore || 0);
const avgData = radarData.avgData || [];
if (!indicators || indicators.length === 0) return;
echarts.dispose(dom);
const chart = echarts.init(dom);
chart.setOption({
tooltip: {},
legend: { show: false },
radar: {
indicator: indicators,
shape: 'polygon',
splitNumber: 4,
axisName: { color: '#666', fontSize: 12 },
splitLine: { lineStyle: { color: 'rgba(0,0,0,0.1)' } },
splitArea: { areaStyle: { color: ['rgba(255,255,255,0)', 'rgba(230,240,255,0.2)'] } }
},
series: [
{
type: 'radar',
data: [
{
value: personalData,
name: '个人成绩',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#4472C4', width: 2 },
areaStyle: { color: 'rgba(68,114,196,0.3)' },
itemStyle: { color: '#4472C4' }
},
...(avgData.length > 0 ? [{
value: avgData,
name: '全校平均',
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#70AD47', width: 2, type: 'dashed' },
areaStyle: { color: 'rgba(0,0,0,0)' },
itemStyle: { color: '#70AD47' }
}] : [])
]
}
]
});
}
onMounted(() => {
getFilterOptions();
getPageList();
});
</script>
<style lang="less" scoped>
.tempTcc {
width: 100%;
min-height: 100%;
padding: 16px;
background: #f0f2f5;
box-sizing: border-box;
// ---- 搜索卡片 ----
.searchArea {
background: #fff;
padding: 16px 20px 12px;
margin-bottom: 16px;
border-radius: 8px;
box-shadow: 0 1px 6px rgba(0, 0, 0, .06);
.searchTitle {
font-size: 15px;
font-weight: 700;
color: rgba(0, 0, 0, .85);
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 8px;
&::before {
content: '';
display: inline-block;
width: 3px;
height: 15px;
background: #1890ff;
border-radius: 2px;
flex-shrink: 0;
}
}
.searchBar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px 20px;
padding-bottom: 4px;
.searchItem {
display: flex;
align-items: center;
gap: 6px;
span {
white-space: nowrap;
font-size: 13px;
color: rgba(0, 0, 0, .65);
}
:deep(.el-input),
:deep(.el-select) {
width: 148px;
}
:deep(.el-input__wrapper) {
border-radius: 6px;
}
:deep(.el-select .el-input__wrapper) {
border-radius: 6px;
}
}
.searchBtns {
display: flex;
gap: 8px;
:deep(.el-button) {
border-radius: 6px;
padding: 0 18px;
}
}
}
}
// ---- 表格卡片 ----
.tableArea {
background: #fff;
padding: 16px 20px 16px;
border-radius: 8px;
box-shadow: 0 1px 6px rgba(0, 0, 0, .06);
:deep(.el-table) {
border-radius: 6px;
overflow: hidden;
th.el-table__cell {
background: #f8f9fb !important;
}
td.el-table__cell {
border-bottom: 1px solid #f5f5f5;
}
.el-table__row:hover > td {
background: #f0f7ff !important;
}
}
.pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
:deep(.el-pagination) {
--el-pagination-border-radius: 6px;
}
}
}
// ---- 详情弹窗 ----
.detailContent {
.sectionTitle {
font-size: 14px;
font-weight: 700;
color: #1890ff;
padding: 0 0 8px 6px;
border-left: 3px solid #1890ff;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 14px;
border-radius: 0;
}
.basicInfo {
background: #fafafa;
border-radius: 6px;
padding: 12px 12px 0;
margin-bottom: 4px;
:deep(.el-form-item) {
margin-bottom: 12px;
}
:deep(.el-input__wrapper),
:deep(.el-textarea__inner) {
background: #fff;
border-radius: 6px;
}
}
.detailBottom {
display: flex;
gap: 20px;
align-items: flex-start;
margin-top: 4px;
.detailTable {
flex: 1;
min-width: 0;
:deep(.el-table) {
border-radius: 6px;
overflow: hidden;
th.el-table__cell {
background: #f8f9fb !important;
}
td.el-table__cell {
border-bottom: 1px solid #f5f5f5;
}
}
}
.radarChart {
width: 340px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
background: #fafafa;
border-radius: 8px;
padding: 12px 8px 8px;
.radarLegend {
display: flex;
gap: 20px;
margin-bottom: 6px;
.legendItem {
display: flex;
align-items: center;
font-size: 12px;
color: rgba(0, 0, 0, .65);
gap: 5px;
.legendDot {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 3px;
flex-shrink: 0;
}
}
}
}
}
}
}
</style>