367 lines
7.9 KiB
Vue
367 lines
7.9 KiB
Vue
<script setup lang="ts">
|
||
import ahTable from "@/components/hTable/index.vue";
|
||
import {
|
||
ConditionalType,
|
||
intTableData,
|
||
TableColumnSearch,
|
||
TableConfig,
|
||
} from "@/components/hTable/hTable";
|
||
import { onMounted, ref } from "vue";
|
||
import { fa } from "element-plus/es/locales.mjs";
|
||
import { hTableAPI } from "@/api/hTable";
|
||
import { getenum } from "@/api/enum";
|
||
import { ruleRequired, ruleRequiredNumber } from "@/utils/rules";
|
||
import { ClassRanking } from "@/api/exam";
|
||
import { ElMessage } from "element-plus";
|
||
const ControllerName = "ExamClassInfo";
|
||
|
||
defineOptions({
|
||
name: "ClassExam",
|
||
});
|
||
|
||
const props = defineProps<{
|
||
data: any;
|
||
}>();
|
||
|
||
function searchCallback(data) {}
|
||
const table = ref<{ initTable: (config: TableConfig) => void }>();
|
||
const tableData: TableConfig = intTableData({
|
||
apiUrl: ControllerName,
|
||
expandColumn: true,
|
||
selectColumn: false, // 列表选择
|
||
border: false, // 是否显示表格边框
|
||
searchCallback: searchCallback,
|
||
expandChange: expandChange,
|
||
search: {
|
||
// 查询条件
|
||
show: true,
|
||
PageSize: 999,
|
||
},
|
||
operationColumn: true, // 显示操作按钮
|
||
operationColumnData: [
|
||
{
|
||
topBtn: false, // 头部按钮
|
||
show: true,
|
||
label: "详情",
|
||
btnType: "custom",
|
||
btnStyle: "primary",
|
||
custom: {
|
||
title: "考试班级详情", // 弹出框title
|
||
src: "exam/classExamRecord", // 组件路径
|
||
width: "1600px", // 弹框宽度
|
||
height: "800px", // 弹框高度
|
||
},
|
||
},
|
||
],
|
||
column: {
|
||
// 行数据
|
||
schoolName: {
|
||
label: "学校",
|
||
width: "180px",
|
||
search: new TableColumnSearch(true, ConditionalType.Like),
|
||
},
|
||
grade: {
|
||
label: "年级",
|
||
width: "120px",
|
||
custom: (row) => row.gradeLevel + row.gradeYear + "届",
|
||
search: new TableColumnSearch(true),
|
||
},
|
||
className: {
|
||
label: "班级",
|
||
width: "80px",
|
||
search: new TableColumnSearch(true, ConditionalType.Like),
|
||
},
|
||
examName: {
|
||
label: "最近考试",
|
||
width: "200px",
|
||
},
|
||
peopleCount: {
|
||
label: "参考人数",
|
||
},
|
||
},
|
||
data: [],
|
||
pageData: {
|
||
total: 0,
|
||
},
|
||
selectRows: [],
|
||
});
|
||
|
||
const showTable = ref(false);
|
||
onMounted(async () => {
|
||
//初始化数据原
|
||
|
||
showTable.value = true;
|
||
});
|
||
// types/exam.ts 或直接在组件中定义
|
||
export interface ExamClassTag {
|
||
Name: string; // 标签名称
|
||
SubjectStr?: string; // 学科枚举,可空(总分则为 null)
|
||
OnLineRanking: number; // 上线考试排名
|
||
OnLineRate: number; // 上线率(注意:C#中是 decimal,TS里用 number)
|
||
OnLineCount: number; // 上线人数
|
||
}
|
||
|
||
async function expandChange(row: any, expandedRows: any[]) {
|
||
if (row.rankingList != null) return;
|
||
let res = await ClassRanking(row.examId, row.classId);
|
||
if (res.code != 200) return ElMessage.error(res.message || "获取数据失败");
|
||
row.rankingList = res.data;
|
||
}
|
||
// 根据学科获取CSS类名
|
||
const getSubjectClass = (subjectStr) => {
|
||
if (!subjectStr) return "";
|
||
|
||
const subjectMap = {
|
||
总分: "total",
|
||
数学: "math",
|
||
英语: "english",
|
||
物理: "physics",
|
||
化学: "chemistry",
|
||
生物: "biology",
|
||
};
|
||
|
||
return subjectMap[subjectStr] || "";
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<div>
|
||
<ahTable v-if="showTable" ref="table" :tableConfig="tableData">
|
||
<template #expandSlot="{ props }">
|
||
<!-- 拓展内容 -->
|
||
<div class="expanded-content expandSlot">
|
||
<div
|
||
v-if="props.row.rankingList && props.row.rankingList.length > 0"
|
||
class="ranking-list"
|
||
>
|
||
<div
|
||
v-for="(tag, tagIndex) in props.row.rankingList"
|
||
:key="tagIndex"
|
||
class="tag-card"
|
||
:class="getSubjectClass(tag.subjectStr)"
|
||
>
|
||
<div class="tag-name">
|
||
<span>{{ tag.name }}</span>
|
||
<span class="subject-badge">{{ tag.subjectStr || "总分" }}</span>
|
||
</div>
|
||
|
||
<div class="tag-details">
|
||
<div class="detail-item">
|
||
<span class="detail-label">上线排名:</span>
|
||
<span class="detail-value">第 {{ tag.onLineRanking }} 名</span>
|
||
</div>
|
||
|
||
<div class="detail-item">
|
||
<span class="detail-label">上线人数:</span>
|
||
<span class="detail-value">{{ tag.onLineCount }} 人</span>
|
||
</div>
|
||
|
||
<div class="detail-item">
|
||
<span class="detail-label">上线率:</span>
|
||
<span class="detail-value"
|
||
>{{ (tag.onLineRate * 100).toFixed(0) }}%</span
|
||
>
|
||
</div>
|
||
|
||
<div class="progress-container">
|
||
<div
|
||
class="progress-bar"
|
||
:style="{ width: tag.onLineRate * 100 + '%' }"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="empty-state">
|
||
<i class="fas fa-inbox" style="font-size: 3rem; margin-bottom: 15px"></i>
|
||
<p>暂无标签数据</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</ahTable>
|
||
</div>
|
||
</template>
|
||
<style>
|
||
.expandSlot {
|
||
padding: 10px 20px;
|
||
background: #f5f7fa;
|
||
}
|
||
.container {
|
||
width: 100%;
|
||
max-width: 1200px;
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(90deg, #3498db, #2c3e50);
|
||
color: white;
|
||
padding: 20px 30px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.header h1 {
|
||
font-weight: 600;
|
||
font-size: 1.8rem;
|
||
}
|
||
|
||
.header .subtitle {
|
||
opacity: 0.9;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.table-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.main-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.main-table th {
|
||
background-color: #f8f9fa;
|
||
padding: 15px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
border-bottom: 2px solid #e9ecef;
|
||
}
|
||
|
||
.main-table td {
|
||
padding: 15px;
|
||
border-bottom: 1px solid #e9ecef;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.main-table tr:hover {
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.expand-btn {
|
||
background: #3498db;
|
||
color: white;
|
||
border: none;
|
||
padding: 6px 12px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 0.85rem;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.expand-btn:hover {
|
||
background: #2980b9;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.ranking-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||
gap: 15px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.tag-card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
|
||
border-left: 4px solid #3498db;
|
||
transition: transform 0.3s, box-shadow 0.3s;
|
||
}
|
||
|
||
.tag-card:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.tag-card.total {
|
||
border-left-color: #777f8a;
|
||
}
|
||
.tag-card.math {
|
||
border-left-color: #e74c3c;
|
||
}
|
||
|
||
.tag-card.english {
|
||
border-left-color: #f39c12;
|
||
}
|
||
|
||
.tag-card.physics {
|
||
border-left-color: #9b59b6;
|
||
}
|
||
|
||
.tag-card.chemistry {
|
||
border-left-color: #1abc9c;
|
||
}
|
||
|
||
.tag-card.biology {
|
||
border-left-color: #2ecc71;
|
||
}
|
||
|
||
.tag-name {
|
||
font-weight: 600;
|
||
font-size: 1.1rem;
|
||
color: #2c3e50;
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.subject-badge {
|
||
padding: 3px 8px;
|
||
border-radius: 12px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
background: #ecf0f1;
|
||
}
|
||
|
||
.tag-details {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.detail-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 0px;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.detail-label {
|
||
color: #7f8c8d;
|
||
}
|
||
|
||
.detail-value {
|
||
font-weight: 500;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.progress-container {
|
||
height: 8px;
|
||
background: #ecf0f1;
|
||
border-radius: 4px;
|
||
margin-top: 5px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #3498db, #2ecc71);
|
||
border-radius: 4px;
|
||
transition: width 0.5s ease;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 30px;
|
||
color: #7f8c8d;
|
||
font-style: italic;
|
||
}
|
||
</style>
|