- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
791 lines
20 KiB
Vue
791 lines
20 KiB
Vue
<template>
|
||
<div class="score-query-container">
|
||
<div class="page-header">
|
||
<h1 class="page-title">查分中心</h1>
|
||
<div class="header-actions">
|
||
<el-date-picker
|
||
v-model="dateRange"
|
||
type="daterange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
@change="handleDateChange"
|
||
/>
|
||
<el-button type="primary" @click="refreshData">
|
||
<el-icon class="el-icon--left"><Refresh /></el-icon>
|
||
刷新数据
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 成绩概览 -->
|
||
<div class="score-overview">
|
||
<div class="overview-card card">
|
||
<div class="card-icon" style="background-color: rgba(102, 126, 234, 0.1)">
|
||
<el-icon :size="32" color="#667eea">
|
||
<Document />
|
||
</el-icon>
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="card-value">{{ overviewData.total_exams || 0 }}</div>
|
||
<div class="card-label">总考试次数</div>
|
||
</div>
|
||
</div>
|
||
<div class="overview-card card">
|
||
<div class="card-icon" style="background-color: rgba(103, 194, 58, 0.1)">
|
||
<el-icon :size="32" color="#67c23a">
|
||
<TrendCharts />
|
||
</el-icon>
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="card-value">{{ overviewData.avg_score?.toFixed(1) || '0.0' }}</div>
|
||
<div class="card-label">平均分</div>
|
||
</div>
|
||
</div>
|
||
<div class="overview-card card">
|
||
<div class="card-icon" style="background-color: rgba(230, 162, 60, 0.1)">
|
||
<el-icon :size="32" color="#e6a23c">
|
||
<CircleCheck />
|
||
</el-icon>
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="card-value">{{ (overviewData.pass_rate || 0).toFixed(1) }}%</div>
|
||
<div class="card-label">通过率</div>
|
||
</div>
|
||
</div>
|
||
<div class="overview-card card">
|
||
<div class="card-icon" style="background-color: rgba(64, 158, 255, 0.1)">
|
||
<el-icon :size="32" color="#409eff">
|
||
<EditPen />
|
||
</el-icon>
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="card-value">{{ overviewData.total_questions || 0 }}</div>
|
||
<div class="card-label">答题总数</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 筛选和搜索 -->
|
||
<div class="filter-section card">
|
||
<el-form :inline="true" :model="filterForm" class="filter-form">
|
||
<el-form-item label="课程">
|
||
<el-select v-model="filterForm.courseId" placeholder="请选择课程" clearable style="width: 200px">
|
||
<el-option label="全部课程" :value="null" />
|
||
<el-option
|
||
v-for="course in courseList"
|
||
:key="course.id"
|
||
:label="course.name"
|
||
:value="course.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="成绩范围">
|
||
<el-select v-model="filterForm.scoreRange" placeholder="请选择" clearable style="width: 180px">
|
||
<el-option label="全部成绩" value="" />
|
||
<el-option label="优秀 (90-100)" value="excellent" />
|
||
<el-option label="良好 (80-89)" value="good" />
|
||
<el-option label="及格 (60-79)" value="pass" />
|
||
<el-option label="不及格 (0-59)" value="fail" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">
|
||
<el-icon class="el-icon--left"><Search /></el-icon>
|
||
搜索
|
||
</el-button>
|
||
<el-button @click="handleReset">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 成绩列表 -->
|
||
<div class="score-list card">
|
||
<el-table
|
||
:data="filteredScoreList"
|
||
style="width: 100%"
|
||
v-loading="loading"
|
||
@row-click="viewScoreDetail"
|
||
>
|
||
<el-table-column prop="exam_name" label="考试名称" min-width="200" />
|
||
<el-table-column prop="course_name" label="课程" width="150" />
|
||
<el-table-column prop="score" label="得分" width="100" sortable>
|
||
<template #default="scope">
|
||
<span :class="getScoreClass(scope.row.score)" class="score-text">
|
||
{{ scope.row.score ?? '-' }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="total_score" label="总分" width="80">
|
||
<template #default="scope">
|
||
<span class="total-score">{{ scope.row.total_score }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="accuracy" label="正确率" width="100" sortable>
|
||
<template #default="scope">
|
||
<el-progress
|
||
v-if="scope.row.accuracy !== null"
|
||
:percentage="scope.row.accuracy"
|
||
:color="getAccuracyColor(scope.row.accuracy)"
|
||
:stroke-width="8"
|
||
/>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="duration_seconds" label="用时" width="100">
|
||
<template #default="scope">
|
||
<span>{{ formatDuration(scope.row.duration_seconds) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="start_time" label="考试时间" width="180" sortable>
|
||
<template #default="scope">
|
||
{{ formatDateTime(scope.row.start_time) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="scope">
|
||
<el-tag :type="getStatusTag(scope.row.status)">
|
||
{{ getStatusText(scope.row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180" fixed="right">
|
||
<template #default="scope">
|
||
<el-button link type="primary" size="small" @click.stop="viewScoreDetail(scope.row)">
|
||
<el-icon><View /></el-icon>
|
||
查看详情
|
||
</el-button>
|
||
<el-button link type="danger" size="small" @click.stop="viewMistakes(scope.row)">
|
||
<el-icon><Warning /></el-icon>
|
||
错题本
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<div class="pagination-wrapper">
|
||
<el-pagination
|
||
v-model:current-page="currentPage"
|
||
v-model:page-size="pageSize"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
:total="total"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 成绩详情弹窗 -->
|
||
<el-dialog
|
||
v-model="detailDialogVisible"
|
||
:title="`${currentScore?.exam_name} - 成绩详情`"
|
||
width="800px"
|
||
>
|
||
<div class="score-detail" v-if="currentScore">
|
||
<!-- 基本信息 -->
|
||
<div class="detail-section">
|
||
<h3>基本信息</h3>
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="考试名称">{{ currentScore.exam_name }}</el-descriptions-item>
|
||
<el-descriptions-item label="课程">{{ currentScore.course_name }}</el-descriptions-item>
|
||
<el-descriptions-item label="考试时间">{{ formatDateTime(currentScore.start_time) }}</el-descriptions-item>
|
||
<el-descriptions-item label="用时">{{ formatDuration(currentScore.duration_seconds) }}</el-descriptions-item>
|
||
<el-descriptions-item label="状态">
|
||
<el-tag :type="getStatusTag(currentScore.status)">
|
||
{{ getStatusText(currentScore.status) }}
|
||
</el-tag>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
|
||
<!-- 成绩统计 -->
|
||
<div class="detail-section">
|
||
<h3>成绩统计</h3>
|
||
<div class="score-stats">
|
||
<div class="stat-item">
|
||
<div class="stat-label">总得分</div>
|
||
<div class="stat-value score" :class="getScoreClass(currentScore.score)">
|
||
{{ currentScore.score ?? '-' }}/{{ currentScore.total_score }}
|
||
</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-label">正确率</div>
|
||
<div class="stat-value">
|
||
<el-progress
|
||
v-if="currentScore.accuracy !== null"
|
||
:percentage="currentScore.accuracy"
|
||
:color="getAccuracyColor(currentScore.accuracy)"
|
||
:stroke-width="12"
|
||
/>
|
||
<span v-else>-</span>
|
||
</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="stat-label">题目统计</div>
|
||
<div class="stat-value">
|
||
<div class="question-stats">
|
||
<span class="correct">正确: {{ currentScore.correct_count ?? 0 }}</span>
|
||
<span class="wrong">错误: {{ currentScore.wrong_count ?? 0 }}</span>
|
||
<span class="total">总计: {{ currentScore.question_count }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 分题型统计 -->
|
||
<div class="detail-section" v-if="currentScore.question_type_stats && currentScore.question_type_stats.length > 0">
|
||
<h3>分题型统计</h3>
|
||
<el-table :data="currentScore.question_type_stats" size="small">
|
||
<el-table-column prop="type" label="题型" width="120">
|
||
<template #default="scope">
|
||
<el-tag size="small">{{ scope.row.type }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="total" label="总题数" width="100" />
|
||
<el-table-column prop="correct" label="正确数" width="100" />
|
||
<el-table-column prop="wrong" label="错误数" width="100" />
|
||
<el-table-column prop="accuracy" label="正确率" width="150">
|
||
<template #default="scope">
|
||
<el-progress
|
||
:percentage="scope.row.accuracy"
|
||
:stroke-width="6"
|
||
:show-text="false"
|
||
/>
|
||
<span style="margin-left: 8px;">{{ scope.row.accuracy }}%</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, computed, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ElMessage } from 'element-plus'
|
||
import {
|
||
getExamRecords,
|
||
getExamStatistics,
|
||
getCourseList,
|
||
type ExamRecord,
|
||
type ExamStatistics as ExamStatsType
|
||
} from '@/api/score'
|
||
|
||
const router = useRouter()
|
||
|
||
// 加载状态
|
||
const loading = ref(false)
|
||
|
||
// 日期范围
|
||
const dateRange = ref<[Date, Date]>([
|
||
new Date(new Date().setDate(new Date().getDate() - 30)),
|
||
new Date()
|
||
])
|
||
|
||
// 分页
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(20)
|
||
const total = ref(0)
|
||
|
||
// 弹窗状态
|
||
const detailDialogVisible = ref(false)
|
||
const currentScore = ref<ExamRecord | null>(null)
|
||
|
||
// 筛选表单
|
||
const filterForm = reactive({
|
||
courseId: null as number | null,
|
||
scoreRange: ''
|
||
})
|
||
|
||
// 概览数据
|
||
const overviewData = ref<ExamStatsType>({
|
||
total_exams: 0,
|
||
avg_score: 0,
|
||
pass_rate: 0,
|
||
total_questions: 0
|
||
})
|
||
|
||
// 课程列表
|
||
const courseList = ref<any[]>([])
|
||
|
||
// 成绩列表数据
|
||
const scoreList = ref<ExamRecord[]>([])
|
||
|
||
// 筛选后的成绩列表(前端筛选成绩范围)
|
||
const filteredScoreList = computed(() => {
|
||
let filtered = scoreList.value
|
||
|
||
// 按成绩范围筛选(前端过滤)
|
||
if (filterForm.scoreRange) {
|
||
filtered = filtered.filter(item => {
|
||
const score = item.score
|
||
if (score === null) return false
|
||
|
||
switch (filterForm.scoreRange) {
|
||
case 'excellent':
|
||
return score >= 90
|
||
case 'good':
|
||
return score >= 80 && score < 90
|
||
case 'pass':
|
||
return score >= 60 && score < 80
|
||
case 'fail':
|
||
return score < 60
|
||
default:
|
||
return true
|
||
}
|
||
})
|
||
}
|
||
|
||
return filtered
|
||
})
|
||
|
||
/**
|
||
* 获取分数样式类
|
||
*/
|
||
const getScoreClass = (score: number | null) => {
|
||
if (score === null) return ''
|
||
if (score >= 90) return 'score-excellent'
|
||
if (score >= 80) return 'score-good'
|
||
if (score >= 60) return 'score-pass'
|
||
return 'score-fail'
|
||
}
|
||
|
||
/**
|
||
* 获取正确率颜色
|
||
*/
|
||
const getAccuracyColor = (accuracy: number) => {
|
||
if (accuracy >= 90) return '#67c23a'
|
||
if (accuracy >= 80) return '#409eff'
|
||
if (accuracy >= 60) return '#e6a23c'
|
||
return '#f56c6c'
|
||
}
|
||
|
||
/**
|
||
* 获取状态标签样式
|
||
*/
|
||
const getStatusTag = (status: string) => {
|
||
const tagMap: Record<string, string> = {
|
||
completed: 'success',
|
||
submitted: 'success',
|
||
in_progress: 'warning',
|
||
started: 'warning',
|
||
timeout: 'info',
|
||
failed: 'danger'
|
||
}
|
||
return tagMap[status] || ''
|
||
}
|
||
|
||
/**
|
||
* 获取状态文本
|
||
*/
|
||
const getStatusText = (status: string) => {
|
||
const textMap: Record<string, string> = {
|
||
completed: '已完成',
|
||
submitted: '已提交',
|
||
in_progress: '进行中',
|
||
started: '进行中',
|
||
timeout: '超时',
|
||
failed: '已失败'
|
||
}
|
||
return textMap[status] || status
|
||
}
|
||
|
||
/**
|
||
* 格式化时长
|
||
*/
|
||
const formatDuration = (seconds: number | null) => {
|
||
if (!seconds) return '-'
|
||
|
||
const hours = Math.floor(seconds / 3600)
|
||
const minutes = Math.floor((seconds % 3600) / 60)
|
||
const secs = seconds % 60
|
||
|
||
if (hours > 0) {
|
||
return `${hours}小时${minutes}分钟`
|
||
} else if (minutes > 0) {
|
||
return `${minutes}分钟${secs}秒`
|
||
} else {
|
||
return `${secs}秒`
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 格式化日期时间
|
||
*/
|
||
const formatDateTime = (dateStr: string | null) => {
|
||
if (!dateStr) return '-'
|
||
return dateStr.replace('T', ' ').substring(0, 19)
|
||
}
|
||
|
||
/**
|
||
* 格式化日期为字符串
|
||
*/
|
||
const formatDate = (date: Date) => {
|
||
return date.toISOString().split('T')[0]
|
||
}
|
||
|
||
/**
|
||
* 加载概览数据
|
||
*/
|
||
const loadOverviewData = async () => {
|
||
try {
|
||
const params: any = {}
|
||
if (filterForm.courseId) {
|
||
params.course_id = filterForm.courseId
|
||
}
|
||
const res = await getExamStatistics(params)
|
||
if (res.code === 200) {
|
||
overviewData.value = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取概览数据失败:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载成绩列表
|
||
*/
|
||
const loadScoreList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params: any = {
|
||
page: currentPage.value,
|
||
size: pageSize.value
|
||
}
|
||
|
||
if (filterForm.courseId) {
|
||
params.course_id = filterForm.courseId
|
||
}
|
||
|
||
if (dateRange.value && dateRange.value.length === 2) {
|
||
params.start_date = formatDate(dateRange.value[0])
|
||
params.end_date = formatDate(dateRange.value[1])
|
||
}
|
||
|
||
const res = await getExamRecords(params)
|
||
if (res.code === 200) {
|
||
scoreList.value = res.data.items
|
||
total.value = res.data.total
|
||
}
|
||
} catch (error) {
|
||
console.error('获取成绩列表失败:', error)
|
||
ElMessage.error('获取成绩列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载课程列表
|
||
*/
|
||
const loadCourseList = async () => {
|
||
try {
|
||
const res = await getCourseList()
|
||
if (res.code === 200) {
|
||
courseList.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('获取课程列表失败:', error)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 日期变化处理
|
||
*/
|
||
const handleDateChange = () => {
|
||
handleSearch()
|
||
}
|
||
|
||
/**
|
||
* 刷新数据
|
||
*/
|
||
const refreshData = async () => {
|
||
await Promise.all([loadOverviewData(), loadScoreList()])
|
||
ElMessage.success('数据已刷新')
|
||
}
|
||
|
||
/**
|
||
* 搜索处理
|
||
*/
|
||
const handleSearch = () => {
|
||
currentPage.value = 1
|
||
loadOverviewData()
|
||
loadScoreList()
|
||
}
|
||
|
||
/**
|
||
* 重置筛选
|
||
*/
|
||
const handleReset = () => {
|
||
filterForm.courseId = null
|
||
filterForm.scoreRange = ''
|
||
dateRange.value = [
|
||
new Date(new Date().setDate(new Date().getDate() - 30)),
|
||
new Date()
|
||
]
|
||
handleSearch()
|
||
}
|
||
|
||
/**
|
||
* 分页大小改变
|
||
*/
|
||
const handleSizeChange = (val: number) => {
|
||
pageSize.value = val
|
||
currentPage.value = 1
|
||
loadScoreList()
|
||
}
|
||
|
||
/**
|
||
* 当前页改变
|
||
*/
|
||
const handleCurrentChange = (val: number) => {
|
||
currentPage.value = val
|
||
loadScoreList()
|
||
}
|
||
|
||
/**
|
||
* 查看成绩详情
|
||
*/
|
||
const viewScoreDetail = (score: ExamRecord) => {
|
||
currentScore.value = score
|
||
detailDialogVisible.value = true
|
||
}
|
||
|
||
/**
|
||
* 查看错题本
|
||
*/
|
||
const viewMistakes = (exam: ExamRecord) => {
|
||
// 跳转到错题本页面,并带上课程ID和考试ID筛选
|
||
router.push({
|
||
path: '/analysis/mistakes',
|
||
query: {
|
||
course_id: exam.course_id,
|
||
exam_id: exam.id
|
||
}
|
||
})
|
||
}
|
||
|
||
// 组件挂载时初始化数据
|
||
onMounted(() => {
|
||
loadCourseList()
|
||
loadOverviewData()
|
||
loadScoreList()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.score-query-container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
|
||
.page-title {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.score-overview {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 24px;
|
||
|
||
.overview-card {
|
||
padding: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
|
||
.card-icon {
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.card-content {
|
||
flex: 1;
|
||
|
||
.card-value {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
color: #333;
|
||
line-height: 1;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.card-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.filter-section {
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
|
||
.filter-form {
|
||
.el-form-item {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.score-list {
|
||
padding: 24px;
|
||
|
||
.score-text {
|
||
font-weight: 600;
|
||
font-size: 16px;
|
||
|
||
&.score-excellent {
|
||
color: #67c23a;
|
||
}
|
||
|
||
&.score-good {
|
||
color: #409eff;
|
||
}
|
||
|
||
&.score-pass {
|
||
color: #e6a23c;
|
||
}
|
||
|
||
&.score-fail {
|
||
color: #f56c6c;
|
||
}
|
||
}
|
||
|
||
.total-score {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.pagination-wrapper {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: 24px;
|
||
}
|
||
}
|
||
|
||
.score-detail {
|
||
.detail-section {
|
||
margin-bottom: 24px;
|
||
|
||
h3 {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 2px solid #f0f0f0;
|
||
}
|
||
}
|
||
|
||
.score-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
|
||
.stat-item {
|
||
text-align: center;
|
||
padding: 20px;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.stat-value {
|
||
&.score {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.question-stats {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: 14px;
|
||
|
||
.correct {
|
||
color: #67c23a;
|
||
}
|
||
|
||
.wrong {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.total {
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 响应式设计
|
||
@media (max-width: 768px) {
|
||
.score-query-container {
|
||
.page-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
|
||
.header-actions {
|
||
width: 100%;
|
||
justify-content: flex-end;
|
||
}
|
||
}
|
||
|
||
.score-overview {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.filter-form {
|
||
.el-form-item {
|
||
display: block;
|
||
margin-bottom: 16px !important;
|
||
}
|
||
}
|
||
|
||
.score-stats {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
}
|
||
</style>
|