Files
swimmingUni/src/pages/dataAnalyze/baoganAnalyze.vue
2026-03-31 08:40:11 +08:00

537 lines
14 KiB
Vue

<template>
<view class="baogan-container">
<!-- 页面标题区域 -->
<view class="header-section">
<view class="header-title">
<text class="title">包干数据</text>
<text class="subtitle">包干项目训练记录统计</text>
</view>
</view>
<!-- 选择器区域 -->
<view class="selector-section">
<view class="selector-item" @click="showProjectPicker = true">
<text class="selector-label">项目名称</text>
<view class="selector-value">
<text class="value-text">{{ selectedProject }}</text>
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
</view>
</view>
<view class="selector-item" @click="showStudentPicker = true">
<text class="selector-label">选择学生</text>
<view class="selector-value">
<text class="value-text">{{ selectedStudent }}</text>
<u-icon name="arrow-down" size="24" color="#999"></u-icon>
</view>
</view>
</view>
<!-- 项目选择器 -->
<u-picker
:show="showProjectPicker"
:columns="[projectList]"
@confirm="handleProjectConfirm"
@cancel="showProjectPicker = false"
keyName="label"></u-picker>
<!-- 学生选择器 -->
<u-picker
:show="showStudentPicker"
:columns="[studentList]"
@confirm="handleStudentConfirm"
@cancel="showStudentPicker = false"
keyName="label"
multiple
:defaultIndex="defaultStudentIndex"></u-picker>
<!-- 日历组件 -->
<view class="calendar-wrapper">
<uni-calendar
:insert="true"
:lunar="false"
:show-month="true"
:selected="selectedDates"
@monthSwitch="handleMonthSwitch"
@change="handleDateChange">
</uni-calendar>
</view>
<!-- 选中日期数据统计 -->
<view class="date-summary" v-if="selectedDate">
<view class="summary-item">
<text class="summary-label">选中日期</text>
<text class="summary-value">{{ selectedDate }}</text>
</view>
<view class="summary-divider"></view>
<view class="summary-item">
<text class="summary-label">训练人数</text>
<text class="summary-value">{{ tableData.length }}</text>
</view>
<view class="summary-divider"></view>
<view class="summary-item">
<text class="summary-label">完成率平均</text>
<text class="summary-value">{{ averageCompletion }}%</text>
</view>
</view>
<!-- 数据表格 -->
<view class="table-section" v-if="selectedDate">
<sl-table
:columns="columns"
:tableData="tableData"
@cell-click="handleCellClick">
<template #empty>
<view class="empty-container">
<view class="empty-icon">
<u-icon name="file-text" size="80" color="#ccc"></u-icon>
</view>
<text class="empty-text">该日期暂无训练数据</text>
</view>
</template>
</sl-table>
</view>
</view>
</template>
<script setup lang="ts">
import { onShow, onLoad } from "@dcloudio/uni-app"
import { Service } from '@/Service/Service'
import { ref, computed } from 'vue'
// 当前年月
const currentYear = ref(new Date().getFullYear())
const currentMonth = ref(new Date().getMonth() + 1)
// 选中的日期
const selectedDate = ref('')
// 表格列定义
const columns = ref([
{
label: '姓名',
prop: 'name',
width: '100px'
},
{
label: '项目名称',
prop: 'projectName',
width: '120px'
},
{
label: '包干计划',
prop: 'plan',
width: '100px'
},
{
label: '完成比例',
prop: 'completion',
width: '100px'
},
{
label: '详细数据',
prop: 'detail',
width: '120px'
}
])
// 表格数据
interface TableDataItem {
name: string
projectName: string
plan: string
completion: number
detail: string
}
const tableData = ref<TableDataItem[]>([])
// 项目列表
const projectList = ref([
{ label: '全部项目', value: 'all' },
{ label: '100米自由泳', value: '100m-free' },
{ label: '200米自由泳', value: '200m-free' },
{ label: '400米自由泳', value: '400m-free' },
{ label: '100米蛙泳', value: '100m-breast' }
])
// 学生列表
const studentList = ref([
{ label: '全部学生', value: 'all' },
{ label: '张小明', value: 'zhang-xiaoming' },
{ label: '李小红', value: 'li-xiaohong' },
{ label: '王小明', value: 'wang-xiaoming' },
{ label: '赵小芳', value: 'zhao-xiaofang' },
{ label: '陈小刚', value: 'chen-xiaogang' }
])
// 选中的项目和学生
const selectedProject = ref('全部项目')
const selectedStudent = ref('全部学生')
const selectedProjectValue = ref('all')
const selectedStudentValues = ref<string[]>([])
// 选择器显示状态
const showProjectPicker = ref(false)
const showStudentPicker = ref(false)
const defaultStudentIndex = ref<number[]>([0])
// 日历打点数据
// const selectedDates = ref([
// { date: '2026-03-15', info: '训练' },
// { date: '2026-03-18', info: '训练' },
// { date: '2026-03-20', info: '训练' },
// { date: '2026-03-22', info: '训练' },
// { date: '2026-03-25', info: '训练' }
// ])
// 平均完成率
const averageCompletion = computed(() => {
if (tableData.value.length === 0) return 0
const total = tableData.value.reduce((sum, item) => sum + item.completion, 0)
return Math.round(total / tableData.value.length)
})
// 完整模拟数据
const mockData: Record<string, TableDataItem[]> = {
'2026-03-28': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
{ name: '张小明', projectName: '200米自由泳', plan: '1500米', completion: 88, detail: '1320/1500' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
{ name: '李小红', projectName: '400米自由泳', plan: '1200米', completion: 85, detail: '1020/1200' },
{ name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' },
{ name: '赵小芳', projectName: '100米蛙泳', plan: '1800米', completion: 88, detail: '1584/1800' },
{ name: '陈小刚', projectName: '200米自由泳', plan: '1500米', completion: 95, detail: '1425/1500' }
],
'2026-03-29': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 90, detail: '1800/2000' },
{ name: '张小明', projectName: '100米蛙泳', plan: '1800米', completion: 85, detail: '1530/1800' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
{ name: '李小红', projectName: '200米自由泳', plan: '1500米', completion: 92, detail: '1380/1500' },
{ name: '王小明', projectName: '200米自由泳', plan: '1500米', completion: 98, detail: '1470/1500' },
{ name: '赵小芳', projectName: '100米自由泳', plan: '2000米', completion: 82, detail: '1640/2000' },
{ name: '陈小刚', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' }
],
'2026-03-30': [
{ name: '张小明', projectName: '100米自由泳', plan: '2000米', completion: 92, detail: '1840/2000' },
{ name: '张小明', projectName: '400米自由泳', plan: '1200米', completion: 90, detail: '1080/1200' },
{ name: '李小红', projectName: '100米自由泳', plan: '2000米', completion: 88, detail: '1760/2000' },
{ name: '李小红', projectName: '100米蛙泳', plan: '1800米', completion: 95, detail: '1710/1800' },
{ name: '王小明', projectName: '100米自由泳', plan: '2000米', completion: 95, detail: '1900/2000' },
{ name: '赵小芳', projectName: '200米自由泳', plan: '1500米', completion: 85, detail: '1275/1500' },
{ name: '陈小刚', projectName: '400米自由泳', plan: '1200米', completion: 92, detail: '1104/1200' }
]
}
// 日历打点数据
const selectedDates = ref([
{ date: '2026-03-28', info: '训练' },
{ date: '2026-03-29', info: '训练' },
{ date: '2026-03-30', info: '训练' }
])
onLoad(() => {
loadData()
})
onShow(() => {
// 页面显示时刷新数据
})
// 加载数据
const loadData = () => {
// TODO: 调用API获取数据
}
// 月份单元格点击处理
const handleCellClick = (event: any) => {
console.log('单元格点击事件:', event)
}
// 月份切换处理
const handleMonthSwitch = (e: any) => {
currentYear.value = e.year
currentMonth.value = e.month
loadData()
// 切换月份后清空选中日期
selectedDate.value = ''
tableData.value = []
}
// 日期选择处理
const handleDateChange = (e: any) => {
const date = e.fulldate
selectedDate.value = date
filterAndLoadData()
}
// 项目选择确认
const handleProjectConfirm = (e: any) => {
const item = e.value[0]
selectedProject.value = item.label
selectedProjectValue.value = item.value
showProjectPicker.value = false
filterAndLoadData()
}
// 学生选择确认
const handleStudentConfirm = (e: any) => {
const items = e.value
selectedStudentValues.value = items.map((item: any) => item.value)
if (selectedStudentValues.value.length === 0) {
selectedStudent.value = '未选择'
} else if (selectedStudentValues.value.includes('all')) {
selectedStudent.value = '全部学生'
} else {
const selectedNames = items.map((item: any) => item.label)
selectedStudent.value = selectedNames.length > 2
? `${selectedNames.slice(0, 2).join(', ')}${selectedNames.length}`
: selectedNames.join(', ')
}
defaultStudentIndex.value = e.indexs
showStudentPicker.value = false
filterAndLoadData()
}
// 过滤并加载数据
const filterAndLoadData = () => {
if (!selectedDate.value) {
tableData.value = []
return
}
const dateData = mockData[selectedDate.value]
if (!dateData) {
tableData.value = []
return
}
let filteredData = [...dateData]
// 按项目过滤
if (selectedProjectValue.value !== 'all') {
const projectMap: Record<string, string> = {
'100m-free': '100米自由泳',
'200m-free': '200米自由泳',
'400m-free': '400米自由泳',
'100m-breast': '100米蛙泳'
}
const targetProject = projectMap[selectedProjectValue.value]
if (targetProject) {
filteredData = filteredData.filter(item => item.projectName === targetProject)
}
}
// 按学生过滤
if (selectedStudentValue.value !== 'all') {
const studentMap: Record<string, string> = {
'zhang-xiaoming': '张小明',
'li-xiaohong': '李小红',
'wang-xiaoming': '王小明',
'zhao-xiaofang': '赵小芳',
'chen-xiaogang': '陈小刚'
}
const targetStudent = studentMap[selectedStudentValue.value]
if (targetStudent) {
filteredData = filteredData.filter(item => item.name === targetStudent)
}
}
tableData.value = filteredData
}
</script>
<style lang="scss" scoped>
page {
background-color: #f5f5f5;
}
.baogan-container {
min-height: 100vh;
padding-bottom: 40rpx;
}
/* 页面标题区域 */
.header-section {
background-color: #fff;
padding: 32rpx 28rpx 24rpx;
margin-bottom: 20rpx;
.header-title {
.title {
font-size: 36rpx;
font-weight: 700;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.subtitle {
font-size: 24rpx;
color: #999;
}
}
}
/* 选择器区域 */
.selector-section {
background-color: #fff;
margin: 0 20rpx 20rpx;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
gap: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.selector-item {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
background-color: #f8f8f8;
border-radius: 12rpx;
.selector-label {
font-size: 28rpx;
color: #666;
margin-right: 16rpx;
}
.selector-value {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
.value-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-right: 8rpx;
}
}
}
}
/* 日历组件包装 */
.calendar-wrapper {
background-color: #fff;
margin-bottom: 20rpx;
padding: 20rpx 0;
}
/* 日期数据统计 */
.date-summary {
background-color: #fff;
margin: 0 20rpx 20rpx;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.summary-item {
text-align: center;
.summary-label {
font-size: 24rpx;
color: #999;
display: block;
margin-bottom: 8rpx;
}
.summary-value {
font-size: 32rpx;
font-weight: 700;
color: #faad14;
}
}
.summary-divider {
width: 1rpx;
height: 50rpx;
background-color: #eee;
}
}
/* 表格区域 */
.table-section {
margin: 0 20rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
/* 空状态容器 */
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
.empty-icon {
margin-bottom: 24rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
/* 提示状态 */
.hint-state {
margin: 0 20rpx;
background-color: #fff;
border-radius: 16rpx;
padding: 80rpx 40rpx;
text-align: center;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.hint-icon {
margin-bottom: 24rpx;
}
.hint-text {
font-size: 28rpx;
color: #999;
}
}
/* sl-table 样式覆盖 */
::v-deep .sl-table {
width: 100%;
}
::v-deep .sl-table__header {
background: linear-gradient(135deg, #faad14 0%, #ffc53d 100%) !important;
}
::v-deep .sl-table__header__cell {
color: #fff !important;
font-weight: 700;
font-size: 28rpx;
}
::v-deep .sl-table__body__cell {
font-size: 26rpx;
color: #333;
padding: 24rpx 16rpx;
}
::v-deep .sl-table__body__row:nth-child(even) {
background-color: #fafafa !important;
}
::v-deep .sl-table__body__row:nth-child(odd) {
background-color: #fff !important;
}
</style>