feat: 接入AI阳光跑页面与年级班级级联筛选
新增阳光跑路由和列表页面,按学生姓名、年级班级及日期查询记录并支持分页。 查询与 Excel 导出共用所选年级、班级 ID,保留学校后台的页面风格。
This commit is contained in:
parent
eedc88e59f
commit
4e77cfeed2
24
src/api/sunshineRun.js
Normal file
24
src/api/sunshineRun.js
Normal file
@ -0,0 +1,24 @@
|
||||
import axios from 'axios'
|
||||
import http from './http'
|
||||
import store from '@/store/index'
|
||||
|
||||
function queryString(params) {
|
||||
const query = new URLSearchParams()
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
query.append(key, value)
|
||||
}
|
||||
})
|
||||
return query.toString()
|
||||
}
|
||||
|
||||
export function getSunshineRunPage(params) {
|
||||
return http.get(`/api/SunshineRun/GetPageList?${queryString(params)}`)
|
||||
}
|
||||
|
||||
export function exportSunshineRun(params) {
|
||||
return axios.get(`/api/SunshineRun/Export?${queryString(params)}`, {
|
||||
responseType: 'blob',
|
||||
headers: { Authorization: store.getters.getToken() }
|
||||
})
|
||||
}
|
||||
@ -428,6 +428,14 @@ const routes = [{
|
||||
keepAlive: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/sunshineRun',
|
||||
name: 'sunshineRun',
|
||||
component: () => import('@/views/motionData/sunshineRun/index.vue'),
|
||||
meta: {
|
||||
keepAlive: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/trainingDetail', //运动数据/训练数据/详情
|
||||
name: 'trainingDetail',
|
||||
|
||||
@ -810,6 +810,7 @@ export default defineComponent({
|
||||
'/userActivity': 'el-icon-s-marketing',
|
||||
'/physicalTestData': 'el-icon-s-data',
|
||||
'/trainingData': 'el-icon-s-data',
|
||||
'/sunshineRun': 'el-icon-s-data',
|
||||
'/visionData': 'el-icon-s-check',
|
||||
'/specialManagement': 'el-icon-s-management',
|
||||
'/ropeJumping': 'el-icon-s-cooperation',
|
||||
|
||||
284
src/views/motionData/sunshineRun/index.vue
Normal file
284
src/views/motionData/sunshineRun/index.vue
Normal file
@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="sunshine-run">
|
||||
<commonManage>
|
||||
<div class="head">
|
||||
<div class="left">
|
||||
<p class="desctipt">姓名:</p>
|
||||
<el-input v-model.trim="filters.studentName" class="m-2" clearable
|
||||
placeholder="请输入姓名" @keyup.enter="search" />
|
||||
</div>
|
||||
<div class="left">
|
||||
<p class="desctipt">班级:</p>
|
||||
<el-cascader v-model="filters.gradeClass" class="grade-class-cascader"
|
||||
:options="gradeClassOptions" :props="cascaderProps" clearable filterable
|
||||
placeholder="请选择年级/班级" />
|
||||
</div>
|
||||
<div class="left">
|
||||
<p class="desctipt">起始日期:</p>
|
||||
<el-date-picker v-model="dateRange" class="date-range" type="daterange"
|
||||
format="YYYY-MM-DD" value-format="YYYY-MM-DD" range-separator="至"
|
||||
start-placeholder="开始日期" end-placeholder="结束日期" clearable />
|
||||
</div>
|
||||
<div class="right">
|
||||
<el-button class="Search" :icon="Search" @click="search">查询</el-button>
|
||||
<el-button class="Audit" :icon="Refresh" @click="reset">重置</el-button>
|
||||
<el-button class="Export" :icon="Download" :loading="exporting" @click="exportData">导出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cont">
|
||||
<el-table v-loading="loading" :data="records" class="record-table"
|
||||
:header-cell-style="headerCellStyle" :cell-style="cellStyle"
|
||||
:row-style="{ height: '0px' }">
|
||||
<el-table-column fixed prop="index" label="序号" width="60" />
|
||||
<el-table-column prop="studentName" label="姓名" min-width="115" />
|
||||
<el-table-column prop="sexName" label="性别" width="75" />
|
||||
<el-table-column prop="className" label="班级" min-width="150" />
|
||||
<el-table-column prop="effectiveDistance" label="有效里程" min-width="110" />
|
||||
<el-table-column prop="effectiveTime" label="有效用时" min-width="110" />
|
||||
<el-table-column prop="totalDistance" label="总里程" min-width="105" />
|
||||
<el-table-column prop="totalTime" label="总用时" min-width="105" />
|
||||
<el-table-column prop="startTime" label="开始时间" min-width="160" />
|
||||
<el-table-column prop="endTime" label="结束时间" min-width="160" />
|
||||
<template #empty>
|
||||
<el-empty description="暂无阳光跑数据" />
|
||||
</template>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pagination"
|
||||
v-model:current-page="pageIndex"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 50, 100, 200]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="changePageSize"
|
||||
@current-change="loadRecords"
|
||||
/>
|
||||
</div>
|
||||
</commonManage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { Download, Refresh, Search } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import commonManage from '@/components/commonManage/index.vue'
|
||||
import { GetGradeNames } from '@/api/grade'
|
||||
import { exportSunshineRun, getSunshineRunPage } from '@/api/sunshineRun'
|
||||
|
||||
const filters = reactive({ studentName: '', gradeClass: [] })
|
||||
const gradeClassOptions = ref([])
|
||||
const cascaderProps = { checkStrictly: true }
|
||||
const dateRange = ref([])
|
||||
const appliedFilters = reactive({ studentName: '', gradeId: '', classId: '', startDate: '', endDate: '' })
|
||||
const records = ref([])
|
||||
const loading = ref(false)
|
||||
const exporting = ref(false)
|
||||
const pageIndex = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const headerCellStyle = {
|
||||
textAlign: 'center',
|
||||
fontSize: '12px',
|
||||
color: '#3F404A',
|
||||
padding: '3px',
|
||||
background: '#F8F8FA',
|
||||
height: '50px'
|
||||
}
|
||||
const cellStyle = {
|
||||
textAlign: 'center',
|
||||
fontSize: '12px',
|
||||
color: '#3F404A',
|
||||
padding: '3px'
|
||||
}
|
||||
|
||||
async function loadRecords() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getSunshineRunPage({
|
||||
...appliedFilters,
|
||||
pageIndex: pageIndex.value,
|
||||
pageSize: pageSize.value
|
||||
})
|
||||
if (response.code !== 200) throw new Error(response.message || '阳光跑数据加载失败')
|
||||
records.value = response.data?.datas || []
|
||||
total.value = response.data?.total || 0
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || '阳光跑数据加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getFilterParams() {
|
||||
return {
|
||||
studentName: filters.studentName,
|
||||
gradeId: filters.gradeClass?.[0] || '',
|
||||
classId: filters.gradeClass?.[1] || '',
|
||||
startDate: dateRange.value?.[0] || '',
|
||||
endDate: dateRange.value?.[1] || ''
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGradeClassOptions() {
|
||||
try {
|
||||
const response = await GetGradeNames()
|
||||
if (response.code !== 200) throw new Error(response.message || '年级班级加载失败')
|
||||
gradeClassOptions.value = (response.data || []).map(grade => ({
|
||||
value: grade.id,
|
||||
label: grade.gradeName,
|
||||
children: (grade.class || []).map(classroom => ({
|
||||
value: classroom.id,
|
||||
label: classroom.className
|
||||
}))
|
||||
}))
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || '年级班级加载失败')
|
||||
}
|
||||
}
|
||||
|
||||
function search() {
|
||||
Object.assign(appliedFilters, getFilterParams())
|
||||
pageIndex.value = 1
|
||||
loadRecords()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
Object.assign(filters, { studentName: '', gradeClass: [] })
|
||||
dateRange.value = []
|
||||
pageSize.value = 10
|
||||
search()
|
||||
}
|
||||
|
||||
function changePageSize() {
|
||||
pageIndex.value = 1
|
||||
loadRecords()
|
||||
}
|
||||
|
||||
async function exportData() {
|
||||
exporting.value = true
|
||||
try {
|
||||
const response = await exportSunshineRun(getFilterParams())
|
||||
const blob = response.data
|
||||
if (!blob || blob.type?.includes('json')) throw new Error('导出失败')
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `AI阳光跑${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.remove()
|
||||
setTimeout(() => URL.revokeObjectURL(url), 1000)
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || '导出失败')
|
||||
} finally {
|
||||
exporting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadGradeClassOptions()
|
||||
loadRecords()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sunshine-run {
|
||||
padding: 16px;
|
||||
|
||||
.head {
|
||||
width: 100%;
|
||||
min-height: 72px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.left {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.desctipt {
|
||||
width: auto;
|
||||
font-size: 16px;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 500;
|
||||
color: #3f404a;
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
width: 200px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.grade-class-cascader {
|
||||
width: 220px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
width: 330px !important;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
gap: 10px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.cont {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 200px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.record-table {
|
||||
width: calc(100% - 40px);
|
||||
height: calc(100vh - 270px);
|
||||
min-height: 360px;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.sunshine-run {
|
||||
padding: 12px;
|
||||
|
||||
.head .left {
|
||||
width: 100%;
|
||||
.desctipt { margin-left: 0; }
|
||||
.m-2 { flex: 1; margin-left: 8px; }
|
||||
.grade-class-cascader { flex: 1; width: auto; min-width: 0; margin-left: 8px; }
|
||||
.date-range { width: auto !important; flex: 1; min-width: 0; }
|
||||
}
|
||||
|
||||
.head .right {
|
||||
width: 100%;
|
||||
padding-right: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.cont .pagination {
|
||||
overflow-x: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user